package tw

  1. Overview
  2. Docs
Legend:
Page
Library
Module
Module type
Parameter
Class
Class type
Source

Source file flex_props.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
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
(** Flexbox property utilities (grow, shrink, basis, order, flex shortcuts).

    These utilities control flexbox item behavior and come after sizing
    utilities in the cascade order. For flex display utilities (flex,
    inline-flex), see Flex module. For direction/wrap utilities, see Flex_layout
    module. *)

module Css = Cascade.Css

module Handler = struct
  open Style
  open Css

  type t =
    (* Flex shortcuts *)
    | Flex_1
    | Flex_auto
    | Flex_initial
    | Flex_none
    | Flex_n of int (* flex-N where N is any integer *)
    | Flex_fraction of int * int (* flex-N/M where N/M is a fraction *)
    | Flex_arbitrary of int (* flex-[123] *)
    (* Grow *)
    | Flex_grow
    | Flex_grow_0
    | Flex_grow_arbitrary of int (* grow-[123] *)
    | Flex_grow_legacy (* flex-grow (deprecated alias, keeps its class name) *)
    | Flex_grow_0_legacy (* flex-grow-0 *)
    (* Shrink *)
    | Flex_shrink
    | Flex_shrink_0
    | Flex_shrink_arbitrary of int (* shrink-[123] *)
    | Flex_shrink_legacy (* flex-shrink *)
    | Flex_shrink_0_legacy (* flex-shrink-0 *)
    (* Basis *)
    | Basis_0
    | Basis_1
    | Basis_spacing of int
    | Basis_auto
    | Basis_full
    | Basis_fraction of int * int
    | Basis_named of string
    | Basis_arbitrary of Css.flex_basis (* basis-[123px] *)
    (* Order *)
    | Order of int
    | Neg_order of int (* -order-4 = calc(4 * -1) *)
    | Neg_order_arbitrary of string (* -order-[var(--value)] *)
    | Order_arbitrary of string (* order-[123] *)
    | Order_first
    | Order_last
    | Order_none

  type Utility.base += Self of t

  let name = "flex_props"

  (* flex/grow/shrink/basis are the flexbox family (priority 7 - after sizing,
     before transforms). order- occupies an early canonical slot (rank 13, right
     after z-index), so it returns priority 0 with a suborder above z-index's
     ~20M band and below container (priority 1). *)
  let priority = function
    | Order _ | Neg_order _ | Neg_order_arbitrary _ | Order_arbitrary _
    | Order_first | Order_last | Order_none ->
        0
    | _ -> 7

  (* Flex shortcuts *)
  let flex_1 = style [ flex (Grow (Number 1.0)) ]
  let flex_auto = style [ flex Auto ]
  let flex_initial = style [ flex (Full (Number 0., Number 1., Auto)) ]
  let flex_none = style [ flex None ]

  (* flex-N: flex: N *)
  let flex_n_style n = style [ flex (Grow (Number (float_of_int n))) ]

  (* flex-N/M: flex: (N/M * 100)% - evaluates the fraction *)
  let flex_fraction_style n m =
    let pct_value = float_of_int n /. float_of_int m *. 100.0 in
    style [ flex (Basis (Pct pct_value)) ]

  (* Grow *)
  let flex_grow_utility = style [ flex_grow 1.0 ]
  let flex_grow_0_utility = style [ flex_grow 0.0 ]

  (* Shrink *)
  let flex_shrink_utility = style [ flex_shrink 1.0 ]
  let flex_shrink_0_utility = style [ flex_shrink 0.0 ]

  (* Basis. Tailwind v4.3 emits [var(--spacing)] for [basis-1] and
     [calc(var(--spacing) * <n>)] otherwise; [basis-full] / [basis-1/1] emit
     literal [100%]. *)
  let basis_spacing n =
    let spacing_decl, _ = Var.binding Theme.spacing_var Theme.spacing_base in
    let value : Css.flex_basis =
      if n = 1 then Var (Var.theme_ref "spacing")
      else Calc Css.Calc.(mul (var "spacing") (float (float_of_int n)))
    in
    style [ spacing_decl; flex_basis value ]

  let basis_0 = basis_spacing 0
  let basis_1 = basis_spacing 1
  let basis_auto = style [ flex_basis Auto ]
  let basis_full = style [ flex_basis (Pct 100.0) ]

  let basis_fraction_style n m =
    let raw = float_of_int n /. float_of_int m *. 100.0 in
    let pct_value = Float.round (raw *. 10000.0) /. 10000.0 in
    style [ flex_basis (Pct pct_value) ]

  let basis_named_style ?theme name =
    let var_name = "container-" ^ name in
    match Scheme.theme_value theme var_name with
    | Some value_str ->
        let decl =
          Css.custom_property ~layer:"theme" ("--" ^ var_name) value_str
        in
        let ref : Css.flex_basis Css.var =
          Var.theme_ref var_name
            ~default:(Css.Zero : Css.flex_basis)
            ~default_css:"0px"
        in
        style [ decl; flex_basis (Var ref) ]
    | None ->
        let ref : Css.flex_basis Css.var =
          Var.theme_ref var_name
            ~default:(Css.Zero : Css.flex_basis)
            ~default_css:"0px"
        in
        style [ flex_basis (Var ref) ]

  (* Order *)
  let order_style n = style [ order (Int n) ]

  let themed_order ?theme name default =
    match Scheme.theme_value theme name with
    | None -> style [ order (Int default) ]
    | Some value ->
        let decl = Css.custom_property ~layer:"theme" ("--" ^ name) value in
        let var_ref = Css.var_ref ~layer:"theme" name in
        style [ decl; order (Var var_ref) ]

  let order_first ?theme () = themed_order ?theme "order-first" (-9999)
  let order_last ?theme () = themed_order ?theme "order-last" 9999
  let order_none = style [ order (Int 0) ]

  let to_style theme =
    let basis_named_style name = basis_named_style ~theme name in
    let order_first () = order_first ~theme () in
    let order_last () = order_last ~theme () in
    function
    | Flex_1 -> flex_1
    | Flex_auto -> flex_auto
    | Flex_initial -> flex_initial
    | Flex_none -> flex_none
    | Flex_n n -> flex_n_style n
    | Flex_fraction (n, m) -> flex_fraction_style n m
    | Flex_arbitrary n -> flex_n_style n
    | Flex_grow -> flex_grow_utility
    | Flex_grow_0 -> flex_grow_0_utility
    | Flex_grow_arbitrary n -> style [ flex_grow (float_of_int n) ]
    | Flex_grow_legacy -> flex_grow_utility
    | Flex_grow_0_legacy -> flex_grow_0_utility
    | Flex_shrink -> flex_shrink_utility
    | Flex_shrink_0 -> flex_shrink_0_utility
    | Flex_shrink_arbitrary n -> style [ flex_shrink (float_of_int n) ]
    | Flex_shrink_legacy -> flex_shrink_utility
    | Flex_shrink_0_legacy -> flex_shrink_0_utility
    | Basis_0 -> basis_0
    | Basis_1 -> basis_1
    | Basis_spacing n -> basis_spacing n
    | Basis_auto -> basis_auto
    | Basis_full -> basis_full
    | Basis_fraction (n, m) -> basis_fraction_style n m
    | Basis_named name -> basis_named_style name
    | Basis_arbitrary len -> style [ flex_basis len ]
    | Order n -> order_style n
    | Neg_order n -> style [ order (Int (-n)) ]
    | Neg_order_arbitrary s -> (
        match int_of_string_opt s with
        | Some n -> style [ order (Int (-n)) ]
        | None ->
            let o = Css.Properties.read_order (Cascade.Cursor.of_string s) in
            style [ order (Calc (Css.Calc.mul (Val o) (Css.Calc.float (-1.)))) ]
        )
    | Order_arbitrary s ->
        style [ order (Css.Properties.read_order (Cascade.Cursor.of_string s)) ]
    | Order_first -> order_first ()
    | Order_last -> order_last ()
    | Order_none -> order_none

  let suborder : t -> int = function
    (* Order (priority 0) - after z-index (~20M band in layout.ml), before
       container (priority 1). Ordering: negative first, then positive, then
       first/last/none, then arbitrary. *)
    | Neg_order n -> 21_000_000 + n (* negative comes first *)
    | Neg_order_arbitrary _ -> 21_000_000 + 50
    | Order n -> 21_000_000 + 100 + n
    | Order_arbitrary _ -> 21_000_000 + 150
    | Order_first -> 21_000_000 + 200
    | Order_last -> 21_000_000 + 201
    | Order_none -> 21_000_000 + 202
    (* Tailwind flex order: flex-1 < fractions < numbers < auto < initial <
       none. Note: flex-* utilities come AFTER order-* utilities *)
    | Flex_1 -> 1000
    (* Fractions: sorted by numerator then denominator (1/2 < 1/3 < 1/4 <
       2/3...) *)
    | Flex_fraction (n, m) -> 1020 + (n * 100) + m
    (* flex-N values: after fractions, ordered by value *)
    | Flex_n n -> 5000 + n
    (* Arbitrary flex values - after regular numbers *)
    | Flex_arbitrary n -> 6000 + n
    (* Named shortcuts come last *)
    | Flex_auto -> 10000
    | Flex_initial -> 10001
    | Flex_none -> 10002
    (* Shrink - legacy flex-shrink* sorts before the canonical shrink* *)
    | Flex_shrink_legacy -> 19998
    | Flex_shrink_0_legacy -> 19999
    | Flex_shrink -> 20000
    | Flex_shrink_0 -> 20001
    | Flex_shrink_arbitrary _ -> 20002
    (* Grow - legacy flex-grow* sorts before the canonical grow* *)
    | Flex_grow_legacy -> 29998
    | Flex_grow_0_legacy -> 29999
    | Flex_grow -> 30000
    | Flex_grow_0 -> 30001
    | Flex_grow_arbitrary _ -> 30002
    (* Basis: fractions → arbitrary → keywords alphabetical → named *)
    | Basis_fraction (n, m) -> 40000 + (n * 10) + m
    | Basis_arbitrary _ -> 42000
    | Basis_0 -> 43000
    | Basis_1 -> 43001
    | Basis_spacing _ -> 43001
    | Basis_auto -> 43002
    | Basis_full -> 43003
    | Basis_named _ -> 44000

  let err_not_utility = Error (`Msg "Not a flex property utility")

  let parse_fraction s =
    (* Parse "N/M" into (N, M) *)
    match String.split_on_char '/' s with
    | [ n_str; m_str ] -> (
        match (int_of_string_opt n_str, int_of_string_opt m_str) with
        | Some n, Some m when n > 0 && m > 0 -> Some (n, m)
        | _ -> None)
    | _ -> None

  let of_class _theme class_name =
    let parts = Parse.split_class class_name in
    match parts with
    | [ "flex"; "1" ] -> Ok Flex_1
    | [ "flex"; "auto" ] -> Ok Flex_auto
    | [ "flex"; "initial" ] -> Ok Flex_initial
    | [ "flex"; "none" ] -> Ok Flex_none
    | [ "flex"; "grow" ] -> Ok Flex_grow_legacy
    | [ "grow" ] -> Ok Flex_grow
    | [ "flex"; "grow"; "0" ] -> Ok Flex_grow_0_legacy
    | [ "grow"; "0" ] -> Ok Flex_grow_0
    | [ "grow"; n ] when Parse.is_bracket_value n -> (
        let inner = Parse.bracket_inner n in
        match int_of_string_opt inner with
        | Some i -> Ok (Flex_grow_arbitrary i)
        | None -> err_not_utility)
    | [ "flex"; "shrink" ] -> Ok Flex_shrink_legacy
    | [ "shrink" ] -> Ok Flex_shrink
    | [ "flex"; "shrink"; "0" ] -> Ok Flex_shrink_0_legacy
    | [ "shrink"; "0" ] -> Ok Flex_shrink_0
    | [ "shrink"; n ] when Parse.is_bracket_value n -> (
        let inner = Parse.bracket_inner n in
        match int_of_string_opt inner with
        | Some i -> Ok (Flex_shrink_arbitrary i)
        | None -> err_not_utility)
    | [ "basis"; "0" ] -> Ok Basis_0
    | [ "basis"; "1" ] -> Ok Basis_1
    | [ "basis"; "auto" ] -> Ok Basis_auto
    | [ "basis"; "full" ] -> Ok Basis_full
    | [ "basis"; value ] when Parse.is_bracket_value value ->
        let inner = Parse.bracket_inner value in
        if String.ends_with ~suffix:"px" inner then
          let n = String.sub inner 0 (String.length inner - 2) in
          match float_of_string_opt n with
          | Some f -> Ok (Basis_arbitrary (Css.Px f))
          | None -> err_not_utility
        else if String.ends_with ~suffix:"rem" inner then
          let n = String.sub inner 0 (String.length inner - 3) in
          match float_of_string_opt n with
          | Some f -> Ok (Basis_arbitrary (Css.Rem f))
          | None -> err_not_utility
        else err_not_utility
    | [ "basis"; value ] -> (
        match int_of_string_opt value with
        | Some n when n >= 0 -> Ok (Basis_spacing n)
        | _ -> (
            match parse_fraction value with
            | Some (n, m) -> Ok (Basis_fraction (n, m))
            | None ->
                if Spacing.is_named_spacing value then Ok (Basis_named value)
                else err_not_utility))
    | [ "order"; "first" ] -> Ok Order_first
    | [ "order"; "last" ] -> Ok Order_last
    | [ "order"; "none" ] -> Ok Order_none
    | "order" :: rest when rest <> [] -> (
        let value = String.concat "-" rest in
        if
          String.length value > 2
          && value.[0] = '['
          && value.[String.length value - 1] = ']'
        then
          let inner = String.sub value 1 (String.length value - 2) in
          Ok (Order_arbitrary inner)
        else
          match int_of_string_opt value with
          | Some n when n >= 0 -> Ok (Order n)
          | _ -> err_not_utility)
    | "" :: "order" :: rest when rest <> [] -> (
        (* Negative order: -order-4, -order-[var(--value)] *)
        let value = String.concat "-" rest in
        if
          String.length value > 2
          && value.[0] = '['
          && value.[String.length value - 1] = ']'
        then
          let inner = String.sub value 1 (String.length value - 2) in
          Ok (Neg_order_arbitrary inner)
        else
          match int_of_string_opt value with
          | Some n when n >= 1 -> Ok (Neg_order n)
          | _ -> err_not_utility)
    | [ "flex"; value ] when String.length value > 0 && value.[0] = '[' ->
        (* Arbitrary flex: flex-[123] *)
        let len = String.length value in
        if len > 2 && value.[len - 1] = ']' then
          let inner = String.sub value 1 (len - 2) in
          match int_of_string_opt inner with
          | Some n -> Ok (Flex_arbitrary n)
          | None -> err_not_utility
        else err_not_utility
    | [ "flex"; value ] -> (
        (* Try fraction first (e.g., "1/2") *)
        match parse_fraction value with
        | Some (n, m) -> Ok (Flex_fraction (n, m))
        | None -> (
            (* Try numeric value (e.g., "99") *)
            match int_of_string_opt value with
            | Some n when n > 1 -> Ok (Flex_n n)
            | _ -> err_not_utility))
    | _ -> err_not_utility

  let to_class = function
    (* Flex shortcuts *)
    | Flex_1 -> "flex-1"
    | Flex_auto -> "flex-auto"
    | Flex_initial -> "flex-initial"
    | Flex_none -> "flex-none"
    | Flex_n n -> "flex-" ^ string_of_int n
    | Flex_fraction (n, m) -> "flex-" ^ string_of_int n ^ "/" ^ string_of_int m
    | Flex_arbitrary n -> "flex-[" ^ string_of_int n ^ "]"
    (* Grow - Tailwind v4 uses shorter names; the flex-* spellings are kept as
       deprecated aliases that preserve their class name *)
    | Flex_grow -> "grow"
    | Flex_grow_0 -> "grow-0"
    | Flex_grow_arbitrary n -> "grow-[" ^ string_of_int n ^ "]"
    | Flex_grow_legacy -> "flex-grow"
    | Flex_grow_0_legacy -> "flex-grow-0"
    (* Shrink - Tailwind v4 uses shorter names *)
    | Flex_shrink -> "shrink"
    | Flex_shrink_0 -> "shrink-0"
    | Flex_shrink_arbitrary n -> "shrink-[" ^ string_of_int n ^ "]"
    | Flex_shrink_legacy -> "flex-shrink"
    | Flex_shrink_0_legacy -> "flex-shrink-0"
    (* Basis *)
    | Basis_0 -> "basis-0"
    | Basis_1 -> "basis-1"
    | Basis_spacing n -> "basis-" ^ string_of_int n
    | Basis_auto -> "basis-auto"
    | Basis_full -> "basis-full"
    | Basis_fraction (n, m) ->
        "basis-" ^ string_of_int n ^ "/" ^ string_of_int m
    | Basis_named s -> "basis-" ^ s
    | Basis_arbitrary len -> (
        match len with
        | Px n ->
            let s = string_of_float n in
            let s =
              if String.ends_with ~suffix:"." s then
                String.sub s 0 (String.length s - 1)
              else s
            in
            "basis-[" ^ s ^ "px]"
        | Rem n ->
            let s = string_of_float n in
            let s =
              if String.ends_with ~suffix:"." s then
                String.sub s 0 (String.length s - 1)
              else s
            in
            "basis-[" ^ s ^ "rem]"
        | _ -> "basis-[<length>]")
    (* Order *)
    | Order n -> "order-" ^ string_of_int n
    | Neg_order n -> "-order-" ^ string_of_int n
    | Neg_order_arbitrary s -> "-order-[" ^ s ^ "]"
    | Order_arbitrary s -> "order-[" ^ s ^ "]"
    | Order_first -> "order-first"
    | Order_last -> "order-last"
    | Order_none -> "order-none"
end

open Handler

(** Register handler with Utility system *)
let () = Utility.register (module Handler)

let utility x = Utility.base (Self x)
let flex_1 = utility Flex_1
let flex_auto = utility Flex_auto
let flex_initial = utility Flex_initial
let flex_none = utility Flex_none
let flex_grow = utility Flex_grow
let flex_grow_0 = utility Flex_grow_0
let flex_shrink = utility Flex_shrink
let flex_shrink_0 = utility Flex_shrink_0
let basis_0 = utility Basis_0
let basis_1 = utility Basis_1
let basis_auto = utility Basis_auto
let basis_full = utility Basis_full
let order n = utility (Order n)
let order_first = utility Order_first
let order_last = utility Order_last
let order_none = utility Order_none