package styled-ppx
Type-safe styled components for ReScript and Melange
Install
Dune Dependency
Authors
Maintainers
Sources
styled-ppx-0.56.0.tbz
sha256=f93a08d11849c6010fc3e1c5650d811a14dc60d1f2ea74edb6e2514f12d9f35f
sha512=ad90141c288c368ee60455380706deacc41daffb412e9716c28eadc7aff0360110ca0b953a71b4c58eb6ad33c4dafbc066a9002cc0b17a2fd2dd8a5714f7c668
doc/src/styled-ppx.emotion_native/CssJs.ml.html
Source file CssJs.ml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371
include Css_Colors include Css_Js_Core module Core = Css_Js_Core module Types = Css_AtomicTypes module Array = struct include Stdlib.Array external getUnsafe : 'a array -> int -> 'a = "%array_unsafe_get" external setUnsafe : 'a array -> int -> 'a -> unit = "%array_unsafe_set" let filter_map a f = let l = length a in let r = ref None in let j = ref 0 in for i = 0 to l - 1 do let v = getUnsafe a i in match f v with | None -> () | Some v -> let r = match !r with | None -> let newr = Array.make l v in r := Some newr; newr | Some r -> r in setUnsafe r !j v; incr j done; match !r with None -> [||] | Some r -> Stdlib.Array.sub r 0 !j end module Autoprefixer = struct (* Implementation of stylis autoprefixer https://github.com/thysultan/stylis *) (* This autoprefix works with ">1%, last 4 versions, Firefox ESR, not ie < 9, not dead" from browserlist and not so precise as stylis implementation *) let webkit property = Printf.sprintf "-webkit-%s" property let moz property = Printf.sprintf "-moz-%s" property let ms property = Printf.sprintf "-ms-%s" property let o property = Printf.sprintf "-o-%s" property let khtml property = Printf.sprintf "-khtml-%s" property let prefix_property (property : string) (value : string) prefixes = prefixes |> List.map (fun prefixer -> Core.D (prefixer property, value)) let prefix_value (property : string) (value : string) prefixes = prefixes |> List.map (fun prefixer -> Core.D (property, prefixer value)) let prefix (rule : Core.rule) : Core.rule list = match rule with | D ( (( "animation" | "animation-name" | "animation-duration" | "animation-delay" | "animation-direction" | "animation-fill-mode" | "animation-iteration-count" | "animation-play-state" | "animation-timing-function" ) as property), value ) | D (("text-decoration" as property), value) | D (("filter" as property), value) | D (("clip-path" as property), value) | D (("backface-visibility" as property), value) | D (("column" as property), value) | D (("box-decoration-break" as property), value) | D ( (( "mask" | "mask-image" | "mask-mode" | "mask-clip" | "mask-size" | "mask-repeat" | "mask-origin" | "mask-position" | "mask-composite" ) as property), value ) | D ( (( "column-count" | "column-fill" | "column-gap" | "column-rule" | "column-rule-color" | "column-rule-style" | "column-rule-width" | "column-span" | "column-width" ) as property), value ) | D (("background-clip" as property), value) | D ( (( "margin-inline-end" | "margin-inline-start" | "padding-inline-start" | "padding-inline-end" ) as property), value ) | D (("columns" as property), value) -> prefix_property property value [ webkit ] @ [ rule ] | D (("user-select" as property), value) | D (("appearance" as property), value) | D (("transform" as property), value) | D (("hyphens" as property), value) | D (("text-size-adjust" as property), value) -> prefix_property property value [ webkit; moz; ms ] @ [ rule ] | D ((("grid-row" | "grid-column") as property), value) -> prefix_property property value [ ms ] @ [ rule ] | D (("flex" as property), value) | D (("flex-direction" as property), value) | D (("scroll-snap-type" as property), value) | D (("writing-mode" as property), value) -> prefix_property property value [ webkit; ms ] @ [ rule ] | D (("tab-size" as property), value) -> prefix_property property value [ moz; o ] @ [ rule ] | D ("color-adjust", value) -> prefix_property "print-color-adjust" value [ webkit ] @ [ rule ] | D ( (( "align-items" | "align-content" | "flex-shrink" | "flex-basis" | "align-self" | "flex-grow" | "justify-content" ) as _property), _value ) -> [ rule ] | D (("cursor" as property), (("grab" | "grabbing") as value)) -> prefix_value property value [ webkit ] @ [ rule ] | D ( (( "width" | "min-width" | "max-width" | "height" | "min-height" | "max-height" | "min-block-size" | "max-block-size" ) as property), (("fit-content" | "max-content" | "min-content" | "fill-available") as value) ) -> prefix_value property value [ webkit; moz ] @ [ rule ] | D ( (( "width" | "min-width" | "max-width" | "height" | "min-height" | "max-height" ) as property), "stretch" ) -> prefix_value property "fill-available" [ webkit ] @ prefix_value property "available" [ moz ] @ [ rule ] (* TODO: Add -webkit-image-set on "background" | "background-image" image-set *) | _ -> [ rule ] end let render_declaration rule = match rule with (* https://emotion.sh/docs/labels should be ignored on the rendering *) | D ("label", _value) -> None | D (property, value) -> Some (Printf.sprintf "%s: %s;" property value) | _ -> None let render_declarations (rules : rule array) = rules |> Array.to_list |> List.map Autoprefixer.prefix |> List.flatten |> List.filter_map render_declaration |> String.concat " " let is_at_rule selector = String.contains selector '@' let is_a_pseudo_selector selector = String.starts_with ~prefix:":" selector let prefix ~pre s = let len = String.length pre in if len > String.length s then false else ( let rec check i = if i = len then true else if String.unsafe_get s i <> String.unsafe_get pre i then false else check (i + 1) in check 0) let chop_prefix ~pre s = if prefix ~pre s then Some (String.sub s (String.length pre) (String.length s - String.length pre)) else None let remove_first_ampersand selector = selector |> chop_prefix ~pre:"&" |> Option.value ~default:selector let replace_ampersand str with_ = let rec replace_ampersand' str var = let len = String.length str in if len = 0 then "" else if str.[0] = '&' then var ^ replace_ampersand' (String.sub str 1 (len - 1)) var else String.sub str 0 1 ^ replace_ampersand' (String.sub str 1 (len - 1)) var in replace_ampersand' str with_ let resolve_ampersand hash selector = replace_ampersand selector ("." ^ hash) let render_prelude hash selector = let new_selector = selector |> remove_first_ampersand |> resolve_ampersand hash in (* S (aka Selectors) are the only ones used by styled-ppx, we don't use PseudoClass neither PseucodClassParam. TODO: Remove them. Meanwhile we have them, it's a good idea to check if the first character of the selector is a `:` because it's expected to not have a space between the selector and the :pseudoselector. *) if is_a_pseudo_selector new_selector then Printf.sprintf ".%s%s" hash new_selector else Printf.sprintf ".%s %s" hash new_selector let render_selectors hash rule = match rule with | S (selector, rules) when is_at_rule selector -> Some (Printf.sprintf "%s { .%s { %s } }" selector hash (render_declarations rules)) | S (selector, rules) -> let prelude = render_prelude hash selector in Some (Printf.sprintf "%s { %s }" prelude (render_declarations rules)) | PseudoClass (pseduoclass, rules) -> Some (Printf.sprintf ".%s:%s { %s }" hash pseduoclass (render_declarations rules)) | PseudoClassParam (pseudoclass, param, rules) -> Some (Printf.sprintf ".%s:%s ( %s ) %s" hash pseudoclass param (render_declarations rules)) | _ -> None let rec rule_to_debug nesting accumulator rule = let next_rule = match rule with | D (property, value) -> Printf.sprintf "Declaration (\"%s\", \"%s\")" property value | S (selector, rules) -> Printf.sprintf "Selector (\"%s\", %s)" selector (to_debug (nesting + 1) rules) | PseudoClass (pseduoclass, rules) -> Printf.sprintf "PseudoClass (\"%s\", %s)" pseduoclass (to_debug (nesting + 1) rules) | PseudoClassParam (pseudoclass, param, rules) -> Printf.sprintf "PseudoClassParam (\"%s\", \"%s\", %s)" pseudoclass param (to_debug (nesting + 1) rules) in let space = if nesting > 0 then String.make (nesting * 2) ' ' else "" in accumulator ^ Printf.sprintf "\n%s" space ^ next_rule and to_debug nesting rules = rules |> Array.fold_left (rule_to_debug nesting) "" let print_rules rules = rules |> Stdlib.Array.iter (fun rule -> print_endline (to_debug 0 [| rule |])) let resolve_selectors rules = let rec unnest ~prefix = List.partition_map (function | S (title, selector_rules) -> let new_prelude = prefix ^ title in let content, tail = unnest ~prefix:(new_prelude ^ " ") (Array.to_list selector_rules) in Right (S (new_prelude, Array.of_list content) :: List.flatten tail) | _ as rule -> Left rule) in let resolve_selector rule = let declarations, selectors = unnest ~prefix:"" [ rule ] in List.flatten (declarations :: selectors) in rules |> List.map resolve_selector |> List.flatten let pp_keyframes hash keyframes = let pp_keyframe (percentage, rules) = Printf.sprintf "%i%% { %s }" percentage (render_declarations rules) in let definition = keyframes |> Array.map pp_keyframe |> Array.to_list |> String.concat " " in Printf.sprintf "@keyframes %s { %s }" hash definition (* Removes nesting on selectors, run the autoprefixer. *) let pp_rules hash rules = (* TODO: Refactor with partition or partition_map. List.filter_map is error prone. Ss might need to respect the order of definition, and this breaks the order *) let list_of_rules = rules |> Array.to_list |> resolve_selectors in let declarations = list_of_rules |> List.map Autoprefixer.prefix |> List.flatten |> List.filter_map render_declaration |> String.concat " " |> fun all -> Printf.sprintf ".%s { %s }" hash all in let selectors = list_of_rules |> List.filter_map (render_selectors hash) |> String.concat " " in Printf.sprintf "%s %s" declarations selectors (* rules_to_string renders the rule in a format where the hash matches with `@emotion/serialise` It doesn't render any whitespace. (compared to pp_rules) TODO: Ensure Selector is rendered correctly. TODO: Ensure PsuedoClass is rendered correctly. TODO: Ensure PseudoClassParam is rendered correctly. *) let rec rules_to_string rules = let buff = Buffer.create 16 in let push = Buffer.add_string buff in let rule_to_string rule = match rule with | D (property, value) -> push (Printf.sprintf "%s:%s;" property value) | S (selector, rules) -> let rules = rules |> Array.to_list |> rules_to_string in push (Printf.sprintf "%s{%s}" selector rules) | PseudoClass (pseudoclass, rules) -> let rules = rules |> Array.to_list |> rules_to_string in push (Printf.sprintf ":%s{%s}" pseudoclass rules) | PseudoClassParam (pseudoclass, param, rules) -> let rules = rules |> Array.to_list |> rules_to_string in push (Printf.sprintf ":%s (%s) {%s}" pseudoclass param rules) in List.iter rule_to_string rules; Buffer.contents buff type declarations = | Classnames of rule array | Keyframes of (int * rule array) array module Stylesheet = struct module Hashes = Set.Make (String) type t = { mutable rules : (string * declarations) list; mutable hashes : Hashes.t; } let make () = { rules = []; hashes = Hashes.empty } let push stylesheet item = let hash = fst item in if Hashes.mem hash stylesheet.hashes then () else ( stylesheet.hashes <- Hashes.add hash stylesheet.hashes; stylesheet.rules <- item :: stylesheet.rules) let get_all stylesheet = List.rev stylesheet.rules let flush stylesheet = stylesheet.rules <- []; stylesheet.hashes <- Hashes.empty end let keyframes_to_string keyframes = let pp_keyframe (percentage, rules) = Printf.sprintf "%d%%{%s}" percentage (rules |> Array.to_list |> rules_to_string) in keyframes |> Array.map pp_keyframe |> Array.to_list |> String.concat "" let render_hash prefix hash styles = let is_label = function D ("label", value) -> Some value | _ -> None in match Array.find_map is_label styles with | None -> Printf.sprintf "%s-%s" prefix hash | Some label -> Printf.sprintf "%s-%s-%s" prefix hash label let instance = Stylesheet.make () let flush () = Stylesheet.flush instance let style (styles : rule array) = match styles with | [||] -> "" | _ -> let hash = Murmur2.default (rules_to_string (Array.to_list styles)) in let className = render_hash "css" hash styles in Stylesheet.push instance (className, Classnames styles); className let keyframes (keyframes : (int * rule array) array) = match keyframes with | [||] -> "" | _ -> let hash = Murmur2.default (keyframes_to_string keyframes) in let animationName = Printf.sprintf "%s-%s" "animation" hash in Stylesheet.push instance (animationName, Keyframes keyframes); animationName let render_style_tag () = Stylesheet.get_all instance |> List.fold_left (fun accumulator (hash, rules) -> match rules with | Classnames rules -> let rules = pp_rules hash rules |> String.trim in Printf.sprintf "%s %s" accumulator rules | Keyframes keyframes -> let rules = pp_keyframes hash keyframes |> String.trim in Printf.sprintf "%s %s" accumulator rules) "" |> String.trim
sectionYPositions = computeSectionYPositions($el), 10)"
x-init="setTimeout(() => sectionYPositions = computeSectionYPositions($el), 10)"
>