Legend:
Page
Library
Module
Module type
Parameter
Class
Class type
Source
Source file containers.ml
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243(** Container query utilities for responsive design based on container size. *)moduleCss=Cascade.CssmoduleHandler=structopenStyleopenCss(** Local container utility type *)typet=|Layout_container(* .container - layout container with width:100% *)|Container(* @container - sets container-type: inline-size *)|Container_normal(* @container-normal - sets container-type: normal *)|Container_size(* @container-size - sets container-type: size *)|Container_namedofstring(* @container/name *)letname="containers"(* Tailwind's utility order opens with the container utilities; the layout
[.container] keeps its own place by its width property. *)letpriority=function|Layout_container->1|Container|Container_normal|Container_size|Container_named_->-2(* Tailwind orders .container by its width property: after the position group
(inset, z-index) and before margin. *)letto_class=function|Layout_container->"container"|Container->"@container"|Container_normal->"@container-normal"|Container_size->"@container-size"|Container_namedname->"@container/"^name(* The unit a breakpoint value carries: what is left once its digits and dots
are gone, or the head of a function call. *)letbreakpoint_unitvalue=matchString.index_optvalue'('with|Somei->String.subvalue0i|None->letunit=Buffer.create(String.lengthvalue)inString.iter(func->ifnot((c>='0'&&c<='9')||c='.')thenBuffer.add_charunitc)value;Buffer.contentsunit(* The integer a breakpoint value starts with, the way [parseInt] reads it. *)letbreakpoint_numbervalue:intoption=letstop=ref0inletlen=String.lengthvalueiniflen>0&&(value.[0]='-'||value.[0]='+')thenincrstop;letdigits=!stopinwhile!stop<len&&value.[!stop]>='0'&&value.[!stop]<='9'doincrstopdone;if!stop=digitsthenNoneelseint_of_string_opt(String.subvalue0!stop)(* Tailwind sorts the scale by unit first and then by number, ascending, so an
[em] breakpoint precedes a [px] one whatever their widths. A value with no
leading number falls back to comparing the spellings. *)letcompare_breakpointsab=ifString.equalabthen0elsematchString.compare(breakpoint_unita)(breakpoint_unitb)with|0->(match(breakpoint_numbera,breakpoint_numberb)with|Somex,Somey->Int.comparexy|_->String.compareab)|order->orderletlayout_container_styletheme=letopenCssin(* Use top-level media queries to match Tailwind's minified output.
Container media queries should come AFTER the base .container rule.
sort.ml handles this ordering since container has only media rules and
base props. *)letcontainer_selector=Selector.class_"container"inletspellinglength=Css.Pp.to_stringCss.pp_lengthlengthinletmedia_rules=Scheme.all_breakpointstheme|>List.mapsnd|>List.stable_sort(funab->compare_breakpoints(spellinga)(spellingb))|>List.map(funlength->media~condition:(media_min_width_lengthlength)[rule~selector:container_selector[max_widthlength]])instyle~rules:(Somemedia_rules)[width(Pct100.)]letcontainer_query=style[container_typeInline_size]letcontainer_normal_style=style[container_typeNormal]letcontainer_size_style=style[container_typeSize](* Tailwind v4 emits the [container] shorthand ([container: <name> /
inline-size]) rather than the longhand pair. *)letcontainer_named_stylename=style[Css.Declaration.container~type_:Inline_sizename]letto_styletheme=function|Layout_container->layout_container_styletheme|Container->container_query|Container_normal->container_normal_style|Container_size->container_size_style|Container_namedname->container_named_stylenameletsuborder=function|Container_named_->0|Container->1|Container_normal->2|Container_size->3(* The .container utility (rank 15) sorts after grid_item's grid-column /
grid-row utilities (priority 1, suborder up to ~2000), and after the
@container query utilities above. *)|Layout_container->9_000_000letof_class_theme=function|"container"->OkLayout_container|"@container"->OkContainer|"@container-normal"->OkContainer_normal|"@container-size"->OkContainer_size|nwhenString.starts_with~prefix:"@container/"n->letname=String.subn11(String.lengthn-11)inOk(Container_namedname)|_->Error(`Msg"Not a container utility")letexamples=[Container]endopenHandlermoduleUtility_factory=Utility.Make(Handler)letutility=Utility_factory.v(** Container Query Modifiers *)letcontainer_smstyles=Utility.Modified(ContainerContainer_sm,Utility.Groupstyles)letcontainer_mdstyles=Utility.Modified(ContainerContainer_md,Utility.Groupstyles)letcontainer_lgstyles=Utility.Modified(ContainerContainer_lg,Utility.Groupstyles)letcontainer_xlstyles=Utility.Modified(ContainerContainer_xl,Utility.Groupstyles)letcontainer_2xlstyles=Utility.Modified(ContainerContainer_2xl,Utility.Groupstyles)letcontainer_query?namemin_widthstyles=letquery=matchnamewith|None->Style.Container_named("",min_width)|Somen->Style.Container_named(n,min_width)inUtility.Group(List.map(funt->Utility.Modified(Containerquery,t))styles)letcontainer=utilityLayout_containerletat_container=utilityContainerletat_container_normal=utilityContainer_normalletat_container_namedname=utility(Container_namedname)(** Helper Functions *)(* Tailwind v4 container-query thresholds (rem) for the named size scale. *)letcontainer_size_rem=function|Style.Container_3xs->Some16.|Style.Container_2xs->Some18.|Style.Container_xs->Some20.|Style.Container_sm->Some24.|Style.Container_md->Some28.|Style.Container_lg->Some32.|Style.Container_xl->Some36.|Style.Container_2xl->Some42.|Style.Container_3xl->Some48.|Style.Container_4xl->Some56.|Style.Container_5xl->Some64.|Style.Container_6xl->Some72.|Style.Container_7xl->Some80.|Style.Container_theme_|Style.Container_named_|Style.Container_size_|Style.Container_len_|Style.Container_len_cmp_|Style.Container_scoped_->None(* A [(width <op> len)] container feature query, matching Tailwind v4's range
syntax: [(width >= 24rem)] for min, [(width < 28rem)] for max. *)letwidth_rangeoplen=Css.Container.Feature_query(Css.Media.Cond(Css.Media.Feature(Css.Media.Range(Css.Media.Width,op,Css.Media.Lengthlen))))(* Tailwind v4's max container query is the negated min: [@max-md] is [not
(width >= 28rem)], which Lightning CSS lowers to [not (min-width:28rem)].
Emitting the negated form keeps parity with Tailwind's optimized output. *)letwidth_condcmplen=matchcmpwith|Style.Min->width_rangeCss.Media.Gelen|Style.Max->Css.Container.Not(width_rangeCss.Media.Gelen)(* The width a named size queries: what the project's [@theme] binds to
[--container-<size>], else the scale above. The value is inlined, as Tailwind
inlines it, so the token is not declared for the query's sake. *)letcontainer_size_length?(theme=Scheme.default)q=letdefault=Option.map(funr:Css.length->Css.Values.Remr)inmatchOption.bind(Scheme.tokentheme("container-"^Style.container_size_nameq))Css.parse_lengthwith|Some_aslen->len|None->default(container_size_remq)(** Convert a container query modifier to a structured Container.t condition *)letreccontainer_query_to_condition?themeq=letgeqlen=width_rangeCss.Media.Geleninletsizedq=Option.value~default:(Css.Values.Rem0.)(container_size_length?themeq)inmatchqwith|Style.Container_3xs|Style.Container_2xs|Style.Container_xs|Style.Container_sm|Style.Container_md|Style.Container_lg|Style.Container_xl|Style.Container_2xl|Style.Container_3xl|Style.Container_4xl|Style.Container_5xl|Style.Container_6xl|Style.Container_7xl|Style.Container_theme_->geq(sizedq)|Style.Container_named("",width)->geq(Css.Values.Px(float_of_intwidth))|Style.Container_named(name,width)->Css.Container.Named(name,geq(Css.Values.Px(float_of_intwidth)))|Style.Container_size(cmp,inner)->width_condcmp(sizedinner)|Style.Container_len(_,len)->geqlen|Style.Container_len_cmp(cmp,_,len)->width_condcmplen|Style.Container_scoped(name,inner)->Css.Container.Named(name,container_query_to_condition?themeinner)(* The selector's class prefix and the class name [Utility.to_class] emits have
to be the same string, so both come from [Style.container_size_name]. *)letcontainer_query_to_class_prefixq="@"^Style.container_size_nameq