Build tooling: updated OCaml bootstrapper, compile-modules, bundle.sh, sx-build-all. WASM browser: rebuilt sx_browser.bc.js/wasm, sx-platform-2.js, .sxbc bytecode files. CSSX/Tailwind: reworked cssx.sx templates and tw-layout, added tw-type support. Content: refreshed essays, plans, geography, reactive islands, docs, demos, handlers. New tools: bisect_sxbc.sh, test-spa.js, render-trace.sx, morph playwright spec. Tests: added test-match.sx, test-examples.sx, updated test-tw.sx and web tests. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
415 lines
14 KiB
Plaintext
415 lines
14 KiB
Plaintext
(define tw-spacing-props {:ml "margin-left:{v}" :mr "margin-right:{v}" :mt "margin-top:{v}" :mb "margin-bottom:{v}" :pl "padding-left:{v}" :gap-y "row-gap:{v}" :m "margin:{v}" :gap-x "column-gap:{v}" :my "margin-top:{v};margin-bottom:{v}" :px "padding-left:{v};padding-right:{v}" :pb "padding-bottom:{v}" :pr "padding-right:{v}" :p "padding:{v}" :gap "gap:{v}" :py "padding-top:{v};padding-bottom:{v}" :pt "padding-top:{v}" :mx "margin-left:{v};margin-right:{v}"})
|
|
|
|
(define tw-displays {:flex "flex" :table "table" :grid "grid" :inline-block "inline-block" :table-row "table-row" :inline "inline" :hidden "none" :block "block" :contents "contents" :inline-flex "inline-flex" :inline-grid "inline-grid" :table-cell "table-cell"})
|
|
|
|
(define tw-max-widths {:xs "20rem" :3xl "48rem" :7xl "80rem" :sm "24rem" :xl "36rem" :full "100%" :md "28rem" :6xl "72rem" :prose "65ch" :max "max-content" :5xl "64rem" :min "min-content" :lg "32rem" :2xl "42rem" :4xl "56rem" :none "none" :screen "100vw" :fit "fit-content"})
|
|
|
|
(define tw-min-widths {:full "100%" :0 "0px" :max "max-content" :min "min-content" :fit "fit-content"})
|
|
|
|
(define
|
|
tw-resolve-layout
|
|
(fn
|
|
(token)
|
|
(let
|
|
((parts (split token "-"))
|
|
(head (first parts))
|
|
(rest (slice parts 1)))
|
|
(cond
|
|
(and (= (len parts) 1) (not (nil? (get tw-displays head))))
|
|
(str "display:" (get tw-displays head))
|
|
(and (= (len parts) 2) (not (nil? (get tw-displays token))))
|
|
(str "display:" (get tw-displays token))
|
|
(and (get tw-spacing-props head) (= (len rest) 1))
|
|
(let
|
|
((tmpl (get tw-spacing-props head))
|
|
(v (tw-spacing-value (first rest))))
|
|
(if (nil? v) nil (tw-template tmpl v)))
|
|
(and
|
|
(= (len rest) 2)
|
|
(get tw-spacing-props (str head "-" (first rest))))
|
|
(let
|
|
((tmpl (get tw-spacing-props (str head "-" (first rest))))
|
|
(v (tw-spacing-value (last rest))))
|
|
(if (nil? v) nil (tw-template tmpl v)))
|
|
(and
|
|
(= head "space")
|
|
(= (len rest) 2)
|
|
(or (= (first rest) "x") (= (first rest) "y")))
|
|
(let
|
|
((v (tw-spacing-value (last rest))) (dir (first rest)))
|
|
(if (nil? v) nil (if (= dir "x") {:suffix ">*+*" :css (str "margin-left:" v)} {:suffix ">*+*" :css (str "margin-top:" v)})))
|
|
(and (= head "flex") (empty? rest))
|
|
"display:flex"
|
|
(and (= head "flex") (= (len rest) 1))
|
|
(case
|
|
(first rest)
|
|
"row"
|
|
"flex-direction:row"
|
|
"col"
|
|
"flex-direction:column"
|
|
"wrap"
|
|
"flex-wrap:wrap"
|
|
"nowrap"
|
|
"flex-wrap:nowrap"
|
|
"1"
|
|
"flex:1 1 0%"
|
|
"auto"
|
|
"flex:1 1 auto"
|
|
"initial"
|
|
"flex:0 1 auto"
|
|
"none"
|
|
"flex:none"
|
|
:else nil)
|
|
(and (= head "flex") (= (len rest) 2))
|
|
(case
|
|
(join "-" rest)
|
|
"row-reverse"
|
|
"flex-direction:row-reverse"
|
|
"col-reverse"
|
|
"flex-direction:column-reverse"
|
|
"wrap-reverse"
|
|
"flex-wrap:wrap-reverse"
|
|
:else nil)
|
|
(= head "grow")
|
|
(if
|
|
(empty? rest)
|
|
"flex-grow:1"
|
|
(if (= (first rest) "0") "flex-grow:0" nil))
|
|
(= head "shrink")
|
|
(if
|
|
(empty? rest)
|
|
"flex-shrink:1"
|
|
(if (= (first rest) "0") "flex-shrink:0" nil))
|
|
(and (= head "basis") (= (len rest) 1))
|
|
(let
|
|
((val (first rest)))
|
|
(cond
|
|
(= val "auto")
|
|
"flex-basis:auto"
|
|
(= val "full")
|
|
"flex-basis:100%"
|
|
(= val "0")
|
|
"flex-basis:0px"
|
|
(contains? val "/")
|
|
(let
|
|
((frac (split val "/")))
|
|
(if
|
|
(= (len frac) 2)
|
|
(let
|
|
((num (parse-int (first frac) nil))
|
|
(den (parse-int (nth frac 1) nil)))
|
|
(if
|
|
(or (nil? num) (nil? den))
|
|
nil
|
|
(str "flex-basis:" (* (/ num den) 100) "%")))
|
|
nil))
|
|
:else (let
|
|
((n (parse-int val nil)))
|
|
(if (nil? n) nil (str "flex-basis:" (* n 0.25) "rem")))))
|
|
(and (= head "justify") (= (len rest) 1))
|
|
(case
|
|
(first rest)
|
|
"start"
|
|
"justify-content:flex-start"
|
|
"end"
|
|
"justify-content:flex-end"
|
|
"center"
|
|
"justify-content:center"
|
|
"between"
|
|
"justify-content:space-between"
|
|
"around"
|
|
"justify-content:space-around"
|
|
"evenly"
|
|
"justify-content:space-evenly"
|
|
"stretch"
|
|
"justify-content:stretch"
|
|
:else nil)
|
|
(and (= head "items") (= (len rest) 1))
|
|
(case
|
|
(first rest)
|
|
"start"
|
|
"align-items:flex-start"
|
|
"end"
|
|
"align-items:flex-end"
|
|
"center"
|
|
"align-items:center"
|
|
"baseline"
|
|
"align-items:baseline"
|
|
"stretch"
|
|
"align-items:stretch"
|
|
:else nil)
|
|
(and (= head "self") (= (len rest) 1))
|
|
(case
|
|
(first rest)
|
|
"auto"
|
|
"align-self:auto"
|
|
"start"
|
|
"align-self:flex-start"
|
|
"end"
|
|
"align-self:flex-end"
|
|
"center"
|
|
"align-self:center"
|
|
"stretch"
|
|
"align-self:stretch"
|
|
"baseline"
|
|
"align-self:baseline"
|
|
:else nil)
|
|
(and (= head "content") (= (len rest) 1))
|
|
(case
|
|
(first rest)
|
|
"start"
|
|
"align-content:flex-start"
|
|
"end"
|
|
"align-content:flex-end"
|
|
"center"
|
|
"align-content:center"
|
|
"between"
|
|
"align-content:space-between"
|
|
"around"
|
|
"align-content:space-around"
|
|
"evenly"
|
|
"align-content:space-evenly"
|
|
"stretch"
|
|
"align-content:stretch"
|
|
:else nil)
|
|
(and (= head "order") (= (len rest) 1))
|
|
(let
|
|
((val (first rest)))
|
|
(cond
|
|
(= val "first")
|
|
"order:-9999"
|
|
(= val "last")
|
|
"order:9999"
|
|
(= val "none")
|
|
"order:0"
|
|
:else (let
|
|
((n (parse-int val nil)))
|
|
(if (nil? n) nil (str "order:" n)))))
|
|
(and (= head "grid") (empty? rest))
|
|
"display:grid"
|
|
(and (= head "grid") (>= (len rest) 2) (= (first rest) "cols"))
|
|
(let
|
|
((val (join "-" (slice rest 1))))
|
|
(cond
|
|
(= val "none")
|
|
"grid-template-columns:none"
|
|
(= val "subgrid")
|
|
"grid-template-columns:subgrid"
|
|
:else (let
|
|
((n (parse-int val nil)))
|
|
(if
|
|
(nil? n)
|
|
nil
|
|
(str "grid-template-columns:repeat(" n ",minmax(0,1fr))")))))
|
|
(and (= head "grid") (>= (len rest) 2) (= (first rest) "rows"))
|
|
(let
|
|
((val (join "-" (slice rest 1))))
|
|
(cond
|
|
(= val "none")
|
|
"grid-template-rows:none"
|
|
(= val "subgrid")
|
|
"grid-template-rows:subgrid"
|
|
:else (let
|
|
((n (parse-int val nil)))
|
|
(if
|
|
(nil? n)
|
|
nil
|
|
(str "grid-template-rows:repeat(" n ",minmax(0,1fr))")))))
|
|
(and (= head "grid") (>= (len rest) 2) (= (first rest) "flow"))
|
|
(case
|
|
(nth rest 1)
|
|
"row"
|
|
"grid-auto-flow:row"
|
|
"col"
|
|
"grid-auto-flow:column"
|
|
"dense"
|
|
"grid-auto-flow:dense"
|
|
:else nil)
|
|
(and (= head "col") (>= (len rest) 2))
|
|
(let
|
|
((sub (first rest)) (val (join "-" (slice rest 1))))
|
|
(cond
|
|
(and (= sub "span") (= val "full"))
|
|
"grid-column:1 / -1"
|
|
(= sub "span")
|
|
(let
|
|
((n (parse-int val nil)))
|
|
(if (nil? n) nil (str "grid-column:span " n " / span " n)))
|
|
(= sub "start")
|
|
(str "grid-column-start:" val)
|
|
(= sub "end")
|
|
(str "grid-column-end:" val)
|
|
:else nil))
|
|
(and (= head "row") (>= (len rest) 2))
|
|
(let
|
|
((sub (first rest)) (val (join "-" (slice rest 1))))
|
|
(cond
|
|
(and (= sub "span") (= val "full"))
|
|
"grid-row:1 / -1"
|
|
(= sub "span")
|
|
(let
|
|
((n (parse-int val nil)))
|
|
(if (nil? n) nil (str "grid-row:span " n " / span " n)))
|
|
(= sub "start")
|
|
(str "grid-row-start:" val)
|
|
(= sub "end")
|
|
(str "grid-row-end:" val)
|
|
:else nil))
|
|
(and (= head "auto") (>= (len rest) 2))
|
|
(let
|
|
((sub (first rest)) (val (join "-" (slice rest 1))))
|
|
(cond
|
|
(and (= sub "cols") (= val "auto"))
|
|
"grid-auto-columns:auto"
|
|
(and (= sub "cols") (= val "min"))
|
|
"grid-auto-columns:min-content"
|
|
(and (= sub "cols") (= val "max"))
|
|
"grid-auto-columns:max-content"
|
|
(and (= sub "cols") (= val "fr"))
|
|
"grid-auto-columns:minmax(0,1fr)"
|
|
(and (= sub "rows") (= val "auto"))
|
|
"grid-auto-rows:auto"
|
|
(and (= sub "rows") (= val "min"))
|
|
"grid-auto-rows:min-content"
|
|
(and (= sub "rows") (= val "max"))
|
|
"grid-auto-rows:max-content"
|
|
(and (= sub "rows") (= val "fr"))
|
|
"grid-auto-rows:minmax(0,1fr)"
|
|
:else nil))
|
|
(and
|
|
(= (len parts) 1)
|
|
(or
|
|
(= head "relative")
|
|
(= head "absolute")
|
|
(= head "fixed")
|
|
(= head "sticky")
|
|
(= head "static")))
|
|
(str "position:" head)
|
|
(and
|
|
(or
|
|
(= head "top")
|
|
(= head "right")
|
|
(= head "bottom")
|
|
(= head "left"))
|
|
(= (len rest) 1))
|
|
(let
|
|
((v (tw-spacing-value (first rest))))
|
|
(if (nil? v) nil (str head ":" v)))
|
|
(and (= head "inset") (= (len rest) 1))
|
|
(let
|
|
((v (tw-spacing-value (first rest))))
|
|
(if (nil? v) nil (str "inset:" v)))
|
|
(and (= head "inset") (= (len rest) 2))
|
|
(let
|
|
((dir (first rest)) (v (tw-spacing-value (nth rest 1))))
|
|
(if
|
|
(nil? v)
|
|
nil
|
|
(case
|
|
dir
|
|
"x"
|
|
(str "left:" v ";right:" v)
|
|
"y"
|
|
(str "top:" v ";bottom:" v)
|
|
:else nil)))
|
|
(and (= head "z") (= (len rest) 1))
|
|
(if
|
|
(= (first rest) "auto")
|
|
"z-index:auto"
|
|
(let
|
|
((n (parse-int (first rest) nil)))
|
|
(if (nil? n) nil (str "z-index:" n))))
|
|
(and (or (= head "w") (= head "h")) (= (len rest) 1))
|
|
(let
|
|
((prop (if (= head "w") "width" "height")) (val (first rest)))
|
|
(cond
|
|
(= val "full")
|
|
(str prop ":100%")
|
|
(= val "screen")
|
|
(str prop (if (= head "w") ":100vw" ":100vh"))
|
|
(= val "auto")
|
|
(str prop ":auto")
|
|
(= val "min")
|
|
(str prop ":min-content")
|
|
(= val "max")
|
|
(str prop ":max-content")
|
|
(= val "fit")
|
|
(str prop ":fit-content")
|
|
(contains? val "/")
|
|
(let
|
|
((frac (split val "/")))
|
|
(if
|
|
(= (len frac) 2)
|
|
(let
|
|
((num (parse-int (first frac) nil))
|
|
(den (parse-int (nth frac 1) nil)))
|
|
(if
|
|
(or (nil? num) (nil? den))
|
|
nil
|
|
(str prop ":" (* (/ num den) 100) "%")))
|
|
nil))
|
|
:else (let
|
|
((n (parse-int val nil)))
|
|
(if (nil? n) nil (str prop ":" (* n 0.25) "rem")))))
|
|
(and (= head "max") (>= (len rest) 2) (= (first rest) "w"))
|
|
(let
|
|
((val-name (join "-" (slice rest 1)))
|
|
(val (get tw-max-widths val-name)))
|
|
(if (nil? val) nil (str "max-width:" val)))
|
|
(and (= head "max") (>= (len rest) 2) (= (first rest) "h"))
|
|
(let
|
|
((val (first (slice rest 1))))
|
|
(cond
|
|
(= val "full")
|
|
"max-height:100%"
|
|
(= val "screen")
|
|
"max-height:100vh"
|
|
(= val "none")
|
|
"max-height:none"
|
|
:else (let
|
|
((n (parse-int val nil)))
|
|
(if (nil? n) nil (str "max-height:" (* n 0.25) "rem")))))
|
|
(and (= head "min") (>= (len rest) 2) (= (first rest) "w"))
|
|
(let
|
|
((val-name (join "-" (slice rest 1)))
|
|
(val (get tw-min-widths val-name)))
|
|
(if (nil? val) nil (str "min-width:" val)))
|
|
(and (= head "min") (>= (len rest) 2) (= (first rest) "h"))
|
|
(let
|
|
((val (first (slice rest 1))))
|
|
(cond
|
|
(= val "0")
|
|
"min-height:0px"
|
|
(= val "full")
|
|
"min-height:100%"
|
|
(= val "screen")
|
|
"min-height:100vh"
|
|
:else nil))
|
|
(and (= head "overflow") (= (len rest) 1))
|
|
(str "overflow:" (first rest))
|
|
(and (= head "overflow") (= (len rest) 2))
|
|
(str "overflow-" (first rest) ":" (nth rest 1))
|
|
(and (= head "aspect") (= (len rest) 1))
|
|
(case
|
|
(first rest)
|
|
"auto"
|
|
"aspect-ratio:auto"
|
|
"square"
|
|
"aspect-ratio:1 / 1"
|
|
"video"
|
|
"aspect-ratio:16 / 9"
|
|
:else nil)
|
|
(and (= head "object") (= (len rest) 1))
|
|
(str "object-fit:" (first rest))
|
|
(and (= (len parts) 1) (= head "visible"))
|
|
"visibility:visible"
|
|
(and (= (len parts) 1) (= head "invisible"))
|
|
"visibility:hidden"
|
|
(and (= (len parts) 1) (= head "collapse"))
|
|
"visibility:collapse"
|
|
(and (= (len parts) 1) (= head "container"))
|
|
"width:100%;max-width:100%"
|
|
(and (= (len parts) 1) (= head "isolate"))
|
|
"isolation:isolate"
|
|
:else nil))))
|