feat(ui): ludic.ui grows toward HTML, CSS and React (Phase 0.U1-U4)

- Natives: ui_native(tag, measure, draw) makes an element the program draws itself; ui_fire
  runs its on-<event> with event.value. input, select and textarea have simple defaults.
- React:
  - keyed <each>, <let>, <provide> context through components, <fragment>;
  - named slots, default props on <component>;
  - on-mount / on-unmount;
  - inline text inside text elements.
- CSS:
  - custom properties and var();
  - position relative/absolute/fixed with insets and z-index;
  - em/rem/vw/vh and @media;
  - wrapping text and ellipsis, overflow;
  - + and ~ combinators, :nth-child(an+b), :checked, :active.
- Developing: errors with file:line, ui_errors(), ui_reload() keeping state, and an
  inspector-style ui_dump.
- view fields infer the type of a literal or a named function's result.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Orkun ÇAKILKAYA 2026-09-24 16:17:25 +03:00
parent 66a2bc2214
commit 637d07400e
39 changed files with 42012 additions and 40795 deletions

View file

@ -15,6 +15,14 @@ function sel_match(s: UiSel, n: UiNode) -> bool {
if not sel_one(s, n) { return false }
if s.prev == null { return true }
if s.comb == 1 { return n.parent != null and sel_match(s.prev, n.parent) }
if s.comb == 2 { return n.parent != null and n.index > 0 and sel_match(s.prev, n.parent.children[n.index - 1]) }
if s.comb == 3 {
if n.parent == null { return false }
for i in 0 .. n.index {
if sel_match(s.prev, n.parent.children[i]) { return true }
}
return false
}
var a = n.parent
while a != null {
if sel_match(s.prev, a) { return true }
@ -62,13 +70,30 @@ function sel_pseudo_ok(p: string, arg: string, n: UiNode) -> bool {
if p == "last-child" { return n.parent != null and n.index == len(n.parent.children) - 1 }
if p == "only-child" { return n.parent != null and len(n.parent.children) == 1 }
if p == "empty" { return len(n.children) == 0 and n.text == "" }
if p == "root" { return n.parent == null or n.parent.tag == "" }
if p == "nth-child" {
let k = n.index + 1
if arg == "odd" { return k % 2 == 1 }
if arg == "even" { return k % 2 == 0 }
return string(k) == arg
}
if p == "root" { return n.parent == null }
if p == "checked" { return ui_attr_on(n, "checked") }
if p == "active" { return n.hovered and fr_down_now }
if p == "nth-child" { return sel_nth(arg, n.index + 1) }
ui_err(`:{p}: there is no such pseudo-class here`)
return false
}
# :nth-child(odd), (even), (3), (2n+1), (n+2), (-n+3): is the k-th child one of them
function sel_nth(arg: string, k: int) -> bool {
if arg == "odd" { return k % 2 == 1 }
if arg == "even" { return k % 2 == 0 }
var npos = -1
for i in 0 .. len(arg) {
if arg[i] == 110 { npos = i }
}
if npos < 0 { return string(k) == arg }
var a = 1
let head: string = arg[0..npos]
if head == "-" { a = -1 } else if len(head) > 0 { a = int(el_num(head)) }
var b = 0
var tail = tpl_words(arg[npos + 1..len(arg)])
if len(tail) > 0 and tail[0] == 43 { tail = tpl_words(tail[1..len(tail)]) }
if len(tail) > 0 { b = int(el_num(tail)) }
if a == 0 { return k == b }
let d = k - b
return d % a == 0 and d / a >= 0
}