123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351(** Utility module for common utility types and functions *)typebase=..typet=|Baseofbase|ModifiedofStyle.modifier*t|Groupoftlist|Importantofbool*t(** [bool] is [true] for the v4 trailing [!] form. *)|Aliasedofstring*t(** [Aliased (class_name, u)] renders as [u] but reports [class_name] from
{!to_class}, so the emitted selector matches the source spelling. Used
for the [prop-(--x)] shorthand, which is [prop-[var(--x)]] in value
but keeps its own class name. *)|Theme_boundofCascade.Css.declarationlist*t(** [Theme_bound (decls, u)] renders as [u] with [decls] alongside its own
declarations. Carries the [\@layer theme] binding of a token a
[theme(--x)] read: resolving the reference does not consume the token,
so Tailwind writes the binding as well. *)letbasex=Basexletimportant?(suffix=false)x=Important(suffix,x)letaliasclass_nameu=Aliased(class_name,u)lettheme_bounddeclsu=matchdeclswith[]->u|_->Theme_bound(decls,u)(* See the .mli. *)moduleProperty_order=structletwidth=1000letslotrank=width*rankletlastrank=slotrank+(width-1)endmoduletypeHandler=sigtypetvalname:stringvalto_style:Scheme.t->t->Style.tvalpriority:t->intvalsuborder:t->intvalof_class:Scheme.t->string->(t,[`Msgofstring])resultvalto_class:t->stringvalexamples:tlistendmoduletypeRegistered=sigincludeHandlervalinject:t->basevalproject:base->toptionendtypehandler=H:(moduleRegisteredwithtypet='a)->handler(* A project's [@utility] sorts at the slot of the property it declares, so that
slot has to be readable from a property. Each handler offers a few of its own
utilities as {!Handler.examples}; running them through [to_style] says which
properties they set, and the lowest order among the ones setting a property
is that property's slot. The ordering itself stays declared once, on the
utilities - nothing here restates it. *)(* [true] on an entry means it came from a property the utility writes on the
element itself, which outranks one it writes only inside a nested rule. *)typeregistry={handlers:handlerlist;property_slots:(Cascade.Css.Declaration.prop_key,bool*(int*int))Hashtbl.toption;}letregistry=Atomic.make{handlers=[];property_slots=None}lethandlers_snapshot()=(Atomic.getregistry).handlersletregister(typea)(moduleM:Registeredwithtypet=a)=letinternal_h=H(moduleM:Registeredwithtypet=a)inletrecadd()=letcurrent=Atomic.getregistryinletnext={handlers=internal_h::current.handlers;property_slots=None}inifnot(Atomic.compare_and_setregistrycurrentnext)thenadd()inadd()moduleMake(M:Handler)=structtypebase+=SelfofM.tmoduleRegistered=structincludeMletinjectvalue=Selfvalueletproject=functionSelfvalue->Somevalue|_->Noneendlet()=register(moduleRegistered)letvvalue=Base(Selfvalue)end(* The declarations a style writes on the element itself: a pseudo-element
suffix or a rule of its own moves them off it. *)letrecstyle_own_declarations(s:Style.t)=matchswith|Style.Style{pseudo_suffix=Some_;_}->[]|Style.Style{props;_}->props|Style.Modified(_,inner)->style_own_declarationsinner|Style.Groupinner->List.concat_mapstyle_own_declarationsinner(* The declarations a style writes, its own and those of the rules it
carries. *)letrecstyle_declarations(s:Style.t)=matchswith|Style.Style{props;rules;_}->props@List.concat_map(funrule->matchCascade.Css.as_rulerulewith|Some(_,decls,_)->decls|None->[])(Option.value~default:[]rules)|Style.Modified(_,inner)->style_declarationsinner|Style.Groupinner->List.concat_mapstyle_declarationsinner(* A width utility writes the style property as a bare reference to the style
utilities' channel, so the declaration says nothing about which family the
utility belongs to. The two carriers are named by the channel they read,
matched on the typed value rather than on the printed declaration, so a
declared [border-style] naming any other variable stays a style utility. *)(* A carrier reads its channel and nothing else. There is no public accessor for
a declaration's typed value - [Properties_intf] is one of cascade's private
modules, so destructuring the record only compiles when cascade is built from
source in the same workspace - and the printed form is the surface cascade
does expose. Comparing it to the one spelling a carrier can have keeps a
declared [border-style] naming any other variable a style utility. *)letcarries_vardeclname=String.equal(Cascade.Css.declaration_value~minify:truedecl)("var(--"^name^")")letis_ordering_carrierdecl=matchCascade.Css.Declaration.property_keydeclwith|KeyBorder_style->carries_vardecl"tw-border-style"|KeyOutline_style->carries_vardecl"tw-outline-style"|_->false(* The property name a vendor-prefixed one stands in for: [-webkit-user-select]
is [user-select]. *)letunprefixed_namename=ifString.lengthname>1&&name.[0]='-'thenOption.map(funi->String.subname(i+1)(String.lengthname-i-1))(String.index_from_optname1'-')elseNone(* A prefixed declaration a later one repeats unprefixed is the same property
written twice for reach, and the slot belongs to the standard spelling. One
with no unprefixed twin ([-webkit-line-clamp]) is the utility's own. *)letis_prefixed_duplicatedeclrest=matchunprefixed_name(Cascade.Css.Declaration.property_namedecl)with|None->false|Someplain->List.exists(fund->String.equal(Cascade.Css.Declaration.property_named)plain)restletordering_propertydeclarations=letrecscan=function|[]->None|decl::rest->(ifis_ordering_carrierdecl||is_prefixed_duplicatedeclrestthenscanrestelsematchCascade.Css.Declaration.property_keydeclwith|Key(Custom_property_)|Key(Unknown_property_)->scanrest|key->Somekey)inscandeclarationsletbuild_property_slotshandlers=lettbl=Hashtbl.create512in(* A property a utility writes on the element itself decides that utility's
family; one it writes only inside a rule it carries does not, unless
nothing else claims the property. [placeholder-transparent] writes [color]
in a [::placeholder] rule, and the [color] slot belongs to the text
colours. *)letrecord~ownkeyorder=matchHashtbl.find_opttblkeywith|Some(true,_)whennotown->()|Some(had_own,existing)whenhad_own=own&&existing<=order->()|_->Hashtbl.replacetblkey(own,order)inList.iter(fun(H(moduleM))->List.iter(funexample->letorder=(M.priorityexample,M.suborderexample)inletstyle=M.to_styleScheme.defaultexamplein(* The property a utility claims is the first named property it
writes: the one it is named for. Theme-token declarations that make
that value available are not slots of their own. A later named
declaration is incidental - line-clamp ends with [display:
-webkit-box] but the display slot belongs to the display utilities,
which sort elsewhere. *)matchordering_property(style_own_declarationsstyle)with|Somekey->record~own:truekeyorder|None->ordering_property(style_declarationsstyle)|>Option.iter(funkey->record~own:falsekeyorder))M.examples)handlers;tblletorder_of_propertykey=letrecslots()=letcurrent=Atomic.getregistryinmatchcurrent.property_slotswith|Sometable->table|None->lettable=build_property_slotscurrent.handlersinletnext={currentwithproperty_slots=Sometable}inifAtomic.compare_and_setregistrycurrentnextthentableelseslots()inOption.mapsnd(Hashtbl.find_opt(slots())key)letname_of_baseu=letrectry_handlers=function|[]->failwith"name_of_base"|H(moduleM)::rest->(matchM.projectuwithSome_->M.name|None->try_handlersrest)intry_handlers(handlers_snapshot())letclass_of_baseu=letvisit(H(moduleM))=Option.mapM.to_class(M.projectu)inmatchList.find_mapvisit(handlers_snapshot())with|Someclass_name->class_name|None->failwith"class_of_base"letbase_of_classthemeclass_name=letrectry_handlers=function|[]->Error(`Msg"Unknown utility")|H(moduleM)::rest->(matchM.of_classthemeclass_namewith|Okx->Ok(M.injectx)|Error_->try_handlersrest)intry_handlers(handlers_snapshot())(* Every utility each handler offers as its own example, as class names. *)letexamples_classes()=List.concat_map(fun(H(moduleM))->List.mapM.to_classM.examples)(handlers_snapshot())(* Every handler that would claim [class_name], by name. [base_of_class] takes
the first, and the order is the dune link order, so a class two handlers both
accept resolves on an unrelated build detail rather than on anything
declared. This is what lets a test assert there is no such class. *)letclaiming_handlersthemeclass_name=List.filter_map(fun(H(moduleM))->matchM.of_classthemeclass_namewith|Ok_->SomeM.name|Error_->None)(handlers_snapshot())(* Keep for backward compatibility with tests *)letbase_of_stringsthemeparts=letclass_name=String.concat"-"partsinbase_of_classthemeclass_nameletbase_to_stylethemeu=lethandlers=handlers_snapshot()inletrectry_handlers=function|[]->prerr_endline("Total handlers registered: "^string_of_int(List.lengthhandlers));failwith"Unknown utility type - handler not registered. This is a bug in the \
utility system."|H(moduleM)::rest->(matchM.projectuwith|Somex->M.to_stylethemex|None->try_handlersrest)intry_handlershandlers(* A theme binding rides on the rule's own declarations, where the theme layer
collects it and the utilities layer filters it out - the same carriage a
container query's binding uses. *)letrecbind_propsdecls=function|Style.Styles->Style.Style{swithprops=decls@s.props}|Style.Modified(m,t)->Style.Modified(m,bind_propsdeclst)|Style.Groupts->Style.Group(List.map(bind_propsdecls)ts)letrecto_class=function|Baseu->class_of_baseu|Modified(m,u)->(matchuwith|Groupus->(* When a modifier wraps a group, apply it to each item in the
group *)String.concat" "(List.map(funitem->to_class(Modified(m,item)))us)|_->Style.pp_modifierm^":"^to_classu)|Groupus->String.concat" "(List.mapto_classus)|Important(suffix,u)->ifsuffixthento_classu^"!"else"!"^to_classu|Aliased(class_name,_)->class_name|Theme_bound(_,u)->to_classuletrecto_styletheme=function|Baseu->base_to_stylethemeu|Modified(m,u)->Style.Modified(m,to_stylethemeu)|Groupus->Style.Group(List.map(to_styletheme)us)|Important(suffix,u)->(* The rules a utility writes itself name its class in their selectors,
and the class the markup carries is the one with the [!]. *)Style.map_important(to_stylethemeu)|>Style.rename_class~old_class:(to_classu)~new_class:(to_class(Important(suffix,u)))|Aliased(_,u)->to_stylethemeu|Theme_bound(decls,u)->bind_propsdecls(to_stylethemeu)letrecpp=function|Baseu->"Base "^class_of_baseu|Modified(m,u)->"Modified ("^Style.pp_modifierm^", "^ppu^")"|Groupus->"Group ["^String.concat"; "(List.mapppus)^"]"|Important(_,u)->"Important ("^ppu^")"|Aliased(class_name,u)->"Aliased ("^class_name^", "^ppu^")"|Theme_bound(_,u)->"Theme_bound ("^ppu^")"letorder(u:base):int*int=letrectry_handlers=function|[]->failwith"Unknown utility type - handler not registered. This is a bug in the \
utility system."|H(moduleM)::rest->(matchM.projectuwith|Somex->(M.priorityx,M.suborderx)|None->try_handlersrest)intry_handlers(handlers_snapshot())letdeduplicateutilities=letrecgoseenacc=function|[]->List.revacc|u::rest->ifList.memuseenthengoseenaccrestelsego(u::seen)(u::acc)restingo[][](List.revutilities)