Source file theme.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
(** Shared theme variables for consistent ordering and avoiding conflicts *)

module Css = Cascade.Css

(** Main ordering scheme: 1. Font families (1) - basic font families 2. Colors
    (2) - color variables 3. Spacing (3) - spacing variables 4. Breakpoints (4)
    \- breakpoint variables 5. Containers (5) - container variables 6.
    Typography (6) - text sizes and other typography 7. Border radius (7) -
    border radius variables 8. Animation/timing (8) - animation variables 9.
    Default fonts (9) - default font family variables *)

(** {1 Spacing Variables} *)

(* Resolve the optionally-threaded theme, defaulting to the base scheme. *)
let resolve_scheme = function Some s -> s | None -> Scheme.default

(* Shared spacing variable used across padding, margin, positioning, etc.
   Tailwind v4 uses a single --spacing: 0.25rem variable and calc() for
   values. *)
let spacing_var = Var.theme Css.Length "spacing" ~runtime:true ~order:(3, 0)

(* The base spacing value: 0.25rem *)
let spacing_base : Css.length = Rem 0.25

(* Create a spacing variable for explicit spacing values (e.g., --spacing-4) *)
let spacing_n_var n = Var.theme Css.Length ("spacing-" ^ Pp.int n) ~order:(3, n)

(* Create a spacing length value. When scheme has explicit spacing for |n|,
   returns var(--spacing-|n|) or calc(var(--spacing-|n|) * -1) for negatives.
   Otherwise returns calc(var(--spacing) * n). Returns the theme declaration and
   the length. *)
let spacing_calc ?theme n : Css.declaration * Css.length =
  let abs_n = abs n in
  let is_negative = n < 0 in
  match Scheme.spacing (resolve_scheme theme) abs_n with
  | Some explicit_length ->
      (* Scheme has explicit spacing: use var(--spacing-|n|) *)
      let spacing_n = spacing_n_var abs_n in
      let decl, spacing_ref = Var.binding spacing_n explicit_length in
      if is_negative then
        (* Negative: wrap in calc(... * -1) *)
        let neg_len : Css.length =
          Css.Calc
            (Css.Calc.mul
               (Css.Calc.length (Css.Var spacing_ref))
               (Css.Calc.float (-1.0)))
        in
        (decl, neg_len)
      else (decl, (Css.Var spacing_ref : Css.length))
  | None ->
      (* Default: calc(var(--spacing) * n). For the unit multiplier we emit a
         bare var(--spacing) rather than calc(var(--spacing) * 1). This shortcut
         exists only to match Tailwind core byte-for-byte: the fixture has p-1
         producing "padding: var(--spacing)" next to p-4 producing
         "calc(var(--spacing) * 4)". Without it our output diverges and the
         examples/parity comparisons flag the difference.

         Runtime expectation: --spacing must resolve to a single length. That is
         the spacing-scale contract (the default 0.25rem and any single-value
         @theme override). The shortcut is NOT sound under a multi-term runtime
         redefinition such as ".dense { --spacing: 1px + 3px }": bare
         var(--spacing) then expands to invalid bare math and falls back to the
         initial value, whereas calc(var(--spacing) * 1) would still compute
         (4px). We inherit this fragility from Tailwind. cascade must not
         perform the equivalent calc(var(--spacing)) -> var(--spacing) rewrite,
         because it optimises arbitrary CSS and cannot assume that contract. *)
      let decl, spacing_ref = Var.binding spacing_var spacing_base in
      if n = 1 then (decl, (Css.Var spacing_ref : Css.length))
      else
        let len : Css.length =
          Css.Calc
            (Css.Calc.mul
               (Css.Calc.length (Css.Var spacing_ref))
               (Css.Calc.float (float_of_int n)))
        in
        (decl, len)

(* Create a spacing length value for float multipliers like 2.5. For integer
   values, checks scheme for explicit spacing. Otherwise uses calc. This handles
   cases like my-2.5 which need calc(var(--spacing) * 2.5). *)
let spacing_calc_float ?theme (n : float) : Css.declaration * Css.length =
  let abs_n = Float.abs n in
  let is_negative = n < 0.0 in
  (* Check if this is an integer value that might have explicit spacing *)
  let is_integer = Float.is_integer abs_n in
  if is_integer then
    (* Use integer version which checks scheme *)
    spacing_calc ?theme (int_of_float n)
  else
    (* Fractional value: always use calc *)
    let decl, spacing_ref = Var.binding spacing_var spacing_base in
    let mult = if is_negative then -.abs_n else abs_n in
    let len : Css.length =
      Css.Calc
        (Css.Calc.mul
           (Css.Calc.length (Css.Var spacing_ref))
           (Css.Calc.float mult))
    in
    (decl, len)