diff --git a/lib/hyperscript/compiler.sx b/lib/hyperscript/compiler.sx index c09941b9..ee9ee518 100644 --- a/lib/hyperscript/compiler.sx +++ b/lib/hyperscript/compiler.sx @@ -56,32 +56,39 @@ (hs-to-sx (nth target 2)) value)) ((= th (quote of)) - ;; Decompose (of prop-expr target) into a set operation - ;; e.g. (of (. (ref "parentNode") "innerHTML") (query "#d1")) - ;; → set parentNode.innerHTML of #d1 → need to navigate target, then set final prop - (let ((prop-ast (nth target 1)) - (obj-ast (nth target 2))) - (if (and (list? prop-ast) (= (first prop-ast) dot-sym)) - ;; (. base "prop") of obj → (dom-set-prop (host-get (compiled-obj) (compiled-base-name)) "prop" value) - (let ((base (nth prop-ast 1)) - (prop-name (nth prop-ast 2))) - (list (quote dom-set-prop) - (list (quote host-get) (hs-to-sx obj-ast) (nth base 1)) + (let + ((prop-ast (nth target 1)) (obj-ast (nth target 2))) + (if + (and (list? prop-ast) (= (first prop-ast) dot-sym)) + (let + ((base (nth prop-ast 1)) + (prop-name (nth prop-ast 2))) + (list + (quote dom-set-prop) + (list + (quote host-get) + (hs-to-sx obj-ast) + (nth base 1)) prop-name value)) - ;; (attr "name") of obj → (dom-set-attr (compiled-obj) "name" value) - (if (and (list? prop-ast) (= (first prop-ast) (quote attr))) - (list (quote dom-set-attr) + (if + (and + (list? prop-ast) + (= (first prop-ast) (quote attr))) + (list + (quote dom-set-attr) (hs-to-sx obj-ast) (nth prop-ast 1) value) - ;; Simple: (ref "prop") of obj → (dom-set-prop (compiled-obj) "prop" value) - (if (and (list? prop-ast) (= (first prop-ast) (quote ref))) - (list (quote dom-set-prop) + (if + (and + (list? prop-ast) + (= (first prop-ast) (quote ref))) + (list + (quote dom-set-prop) (hs-to-sx obj-ast) (nth prop-ast 1) value) - ;; Fallback (list (quote set!) (hs-to-sx target) value)))))) (true (list (quote set!) (hs-to-sx target) value))))))) (define @@ -427,7 +434,7 @@ ((= head (quote null-literal)) nil) ((= head (quote not)) (list (quote not) (hs-to-sx (nth ast 1)))) - ((or (= head (quote starts-with?)) (= head (quote ends-with?)) (= head (quote contains?)) (= head (quote matches?)) (= head (quote precedes?)) (= head (quote follows?)) (= head (quote exists?))) + ((or (= head (quote starts-with?)) (= head (quote ends-with?)) (= head (quote contains?)) (= head (quote precedes?)) (= head (quote follows?)) (= head (quote exists?))) (cons head (map hs-to-sx (rest ast)))) ((= head (quote object-literal)) (let @@ -656,6 +663,11 @@ (quote dom-get-style) (hs-to-sx (nth ast 2)) (nth ast 1))) + ((= head (quote has-class?)) + (list + (quote dom-has-class?) + (hs-to-sx (nth ast 1)) + (nth ast 2))) ((= head (quote local)) (make-symbol (nth ast 1))) ((= head (quote array)) (cons (quote list) (map hs-to-sx (rest ast)))) @@ -713,15 +725,30 @@ (quote not) (list (quote nil?) (hs-to-sx (nth ast 1))))) ((= head (quote matches?)) - (list - (quote hs-matches?) - (hs-to-sx (nth ast 1)) - (hs-to-sx (nth ast 2)))) + (let + ((left (nth ast 1)) (right (nth ast 2))) + (if + (and (list? right) (= (first right) (quote query))) + (list (quote hs-matches?) (hs-to-sx left) (nth right 1)) + (list + (quote hs-matches?) + (hs-to-sx left) + (hs-to-sx right))))) ((= head (quote matches-ignore-case?)) (list (quote hs-matches-ignore-case?) (hs-to-sx (nth ast 1)) (hs-to-sx (nth ast 2)))) + ((= head (quote starts-with-ic?)) + (list + (quote hs-starts-with-ic?) + (hs-to-sx (nth ast 1)) + (hs-to-sx (nth ast 2)))) + ((= head (quote ends-with-ic?)) + (list + (quote hs-ends-with-ic?) + (hs-to-sx (nth ast 1)) + (hs-to-sx (nth ast 2)))) ((= head (quote contains?)) (list (quote hs-contains?) @@ -824,6 +851,23 @@ (map (fn (cls) (list (quote dom-add-class) target cls)) classes)))) + ((= head (quote add-class-when)) + (let + ((cls (nth ast 1)) + (raw-tgt (nth ast 2)) + (when-cond (nth ast 3))) + (let + ((tgt-expr (cond ((and (list? raw-tgt) (= (first raw-tgt) (quote query))) (list (quote hs-query-all) (nth raw-tgt 1))) ((and (list? raw-tgt) (= (first raw-tgt) (quote in?)) (list? (nth raw-tgt 1)) (= (first (nth raw-tgt 1)) (quote query))) (list (quote host-call) (hs-to-sx (nth raw-tgt 2)) "querySelectorAll" (nth (nth raw-tgt 1) 1))) (true (hs-to-sx raw-tgt))))) + (list + (quote for-each) + (list + (quote fn) + (list (quote it)) + (list + (quote when) + (hs-to-sx when-cond) + (list (quote dom-add-class) (quote it) cls))) + tgt-expr)))) ((= head (quote multi-remove-class)) (let ((target (hs-to-sx (nth ast 1))) @@ -895,10 +939,12 @@ (nth ast 1) (nth ast 2))) ((= head (quote toggle-style)) - (list - (quote hs-toggle-style!) - (hs-to-sx (nth ast 2)) - (nth ast 1))) + (let + ((raw-tgt (nth ast 2))) + (list + (quote hs-toggle-style!) + (if (nil? raw-tgt) (quote me) (hs-to-sx raw-tgt)) + (nth ast 1)))) ((= head (quote toggle-style-between)) (list (quote hs-toggle-style-between!) diff --git a/lib/hyperscript/parser.sx b/lib/hyperscript/parser.sx index b0423811..c59cdd5f 100644 --- a/lib/hyperscript/parser.sx +++ b/lib/hyperscript/parser.sx @@ -513,12 +513,26 @@ (do (adv!) (match-kw "with") - (list (quote starts-with?) left (parse-expr)))) + (let + ((rhs (parse-atom))) + (if + (match-kw "ignoring") + (do + (match-kw "case") + (list (quote starts-with-ic?) left rhs)) + (list (quote starts-with?) left rhs))))) ((and (or (= typ "keyword") (= typ "ident")) (= val "ends")) (do (adv!) (match-kw "with") - (list (quote ends-with?) left (parse-expr)))) + (let + ((rhs (parse-atom))) + (if + (match-kw "ignoring") + (do + (match-kw "case") + (list (quote ends-with-ic?) left rhs)) + (list (quote ends-with?) left rhs))))) ((and (= typ "keyword") (= val "matches")) (do (adv!) @@ -607,11 +621,17 @@ (quote not) (list (quote contains?) left (parse-expr)))) ((match-kw "start") - (do (match-kw "with") - (list (quote not) (list (quote starts-with?) left (parse-expr))))) + (do + (match-kw "with") + (list + (quote not) + (list (quote starts-with?) left (parse-expr))))) ((match-kw "end") - (do (match-kw "with") - (list (quote not) (list (quote ends-with?) left (parse-expr))))) + (do + (match-kw "with") + (list + (quote not) + (list (quote ends-with?) left (parse-expr))))) (true left)))) ((and (= typ "keyword") (= val "equals")) (do (adv!) (list (quote =) left (parse-expr)))) @@ -693,12 +713,20 @@ nil (do (when - (and (number? left) (= (tp-type) "ident") - (not (or (= (tp-val) "starts") (= (tp-val) "ends") - (= (tp-val) "contains") (= (tp-val) "matches") - (= (tp-val) "is") (= (tp-val) "does") - (= (tp-val) "in") (= (tp-val) "precedes") - (= (tp-val) "follows")))) + (and + (number? left) + (= (tp-type) "ident") + (not + (or + (= (tp-val) "starts") + (= (tp-val) "ends") + (= (tp-val) "contains") + (= (tp-val) "matches") + (= (tp-val) "is") + (= (tp-val) "does") + (= (tp-val) "in") + (= (tp-val) "precedes") + (= (tp-val) "follows")))) (let ((unit (tp-val))) (do @@ -757,12 +785,25 @@ (collect-classes!) (let ((tgt (parse-tgt-kw "to" (list (quote me))))) - (if - (empty? extra-classes) - (list (quote add-class) cls tgt) - (cons - (quote multi-add-class) - (cons tgt (cons cls extra-classes)))))) + (let + ((when-clause (if (match-kw "when") (parse-expr) nil))) + (if + (empty? extra-classes) + (if + when-clause + (list (quote add-class-when) cls tgt when-clause) + (list (quote add-class) cls tgt)) + (if + when-clause + (list + (quote multi-add-class-when) + tgt + when-clause + cls + extra-classes) + (cons + (quote multi-add-class) + (cons tgt (cons cls extra-classes)))))))) nil))) (define parse-remove-cmd @@ -893,6 +934,70 @@ (let ((tgt (parse-tgt-kw "on" (list (quote me))))) (list (quote toggle-attr) attr-name tgt))))) + ((and (= (tp-type) "keyword") (= (tp-val) "my")) + (do + (adv!) + (cond + ((= (tp-type) "style") + (let + ((prop (get (adv!) "value"))) + (if + (match-kw "between") + (let + ((val1 (parse-expr))) + (expect-kw! "and") + (let + ((val2 (parse-expr))) + (let + ((tgt (if (match-kw "on") (parse-expr) nil))) + (list + (quote toggle-style-between) + prop + val1 + val2 + tgt)))) + (let + ((tgt (if (match-kw "on") (parse-expr) nil))) + (list (quote toggle-style) prop tgt))))) + ((= (tp-type) "attr") + (let + ((attr-name (get (adv!) "value"))) + (let + ((tgt (if (match-kw "on") (parse-expr) nil))) + (list (quote toggle-attr) attr-name tgt)))) + (true nil)))) + ((and (= (tp-type) "keyword") (= (tp-val) "the")) + (do + (adv!) + (let + ((expr (parse-the-expr))) + (cond + ((and (list? expr) (= (first expr) (quote style))) + (let + ((prop (nth expr 1)) (tgt (nth expr 2))) + (if + (match-kw "between") + (let + ((val1 (parse-expr))) + (expect-kw! "and") + (let + ((val2 (parse-expr))) + (list + (quote toggle-style-between) + prop + val1 + val2 + tgt))) + (list (quote toggle-style) prop tgt)))) + ((and (list? expr) (= (first expr) (quote attr))) + (let + ((attr-name (nth expr 1)) (tgt (nth expr 2))) + (list (quote toggle-attr) attr-name tgt))) + ((and (list? expr) (= (first expr) (quote has-class?))) + (let + ((tgt (nth expr 1)) (cls (nth expr 2))) + (list (quote toggle-class) cls tgt))) + (true nil))))) (true nil)))) (define parse-set-cmd @@ -1080,21 +1185,26 @@ (fn () (let - ((prop (cond ((= (tp-type) "style") (get (adv!) "value")) ((= (tp-val) "my") (do (adv!) (if (= (tp-type) "style") (get (adv!) "value") (get (adv!) "value")))) (true (get (adv!) "value"))))) + ((tgt (cond ((and (= (tp-type) "ident") (= (tp-val) "element")) (do (adv!) (parse-atom))) ((= (tp-type) "id") (parse-atom)) ((= (tp-type) "class") (parse-atom)) ((= (tp-type) "selector") (parse-atom)) (true nil)))) (let - ((from-val (if (match-kw "from") (parse-expr) nil))) - (expect-kw! "to") + ((prop (cond ((= (tp-type) "style") (get (adv!) "value")) ((= (tp-val) "my") (do (adv!) (if (= (tp-type) "style") (get (adv!) "value") (get (adv!) "value")))) (true (get (adv!) "value"))))) (let - ((value (parse-expr))) + ((from-val (if (match-kw "from") (parse-expr) nil))) + (expect-kw! "to") (let - ((dur (if (match-kw "over") (parse-expr) nil))) - (if - from-val - (list (quote transition-from) prop from-val value dur) + ((value (parse-expr))) + (let + ((dur (if (match-kw "over") (parse-expr) nil))) (if - dur - (list (quote transition) prop value dur nil) - (list (quote transition) prop value nil))))))))) + from-val + (list + (quote transition-from) + prop + from-val + value + dur + tgt) + (list (quote transition) prop value dur tgt))))))))) (define parse-repeat-cmd (fn @@ -1223,22 +1333,53 @@ () (let ((typ (tp-type)) (val (tp-val))) - (if - (or (= typ "ident") (= typ "keyword")) - (do - (adv!) - (if - (match-kw "of") - (list (make-symbol ".") (parse-expr) val) - (cond - ((= val "result") (list (quote it))) - ((= val "first") (parse-pos-kw (quote first))) - ((= val "last") (parse-pos-kw (quote last))) - ((= val "closest") (parse-trav (quote closest))) - ((= val "next") (parse-trav (quote next))) - ((= val "previous") (parse-trav (quote previous))) - (true (list (quote ref) val))))) - (parse-atom))))) + (cond + ((= typ "style") + (do + (adv!) + (if + (match-kw "of") + (list (quote style) val (parse-expr)) + (list (quote style) val (list (quote me)))))) + ((= typ "attr") + (do + (adv!) + (if + (match-kw "of") + (list (quote attr) val (parse-expr)) + (list (quote attr) val (list (quote me)))))) + ((= typ "class") + (do + (adv!) + (if + (match-kw "of") + (list (quote has-class?) (parse-expr) val) + (list (quote has-class?) (list (quote me)) val)))) + ((= typ "selector") + (do + (adv!) + (if + (match-kw "in") + (list + (quote in?) + (list (quote query) val) + (parse-expr)) + (list (quote query) val)))) + ((or (= typ "ident") (= typ "keyword")) + (do + (adv!) + (if + (match-kw "of") + (list (make-symbol ".") (parse-expr) val) + (cond + ((= val "result") (list (quote it))) + ((= val "first") (parse-pos-kw (quote first))) + ((= val "last") (parse-pos-kw (quote last))) + ((= val "closest") (parse-trav (quote closest))) + ((= val "next") (parse-trav (quote next))) + ((= val "previous") (parse-trav (quote previous))) + (true (list (quote ref) val)))))) + (true (parse-atom)))))) (define parse-array-lit (fn diff --git a/lib/hyperscript/runtime.sx b/lib/hyperscript/runtime.sx index 41286add..ee3ab575 100644 --- a/lib/hyperscript/runtime.sx +++ b/lib/hyperscript/runtime.sx @@ -88,7 +88,7 @@ ((or (= prop "display") (= prop "opacity")) (if (or (= cur "none") (= cur "0")) - (dom-set-style target prop "") + (dom-set-style target prop (if (= prop "opacity") "1" "")) (dom-set-style target prop (if (= prop "display") "none" "0")))) (true (if @@ -102,17 +102,30 @@ (define hs-take! (fn - (target kind name scope) + (target kind name scope &rest extra) (let - ((els (if scope (if (list? scope) scope (list scope)) (let ((parent (host-get target "parentNode"))) (if parent (dom-child-list parent) (list)))))) + ((els (if scope (if (list? scope) scope (list scope)) (let ((parent (dom-parent target))) (if parent (dom-child-list parent) (list)))))) (if (= kind "class") (do (for-each (fn (el) (dom-remove-class el name)) els) (dom-add-class target name)) - (do - (for-each (fn (el) (dom-remove-attr el name)) els) - (dom-set-attr target name "true")))))) + (let + ((attr-val (if (> (len extra) 0) (first extra) nil)) + (with-val (if (> (len extra) 1) (nth extra 1) nil))) + (do + (for-each + (fn + (el) + (if + with-val + (dom-set-attr el name with-val) + (dom-remove-attr el name))) + els) + (if + attr-val + (dom-set-attr target name attr-val) + (dom-set-attr target name "")))))))) ;; Find next sibling matching a selector (or any sibling). (define @@ -204,7 +217,9 @@ ;; Fetch a URL, parse response according to format. ;; (hs-fetch url format) — format is "json" | "text" | "html" -(define hs-query-all (fn (sel) (dom-query-all (dom-body) sel))) +(define + hs-query-all + (fn (sel) (host-call (dom-body) "querySelectorAll" sel))) ;; ── Type coercion ─────────────────────────────────────────────── @@ -290,29 +305,72 @@ ((= type-name "Bool") (not (hs-falsy? value))) ((= type-name "Boolean") (not (hs-falsy? value))) ((= type-name "Array") (if (list? value) value (list value))) - ((= type-name "JSON") (str value)) - ((= type-name "Object") (if (string? value) value value)) - ((or (= type-name "Fixed") (string-contains? type-name "Fixed:")) + ((= type-name "HTML") (str value)) + ((= type-name "JSON") + (if + (string? value) + value + (host-call (host-global "JSON") "stringify" value))) + ((= type-name "Object") + (if + (string? value) + (host-call (host-global "JSON") "parse" value) + value)) + ((or (= type-name "Fixed") (= type-name "Fixed:")) (let - ((digits (if (string-contains? type-name ":") (parse-number (nth (split type-name ":") 1)) 0)) + ((digits (if (> (string-length type-name) 6) (+ (substring type-name 6 (string-length type-name)) 0) 0)) (num (+ value 0))) (if (= digits 0) (str (floor num)) (let - ((factor (reduce (fn (acc _) (* acc 10)) 1 (range 0 digits)))) - (let - ((rounded (/ (floor (+ (* num factor) 0.5)) factor))) - (str rounded)))))) - ((= type-name "HTML") (str value)) - ((= type-name "Values") value) - ((= type-name "Fragment") (str value)) - ((= type-name "Date") (str value)) + ((factor (** 10 digits))) + (str (/ (floor (+ (* num factor) 0.5)) factor)))))) + ((= type-name "Selector") (str value)) + ((= type-name "Fragment") value) + ((= type-name "Values") + (if + (dict? value) + (map (fn (k) (get value k)) (keys value)) + value)) + ((= type-name "Keys") (if (dict? value) (keys value) value)) + ((= type-name "Entries") + (if + (dict? value) + (map (fn (k) (list k (get value k))) (keys value)) + value)) + ((= type-name "Reversed") (if (list? value) (reverse value) value)) + ((= type-name "Unique") + (if + (list? value) + (reduce + (fn + (acc x) + (if (some (fn (a) (= a x)) acc) acc (append acc (list x)))) + (list) + value) + value)) + ((or (= type-name "Flattened") (= type-name "Flat")) + (if + (list? value) + (reduce + (fn + (acc x) + (if (list? x) (append acc x) (append acc (list x)))) + (list) + value) + value)) (true value)))) (define hs-add - (fn (a b) (if (or (string? a) (string? b)) (str a b) (+ a b)))) + (fn + (a b) + (cond + ((list? a) (if (list? b) (append a b) (append a (list b)))) + ((list? b) (cons a b)) + ((or (string? a) (string? b)) (str a b)) + (true (+ a b))))) (define hs-make @@ -371,7 +429,12 @@ ((= type-name "Boolean") (or (= value true) (= value false))) ((= type-name "Array") (list? value)) ((= type-name "Object") (dict? value)) - (true true))))) + ((= type-name "Element") (= (host-typeof value) "element")) + ((= type-name "Node") + (or + (= (host-typeof value) "element") + (= (host-typeof value) "text"))) + (true (= (host-typeof value) (downcase type-name))))))) @@ -392,12 +455,18 @@ hs-eq-ignore-case (fn (a b) (= (downcase (str a)) (downcase (str b))))) ;; DOM query stub — sandbox returns empty list +(define + hs-starts-with-ic? + (fn (str prefix) (starts-with? (downcase str) (downcase prefix)))) +;; Method dispatch — obj.method(args) (define hs-contains-ignore-case? (fn (haystack needle) (contains? (downcase (str haystack)) (downcase (str needle))))) -;; Method dispatch — obj.method(args) + +;; ── 0.9.90 features ───────────────────────────────────────────── +;; beep! — debug logging, returns value unchanged (define hs-falsy? (fn @@ -409,18 +478,18 @@ ((and (list? v) (= (len v) 0)) true) ((= v 0) true) (true false)))) - -;; ── 0.9.90 features ───────────────────────────────────────────── -;; beep! — debug logging, returns value unchanged +;; Property-based is — check obj.key truthiness (define hs-matches? (fn (target pattern) - (if - (string? target) - (if (= pattern ".*") true (string-contains? target pattern)) - false))) -;; Property-based is — check obj.key truthiness + (cond + ((string? target) + (if (= pattern ".*") true (string-contains? target pattern))) + ((= (host-typeof target) "element") + (if (string? pattern) (host-call target "matches" pattern) false)) + (true false)))) +;; Array slicing (inclusive both ends) (define hs-contains? (fn @@ -440,9 +509,9 @@ true (hs-contains? (rest collection) item))))) (true false)))) -;; Array slicing (inclusive both ends) -(define precedes? (fn (a b) (< (str a) (str b)))) ;; Collection: sorted by +(define precedes? (fn (a b) (< (str a) (str b)))) +;; Collection: sorted by descending (define hs-empty? (fn @@ -453,7 +522,7 @@ ((list? v) (= (len v) 0)) ((dict? v) (= (len (keys v)) 0)) (true false)))) -;; Collection: sorted by descending +;; Collection: split by (define hs-empty-target! (fn @@ -474,7 +543,7 @@ (dom-set-prop target "value" "")))) ((= tag "FORM") (dom-set-inner-html target "")) (true (dom-set-inner-html target "")))))))) -;; Collection: split by +;; Collection: joined by (define hs-open! (fn @@ -485,7 +554,7 @@ (= tag "DIALOG") (host-call el "showModal") (dom-set-prop el "open" true))))) -;; Collection: joined by + (define hs-close! (fn diff --git a/shared/static/wasm/sx/hs-compiler.sx b/shared/static/wasm/sx/hs-compiler.sx index c09941b9..ee9ee518 100644 --- a/shared/static/wasm/sx/hs-compiler.sx +++ b/shared/static/wasm/sx/hs-compiler.sx @@ -56,32 +56,39 @@ (hs-to-sx (nth target 2)) value)) ((= th (quote of)) - ;; Decompose (of prop-expr target) into a set operation - ;; e.g. (of (. (ref "parentNode") "innerHTML") (query "#d1")) - ;; → set parentNode.innerHTML of #d1 → need to navigate target, then set final prop - (let ((prop-ast (nth target 1)) - (obj-ast (nth target 2))) - (if (and (list? prop-ast) (= (first prop-ast) dot-sym)) - ;; (. base "prop") of obj → (dom-set-prop (host-get (compiled-obj) (compiled-base-name)) "prop" value) - (let ((base (nth prop-ast 1)) - (prop-name (nth prop-ast 2))) - (list (quote dom-set-prop) - (list (quote host-get) (hs-to-sx obj-ast) (nth base 1)) + (let + ((prop-ast (nth target 1)) (obj-ast (nth target 2))) + (if + (and (list? prop-ast) (= (first prop-ast) dot-sym)) + (let + ((base (nth prop-ast 1)) + (prop-name (nth prop-ast 2))) + (list + (quote dom-set-prop) + (list + (quote host-get) + (hs-to-sx obj-ast) + (nth base 1)) prop-name value)) - ;; (attr "name") of obj → (dom-set-attr (compiled-obj) "name" value) - (if (and (list? prop-ast) (= (first prop-ast) (quote attr))) - (list (quote dom-set-attr) + (if + (and + (list? prop-ast) + (= (first prop-ast) (quote attr))) + (list + (quote dom-set-attr) (hs-to-sx obj-ast) (nth prop-ast 1) value) - ;; Simple: (ref "prop") of obj → (dom-set-prop (compiled-obj) "prop" value) - (if (and (list? prop-ast) (= (first prop-ast) (quote ref))) - (list (quote dom-set-prop) + (if + (and + (list? prop-ast) + (= (first prop-ast) (quote ref))) + (list + (quote dom-set-prop) (hs-to-sx obj-ast) (nth prop-ast 1) value) - ;; Fallback (list (quote set!) (hs-to-sx target) value)))))) (true (list (quote set!) (hs-to-sx target) value))))))) (define @@ -427,7 +434,7 @@ ((= head (quote null-literal)) nil) ((= head (quote not)) (list (quote not) (hs-to-sx (nth ast 1)))) - ((or (= head (quote starts-with?)) (= head (quote ends-with?)) (= head (quote contains?)) (= head (quote matches?)) (= head (quote precedes?)) (= head (quote follows?)) (= head (quote exists?))) + ((or (= head (quote starts-with?)) (= head (quote ends-with?)) (= head (quote contains?)) (= head (quote precedes?)) (= head (quote follows?)) (= head (quote exists?))) (cons head (map hs-to-sx (rest ast)))) ((= head (quote object-literal)) (let @@ -656,6 +663,11 @@ (quote dom-get-style) (hs-to-sx (nth ast 2)) (nth ast 1))) + ((= head (quote has-class?)) + (list + (quote dom-has-class?) + (hs-to-sx (nth ast 1)) + (nth ast 2))) ((= head (quote local)) (make-symbol (nth ast 1))) ((= head (quote array)) (cons (quote list) (map hs-to-sx (rest ast)))) @@ -713,15 +725,30 @@ (quote not) (list (quote nil?) (hs-to-sx (nth ast 1))))) ((= head (quote matches?)) - (list - (quote hs-matches?) - (hs-to-sx (nth ast 1)) - (hs-to-sx (nth ast 2)))) + (let + ((left (nth ast 1)) (right (nth ast 2))) + (if + (and (list? right) (= (first right) (quote query))) + (list (quote hs-matches?) (hs-to-sx left) (nth right 1)) + (list + (quote hs-matches?) + (hs-to-sx left) + (hs-to-sx right))))) ((= head (quote matches-ignore-case?)) (list (quote hs-matches-ignore-case?) (hs-to-sx (nth ast 1)) (hs-to-sx (nth ast 2)))) + ((= head (quote starts-with-ic?)) + (list + (quote hs-starts-with-ic?) + (hs-to-sx (nth ast 1)) + (hs-to-sx (nth ast 2)))) + ((= head (quote ends-with-ic?)) + (list + (quote hs-ends-with-ic?) + (hs-to-sx (nth ast 1)) + (hs-to-sx (nth ast 2)))) ((= head (quote contains?)) (list (quote hs-contains?) @@ -824,6 +851,23 @@ (map (fn (cls) (list (quote dom-add-class) target cls)) classes)))) + ((= head (quote add-class-when)) + (let + ((cls (nth ast 1)) + (raw-tgt (nth ast 2)) + (when-cond (nth ast 3))) + (let + ((tgt-expr (cond ((and (list? raw-tgt) (= (first raw-tgt) (quote query))) (list (quote hs-query-all) (nth raw-tgt 1))) ((and (list? raw-tgt) (= (first raw-tgt) (quote in?)) (list? (nth raw-tgt 1)) (= (first (nth raw-tgt 1)) (quote query))) (list (quote host-call) (hs-to-sx (nth raw-tgt 2)) "querySelectorAll" (nth (nth raw-tgt 1) 1))) (true (hs-to-sx raw-tgt))))) + (list + (quote for-each) + (list + (quote fn) + (list (quote it)) + (list + (quote when) + (hs-to-sx when-cond) + (list (quote dom-add-class) (quote it) cls))) + tgt-expr)))) ((= head (quote multi-remove-class)) (let ((target (hs-to-sx (nth ast 1))) @@ -895,10 +939,12 @@ (nth ast 1) (nth ast 2))) ((= head (quote toggle-style)) - (list - (quote hs-toggle-style!) - (hs-to-sx (nth ast 2)) - (nth ast 1))) + (let + ((raw-tgt (nth ast 2))) + (list + (quote hs-toggle-style!) + (if (nil? raw-tgt) (quote me) (hs-to-sx raw-tgt)) + (nth ast 1)))) ((= head (quote toggle-style-between)) (list (quote hs-toggle-style-between!) diff --git a/shared/static/wasm/sx/hs-parser.sx b/shared/static/wasm/sx/hs-parser.sx index b0423811..c59cdd5f 100644 --- a/shared/static/wasm/sx/hs-parser.sx +++ b/shared/static/wasm/sx/hs-parser.sx @@ -513,12 +513,26 @@ (do (adv!) (match-kw "with") - (list (quote starts-with?) left (parse-expr)))) + (let + ((rhs (parse-atom))) + (if + (match-kw "ignoring") + (do + (match-kw "case") + (list (quote starts-with-ic?) left rhs)) + (list (quote starts-with?) left rhs))))) ((and (or (= typ "keyword") (= typ "ident")) (= val "ends")) (do (adv!) (match-kw "with") - (list (quote ends-with?) left (parse-expr)))) + (let + ((rhs (parse-atom))) + (if + (match-kw "ignoring") + (do + (match-kw "case") + (list (quote ends-with-ic?) left rhs)) + (list (quote ends-with?) left rhs))))) ((and (= typ "keyword") (= val "matches")) (do (adv!) @@ -607,11 +621,17 @@ (quote not) (list (quote contains?) left (parse-expr)))) ((match-kw "start") - (do (match-kw "with") - (list (quote not) (list (quote starts-with?) left (parse-expr))))) + (do + (match-kw "with") + (list + (quote not) + (list (quote starts-with?) left (parse-expr))))) ((match-kw "end") - (do (match-kw "with") - (list (quote not) (list (quote ends-with?) left (parse-expr))))) + (do + (match-kw "with") + (list + (quote not) + (list (quote ends-with?) left (parse-expr))))) (true left)))) ((and (= typ "keyword") (= val "equals")) (do (adv!) (list (quote =) left (parse-expr)))) @@ -693,12 +713,20 @@ nil (do (when - (and (number? left) (= (tp-type) "ident") - (not (or (= (tp-val) "starts") (= (tp-val) "ends") - (= (tp-val) "contains") (= (tp-val) "matches") - (= (tp-val) "is") (= (tp-val) "does") - (= (tp-val) "in") (= (tp-val) "precedes") - (= (tp-val) "follows")))) + (and + (number? left) + (= (tp-type) "ident") + (not + (or + (= (tp-val) "starts") + (= (tp-val) "ends") + (= (tp-val) "contains") + (= (tp-val) "matches") + (= (tp-val) "is") + (= (tp-val) "does") + (= (tp-val) "in") + (= (tp-val) "precedes") + (= (tp-val) "follows")))) (let ((unit (tp-val))) (do @@ -757,12 +785,25 @@ (collect-classes!) (let ((tgt (parse-tgt-kw "to" (list (quote me))))) - (if - (empty? extra-classes) - (list (quote add-class) cls tgt) - (cons - (quote multi-add-class) - (cons tgt (cons cls extra-classes)))))) + (let + ((when-clause (if (match-kw "when") (parse-expr) nil))) + (if + (empty? extra-classes) + (if + when-clause + (list (quote add-class-when) cls tgt when-clause) + (list (quote add-class) cls tgt)) + (if + when-clause + (list + (quote multi-add-class-when) + tgt + when-clause + cls + extra-classes) + (cons + (quote multi-add-class) + (cons tgt (cons cls extra-classes)))))))) nil))) (define parse-remove-cmd @@ -893,6 +934,70 @@ (let ((tgt (parse-tgt-kw "on" (list (quote me))))) (list (quote toggle-attr) attr-name tgt))))) + ((and (= (tp-type) "keyword") (= (tp-val) "my")) + (do + (adv!) + (cond + ((= (tp-type) "style") + (let + ((prop (get (adv!) "value"))) + (if + (match-kw "between") + (let + ((val1 (parse-expr))) + (expect-kw! "and") + (let + ((val2 (parse-expr))) + (let + ((tgt (if (match-kw "on") (parse-expr) nil))) + (list + (quote toggle-style-between) + prop + val1 + val2 + tgt)))) + (let + ((tgt (if (match-kw "on") (parse-expr) nil))) + (list (quote toggle-style) prop tgt))))) + ((= (tp-type) "attr") + (let + ((attr-name (get (adv!) "value"))) + (let + ((tgt (if (match-kw "on") (parse-expr) nil))) + (list (quote toggle-attr) attr-name tgt)))) + (true nil)))) + ((and (= (tp-type) "keyword") (= (tp-val) "the")) + (do + (adv!) + (let + ((expr (parse-the-expr))) + (cond + ((and (list? expr) (= (first expr) (quote style))) + (let + ((prop (nth expr 1)) (tgt (nth expr 2))) + (if + (match-kw "between") + (let + ((val1 (parse-expr))) + (expect-kw! "and") + (let + ((val2 (parse-expr))) + (list + (quote toggle-style-between) + prop + val1 + val2 + tgt))) + (list (quote toggle-style) prop tgt)))) + ((and (list? expr) (= (first expr) (quote attr))) + (let + ((attr-name (nth expr 1)) (tgt (nth expr 2))) + (list (quote toggle-attr) attr-name tgt))) + ((and (list? expr) (= (first expr) (quote has-class?))) + (let + ((tgt (nth expr 1)) (cls (nth expr 2))) + (list (quote toggle-class) cls tgt))) + (true nil))))) (true nil)))) (define parse-set-cmd @@ -1080,21 +1185,26 @@ (fn () (let - ((prop (cond ((= (tp-type) "style") (get (adv!) "value")) ((= (tp-val) "my") (do (adv!) (if (= (tp-type) "style") (get (adv!) "value") (get (adv!) "value")))) (true (get (adv!) "value"))))) + ((tgt (cond ((and (= (tp-type) "ident") (= (tp-val) "element")) (do (adv!) (parse-atom))) ((= (tp-type) "id") (parse-atom)) ((= (tp-type) "class") (parse-atom)) ((= (tp-type) "selector") (parse-atom)) (true nil)))) (let - ((from-val (if (match-kw "from") (parse-expr) nil))) - (expect-kw! "to") + ((prop (cond ((= (tp-type) "style") (get (adv!) "value")) ((= (tp-val) "my") (do (adv!) (if (= (tp-type) "style") (get (adv!) "value") (get (adv!) "value")))) (true (get (adv!) "value"))))) (let - ((value (parse-expr))) + ((from-val (if (match-kw "from") (parse-expr) nil))) + (expect-kw! "to") (let - ((dur (if (match-kw "over") (parse-expr) nil))) - (if - from-val - (list (quote transition-from) prop from-val value dur) + ((value (parse-expr))) + (let + ((dur (if (match-kw "over") (parse-expr) nil))) (if - dur - (list (quote transition) prop value dur nil) - (list (quote transition) prop value nil))))))))) + from-val + (list + (quote transition-from) + prop + from-val + value + dur + tgt) + (list (quote transition) prop value dur tgt))))))))) (define parse-repeat-cmd (fn @@ -1223,22 +1333,53 @@ () (let ((typ (tp-type)) (val (tp-val))) - (if - (or (= typ "ident") (= typ "keyword")) - (do - (adv!) - (if - (match-kw "of") - (list (make-symbol ".") (parse-expr) val) - (cond - ((= val "result") (list (quote it))) - ((= val "first") (parse-pos-kw (quote first))) - ((= val "last") (parse-pos-kw (quote last))) - ((= val "closest") (parse-trav (quote closest))) - ((= val "next") (parse-trav (quote next))) - ((= val "previous") (parse-trav (quote previous))) - (true (list (quote ref) val))))) - (parse-atom))))) + (cond + ((= typ "style") + (do + (adv!) + (if + (match-kw "of") + (list (quote style) val (parse-expr)) + (list (quote style) val (list (quote me)))))) + ((= typ "attr") + (do + (adv!) + (if + (match-kw "of") + (list (quote attr) val (parse-expr)) + (list (quote attr) val (list (quote me)))))) + ((= typ "class") + (do + (adv!) + (if + (match-kw "of") + (list (quote has-class?) (parse-expr) val) + (list (quote has-class?) (list (quote me)) val)))) + ((= typ "selector") + (do + (adv!) + (if + (match-kw "in") + (list + (quote in?) + (list (quote query) val) + (parse-expr)) + (list (quote query) val)))) + ((or (= typ "ident") (= typ "keyword")) + (do + (adv!) + (if + (match-kw "of") + (list (make-symbol ".") (parse-expr) val) + (cond + ((= val "result") (list (quote it))) + ((= val "first") (parse-pos-kw (quote first))) + ((= val "last") (parse-pos-kw (quote last))) + ((= val "closest") (parse-trav (quote closest))) + ((= val "next") (parse-trav (quote next))) + ((= val "previous") (parse-trav (quote previous))) + (true (list (quote ref) val)))))) + (true (parse-atom)))))) (define parse-array-lit (fn diff --git a/shared/static/wasm/sx/hs-runtime.sx b/shared/static/wasm/sx/hs-runtime.sx index c294ccf6..ee3ab575 100644 --- a/shared/static/wasm/sx/hs-runtime.sx +++ b/shared/static/wasm/sx/hs-runtime.sx @@ -88,7 +88,7 @@ ((or (= prop "display") (= prop "opacity")) (if (or (= cur "none") (= cur "0")) - (dom-set-style target prop "") + (dom-set-style target prop (if (= prop "opacity") "1" "")) (dom-set-style target prop (if (= prop "display") "none" "0")))) (true (if @@ -102,17 +102,30 @@ (define hs-take! (fn - (target kind name scope) + (target kind name scope &rest extra) (let - ((els (if scope (if (list? scope) scope (list scope)) (let ((parent (host-get target "parentNode"))) (if parent (dom-child-list parent) (list)))))) + ((els (if scope (if (list? scope) scope (list scope)) (let ((parent (dom-parent target))) (if parent (dom-child-list parent) (list)))))) (if (= kind "class") (do (for-each (fn (el) (dom-remove-class el name)) els) (dom-add-class target name)) - (do - (for-each (fn (el) (dom-remove-attr el name)) els) - (dom-set-attr target name "true")))))) + (let + ((attr-val (if (> (len extra) 0) (first extra) nil)) + (with-val (if (> (len extra) 1) (nth extra 1) nil))) + (do + (for-each + (fn + (el) + (if + with-val + (dom-set-attr el name with-val) + (dom-remove-attr el name))) + els) + (if + attr-val + (dom-set-attr target name attr-val) + (dom-set-attr target name "")))))))) ;; Find next sibling matching a selector (or any sibling). (define @@ -204,7 +217,9 @@ ;; Fetch a URL, parse response according to format. ;; (hs-fetch url format) — format is "json" | "text" | "html" -(define hs-query-all (fn (sel) (dom-query-all (dom-body) sel))) +(define + hs-query-all + (fn (sel) (host-call (dom-body) "querySelectorAll" sel))) ;; ── Type coercion ─────────────────────────────────────────────── @@ -287,32 +302,75 @@ ((= type-name "Float") (+ value 0)) ((= type-name "Number") (+ value 0)) ((= type-name "String") (str value)) - ((= type-name "Bool") (if value true false)) - ((= type-name "Boolean") (if value true false)) + ((= type-name "Bool") (not (hs-falsy? value))) + ((= type-name "Boolean") (not (hs-falsy? value))) ((= type-name "Array") (if (list? value) value (list value))) - ((= type-name "JSON") (str value)) - ((= type-name "Object") (if (string? value) value value)) - ((or (= type-name "Fixed") (string-contains? type-name "Fixed:")) + ((= type-name "HTML") (str value)) + ((= type-name "JSON") + (if + (string? value) + value + (host-call (host-global "JSON") "stringify" value))) + ((= type-name "Object") + (if + (string? value) + (host-call (host-global "JSON") "parse" value) + value)) + ((or (= type-name "Fixed") (= type-name "Fixed:")) (let - ((digits (if (string-contains? type-name ":") (parse-number (nth (split type-name ":") 1)) 0)) + ((digits (if (> (string-length type-name) 6) (+ (substring type-name 6 (string-length type-name)) 0) 0)) (num (+ value 0))) (if (= digits 0) (str (floor num)) (let - ((factor (reduce (fn (acc _) (* acc 10)) 1 (range 0 digits)))) - (let - ((rounded (/ (floor (+ (* num factor) 0.5)) factor))) - (str rounded)))))) - ((= type-name "HTML") (str value)) - ((= type-name "Values") value) - ((= type-name "Fragment") (str value)) - ((= type-name "Date") (str value)) + ((factor (** 10 digits))) + (str (/ (floor (+ (* num factor) 0.5)) factor)))))) + ((= type-name "Selector") (str value)) + ((= type-name "Fragment") value) + ((= type-name "Values") + (if + (dict? value) + (map (fn (k) (get value k)) (keys value)) + value)) + ((= type-name "Keys") (if (dict? value) (keys value) value)) + ((= type-name "Entries") + (if + (dict? value) + (map (fn (k) (list k (get value k))) (keys value)) + value)) + ((= type-name "Reversed") (if (list? value) (reverse value) value)) + ((= type-name "Unique") + (if + (list? value) + (reduce + (fn + (acc x) + (if (some (fn (a) (= a x)) acc) acc (append acc (list x)))) + (list) + value) + value)) + ((or (= type-name "Flattened") (= type-name "Flat")) + (if + (list? value) + (reduce + (fn + (acc x) + (if (list? x) (append acc x) (append acc (list x)))) + (list) + value) + value)) (true value)))) (define hs-add - (fn (a b) (if (or (string? a) (string? b)) (str a b) (+ a b)))) + (fn + (a b) + (cond + ((list? a) (if (list? b) (append a b) (append a (list b)))) + ((list? b) (cons a b)) + ((or (string? a) (string? b)) (str a b)) + (true (+ a b))))) (define hs-make @@ -371,7 +429,12 @@ ((= type-name "Boolean") (or (= value true) (= value false))) ((= type-name "Array") (list? value)) ((= type-name "Object") (dict? value)) - (true true))))) + ((= type-name "Element") (= (host-typeof value) "element")) + ((= type-name "Node") + (or + (= (host-typeof value) "element") + (= (host-typeof value) "text"))) + (true (= (host-typeof value) (downcase type-name))))))) @@ -388,6 +451,22 @@ (fn (a b) (and (= (type-of a) (type-of b)) (= a b)))) ;; ── Sandbox/test runtime additions ────────────────────────────── ;; Property access — dot notation and .length +(define + hs-eq-ignore-case + (fn (a b) (= (downcase (str a)) (downcase (str b))))) +;; DOM query stub — sandbox returns empty list +(define + hs-starts-with-ic? + (fn (str prefix) (starts-with? (downcase str) (downcase prefix)))) +;; Method dispatch — obj.method(args) +(define + hs-contains-ignore-case? + (fn + (haystack needle) + (contains? (downcase (str haystack)) (downcase (str needle))))) + +;; ── 0.9.90 features ───────────────────────────────────────────── +;; beep! — debug logging, returns value unchanged (define hs-falsy? (fn @@ -399,16 +478,18 @@ ((and (list? v) (= (len v) 0)) true) ((= v 0) true) (true false)))) -;; DOM query stub — sandbox returns empty list +;; Property-based is — check obj.key truthiness (define hs-matches? (fn (target pattern) - (if - (string? target) - (if (= pattern ".*") true (string-contains? target pattern)) - false))) -;; Method dispatch — obj.method(args) + (cond + ((string? target) + (if (= pattern ".*") true (string-contains? target pattern))) + ((= (host-typeof target) "element") + (if (string? pattern) (host-call target "matches" pattern) false)) + (true false)))) +;; Array slicing (inclusive both ends) (define hs-contains? (fn @@ -428,11 +509,9 @@ true (hs-contains? (rest collection) item))))) (true false)))) - -;; ── 0.9.90 features ───────────────────────────────────────────── -;; beep! — debug logging, returns value unchanged +;; Collection: sorted by (define precedes? (fn (a b) (< (str a) (str b)))) -;; Property-based is — check obj.key truthiness +;; Collection: sorted by descending (define hs-empty? (fn @@ -443,7 +522,7 @@ ((list? v) (= (len v) 0)) ((dict? v) (= (len (keys v)) 0)) (true false)))) -;; Array slicing (inclusive both ends) +;; Collection: split by (define hs-empty-target! (fn @@ -464,11 +543,61 @@ (dom-set-prop target "value" "")))) ((= tag "FORM") (dom-set-inner-html target "")) (true (dom-set-inner-html target "")))))))) -;; Collection: sorted by +;; Collection: joined by +(define + hs-open! + (fn + (el) + (let + ((tag (dom-get-prop el "tagName"))) + (if + (= tag "DIALOG") + (host-call el "showModal") + (dom-set-prop el "open" true))))) + +(define + hs-close! + (fn + (el) + (let + ((tag (dom-get-prop el "tagName"))) + (if + (= tag "DIALOG") + (host-call el "close") + (dom-set-prop el "open" false))))) + +(define + hs-hide! + (fn + (el strategy) + (let + ((tag (dom-get-prop el "tagName"))) + (cond + ((= tag "DIALOG") + (when (dom-has-attr? el "open") (host-call el "close"))) + ((= tag "DETAILS") (dom-set-prop el "open" false)) + ((= strategy "opacity") (dom-set-style el "opacity" "0")) + ((= strategy "visibility") (dom-set-style el "visibility" "hidden")) + (true (dom-set-style el "display" "none")))))) + +(define + hs-show! + (fn + (el strategy) + (let + ((tag (dom-get-prop el "tagName"))) + (cond + ((= tag "DIALOG") + (when (not (dom-has-attr? el "open")) (host-call el "showModal"))) + ((= tag "DETAILS") (dom-set-prop el "open" true)) + ((= strategy "opacity") (dom-set-style el "opacity" "1")) + ((= strategy "visibility") (dom-set-style el "visibility" "visible")) + (true (dom-set-style el "display" "")))))) + (define hs-first (fn (lst) (first lst))) -;; Collection: sorted by descending + (define hs-last (fn (lst) (last lst))) -;; Collection: split by + (define hs-template (fn @@ -554,7 +683,7 @@ (set! i (+ i 1)) (tpl-loop))))))) (do (tpl-loop) result)))) -;; Collection: joined by + (define hs-make-object (fn diff --git a/tests/hs-run-fast.js b/tests/hs-run-fast.js index 6002facc..b18fcef0 100644 --- a/tests/hs-run-fast.js +++ b/tests/hs-run-fast.js @@ -21,8 +21,9 @@ function setStepLimit(n) { K.setStepLimit(n); } function resetStepCount() { K.resetStepCount(); } // ─── DOM mock ────────────────────────────────────────────────── +function mkStyle() { const s={}; s.setProperty=function(p,v){s[p]=v;}; s.getPropertyValue=function(p){return s[p]||'';}; s.removeProperty=function(p){delete s[p];}; return s; } class El { - constructor(t) { this.tagName=t.toUpperCase(); this.nodeName=this.tagName; this.nodeType=1; this.id=''; this.className=''; this.classList=new CL(this); this.style={}; this.attributes={}; this.children=[]; this.childNodes=[]; this.parentElement=null; this.parentNode=null; this.textContent=''; this.innerHTML=''; this._listeners={}; this.dataset={}; this.open=false; this.value=''; this.checked=false; this.disabled=false; this.type=''; this.name=''; this.selectedIndex=-1; this.options=[]; } + constructor(t) { this.tagName=t.toUpperCase(); this.nodeName=this.tagName; this.nodeType=1; this.id=''; this.className=''; this.classList=new CL(this); this.style=mkStyle(); this.attributes={}; this.children=[]; this.childNodes=[]; this.parentElement=null; this.parentNode=null; this.textContent=''; this.innerHTML=''; this._listeners={}; this.dataset={}; this.open=false; this.value=''; this.checked=false; this.disabled=false; this.type=''; this.name=''; this.selectedIndex=-1; this.options=[]; } setAttribute(n,v) { this.attributes[n]=String(v); if(n==='id')this.id=v; if(n==='class'){this.className=v;this.classList._sync(v);} if(n==='value')this.value=v; if(n==='disabled')this.disabled=true; } getAttribute(n) { return this.attributes[n]!==undefined?this.attributes[n]:null; } removeAttribute(n) { delete this.attributes[n]; if(n==='disabled')this.disabled=false; } @@ -39,7 +40,7 @@ class El { closest(s) { let e=this; while(e){if(mt(e,s))return e; e=e.parentElement;} return null; } matches(s) { return mt(this,s); } contains(o) { if(o===this)return true; for(const c of this.children)if(c===o||c.contains(o))return true; return false; } - cloneNode(d) { const e=new El(this.tagName.toLowerCase()); Object.assign(e.attributes,this.attributes); e.id=this.id; e.className=this.className; e.classList._sync(this.className); Object.assign(e.style,this.style); e.textContent=this.textContent; e.innerHTML=this.innerHTML; e.value=this.value; if(d)for(const c of this.children)e.appendChild(c.cloneNode(true)); return e; } + cloneNode(d) { const e=new El(this.tagName.toLowerCase()); Object.assign(e.attributes,this.attributes); e.id=this.id; e.className=this.className; e.classList._sync(this.className); for(const k of Object.keys(this.style)){if(typeof this.style[k]!=='function')e.style[k]=this.style[k];} e.textContent=this.textContent; e.innerHTML=this.innerHTML; e.value=this.value; if(d)for(const c of this.children)e.appendChild(c.cloneNode(true)); return e; } focus(){} blur(){} click(){this.dispatchEvent(new Ev('click',{bubbles:true}));} remove(){if(this.parentElement)this.parentElement.removeChild(this);} _syncText() { // Sync textContent from children @@ -149,7 +150,7 @@ function mt(e,s) { return e.tagName.toLowerCase() === s.toLowerCase(); } function fnd(e,s) { for(const c of(e.children||[])){if(mt(c,s))return c;const f=fnd(c,s);if(f)return f;} return null; } -function fndAll(e,s) { const r=[];for(const c of(e.children||[])){if(mt(c,s))r.push(c);r.push(...fndAll(c,s));}return r; } +function fndAll(e,s) { const r=[];for(const c of(e.children||[])){if(mt(c,s))r.push(c);r.push(...fndAll(c,s));}r.item=function(i){return r[i]||null;};return r; } const _body = new El('body'); const _html = new El('html');