123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868(** Positioning utilities for controlling element placement *)moduleCss=Cascade.Css(** {1 Helper Functions} *)(* Negate an inset length: simple units flip sign directly; var()/calc() and
parenthesized expressions wrap in [calc(... * -1)] to match Tailwind. *)letnegate_length(l:Css.length):Css.length=letopenCssinmatchlwith|Pxn->Px(-.n)|Remn->Rem(-.n)|Emn->Em(-.n)|Pctn->Pct(-.n)|Vwn->Vw(-.n)|Vhn->Vh(-.n)|other->Calc(Calc.mul(Calc.lengthother)(Calc.float(-1.)))(* A bare parenthesised group is a calc body Tailwind writes without the [calc]
function, as in [left-[(var(--a)+var(--b))]]. Wrap it before normalising so
[+] and [-] read as math operators inside the group. *)letread_paren_calcinner:Css.lengthCss.calcoption=letlen=String.lengthinneriniflen<2||inner.[0]<>'('||inner.[len-1]<>')'thenNoneelseletcalc=Parse.normalize_css_math_operators("calc("^inner^")")inmatchCascade.Cursor.try_parse_full_err(Css.Values.read_calcCss.Values.read_length)(Cascade.Cursor.of_stringcalc)with|Okc->Somec|Error_->None(* The inside of an arbitrary inset value, as a length: one reader for every
side and both signs.
A bare parenthesised group is a length only under [negate]. Tailwind writes
the negative as [calc((a + b) * -1)], where the group is a calc
sub-expression; the positive it writes as [top: (a + b)], which is no
declaration a browser accepts, so there is nothing to agree with. *)(* A bracket no length reader took is still one declaration value, which the
side's own longhand takes verbatim. *)letbracket_token_streams=ifnot(Parse.is_bracket_values)thenNoneelseParse.arbitrary_declaration_value(Parse.bracket_inners)letparse_bracket_length?(negate=false)s:Css.lengthoption=ifnot(Parse.is_bracket_values)thenNoneelse(* A negated arbitrary length is [calc(<value> * -1)], the spelling Tailwind
writes whatever the unit. [negate_length] still folds the sign for the
[px] step and a named inset, where the CLI writes [-1px] itself. *)letsigned(l:Css.length):Css.length=ifnegatethenCss.Calc(Css.Calc.mul(Css.Calc.lengthl)(Css.Calc.float(-1.)))elselin(* A data-type hint chooses the longhand and says nothing about the value.
An inset side writes one longhand, so every hint lands here and the
readers below are handed what follows it; the class name keeps the whole
bracket, which is what the caller stores. *)matchParse.value_after_hint(Parse.bracket_inners)with|None->None|Someinner->(ifParse.is_varinnerthenSome(signed(Css.Var(Var.bracket(Parse.extract_var_nameinner))))elsematchParse.arbitrary_lengthinnerwith|Somel->Some(signedl)|Nonewhennotnegate->None|None->(matchread_paren_calc(Parse.decode_arbitrary_valueinner)with|None->None|Somec->Some(Css.Calc(Css.Calc.mulc(Css.Calc.float(-1.))))))(* The theme token a named inset (top-header) reads: Tailwind resolves the name
against the [--inset-*] namespace and falls back to [--spacing-*]. The value
is the theme's, so the utility only references the token and the theme layer
emits the binding; setting one here writes a length the theme never declared
over whichever namespace the name actually came from. *)letnamed_inset_valuethemename:Css.length=lettoken=ifScheme.theme_value(Sometheme)("inset-"^name)<>Nonethen"inset-"^nameelse"spacing-"^nameinCss.Var(Var.theme_reftoken)(* Create a spacing value using calc(var(--spacing) * n). Returns the theme
declaration and a length that references the variable. *)letspacing_value?themen:Css.declaration*Css.length=Theme.spacing_calc?themen(* The inset sides that take the spacing scale, so a fractional step (top-2.5)
or the px step (left-px) can share one constructor. *)moduleSide=structtypet=|Top|Right|Bottom|Left|Inset|Inset_x|Inset_y|Inset_s|Inset_e|Inset_bs|Inset_be|Start|Endletname=function|Top->"top"|Right->"right"|Bottom->"bottom"|Left->"left"|Inset->"inset"|Inset_x->"inset-x"|Inset_y->"inset-y"|Inset_s->"inset-s"|Inset_e->"inset-e"|Inset_bs->"inset-bs"|Inset_be->"inset-be"|Start->"start"|End->"end"(* What the side writes. [start] and [inset-s] name one property under two
spellings, as [end] and [inset-e] do. *)(* The longhand each side names, for a value no typed setter can hold. *)letproperties=function|Top->["top"]|Right->["right"]|Bottom->["bottom"]|Left->["left"]|Inset->["inset"]|Inset_x->["inset-inline"]|Inset_y->["inset-block"]|Start|Inset_s->["inset-inline-start"]|End|Inset_e->["inset-inline-end"]|Inset_bs->["inset-block-start"]|Inset_be->["inset-block-end"]letdeclarationsside(len:Css.length)=matchsidewith|Top->[Css.toplen]|Right->[Css.rightlen]|Bottom->[Css.bottomlen]|Left->[Css.leftlen]|Inset->[Css.inset[len]]|Inset_x->[Css.inset_inline[len]]|Inset_y->[Css.inset_block[len]]|Start|Inset_s->[Css.inset_inline_startlen]|End|Inset_e->[Css.inset_inline_endlen]|Inset_bs->[Css.inset_block_startlen]|Inset_be->[Css.inset_block_endlen](* Tailwind folds the zero and unit multipliers of the spacing scale to [0px]
and [var(--spacing)], but its [start]/[end] handler resolves the step
itself and keeps [calc(var(--spacing) * n)] for every step. *)letfolds_spacing_scale=functionStart|End->false|_->trueend(* A spacing step is a non-negative multiple of 0.25 ([isValidSpacingMultiplier]
upstream), so [top-1.25] is a utility and [top-1.7] is not. *)letis_quarter_multiplef=f>=0.&&Float.remf0.25=0.(* [top-2.5] / [right-0.5] / [left-px]: a spacing token that is not a plain
integer (those keep their existing [Top of int] path). *)letparse_pos_spacings:Style.spacingoption=ifs="px"thenSome`PxelsematchParse.decimal_floatswith|Somefwhenis_quarter_multiplef&¬(Float.is_integerf)->Some(`Rem(f*.0.25))|_->None(* Resolve a spacing token to (optional --spacing binding, length), mirroring
the padding family so the px and fractional steps render identically. *)letlen_of_pos_spacing?themeside(s:Style.spacing):Css.declarationoption*Css.length=matchswith|`Remf->letstep=f/.0.25inletdecl,len=ifSide.folds_spacing_scalesidethenTheme.spacing_calc_float?themestepelseTheme.spacing_product?themestepin(Somedecl,len)|`Px->(None,Css.Px1.)|`Full->(None,Css.Pct100.)|`Namedname->(None,Css.Var(Var.theme_ref("spacing-"^name)))letpos_spacing_style?themeside(s:Style.spacing)=letdecl_opt,len=len_of_pos_spacing?themesidesinStyle.style(Option.to_listdecl_opt@Side.declarationssidelen)(* The same step, negated: [-inset-x-0.5]. The px step flips its sign directly,
the scale steps through the negated multiplier so the calc reads
[calc(var(--spacing) * -.5)] as Tailwind writes it. *)letneg_pos_spacing_style?themeside(s:Style.spacing)=letnegated:Style.spacing=matchswith`Remf->`Rem(-.f)|other->otherinletdecl_opt,len=len_of_pos_spacing?themesidenegatedinletlen=matchswith`Px->negate_lengthlen|_->leninStyle.style(Option.to_listdecl_opt@Side.declarationssidelen)(* A position fraction [n/m] resolves to [n/m * 100%], the same reading the
sizing families give it: any numerator over any positive denominator, and an
improper fraction (6/5 -> 120%) is a position like any other. It is written
as Tailwind's calc, negated by wrapping it. *)letfrac_validfrac=Parse.fraction_pctfrac<>Noneletfrac_length~negfrac:Css.length=matchParse.fractionfracwith|Some(n,m)->ifnegthenParse.neg_fraction_lengthnmelseParse.fraction_lengthnm|None->Css.Pct0.moduleHandler=structopenStyleopenCss(** Local position utility type *)typet=|Position_static|Position_relative|Position_absolute|Position_fixed|Position_sticky|Inset_0|Inset_x_0|Inset_y_0|Inset_auto|Inset_full|Neg_inset_full|Inset_x_auto|Inset_x_full|Neg_inset_x_full|Inset_y_auto|Inset_y_full|Neg_inset_y_full|Pos_spacingofSide.t*Style.spacing|Neg_pos_spacingofSide.t*Style.spacing(* fractional or px spacing step on any inset side *)|Pos_fractionofSide.t*string|Neg_pos_fractionofSide.t*string(* raw fraction suffix kept for the class name *)|Pos_arbitraryofSide.t*string*Css.length(* raw bracket suffix kept for the class name, value already signed *)|Neg_pos_arbitraryofSide.t*string*Css.length|Pos_rawofSide.t*string*string(* top-[foo]: an inset side writes one longhand, so a bracket no length
reader took still names it and the value is forwarded verbatim -
Tailwind's token-stream contract. *)|Neg_pos_rawofSide.t*string*string|Pos_namedofSide.t*string|Neg_pos_namedofSide.t*string(* theme token reference like inset-shadowned *)|Insetofint|Inset_xofint|Inset_yofint(* Logical position utilities: inset-s, inset-e, inset-bs, inset-be *)|Inset_sofint|Inset_s_auto|Inset_s_full|Neg_inset_s_full|Inset_eofint|Inset_e_auto|Inset_e_full|Neg_inset_e_full|Inset_bsofint|Inset_bs_auto|Inset_bs_full|Neg_inset_bs_full|Inset_beofint|Inset_be_auto|Inset_be_full|Neg_inset_be_full|Topofint|Top_auto|Top_full|Neg_top_full|Rightofint|Right_auto|Right_full|Neg_right_full|Bottomofint|Bottom_auto|Bottom_full|Neg_bottom_full|Leftofint|Left_auto|Left_full|Neg_left_full|Start_auto|Start_full|Neg_start_full|End_auto|End_full|Neg_end_full(** Extensible variant for position utilities *)letname="position"(** Priority for position utilities *)letpriority_=0(** {1 Utility Conversion Functions} *)letto_styletheme=letspacing_valuen=spacing_value~themeninfunction|Position_static->style[positionStatic]|Position_relative->style[positionRelative]|Position_absolute->style[positionAbsolute]|Position_fixed->style[positionFixed]|Position_sticky->style[positionSticky]|Inset_0->letdecl,zero_value=spacing_value0instyle(decl::[Css.inset[zero_value]])|Inset_x_0->letdecl,zero_value=spacing_value0instyle(decl::[Css.inset_inline[zero_value]])|Inset_y_0->letdecl,zero_value=spacing_value0instyle(decl::[Css.inset_block[zero_value]])|Inset_auto->style[Css.inset[Auto]]|Inset_full->style[Css.inset[Pct100.0]]|Neg_inset_full->style[Css.inset[Pct(-100.0)]]|Inset_x_auto->style[Css.inset_inline[Auto]]|Inset_x_full->style[Css.inset_inline[Pct100.0]]|Neg_inset_x_full->style[Css.inset_inline[Pct(-100.0)]]|Inset_y_auto->style[Css.inset_block[Auto]]|Inset_y_full->style[Css.inset_block[Pct100.0]]|Neg_inset_y_full->style[Css.inset_block[Pct(-100.0)]]|Pos_spacing(side,sp)->pos_spacing_style~themesidesp|Neg_pos_spacing(side,sp)->neg_pos_spacing_style~themesidesp|Pos_fraction(side,f)->style(Side.declarationsside(frac_length~neg:falsef))|Neg_pos_fraction(side,f)->style(Side.declarationsside(frac_length~neg:truef))|Pos_arbitrary(side,_,len)|Neg_pos_arbitrary(side,_,len)->style(Side.declarationssidelen)|Pos_raw(side,_,value)|Neg_pos_raw(side,_,value)->style(List.filter_map(funproperty->Parse.opaque_declarationpropertyvalue)(Side.propertiesside))|Pos_named(side,name)->style(Side.declarationsside(named_inset_valuethemename))|Neg_pos_named(side,name)->style(Side.declarationsside(negate_length(named_inset_valuethemename)))|Insetn->letdecl,value=spacing_valueninstyle(decl::[Css.inset[value]])|Inset_xn->letdecl,value=spacing_valueninstyle(decl::[Css.inset_inline[value]])|Inset_yn->letdecl,value=spacing_valueninstyle(decl::[Css.inset_block[value]])(* inset-s = inset-inline-start *)|Inset_sn->letdecl,value=spacing_valueninstyle(decl::[Css.inset_inline_startvalue])|Inset_s_auto->style[Css.inset_inline_startAuto]|Inset_s_full->style[Css.inset_inline_start(Pct100.0)]|Neg_inset_s_full->style[Css.inset_inline_start(Pct(-100.0))](* inset-e = inset-inline-end *)|Inset_en->letdecl,value=spacing_valueninstyle(decl::[Css.inset_inline_endvalue])|Inset_e_auto->style[Css.inset_inline_endAuto]|Inset_e_full->style[Css.inset_inline_end(Pct100.0)]|Neg_inset_e_full->style[Css.inset_inline_end(Pct(-100.0))](* inset-bs = inset-block-start *)|Inset_bsn->letdecl,value=spacing_valueninstyle(decl::[Css.inset_block_startvalue])|Inset_bs_auto->style[Css.inset_block_startAuto]|Inset_bs_full->style[Css.inset_block_start(Pct100.0)]|Neg_inset_bs_full->style[Css.inset_block_start(Pct(-100.0))](* inset-be = inset-block-end *)|Inset_ben->letdecl,value=spacing_valueninstyle(decl::[Css.inset_block_endvalue])|Inset_be_auto->style[Css.inset_block_endAuto]|Inset_be_full->style[Css.inset_block_end(Pct100.0)]|Neg_inset_be_full->style[Css.inset_block_end(Pct(-100.0))]|Topn->letdecl,value=spacing_valueninstyle(decl::[Css.topvalue])|Top_auto->style[Css.topAuto]|Top_full->style[Css.top(Pct100.0)]|Neg_top_full->style[Css.top(Pct(-100.0))]|Rightn->letdecl,value=spacing_valueninstyle(decl::[Css.rightvalue])|Right_auto->style[Css.rightAuto]|Right_full->style[Css.right(Pct100.0)]|Neg_right_full->style[Css.right(Pct(-100.0))]|Bottomn->letdecl,value=spacing_valueninstyle(decl::[Css.bottomvalue])|Bottom_auto->style[Css.bottomAuto]|Bottom_full->style[Css.bottom(Pct100.0)]|Neg_bottom_full->style[Css.bottom(Pct(-100.0))]|Leftn->letdecl,value=spacing_valueninstyle(decl::[Css.leftvalue])|Left_auto->style[Css.leftAuto]|Left_full->style[Css.left(Pct100.0)]|Neg_left_full->style[Css.left(Pct(-100.0))]|Start_auto->style[Css.inset_inline_startAuto]|Start_full->style[Css.inset_inline_start(Pct100.0)]|Neg_start_full->style[Css.inset_inline_start(Pct(-100.0))]|End_auto->style[Css.inset_inline_endAuto]|End_full->style[Css.inset_inline_end(Pct100.0)]|Neg_end_full->style[Css.inset_inline_end(Pct(-100.0))]letint_of_string_with_sign=Parse.int_any(* Tailwind assigns one order slot to every candidate that writes the same
position property, so [start] and [inset-s] share theirs. The shared
candidate-name tiebreak then handles negatives, arbitrary values,
fractions, keywords and scale values. *)letside_slot=function|Side.Inset->1_000_000|Side.Inset_x->2_000_000|Side.Inset_y->3_000_000|Side.Inset_s|Side.Start->4_000_000|Side.Inset_e|Side.End->5_000_000|Side.Inset_bs->6_000_000|Side.Inset_be->7_000_000|Side.Top->8_000_000|Side.Right->9_000_000|Side.Bottom->10_000_000|Side.Left->11_000_000letsuborder=function|Position_absolute->0|Position_fixed->1|Position_relative->2|Position_static->3|Position_sticky->4|Pos_spacing(side,_)|Neg_pos_spacing(side,_)|Pos_fraction(side,_)|Neg_pos_fraction(side,_)|Pos_arbitrary(side,_,_)|Neg_pos_arbitrary(side,_,_)|Pos_raw(side,_,_)|Neg_pos_raw(side,_,_)|Pos_named(side,_)|Neg_pos_named(side,_)->side_slotside|Inset_0|Inset_auto|Inset_full|Neg_inset_full|Inset_->side_slotSide.Inset|Inset_x_0|Inset_x_auto|Inset_x_full|Neg_inset_x_full|Inset_x_->side_slotSide.Inset_x|Inset_y_0|Inset_y_auto|Inset_y_full|Neg_inset_y_full|Inset_y_->side_slotSide.Inset_y|Inset_s_|Inset_s_auto|Inset_s_full|Neg_inset_s_full|Start_auto|Start_full|Neg_start_full->side_slotSide.Inset_s|Inset_e_|Inset_e_auto|Inset_e_full|Neg_inset_e_full|End_auto|End_full|Neg_end_full->side_slotSide.Inset_e|Inset_bs_|Inset_bs_auto|Inset_bs_full|Neg_inset_bs_full->side_slotSide.Inset_bs|Inset_be_|Inset_be_auto|Inset_be_full|Neg_inset_be_full->side_slotSide.Inset_be|Top_|Top_auto|Top_full|Neg_top_full->side_slotSide.Top|Right_|Right_auto|Right_full|Neg_right_full->side_slotSide.Right|Bottom_|Bottom_auto|Bottom_full|Neg_bottom_full->side_slotSide.Bottom|Left_|Left_auto|Left_full|Neg_left_full->side_slotSide.Left(* A named inset (top-header) is valid only when the theme defines the token.
Tailwind resolves the name against the [--inset-*] namespace, then falls
back to [--spacing-*]; without this gate a stray source token like
[top-level] would parse as a utility and emit a bogus value. *)letis_named_insetthemen=Scheme.theme_value(Sometheme)("inset-"^n)<>None||Scheme.theme_value(Sometheme)("spacing-"^n)<>Noneletof_classthemeclass_name=(* Every numeric inset step reads the spacing scale, so both readers below
answer for the whole family once the theme removes it. *)letint_of_string_with_signn=matchint_of_string_with_signnwith|OkxwhenTheme.has_spacing_step~theme(Float.abs(float_of_intx))->Okx|Ok_->Error(`Msg"the spacing scale has no such step")|Error_aserror->errorinletparse_pos_spacingn:Style.spacingoption=matchparse_pos_spacingnwith|Some(`Remf)whennot(Theme.has_spacing_step~theme(f/.0.25))->None|parsed->parsedin(* [start-4] and [start-0.5] share one constructor: the logical inline sides
have no separate integer step, so their reader spans the scale. *)letparse_scale_stepn:Style.spacingoption=matchint_of_string_with_signnwith|Okxwhenx>=0->Some(`Rem(float_of_intx*.0.25))|Ok_->None|Error_->parse_pos_spacingnin(* The tail every inset side shares once its own scale reader has declined:
an arbitrary bracket, then a name the theme binds. Both signs read the
same tail; the negative writes the name out as [calc(var(--x) * -1)]. *)letarbitrary_or_namedsiden=matchparse_bracket_lengthnwith|Somelen->Ok(Pos_arbitrary(side,n,len))|NonewhenParse.is_valid_theme_namen&&is_named_insetthemen->Ok(Pos_named(side,n))|None->(matchbracket_token_streamnwith|Someraw->Ok(Pos_raw(side,n,raw))|None->Error(`Msg"invalid"))inletneg_arbitrary_or_namedsiden=matchparse_bracket_length~negate:truenwith|Somelen->Ok(Neg_pos_arbitrary(side,n,len))|NonewhenParse.is_valid_theme_namen&&is_named_insetthemen->Ok(Neg_pos_named(side,n))|None->((* [calc(<value> * -1)] is what a negated arbitrary writes whatever
the unit, and a value no reader took is no exception. *)matchbracket_token_streamnwith|Someraw->Ok(Neg_pos_raw(side,n,"calc("^raw^" * -1)"))|None->Error(`Msg"invalid"))in(* A fraction, then the side's own scale, then the shared tail. Every inset
side reads all three, so the suffix vocabulary is one function of the
reader the side uses for the scale. *)letvalue~negscalesiden=iffrac_validnthenOk(ifnegthenNeg_pos_fraction(side,n)elsePos_fraction(side,n))elsematchscalenwith|Somesp->Ok(ifnegthenNeg_pos_spacing(side,sp)elsePos_spacing(side,sp))|Nonewhenneg->neg_arbitrary_or_namedsiden|None->arbitrary_or_namedsideninletscale_or_arbitrarysiden=value~neg:falseparse_pos_spacingsideninletneg_scale_or_arbitrarysiden=value~neg:trueparse_pos_spacingsideninletstep_or_arbitrarysiden=value~neg:falseparse_scale_stepsideninletneg_step_or_arbitrarysiden=value~neg:trueparse_scale_stepsideninletparts=Parse.split_classclass_nameinmatchpartswith|["static"]->OkPosition_static|["relative"]->OkPosition_relative|["absolute"]->OkPosition_absolute|["fixed"]->OkPosition_fixed|["sticky"]->OkPosition_sticky|["inset";"0"]->OkInset_0|["inset";"x";"0"]->OkInset_x_0|["inset";"y";"0"]->OkInset_y_0|["inset";"auto"]->OkInset_auto|["inset";"full"]->OkInset_full|["";"inset";"full"]->OkNeg_inset_full|["inset";"x";"auto"]->OkInset_x_auto|["inset";"x";"full"]->OkInset_x_full|["";"inset";"x";"full"]->OkNeg_inset_x_full|["inset";"y";"auto"]->OkInset_y_auto|["inset";"y";"full"]->OkInset_y_full|["";"inset";"y";"full"]->OkNeg_inset_y_full|["inset";"x";n]->(matchint_of_string_with_signnwith|Okx->Ok(Inset_xx)|Error_->scale_or_arbitrarySide.Inset_xn)|["";"inset";"x";n]->(matchint_of_string_with_signnwith|Okx->Ok(Inset_x(-x))|Error_->neg_scale_or_arbitrarySide.Inset_xn)|["inset";"y";n]->(matchint_of_string_with_signnwith|Okx->Ok(Inset_yx)|Error_->scale_or_arbitrarySide.Inset_yn)|["";"inset";"y";n]->(matchint_of_string_with_signnwith|Okx->Ok(Inset_y(-x))|Error_->neg_scale_or_arbitrarySide.Inset_yn)(* inset-s = inset-inline-start *)|["inset";"s";"auto"]->OkInset_s_auto|["inset";"s";"full"]->OkInset_s_full|["";"inset";"s";"full"]->OkNeg_inset_s_full|["inset";"s";n]->(matchint_of_string_with_signnwith|Okx->Ok(Inset_sx)|Error_->scale_or_arbitrarySide.Inset_sn)|["";"inset";"s";n]->(matchint_of_string_with_signnwith|Okx->Ok(Inset_s(-x))|Error_->neg_scale_or_arbitrarySide.Inset_sn)(* inset-e = inset-inline-end *)|["inset";"e";"auto"]->OkInset_e_auto|["inset";"e";"full"]->OkInset_e_full|["";"inset";"e";"full"]->OkNeg_inset_e_full|["inset";"e";n]->(matchint_of_string_with_signnwith|Okx->Ok(Inset_ex)|Error_->scale_or_arbitrarySide.Inset_en)|["";"inset";"e";n]->(matchint_of_string_with_signnwith|Okx->Ok(Inset_e(-x))|Error_->neg_scale_or_arbitrarySide.Inset_en)(* inset-bs = inset-block-start *)|["inset";"bs";"auto"]->OkInset_bs_auto|["inset";"bs";"full"]->OkInset_bs_full|["";"inset";"bs";"full"]->OkNeg_inset_bs_full|["inset";"bs";n]->(matchint_of_string_with_signnwith|Okx->Ok(Inset_bsx)|Error_->scale_or_arbitrarySide.Inset_bsn)|["";"inset";"bs";n]->(matchint_of_string_with_signnwith|Okx->Ok(Inset_bs(-x))|Error_->neg_scale_or_arbitrarySide.Inset_bsn)(* inset-be = inset-block-end *)|["inset";"be";"auto"]->OkInset_be_auto|["inset";"be";"full"]->OkInset_be_full|["";"inset";"be";"full"]->OkNeg_inset_be_full|["inset";"be";n]->(matchint_of_string_with_signnwith|Okx->Ok(Inset_bex)|Error_->scale_or_arbitrarySide.Inset_ben)|["";"inset";"be";n]->(matchint_of_string_with_signnwith|Okx->Ok(Inset_be(-x))|Error_->neg_scale_or_arbitrarySide.Inset_ben)|["inset";n]whenn<>"shadow"&&n<>"ring"&&n<>"x"&&n<>"y"&&n<>"s"&&n<>"e"&&n<>"bs"&&n<>"be"->(matchint_of_string_with_signnwith|Okx->Ok(Insetx)|Error_->scale_or_arbitrarySide.Insetn)|["";"inset";n]->(matchint_of_string_with_signnwith|Okx->Ok(Inset(-x))|Error_->neg_scale_or_arbitrarySide.Insetn)|["top";"auto"]->OkTop_auto|["top";"full"]->OkTop_full|["";"top";"full"]->OkNeg_top_full|["top";n]->(matchint_of_string_with_signnwith|Okx->Ok(Topx)|Error_->scale_or_arbitrarySide.Topn)|["";"top";n]->(matchint_of_string_with_signnwith|Okx->Ok(Top(-x))|Error_->neg_scale_or_arbitrarySide.Topn)|["right";"auto"]->OkRight_auto|["right";"full"]->OkRight_full|["";"right";"full"]->OkNeg_right_full|["right";n]->(matchint_of_string_with_signnwith|Okx->Ok(Rightx)|Error_->scale_or_arbitrarySide.Rightn)|["";"right";n]->(matchint_of_string_with_signnwith|Okx->Ok(Right(-x))|Error_->neg_scale_or_arbitrarySide.Rightn)|["bottom";"auto"]->OkBottom_auto|["bottom";"full"]->OkBottom_full|["";"bottom";"full"]->OkNeg_bottom_full|["bottom";n]->(matchint_of_string_with_signnwith|Okx->Ok(Bottomx)|Error_->scale_or_arbitrarySide.Bottomn)|["";"bottom";n]->(matchint_of_string_with_signnwith|Okx->Ok(Bottom(-x))|Error_->neg_scale_or_arbitrarySide.Bottomn)|["left";"auto"]->OkLeft_auto|["left";"full"]->OkLeft_full|["";"left";"full"]->OkNeg_left_full|["left";n]->(matchint_of_string_with_signnwith|Okx->Ok(Leftx)|Error_->scale_or_arbitrarySide.Leftn)|["";"left";n]->(matchint_of_string_with_signnwith|Okx->Ok(Left(-x))|Error_->neg_scale_or_arbitrarySide.Leftn)|["start";"auto"]->OkStart_auto|["start";"full"]->OkStart_full|["";"start";"full"]->OkNeg_start_full|["start";n]->step_or_arbitrarySide.Startn|["";"start";n]->neg_step_or_arbitrarySide.Startn|["end";"auto"]->OkEnd_auto|["end";"full"]->OkEnd_full|["";"end";"full"]->OkNeg_end_full|["end";n]->step_or_arbitrarySide.Endn|["";"end";n]->neg_step_or_arbitrarySide.Endn|_->Error(`Msg"Not a position utility")letto_class=function|Position_static->"static"|Position_relative->"relative"|Position_absolute->"absolute"|Position_fixed->"fixed"|Position_sticky->"sticky"|Inset_0->"inset-0"|Inset_x_0->"inset-x-0"|Inset_y_0->"inset-y-0"|Inset_auto->"inset-auto"|Inset_full->"inset-full"|Neg_inset_full->"-inset-full"|Inset_x_auto->"inset-x-auto"|Inset_x_full->"inset-x-full"|Neg_inset_x_full->"-inset-x-full"|Inset_y_auto->"inset-y-auto"|Inset_y_full->"inset-y-full"|Neg_inset_y_full->"-inset-y-full"|Pos_spacing(side,sp)->Side.nameside^"-"^Spacing.pp_spacing_suffixsp|Neg_pos_spacing(side,sp)->"-"^Side.nameside^"-"^Spacing.pp_spacing_suffixsp|Pos_arbitrary(side,raw,_)|Pos_raw(side,raw,_)->Side.nameside^"-"^raw|Neg_pos_arbitrary(side,raw,_)|Neg_pos_raw(side,raw,_)->"-"^Side.nameside^"-"^raw|Pos_fraction(side,f)->Side.nameside^"-"^f|Neg_pos_fraction(side,f)->"-"^Side.nameside^"-"^f|Pos_named(side,name)->Side.nameside^"-"^name|Neg_pos_named(side,name)->"-"^Side.nameside^"-"^name|Insetn->letprefix=ifn<0then"-"else""inprefix^"inset-"^string_of_int(absn)|Inset_xn->letprefix=ifn<0then"-"else""inprefix^"inset-x-"^string_of_int(absn)|Inset_yn->letprefix=ifn<0then"-"else""inprefix^"inset-y-"^string_of_int(absn)(* inset-s = inset-inline-start *)|Inset_sn->letprefix=ifn<0then"-"else""inprefix^"inset-s-"^string_of_int(absn)|Inset_s_auto->"inset-s-auto"|Inset_s_full->"inset-s-full"|Neg_inset_s_full->"-inset-s-full"(* inset-e = inset-inline-end *)|Inset_en->letprefix=ifn<0then"-"else""inprefix^"inset-e-"^string_of_int(absn)|Inset_e_auto->"inset-e-auto"|Inset_e_full->"inset-e-full"|Neg_inset_e_full->"-inset-e-full"(* inset-bs = inset-block-start *)|Inset_bsn->letprefix=ifn<0then"-"else""inprefix^"inset-bs-"^string_of_int(absn)|Inset_bs_auto->"inset-bs-auto"|Inset_bs_full->"inset-bs-full"|Neg_inset_bs_full->"-inset-bs-full"(* inset-be = inset-block-end *)|Inset_ben->letprefix=ifn<0then"-"else""inprefix^"inset-be-"^string_of_int(absn)|Inset_be_auto->"inset-be-auto"|Inset_be_full->"inset-be-full"|Neg_inset_be_full->"-inset-be-full"|Top_auto->"top-auto"|Top_full->"top-full"|Neg_top_full->"-top-full"|Topn->letprefix=ifn<0then"-"else""inprefix^"top-"^string_of_int(absn)|Right_auto->"right-auto"|Right_full->"right-full"|Neg_right_full->"-right-full"|Rightn->letprefix=ifn<0then"-"else""inprefix^"right-"^string_of_int(absn)|Bottom_auto->"bottom-auto"|Bottom_full->"bottom-full"|Neg_bottom_full->"-bottom-full"|Bottomn->letprefix=ifn<0then"-"else""inprefix^"bottom-"^string_of_int(absn)|Left_auto->"left-auto"|Left_full->"left-full"|Neg_left_full->"-left-full"|Leftn->letprefix=ifn<0then"-"else""inprefix^"left-"^string_of_int(absn)|Start_auto->"start-auto"|Start_full->"start-full"|Neg_start_full->"-start-full"|End_auto->"end-auto"|End_full->"end-full"|Neg_end_full->"-end-full"letexamples=[Position_static;Inset_0;Inset_x_0;Inset_y_0;Top_auto;Right_auto;Bottom_auto;Left_auto;Start_auto;End_auto;]endopenHandlermoduleUtility_factory=Utility.Make(Handler)(** Register handler with Utility system *)(** Public API combinators *)letutility=Utility_factory.vletstatic=utilityPosition_staticletrelative=utilityPosition_relativeletabsolute=utilityPosition_absoluteletfixed=utilityPosition_fixedletsticky=utilityPosition_stickyletinsetn=utility(Insetn)letinset_0=utilityInset_0letinset_xn=utility(Inset_xn)letinset_x_0=utilityInset_x_0letinset_yn=utility(Inset_yn)letinset_y_0=utilityInset_y_0lettopn=utility(Topn)letrightn=utility(Rightn)letbottomn=utility(Bottomn)letleftn=utility(Leftn)lettop_1_2=utility(Pos_fraction(Side.Top,"1/2"))letleft_1_2=utility(Pos_fraction(Side.Left,"1/2"))(* A half-step (or any float) spacing value on a physical/axis inset side:
[Pos_spacing]/[Neg_pos_spacing] already carry the scale factor as a
[Style.spacing], the same representation [top-0.5] parses to from a class
string. *)letpos_primesidef=iff<0.0thenutility(Neg_pos_spacing(side,`Rem(Float.absf*.0.25)))elseutility(Pos_spacing(side,`Rem(f*.0.25)))letinset'f=pos_primeSide.Insetfletinset_x'f=pos_primeSide.Inset_xfletinset_y'f=pos_primeSide.Inset_yflettop'f=pos_primeSide.Topfletright'f=pos_primeSide.Rightfletbottom'f=pos_primeSide.Bottomfletleft'f=pos_primeSide.Leftf