123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910moduleStable=structopenCore.Core_stablemoduleV1=struct(** (field * value) list. Where value should be escaped / quoted
as necessary as per https://www.w3.org/TR/CSS21/syndata.html#rule-sets. *)typet=(string*string)list[@@derivingsexp,compare,bin_io]endendopenCoreincludeStable.V1typecss_global_values=[`Inherit|`Initial][@@derivingsexp,bin_io,compare]modulePrivate=structletfloat_to_string_with_fixed=ref(fundigitsf->sprintf"%.*f"digitsf)endletf2sdigitsf=!Private.float_to_string_with_fixeddigitsfmoduleColor=structmoduleT=structmoduleRGBA=structtypet={r:int;g:int;b:int;a:Percent.toption}[@@derivingsexp,bin_io,compare,fields]letcreate~r~g~b?a()={r;g;b;a}endmoduleHSLA=structtypet={h:int;s:Percent.t;l:Percent.t;a:Percent.toption}[@@derivingsexp,bin_io,compare,fields]letcreate~h~s~l?a()={h;s;l;a}endtypet=[`RGBAofRGBA.t|`HSLAofHSLA.t|`Nameofstring|`Hexofstring|`Varofstring|css_global_values][@@derivingsexp,bin_io,compare]endincludeTincludeSexpable.To_stringable(T)letto_string_css:[<t]->string=function|`Inherit->"inherit"|`Initial->"initial"|`RGBA{RGBA.r;g;b;a}->(matchawith|None->[%string"rgb(%{r#Int},%{g#Int},%{b#Int})"]|Somep->[%string"rgba(%{r#Int},%{g#Int},%{b#Int},%{f2s 2 (Percent.to_multp)})"])|`HSLA{HSLA.h;s;l;a}->(matchawith|None->[%string"hsl(%{h#Int},%{f2s 0 (Percent.to_percentage s)}%,%{f2s 0 \
(Percent.to_percentage l)}%)"]|Somep->[%string"hsla(%{h#Int},%{f2s 0 (Percent.to_percentage s)}%,%{f2s 0 \
(Percent.to_percentage l)}%,%{f2s 2 (Percent.to_mult p)})"])|`Namename->name|`Hexhex->hex|`Varvar->[%string"var(%{var})"];;endmoduleAlignment=structtypet=[`Left|`Right|`Center(* horizontal *)|`Top|`Bottom|`Middle(* vertical *)|`Justify(* text-align (in addition to [horizontal]) *)|`Super(* vertical *)|`Sub(* vertical *)|css_global_values][@@derivingbin_io,compare]letto_string_css=function|`Justify->"justify"|`Top->"top"|`Right->"right"|`Left->"left"|`Center->"center"|`Inherit->"inherit"|`Middle->"middle"|`Bottom->"bottom"|`Super->"super"|`Sub->"sub"|`Initial->"initial";;endmoduleLength=structtypet=[`Rawofstring|`Choffloat|`Remoffloat|`Emofint|`Em_floatoffloat|`PercentofPercent.t|`Ptoffloat|`Pxofint|`Px_floatoffloat|`VhofPercent.t|`VwofPercent.t|css_global_values][@@derivingsexp,bin_io,compare]letto_string_css=function|`Raws->s|`Chc->[%string"%{f2s 2 c}ch"]|`Remf->[%string"%{f2s 2 f}rem"]|`Emi->[%string"%{i#Int}em"]|`Em_floatf->[%string"%{f2s 2 f}em"]|`Percentp->[%string"%{f2s 2 (Percent.to_percentagep)}%"]|`Ptp->[%string"%{f2s 2 p}pt"]|`Pxi->[%string"%{i#Int}px"]|`Px_floatf->[%string"%{f2s 2 f}px"]|`Vhp->[%string"%{f2s 2 (Percent.to_percentagep)}vh"]|`Vwp->[%string"%{f2s 2 (Percent.to_percentagep)}vw"]|`Inherit->"inherit"|`Initial->"initial";;letpercent100=`Percent(Percent.of_percentage100.)endmoduleAuto_or_length=structtypet=[`Auto|Length.t][@@derivingbin_io,compare,sexp]letto_string_css=function|`Auto->"auto"|#Length.tasl->Length.to_string_cssl;;endletvalue_mapo~f=Option.value_mapo~default:""~fletcombinet1t2=t1@t2let(@>)=combineletconcatl=List.concatlletto_string_list=Fn.idletto_string_csst=List.mapt~f:(fun(field,value)->[%string"%{field}: %{value}"])|>String.concat~sep:";";;letof_string_css_exns=Css_parser.parse_declaration_lists|>Or_error.ok_exn(** create_raw creates a single field, value pair. It assumes that the value is a valid
css value. As such it is unsafe to use with arbitrary value strings. But for the
vast majority of combinators in this module it is the right thing to use, as we know
by construction that the values do not need quoting / escaping. *)letcreate_raw~field~value=[field,value]moduleExpert=structletshould_validate=reffalseendletcreate~field~value=if!Expert.should_validatethenCss_parser.validate_valuevalue|>Or_error.ok_exn;create_raw~field~value;;letempty=[]letis_empty=List.is_emptyletcreate_placementnamelength=create~field:name~value:(Length.to_string_csslength)letleft=create_placement"left"lettop=create_placement"top"letbottom=create_placement"bottom"letright=create_placement"right"letposition?top:tp?bottom:bt?left:lt?right:rtpos=letpos=letvalue=matchposwith|`Static->"static"|`Absolute->"absolute"|`Sticky->"sticky"|`Relative->"relative"|`Fixed->"fixed"increate~field:"position"~valueinletconvertopt_lf=Option.value_mapopt_l~default:empty~finconcat[pos;converttptop;convertltleft;convertrtright;convertbtbottom];;letbox_sizingv=letvalue=matchvwith|`Content_box->"content-box"|`Border_box->"border-box"|`Inherit->"inherit"|`Initial->"initial"increate_raw~field:"box-sizing"~value;;letdisplayv=letvalue=matchvwith|`Inline->"inline"|`Block->"block"|`Inline_block->"inline-block"|`List_item->"list-item"|`Table->"table"|`Inline_table->"inline-table"|`None->"none"|`Flex->"flex"|`Inline_flex->"inline-flex"|`Inline_grid->"inline-grid"|`Inherit->"inherit"|`Initial->"initial"increate_raw~field:"display"~value;;letvisibilityv=letvalue=matchvwith|`Visible->"visible"|`Hidden->"hidden"|`Collapse->"collapse"|`Inherit->"inherit"|`Initial->"initial"increate_raw~field:"visibility"~value;;typeoverflow=[`Visible|`Hidden|`Scroll|`Auto|css_global_values]letmake_overflowfieldv=letvalue=matchvwith|`Visible->"visible"|`Hidden->"hidden"|`Scroll->"scroll"|`Auto->"auto"|`Inherit->"inherit"|`Initial->"initial"increate_raw~field~value;;letoverflow=make_overflow"overflow"letoverflow_x=make_overflow"overflow-x"letoverflow_y=make_overflow"overflow-y"letz_indexi=create_raw~field:"z-index"~value:(Int.to_stringi)letopacityi=create_raw~field:"opacity"~value:(f2s6i)letcreate_length_fieldfieldl=create_raw~field~value:(Auto_or_length.to_string_cssl);;letwhite_spacev=letvalue=matchvwith|`Normal->"normal"|`Nowrap->"nowrap"|`Pre->"pre"|`Pre_line->"pre-line"|`Pre_wrap->"pre-wrap"|`Initial->"initial"|`Inherit->"inherit"increate~field:"white-space"~value;;typefont_style=[`Normal|`Italic|`Oblique|css_global_values]typefont_weight=[`Normal|`Bold|`Bolder|`Lighter|`Numberofint|css_global_values]typefont_variant=[`Normal|`Small_caps|css_global_values]letfont_size=create_length_field"font-size"letfont_familyl=create_raw~field:"font-family"~value:(String.concatl~sep:",")letfont_styles=letvalue=matchswith|`Normal->"normal"|`Italic->"italic"|`Oblique->"oblique"|`Inherit->"inherit"|`Initial->"initial"increate_raw~field:"font-style"~value;;letfont_weights=letvalue=matchswith|`Numberi->Int.to_stringi|`Bold->"bold"|`Normal->"normal"|`Lighter->"lighter"|`Inherit->"inherit"|`Bolder->"bolder"|`Initial->"initial"increate_raw~field:"font-weight"~value;;letbold=font_weight`Boldletfont_variants=letvalue=matchswith|`Normal->"normal"|`Small_caps->"small-caps"|`Inherit->"inherit"|`Initial->"initial"increate_raw~field:"font-variant"~value;;letfont~size~family?style?weight?variant()=[Some(font_sizesize);Some(font_familyfamily);Option.mapstyle~f:font_style;Option.mapweight~f:font_weight;Option.mapvariant~f:font_variant]|>List.filter_opt|>concat;;letcreate_with_color~field~color=create_raw~field~value:(Color.to_string_csscolor)letcolorcolor=create_with_color~field:"color"~colorletbackground_colorcolor=create_with_color~field:"background-color"~colorletfillcolor=create_with_color~field:"fill"~colortypestops=(Percent.t*Color.t)listtypelinear_gradient={direction:[`Degofint];stops:stops}typeradial_gradient={stops:stops}typebackground_image=[`Urlofstring|`Linear_gradientoflinear_gradient|`Radial_gradientofradial_gradient]typetext_align=[`Left|`Right|`Center|`Justify|css_global_values]letstops_to_stringstops=List.mapstops~f:(fun(pct,color)->(* Note: Percent.to_string produced e.g. "0x", "1x", won't work here. *)[%string"%{Color.to_string_css color} %{f2s 6 (Percent.to_percentagepct)}%"])|>String.concat~sep:", ";;letbackground_imagespec=letvalue=matchspecwith|`Urlurl->[%string"url(%{url})"]|`Linear_gradient{direction=`Degdirection;stops}->[%string"linear-gradient(%{direction#Int}deg, %{stops_to_string stops})"]|`Radial_gradient{stops}->[%string"radial-gradient(%{stops_to_string stops})"]increate_raw~field:"background-image"~value;;letcreate_alignmentfielda=create_raw~field~value:(Alignment.to_string_css(a:>Alignment.t));;lettext_align=create_alignment"text-align"lethorizontal_align=create_alignment"horizontal-align"letvertical_align=create_alignment"vertical-align"letfloatf=letvalue=matchfwith|`None->"none"|`Left->"left"|`Right->"right"|`Inherit->"inherit"|`Initial->"initial"increate_raw~field:"float"~value;;letline_height=create_length_field"line-height"letwidth=create_length_field"width"letmin_width=create_length_field"min-width"letmax_width=create_length_field"max-width"letheight=create_length_field"height"letmin_height=create_length_field"min-height"letmax_height=create_length_field"max-height"letpadding_top=create_length_field"padding-top"letpadding_bottom=create_length_field"padding-bottom"letpadding_left=create_length_field"padding-left"letpadding_right=create_length_field"padding-right"letpadding?top?bottom?left?right()=letm=Option.mapin[mtop~f:padding_top;mbottom~f:padding_bottom;mleft~f:padding_left;mright~f:padding_right]|>List.filter_opt|>concat;;letuniform_paddingl=padding~top:l~bottom:l~left:l~right:l()letmargin_top=create_length_field"margin-top"letmargin_bottom=create_length_field"margin-bottom"letmargin_left=create_length_field"margin-left"letmargin_right=create_length_field"margin-right"letmargin?top?bottom?left?right()=letm=Option.mapin[mtop~f:margin_top;mbottom~f:margin_bottom;mleft~f:margin_left;mright~f:margin_right]|>List.filter_opt|>concat;;letuniform_marginl=margin~top:l~bottom:l~left:l~right:l()letrow_gap=create_length_field"row-gap"letcolumn_gap=create_length_field"column-gap"typeborder_style=[`None|`Hidden|`Dotted|`Dashed|`Solid|`Double|`Groove|`Ridge|`Inset|`Outset|css_global_values](** Concat 2 values with a space in between. If either is the empty string
don't put in unnecessary whitespace. *)letconcat2vv1v2=matchv1,v2with|"",x->x|x,""->x|x,y->x^" "^y;;(** Concat up to 3 values with spaces in between. *)letconcat3vv1v2v3=concat2v(concat2vv1v2)v3letborder_value?width?color~(style:border_style)()=letstyle=matchstylewith|`Ridge->"ridge"|`Outset->"outset"|`None->"none"|`Groove->"groove"|`Dashed->"dashed"|`Inherit->"inherit"|`Inset->"inset"|`Hidden->"hidden"|`Double->"double"|`Dotted->"dotted"|`Initial->"initial"|`Solid->"solid"inletwidth=value_mapwidth~f:Length.to_string_cssinletcolor=value_mapcolor~f:Color.to_string_cssinconcat3vwidthstylecolor;;letcreate_border?side()=letfield=matchsidewith|Some`Top->"border-top"|Some`Bottom->"border-bottom"|Some`Right->"border-right"|Some`Left->"border-left"|None->"border"infun?width?color~style()->create_raw~field~value:(border_value?width?color~style());;letborder_top?width?color~style()=create_border~side:`Top()?width?color~style();;letborder_bottom?width?color~style()=create_border~side:`Bottom()?width?color~style();;letborder_left?width?color~style()=create_border~side:`Left()?width?color~style();;letborder_right?width?color~style()=create_border~side:`Right()?width?color~style();;letborder?width?color~style()=create_border?side:None?width?color~style()()letoutline?width?color~style()=create_raw~field:"outline"~value:(border_value?width?color~style());;letborder_collapsev=letvalue=matchvwith|`Separate->"separate"|`Collapse->"collapse"|`Inherit->"inherit"|`Initial->"initial"increate_raw~field:"border-collapse"~value;;letborder_spacing=create_length_field"border-spacing"letborder_radiusl=create~field:"border-radius"~value:(Length.to_string_cssl)typetext_decoration_line=[`None|`Underline|`Overline|`Line_through|css_global_values][@@derivingsexp]typetext_decoration_style=[`Solid|`Double|`Dotted|`Dashed|`Wavy|css_global_values][@@derivingsexp]lettext_decoration?style?color~line()=letvalue=letline=List.mapline~f:(function|`Line_through->"line-through"|`None->"none"|`Inherit->"inherit"|`Overline->"overline"|`Underline->"underline"|`Initial->"initial")|>String.concat~sep:" "inletstyle=matchstylewith|None->""|Some`Solid->"solid"|Some`Double->"double"|Some`Dotted->"dotted"|Some`Dashed->"dashed"|Some`Wavy->"wavy"|Some`Inherit->"inherit"|Some`Initial->"initial"inletcolor=value_mapcolor~f:Color.to_string_cssinconcat3vlinestylecolorincreate_raw~field:"text-decoration"~value;;typecontent_alignment=[`Normal|`Flex_start|`Flex_end|`Center|`Space_between|`Space_around|`Space_evenly|`Stretch]letcontent_alignment_to_string_css=function|`Normal->"normal"|`Flex_start->"flex-start"|`Flex_end->"flex-end"|`Center->"center"|`Space_between->"space-between"|`Space_around->"space-around"|`Space_evenly->"space-evenly"|`Stretch->"stretch";;typeitem_alignment=[`Auto|`Flex_start|`Flex_end|`Center|`Baseline|`Stretch]letitem_alignment_to_string_css=function|`Auto->"auto"|`Flex_start->"flex-start"|`Flex_end->"flex-end"|`Center->"center"|`Baseline->"baseline"|`Stretch->"stretch";;typejustify_content=[`Flex_start|`Flex_end|`Center|`Space_between|`Space_around|`Space_evenly]letjustify_content_to_string_css=function|`Flex_start->"flex-start"|`Flex_end->"flex-end"|`Center->"center"|`Space_between->"space-between"|`Space_around->"space-around"|`Space_evenly->"space-evenly";;letflex_container?(inline=false)?(direction=`Row)?(wrap=`Nowrap)?align_items?align_content?justify_content?row_gap:rg?column_gap:cg()=letdirection=letmake_dirv=create_raw~field:"flex-direction"~value:vinmatchdirectionwith|`Row->make_dir"row"|`Row_reverse->make_dir"row-reverse"|`Column->make_dir"column"|`Column_reverse->make_dir"column-reverse"|`Default->emptyinletwrap=letmake_wrapv=create_raw~field:"flex-wrap"~value:vinmatchwrapwith|`Nowrap->make_wrap"nowrap"|`Wrap->make_wrap"wrap"|`Wrap_reverse->make_wrap"wrap-reverse"|`Default->emptyinletalign_items=matchalign_itemswith|None->empty|Somea->create_raw~field:"align-items"~value:(item_alignment_to_string_cssa)inletalign_content=matchalign_contentwith|None->empty|Somea->create_raw~field:"align-content"~value:(content_alignment_to_string_cssa)inletjustify_content=matchjustify_contentwith|None->empty|Somea->create_raw~field:"justify-content"~value:(justify_content_to_string_cssa)inletrow_gap=Option.value_map~default:empty~f:row_gaprginletcolumn_gap=Option.value_map~default:empty~f:column_gapcginconcat[display(ifinlinethen`Inline_flexelse`Flex);direction;wrap;align_items;align_content;justify_content;column_gap;row_gap];;letflex_item?order?(basis=`Auto)?(shrink=1.)~grow()=letorder=Option.maporder~f:(funi->create_raw~field:"order"~value:(Int.to_stringi))|>Option.to_list|>List.joininletflex=letbasis=Auto_or_length.to_string_cssbasisincreate_raw~field:"flex"~value:[%string"%{f2s 6 grow} %{f2s 6 shrink} %{basis}"]inconcat[flex;order];;letalign_selfa=letvalue=item_alignment_to_string_cssaincreate_raw~field:"align-self"~value;;letresize(value:[`None|`Both|`Horizontal|`Vertical|css_global_values])=letvalue=matchvaluewith|`None->"none"|`Both->"both"|`Horizontal->"horizontal"|`Vertical->"vertical"|`Initial->"initial"|`Inherit->"inherit"increate_raw~field:"resize"~value;;letanimation~name~duration?delay?direction?fill_mode?iter_count?timing_function()=letm=Option.mapinletspan_to_strings=[%string"%{f2s 2 (Time_ns.Span.to_secs)}s"]inletdirection=mdirection~f:(fund->letvalue=matchdwith|`Normal->"normal"|`Reverse->"reverse"|`Alternate->"alternate"|`Alternate_reverse->"alternate-reverse"|`Inherit->"inherit"|`Initial->"initial"increate_raw~field:"animation-direction"~value)inletfill_mode=mfill_mode~f:(funf->letvalue=matchfwith|`None->"none"|`Forwards->"forwards"|`Backwards->"backwards"|`Both->"both"|`Inherit->"inherit"|`Initial->"initial"increate_raw~field:"animation-fill-mode"~value)in[Some(create_raw~field:"animation-name"~value:name);Some(create_raw~field:"animation-duration"~value:(span_to_stringduration));mdelay~f:(funs->create_raw~field:"animation-delay"~value:(span_to_strings));miter_count~f:(funi->create_raw~field:"animation-iteration-count"~value:(Int.to_stringi));mtiming_function~f:(funvalue->create_raw~field:"animation-timing-function"~value);direction;fill_mode]|>List.filter_opt|>concat;;typeuser_select=[`All|`Auto|`None|`Text]letuser_select_to_string_css=function|`All->"all"|`Auto->"auto"|`None->"none"|`Text->"text";;letuser_selects=letvalue=user_select_to_string_csssincreate_raw~field:"user-select"~value;;let%test_module"tests"=(modulestructlet%expect_test"to_string_css -> of_string_css_exn -> to_string_css"=lettcss=lets=to_string_csscssinlets2=to_string_css(of_string_css_exns)inprint_endlines;print_endlines2int(flex_item~grow:1.0()@>overflow`Scroll);t(flex_container~inline:true~direction:`Column()@>border~style:`Dashed());t(color(`RGBA(Color.RGBA.create~r:100~g:100~b:100())));t(color(`HSLA(Color.HSLA.create~h:100~s:(Percent.of_mult0.75)~l:(Percent.of_mult0.60)())));t(create~field:"content"~value:{|";"|});[%expect{|
flex: 1.000000 1.000000 auto;overflow: scroll
flex: 1.000000 1.000000 auto;overflow: scroll
display: inline-flex;flex-direction: column;flex-wrap: nowrap;border: dashed
display: inline-flex;flex-direction: column;flex-wrap: nowrap;border: dashed
color: rgb(100,100,100)
color: rgb(100,100,100)
color: hsl(100,75%,60%)
color: hsl(100,75%,60%)
content: ";"
content: ";" |}];;let%expect_test"gradients"=letpx=Percent.of_multxinletcs=`Namesinlettcss=print_endline(to_string_csscss)int(background_image(`Linear_gradient{direction=`Deg90;stops=[p0.,c"black";p0.2,c"#ff0000";p0.4,c"red";(p1.,`RGBA(Color.RGBA.create~r:100~g:50~b:30~a:(Percent.of_mult0.75)()))]}));[%expect{| background-image: linear-gradient(90deg, black 0.000000%, #ff0000 20.000000%, red 40.000000%, rgba(100,50,30,0.75) 100.000000%) |}];t(background_image(`Radial_gradient{stops=[p0.,c"black";p1.,c"red"]}));[%expect{| background-image: radial-gradient(black 0.000000%, red 100.000000%) |}];;end);;