@qxuken/kui 0.1.0-alpha.45 → 0.1.0-alpha.46
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +183 -0
- package/docs/adr/0041-a-mask-turns-about-its-centre.md +3 -1
- package/docs/adr/0043-a-node-turns-about-its-pivot.md +370 -0
- package/encoder.js +5 -1
- package/howto.md +141 -3
- package/index.d.ts +159 -4
- package/index.js +12 -1
- package/jsx-runtime.d.ts +26 -4
- package/package.json +1 -1
- package/prebuilds/darwin-arm64/kui_node.node +0 -0
- package/prebuilds/linux-arm64/kui_node.node +0 -0
- package/prebuilds/linux-x64/kui_node.node +0 -0
- package/prebuilds/win32-x64/kui_node.node +0 -0
- package/props.md +21 -5
package/props.md
CHANGED
|
@@ -35,8 +35,8 @@ where they make sense); text props apply to `<text>` and `<edit>`.
|
|
|
35
35
|
| `disabled` | `disabled` | `disabled` | `Spec.disabled` | boolean | Inert: no click, drag or key sink, no hover / pressed / focus background, skipped by Tab, reported disabled to assistive technology; hover tracking stays so a `tooltip` can say why. |
|
|
36
36
|
| `dropBg` | `drop_bg` | `drop_bg` | `Spec.drop_bg` | color (`#hex` or `0xRRGGBBAA`), or a `"$color"` token | Background while files dragged in from the OS are over this node (ADR 0031); wins over pressedBg, focusBg and hoverBg, clears when they leave, land or the drag is cancelled. Implies hover tracking, eases with `transition`. |
|
|
37
37
|
| `easing` | `easing` | `easing` (`KUI_EASE_*`) | `Spec.easing` | `easeOut` \\| `linear` \\| `easeIn` \\| `easeInOut` \\| `spring` \\| `bouncy` \\| `smooth` \\| `snappy` | Easing for `transition` (default easeOut). The springs — `smooth` (no overshoot), `snappy`, `spring` and `bouncy` (the most), each a `bounce` of its own — integrate with momentum, so a value retargeted mid-flight keeps moving the way it was; `transition` is then about how long one takes to get there. |
|
|
38
|
-
| `enter` | `enter` | `enter` (`KuiEnter`, with `set` bits) | `Spec.enter` | entrance (`{ dx?, dy?, width?, height?, bg?, radius?, opacity? }`) | Where the node starts the first frame it is seen `{ dx?, dy?, width?, height?, bg?, radius?, opacity? }`: those slots ease in from there over `transition` ms instead of snapping (`dx`/`dy` slide it in from that far away, `opacity: 0` fades the whole subtree in). |
|
|
39
|
-
| `exit` | `exit` | `exit` (`KuiEnter`, with `set` bits) | `Spec.exit` | entrance (`{ dx?, dy?, width?, height?, bg?, radius?, opacity? }`) | Where the node ends the frame after the view stops declaring it `{ dx?, dy?, width?, height?, bg?, radius?, opacity? }` — an `enter` read the other way. It plays when the node itself is removed, its parent still declared; a node that goes because an ancestor went — a tab switched away, a panel closed around it — goes at once with it, unless that ancestor has an `exit` of its own, whose picture carries it (backlog DX19; React's `AnimatePresence` rule). With a `transition`, the departing subtree is copied out of the last frame that had it and replayed frozen, in its place (the pass it painted in, just under the node that painted after it — a panel under a HUD leaves under it) and inert (no clicks, no Tab stop, no access row) while those slots ease from where they were, then dropped; without one it vanishes at once as it always did. `width`/`height` resize the departing node's own box only — the subtree inside it is a picture and is not laid out again. Needs a stable key across frames. |
|
|
38
|
+
| `enter` | `enter` | `enter` (`KuiEnter`, with `set` bits) | `Spec.enter` | entrance (`{ dx?, dy?, width?, height?, bg?, radius?, opacity?, rotate?, scale? }`) | Where the node starts the first frame it is seen `{ dx?, dy?, width?, height?, bg?, radius?, opacity?, rotate?, scale? }`: those slots ease in from there over `transition` ms instead of snapping (`dx`/`dy` slide it in from that far away, `opacity: 0` fades the whole subtree in, `scale: 0.8` settles it in). |
|
|
39
|
+
| `exit` | `exit` | `exit` (`KuiEnter`, with `set` bits) | `Spec.exit` | entrance (`{ dx?, dy?, width?, height?, bg?, radius?, opacity?, rotate?, scale? }`) | Where the node ends the frame after the view stops declaring it `{ dx?, dy?, width?, height?, bg?, radius?, opacity?, rotate?, scale? }` — an `enter` read the other way. It plays when the node itself is removed, its parent still declared; a node that goes because an ancestor went — a tab switched away, a panel closed around it — goes at once with it, unless that ancestor has an `exit` of its own, whose picture carries it (backlog DX19; React's `AnimatePresence` rule). With a `transition`, the departing subtree is copied out of the last frame that had it and replayed frozen, in its place (the pass it painted in, just under the node that painted after it — a panel under a HUD leaves under it) and inert (no clicks, no Tab stop, no access row) while those slots ease from where they were, then dropped; without one it vanishes at once as it always did. `width`/`height` resize the departing node's own box only — the subtree inside it is a picture and is not laid out again. The exit read is the one the last frame that had the node declared, unless `exit_with` named another for the frame it went in: a card a button throws left or right is aimed by the handler that removes it, with no frame drawn first to point it. Needs a stable key across frames. |
|
|
40
40
|
| `expanded` | `expanded` | `expanded` (`KUI_EXPANDED_*`) | `Spec.expanded` | `collapsed` \\| `expanded` | A disclosure's state: what a node that shows and hides something (a twisty, an accordion header, a menu button) reads as. Unset, the node does not expand at all — which is why this names its state instead of being a flag. |
|
|
41
41
|
| `focusBg` | `focus_bg` | `focus_bg` | `Spec.focus_bg` | color (`#hex` or `0xRRGGBBAA`), or a `"$color"` token | Background while the node holds keyboard-visible focus (moved there by Tab or assistive technology, not a click); replaces the default focus ring. Pressed wins over focus wins over hover; eases with `transition`. |
|
|
42
42
|
| `focusRegion` | `focus_region` | `focus_region` | `Spec.focus_region` | boolean | Makes this node's subtree a focus region: a Tab ring of its own that the ring outside never enters and that never leaves — a devtools dock, an inspector beside the app (`docs/adr/0022-focus-regions.md`). Entered on purpose: `focusRegion(name)` (`Ui::focus_region`, `env.focus_region`, `kui_focus_region`) moves focus in — to the focus the region last held, else its `initialFocus`, else its first stop — and `focusRegion(null)` moves it back to the main ring the same way; a press inside the region, or an explicit focus on a node in it, enters it too. Tab then walks that ring alone, wrapping inside it; with nothing focused, Tab enters the ring of the region in effect (`region()`). A region that stops being declared hands focus back to what the main ring last held. Only the ring is scoped: keys still bubble through the boundary to the sink above (a region that wants its own keymap is an `onKey` sink), the pointer and assistive technology see a plain node, and a `modal` in effect is the ring wherever it sits. Nested regions are skipped by the outer ring the way the main ring skips them. |
|
|
@@ -49,10 +49,11 @@ where they make sense); text props apply to `<text>` and `<edit>`.
|
|
|
49
49
|
| `hoverSound` | `hover_sound` | `hover_sound` | `Spec.hover_sound` | resource handle | A registered sound (addSound) played when the pointer enters the node; implies hover tracking. |
|
|
50
50
|
| `hoverable` | `hoverable` | `hoverable` | `Spec.hoverable` | boolean | Hover-track without a click payload (for isHovered-driven styling). |
|
|
51
51
|
| `initialFocus` | `initial_focus` | `initial_focus` | `Spec.initial_focus` | boolean | Where focus lands when the enclosing `modal` scope is entered: the first node in the modal's Tab ring declaring it, so a destructive confirm opens on its Cancel rather than on whichever control is declared first. Read on entry only — a Tab press afterwards stands, and the scope re-entered (a nested confirm closing) leaves focus where it was. Declared on nothing, or only on nodes the ring skips (disabled, `role="none"`, not focusable), entry stays the ring's first node. |
|
|
52
|
+
| `iterations` | `iterations` | `iterations` (0 is for ever) | `Spec.iterations` | number, or a `"$length"` token | How many times the `keyframes` cycle runs (CSS `animation-iteration-count`, backlog F133); left out, for ever. A finite cycle plays from the first frame the node is declared with it — a node that leaves and comes back plays again — holds its first stop through its `delay`, and rests where its last iteration ended (CSS's fill `both`): `1` plays a burst or a shake once, `2` with `repeat: "alternate"` goes out and back and ends where it began, `0.5` stops halfway. Once it is over the node owes no frame, so `animating()` and `owed()` go quiet as a settled transition's do; `delay` plus `iterations: 1` staggers one-shots. A count that is not a positive number is for ever. C: 0 is for ever. |
|
|
52
53
|
| `keepFocus` | `keep_focus` | `keep_focus` | `Spec.keep_focus` | boolean | A press on this node, or anywhere inside it, leaves keyboard focus where it was: a toolbar button, a tab or a divider that acts without taking the keyboard from the editor or key sink that had it. Without it a press on an `onClick` node focuses the node, and the app's keys stop reaching the sink until it takes focus back. The press also leaves a text or cell selection and the Tab ring where they were, so a Copy button copies what was selected. An `<edit>` inside still takes its caret and focus, as the keyboard's own owner. The click, drag and hover are unchanged, and Tab and assistive technology still reach the node. |
|
|
53
54
|
| `keyUp` | `key_up` | `key_up` | `Spec.key_up` | boolean | With `onKey`: releases arrive too, as the same payload with phase:"up" (`text` null, `repeat` false) — for a held-key interaction (WASD, press-and-hold, a key that arms a mode while it is down). A key only comes up where it went down: a release whose press the sink never got is dropped, and focus leaving while a key is held delivers the `up` first, so nothing is left stuck down. Without it a sink hears presses only, which is what a keymap wants — one that heard both halves would run every binding twice. |
|
|
54
|
-
| `keyframes` | `keyframes` | `keyframes` + `keyframes_len` (`KuiKeyframe[]`) | `Spec.keyframes` | keyframe list (`[{ at?, width?, height?, bg?, radius?, opacity? }, …]`) | CSS-style stops `[{ at?, width?, height?, bg?, radius?, opacity? }, …]`: the slots they name cycle through them over `transition` ms,
|
|
55
|
-
| `label` | `label` | `label` (KuiStr) | `Spec.label` | string | The accessible name. Without one a button, link, tab or heading is named by the text inside it; an image, an icon-only button and a `modal` dialog have none, and the core warns (`image-without-label`, `control-without-name`, `modal-without-name`). |
|
|
55
|
+
| `keyframes` | `keyframes` | `keyframes` + `keyframes_len` (`KuiKeyframe[]`) | `Spec.keyframes` | keyframe list (`[{ at?, dx?, dy?, width?, height?, bg?, radius?, opacity?, rotate?, scale? }, …]`) | CSS-style stops `[{ at?, dx?, dy?, width?, height?, bg?, radius?, opacity?, rotate?, scale? }, …]`: the slots they name cycle through them over `transition` ms, for ever unless `iterations` says how many times, without the view redrawing; `at` is 0..1 and spreads evenly when omitted. `dx` / `dy` are logical px from where layout put the node (backlog F132), as an entrance's are: the node and its subtree are drawn and hit that far away at the stop, a lane a stop leaves out is 0, and the offset adds to a `slide`'s, so `[{ dy: 0 }, { dy: -6 }]` with `repeat: 'alternate'` bobs a box and a sparkle drifts up its stops. Paint, hit and access only: layout and the room the node takes are its own place's, and an `onLayout` node reports its layout rect, not the cycle, which would post an event every frame it runs. |
|
|
56
|
+
| `label` | `label` | `label` (KuiStr) | `Spec.label` | string | The accessible name. Without one a button, link, tab or heading is named by the text inside it; an image, an icon-only button and a `modal` dialog have none, and the core warns (`image-without-label`, `control-without-name`, `modal-without-name`). Not the key label a `key` prop or `with_keyed` declares, which `key_of` looks up and a reader never hears; `key_named` looks a node up by this name. |
|
|
56
57
|
| `live` | `live` | `live` (`KUI_LIVE_*`) | `Spec.live` | `off` \\| `polite` \\| `assertive` | Marks this node a live region: when the text inside it changes, a screen reader reads the change without being asked — `polite` at the next pause, `assertive` interrupting. Put it on the smallest node that holds the message, since everything inside a live node is live. For a one-off with no node behind it ("Saved") the binding's `announce` verb is the other half. |
|
|
57
58
|
| `mainAlign` | `main_align` | `main_align` | `Spec.main_align` | `start` \\| `center` \\| `end` \\| `spaceBetween` \\| `spaceAround` \\| `spaceEvenly` \\| `baseline` | Child alignment along the main axis. `start`, `center` and `end` put the children together; `spaceBetween` deals the free space out between them (none at the ends), `spaceAround` gives each child an equal share split to its two sides, and `spaceEvenly` makes every gap and both ends equal — CSS's `justify-content`. The spread is added to `gap`, and there is none when nothing is free: a `grow` child takes it all, and an overflowing run keeps its gaps. `baseline` means nothing here and lays out as `start`, with a warning. |
|
|
58
59
|
| `maxHeight` | `max_height` | `max_height` | `Spec.max_height` | maximum (`number` \\| a size expression \\| `"$length"`) | Upper height clamp: logical px or a size expression (see `width`). |
|
|
@@ -76,6 +77,8 @@ where they make sense); text props apply to `<text>` and `<edit>`.
|
|
|
76
77
|
| `onScroll` | `on_scroll` | `on_scroll` (a borrowed `KuiValue*`, cloned while the node opens) | `Spec.on_scroll` | tag (a message merged into the event under `tag`, or `null` for none) | Scroll tag: the wheel over this node emits {kind:"scroll", x, y, dx, dy, lines, tag} on it instead of scrolling anything — `dx`/`dy` the delta in logical px as the driver reported it (positive `dy` is the wheel rolling up, toward earlier content), `x`/`y` the pointer, and `lines` on a `cells` grid the whole lines the delta covers (positive = later history, the sign `originLine` grows in; the fraction is carried to the next notch so a trackpad's small steps add up) and null on any other node. The node takes the wheel on the axes `scrollAxes` names (both unless it narrows them): a gesture that starts over it is its own whether or not it has anywhere to go — except on an axis the node also scrolls (`scrollX`/`scrollY`, its offset the app's to set), where it is answered by its room as a container is, so at its edge a gesture that way passes to the scroller around it (backlog F118) — and stays its own until it ends, wherever the pointer goes (backlog F107); it reaches no scroll container above it, and a scroller inside it still takes the axes it scrolls while it can move that way, passing this node the rest — the other axis, and a gesture that begins with that scroller at its limit (`overscroll: "contain"` on the scroller keeps it there). The core moves nothing — a grid re-declares `originLine`, a canvas zooms. A drag-select held past a `cells` grid's top or bottom edge arrives here too, once a frame with the lines that frame scrolled by (`docs/adr/0029-a-selection-follows-the-pointer-past-the-edge.md`). |
|
|
77
78
|
| `opacity` | `opacity` | `opacity` with `opacity_set` | `Spec.opacity` | number, or a `"$length"` token | Group opacity 0..1 (default 1): fades this node and its whole subtree. A per-quad alpha multiply rather than an offscreen composite, so overlapping pieces of one subtree show their seams through the fade. Layout, hit-testing and the access tree are untouched; eases with `transition`, and `enter: { opacity: 0 }` fades a panel in. |
|
|
78
79
|
| `overscroll` | `overscroll` | `overscroll` (`KUI_OVERSCROLL_*`; zeroed, auto) | `Spec.overscroll` | `auto` \\| `contain` | What a scroll gesture that starts over this scroller does when it is already at its limit that way (backlog F107, CSS's `overscroll-behavior`): `auto` (the default) passes the gesture on to the scroller around it, `contain` keeps it here, moving nothing until it turns back. A gesture picks its target once, when it starts — the innermost scroller under the pointer that can still move the way it goes — and keeps it until it ends, wherever the pointer or the content has gone; one that reaches a limit midway stops there, whatever this says. Only on the axes the node scrolls: a `scrollY` list that contains still passes a sideways swipe to the strip around it. For a panel or a popup's list whose scrolling must never move what is behind it. |
|
|
80
|
+
| `pivotX` | `pivot_x` | `pivot_x` with `pivot_set` | `Spec.pivot_x` | number, or a `"$length"` token | Where across the box `rotate` and `scale` are about, as a fraction of its width: 0 the left edge, 0.5 (the default) the middle, 1 the right edge; outside 0..1 is a point past the box. C: `pivot_x` with `pivot_set`. |
|
|
81
|
+
| `pivotY` | `pivot_y` | `pivot_y` with `pivot_set` | `Spec.pivot_y` | number, or a `"$length"` token | Where down the box `rotate` and `scale` are about, as a fraction of its height: 0 the top, 0.5 (the default) the middle, 1 the bottom. C: `pivot_y` with `pivot_set`. |
|
|
79
82
|
| `pixelSnap` | `pixel_snap` | `pixel_snap` | `Spec.pixel_snap` | boolean | Paint this node's background, border, shadow and fragment with each edge on a whole physical pixel: `x` and `x + width` rounded on their own, from where layout put them, as a text's span backgrounds are. Off by default, and a box is drawn where layout put it, so a 1 px `gap` between boxes is there at any scale. On, boxes that share an edge in layout meet on one pixel line, where a join inside a pixel was drawn by halves and left a seam — rows of a band stacked at a pitch that is not whole pixels, or a box that continues a text's selection. Layout, hit-testing, the clip and the children are untouched. A snapped box can draw up to half a pixel from its layout edge and its size can differ by a pixel, so a snapped hairline is 1 or 2 px thick by where it sits. |
|
|
80
83
|
| `pressedBg` | `pressed_bg` | `pressed_bg` | `Spec.pressed_bg` | color (`#hex` or `0xRRGGBBAA`), or a `"$color"` token | Background while pressed (or while its hoverGroup is); implies hover tracking. |
|
|
81
84
|
| `radius` | `radius` | `radius` | `Spec.radius` | number, or a `"$length"` token | Corner radius for all four corners (logical px); the per-corner props override it when listed after it. On a node that also clips or scrolls it rounds the clip as well, so children stay inside the corners. |
|
|
@@ -85,8 +88,10 @@ where they make sense); text props apply to `<text>` and `<edit>`.
|
|
|
85
88
|
| `radiusTR` | `radius_tr` | `radius_tr` with `per_corner` | `Spec.radius_tr` | number, or a `"$length"` token | Top-right corner radius (logical px). |
|
|
86
89
|
| `repeat` | `repeat` | `repeat` (`KUI_REPEAT_*`) | `Spec.repeat` | `normal` \\| `reverse` \\| `alternate` \\| `alternateReverse` | How `keyframes` cycle (CSS `animation-direction`, default normal). Lua: `direction`, since `repeat` is a keyword. |
|
|
87
90
|
| `role` | `role` | `role` (`KUI_ROLE_*`) | `Spec.role` | `none` \\| `button` \\| `checkbox` \\| `radio` \\| `switch` \\| `slider` \\| `tab` \\| `tabList` \\| `link` \\| `heading` \\| `list` \\| `listItem` \\| `image` \\| `dialog` \\| `group` \\| `textInput` \\| `multilineTextInput` \\| `line` \\| `radioGroup` \\| `menu` \\| `menuItem` \\| `terminal` | What the node is to assistive technology. Unset, the core derives one (an `onClick` node is a button, an editor a text input, a scrolling box a scroll view, a plain box nothing); `none` hides the node and its subtree from the access tree. A `radio` belongs inside a `radioGroup` and a `tab` inside a `tabList`, labelled with what the choice is: the pair is a composite (`docs/adr/0007-composite-keyboard-patterns.md`) — one Tab stop for the set, the arrows, Home and End moving the choice inside it (each step is the item's click, so the choice follows focus), and a screen reader reading "2 of 3". A `radio` or `tab` with no container above it is a Tab stop of its own that no arrow moves, and the core warns (`item-outside-container`). `menu` holds `menuItem`s and `list` holds `listItem`s the same way. |
|
|
91
|
+
| `rotate` | `rotate` | `rotate` | `Spec.rotate` | number, or a `"$length"` token | Turns this node and everything under it, in turns clockwise (0.25 is a quarter turn right), about its pivot — the centre unless `pivotX` / `pivotY` say — after layout (`docs/adr/0043-a-node-turns-about-its-pivot.md`). Paint-only: the node takes the room its upright self takes, nothing around it moves, `onLayout` reports the layout rect. Everything the subtree draws turns with it — backgrounds, borders, shadows, text, images, strokes, fragments — and so does what it clips: a child cut by a turned card's rounded corners stays inside them. Hit where drawn: a tilted card is grabbed on its tilted edge and a press in its box past its edge falls through; drag payloads stay in viewport px. The access rect is the bounding box. Nests by composition. Tweens with `transition` as one slot with `scale`, and an entrance, an exit or a keyframe stop may name it (`enter: { rotate: -0.02 }`, `keyframes: [{ rotate: 0 }, { rotate: 1 }]` spins a box). A float anchored to the parent turns with it; a viewport float does not. On a `path` this is the path's own turn (ADR 0041), which does not tween — wrap it in a box for one that does. Text under a turn leaves the pixel grid, as a turned mask does. `backdropBlur` under a turn blurs the upright box. |
|
|
88
92
|
| `ruleWidth` | `rule_width` | `rule_w` | `Spec.rule_width` | number, or a `"$length"` token | The width of a table's `rules` in logical px; 1 when unset. |
|
|
89
93
|
| `rules` | `rules` | `rules` | `Spec.rules` | color (`#hex` or `0xRRGGBBAA`), or a `"$color"` token | On a table (`dir="table"`, ADR 0033): grid lines of this colour between its columns and between its rows (backlog DX21) — down the middle of each gap between the columns of its widest row, from the first row's top to the last row's bottom, and across the middle of each gap between rows, the content box wide. Drawn with the table's box, under its cells and on whole pixels, so give the table and its rows a `gap` at least `ruleWidth` for the lines to show between cells; the outer edge is the table's `border`. Ignored on anything but a table. |
|
|
94
|
+
| `scale` | `scale` | `scale` (0 is 1) | `Spec.scale` | number, or a `"$length"` token | Scales this node and everything under it by this factor, uniformly, about its pivot, after layout (`docs/adr/0043-a-node-turns-about-its-pivot.md`); 1 is none. 0 draws nothing in Rust, Node and Lua; in C and Odin, where a zeroed field is unset, 0 is 1. Paint-only, as `rotate` is: layout, the room taken and `onLayout` are the upright node's; what it draws, clips and hits scales. Tweens with `transition` as one slot with `rotate`; `enter: { scale: 0.8 }` settles a chip in, `keyframes: [{ scale: 1.05, at: 0.5 }]` pulses. The edge ramps scale with the box, so a box scaled far up reads soft. |
|
|
90
95
|
| `scrollAxes` | `scroll_axes` | `scroll_axes` (`KUI_SCROLL_AXES_*`; zeroed, both) | `Spec.scroll_axes` | `both` \\| `x` \\| `y` | Which axes `onScroll` takes (backlog F107): `both` (the default), `x` or `y`. A scroll gesture on an axis the node does not take passes it by, to the scroller around it, and hears nothing here: a terminal that scrolls its history says `y`, and a sideways swipe that starts over it moves the strip it sits in. (A swipe that started elsewhere is not the node's either way: a gesture keeps the target it started with.) Meaningless without `onScroll`. |
|
|
91
96
|
| `scrollMods` | `scroll_mods` | `scroll_mods` (`KUI_KMOD_*` bits; zeroed, none) | `Spec.scroll_mods` | string | The modifiers `onScroll` is for (backlog F122): `"shift"`, `"ctrl"`, `"alt"` and `"super"` (⌘, the Windows key), separated by spaces or commas — `"ctrl super"`. With any named, the node hears only a scroll gesture that began with one of them held, and hears it first: ahead of every scroll container and every `onScroll` that names none, wherever under the pointer the gesture began, the innermost such node winning — so a Ctrl-wheel zoom declared on the window's root is heard over a list, and the list does not scroll. A wheel with none of them held passes the node by, as if it had no `onScroll`: a node that scrolls as well (`overflow`) scrolls for it as any container does. Its `scroll` events carry `mods`, the modifiers held when the gesture began; the gesture stays the node's to the end of its glide, whatever is let go meanwhile, and one begun without them never becomes its. A word that is none of the four is skipped. Unset, a handler like any other. Meaningless without `onScroll`. |
|
|
92
97
|
| `scrollbar` | `scrollbar` | `scrollbar` | `Spec.scrollbar` | `visible` \\| `hidden` \\| `auto` | When a scrolling node draws its bars: `visible` (the default — the stock overlay thumb, drawn while the content overflows), `hidden` (no thumb, no track to press; the wheel, the keyboard, `reveal` and the caret still scroll it — for a list that draws its own indicator, or a pane whose bar would sit on a border), or `auto` (shown while the scroll state is changing — the offset or the content's extent moved, the pointer is on the track, a thumb is dragged — and for a second after, then faded out over a quarter of one; a node first seen shows it the same second; what an overlay bar does on macOS). `auto` needs the driver's clock and is `visible` without one. The bars are overlays and take no layout space in any mode. From the last change until it has faded — a second and a quarter — an `auto` bar asks for frames the way a transition of that length would (nothing else could wake the core when the hold ends); while the pointer holds it, it asks for none. |
|
|
@@ -251,6 +256,7 @@ people. In Node the codes are the `WarningCode` union.
|
|
|
251
256
|
| `transition-auto-key` | The child count of a node changed while one of its children carries a transition under an auto-assigned key. Auto keys are sibling positions, so the children that shifted became new nodes and snapped instead of easing. Give list items a key. |
|
|
252
257
|
| `duplicate-key` | Two nodes in one frame share a key: everything retained per key (transitions, scroll offsets, editors, layout events, hover state) is mixed between them. Siblings need distinct keys. |
|
|
253
258
|
| `ambiguous-key` | A label resolved by name (`focus("beta")` in Node, `env.set_focus("beta")` in Lua, `kui_key_of` in C) is declared by more than one node in the frame, under different parents, so they have distinct keys and the name picked the first in tree order. Labels are unique among siblings, not across a tree. An extension asking from inside its fill is answered from the nodes it opened and no one else's, and the host from its own first — so this is a clash among the asker's own. Give the node meant a label nothing else declares, or pass the hex key an event carried. Two nodes with the *same* key are `duplicate-key`. |
|
|
259
|
+
| `ambiguous-name` | A lookup by accessible name (`Core::key_named`, `ctx.keyNamed`, `kui_key_named`) found more than one node with that name in the last frame — two buttons both read as "Delete" — and used the first in tree order. A reader hears the same name twice too: give each a `label` that says which, or look the one meant up by its key label. |
|
|
254
260
|
| `focus-region-without-node` | A `focusRegion(name)` (`Core::focus_region`, `env.focus_region`, `kui_focus_region`) named a node the frame after it did not declare as a `focusRegion` — no node under the label, or a node without the row — so nothing was entered and focus stayed where it was. The call is resolved against the frame it lands on, so an `update` that toggles a dock on and enters it in one go is fine; this is that call with the view half missing, with a name the view spells differently, or naming a node that is not a region. |
|
|
255
261
|
| `label-without-node` | A `reveal` or `setScroll` by label (`env.reveal("rows")`, `win.reveal("rows")`, `Core::reveal_label`) named a label the frame it resolved against did not declare, so nothing moved. A label is resolved when the frame finishes, so a view may name a node it is declaring right now, or one the next frame declares; this is the name spelled differently from the `key` that declares it, or the node not declared at all. |
|
|
256
262
|
| `unknown-family` | A text's `family` named a family no installed or loaded font has, so it shaped as sans. `sans`, `serif` and `mono` are kui's own; any other name is matched as `addSystemFont` matches it, and `systemFonts()` lists the names a machine has. |
|
|
@@ -358,6 +364,7 @@ one reading that changes what a view *says* rather than what it draws.
|
|
|
358
364
|
| `focus` | `Core::focus()`, the frame's | — | `focus` | `kui_focused()` | `kui.focused()` | The focused *node*'s key, as events carry it (absent for none). A value the host wrote before the view ran, so it lags a same-frame verb by one frame; `env.is_focused(key)` is the live query. Node spells it as the call `focused()` on the context, and C as `kui_focused`, rather than a key on `env`. |
|
|
359
365
|
| `focus_visible` | `Core::focus_visible()`, the frame's | — | `focus_visible` | `kui_focus_visible()` | `kui.focus_visible()` | Whether focus shows — the keyboard or assistive technology put it where it is, or acted on it there; a click alone does not. Node: `focusVisible()` on the context. |
|
|
360
366
|
| `caret_visible` | `Core::caret_visible()`, the frame's | — | `caret_visible` | `kui_caret_visible()` | `kui.caret_visible()` | The caret's blink phase — `true` draws it (backlog C35). The driver's clock sets it while there is a caret to blink: a focused `edit`'s, or the `caret` a `line` under a focused `onKey` sink declares; a custom editor draws its caret node on the on phase and skips it on the off, keeping the `caret` row on its `line` either way, so it blinks in step with the stock editor and, in a window without the keyboard, not at all. Always `true` headless. Node: `caretVisible()` on the context (`setCaretVisible` is the driver's half, for a test that drives the phase). |
|
|
367
|
+
| `now` | `Core::now()`, the frame's | — | `now` | `kui_now()` | `kui.now()` | The frame clock in seconds (backlog F134): the driver's monotonic clock, any origin, the one `transition` and `keyframes` read this frame — 0 before a driver sets one. Read a deadline off it (a toast's expiry, a sequence's beats) rather than off a clock of the app's own, so a test that moves the frame clock moves both. Node: `now()` on the context. |
|
|
361
368
|
| `region` | `Core::region()`, the frame's | — | `region` | `kui_region()` | `kui.region()` | The focus region in effect — the key of the `focusRegion` node whose ring Tab walks (absent for the main ring; `docs/adr/0022-focus-regions.md`). What a chord that toggles between a dock and the app reads to know which way it is going. Node spells it as the call `region()` on the context, and C as `kui_region`. |
|
|
362
369
|
|
|
363
370
|
## Theme
|
|
@@ -521,7 +528,8 @@ its generator (`nu scripts/odin.nu gen --check`).
|
|
|
521
528
|
| `Ui::focused` | `kui_focused` | `focused` | `focused` | `env.focus`, a reading | The focused node's key. |
|
|
522
529
|
| `Ui::is_focused` | `kui_is_focused` | `is_focused` | `isFocused` | `is_focused` | Whether a node has focus. |
|
|
523
530
|
| `Ui::focus_visible` | `kui_focus_visible` | `focus_visible` | `focusVisible` | `env.focus_visible`, a reading | Whether focus came from the keyboard and the ring should show. |
|
|
524
|
-
| `Ui::key_of` | `kui_key_of` | `key_of` | `keyOf` | every query and verb takes the label itself (`key_query`) | The key a label names this frame. |
|
|
531
|
+
| `Ui::key_of` | `kui_key_of` | `key_of` | `keyOf` | every query and verb takes the label itself (`key_query`) | The key a key label names this frame: the name the view opened the node under, not the one a reader hears. |
|
|
532
|
+
| `Core::key_named` | `kui_key_named` | `key_named` | `keyNamed` | *none: a script is a guest in the host's frame (ADR 0014): its env is the view's reading, and registering, driving, pacing and reading back are the host's* | The key of the first node a reader hears named so in the last frame: a test presses the button named Like. |
|
|
525
533
|
| `Core::label_of` | *none: the label is the app's own word for the node, and every door names a node by it or by the key an event carried; the one reader is the devtools' inspector, in the core* | *none: Odin's doors are kui.h's (packages/odin): C has none, for the reason in C's cell* | *none: the same reason as C's* | *none: the same reason as C's* | The label a key was opened under. |
|
|
526
534
|
| `Ui::caret_visible` | `kui_caret_visible` | `caret_visible` | `caretVisible` | `env.caret_visible`, a reading | The blink phase a custom editor draws its caret on. |
|
|
527
535
|
| `Core::set_caret_visible` | `kui_set_caret_visible` | `set_caret_visible` | `setCaretVisible` | *none: a script is a guest in the host's frame (ADR 0014): its env is the view's reading, and registering, driving, pacing and reading back are the host's* | The host's blink clock writes the phase. |
|
|
@@ -644,6 +652,10 @@ its generator (`nu scripts/odin.nu gen --check`).
|
|
|
644
652
|
| `Core::output` | `kui_draw_data` | `draw_data` | `quads` | *none: a script is a guest in the host's frame (ADR 0014): its env is the view's reading, and registering, driving, pacing and reading back are the host's* | The draw list: quads, clips, fragment and texture draws (`clips`, `fragmentDraws`, `textureDraws` beside `quads` in Node) and the frame's stats. |
|
|
645
653
|
| `Core::take_pending_events` | `kui_poll_event` | `poll_event` | `pollEvents` | `on_event(ev)`, pushed after each frame | What the frame and the input since produced, for `update`. |
|
|
646
654
|
| `Core::set_time` | `kui_set_time` | `set_time` | `Ctx.setTime` | *none: a script is a guest in the host's frame (ADR 0014): its env is the view's reading, and registering, driving, pacing and reading back are the host's* | The clock the tweens read; a window's runner sets it from the display. |
|
|
655
|
+
| `Ui::exit_with` | `kui_exit_with` | `exit_with` | `exitWith` | `exit_with` | Names the exit a node leaves by if this frame stops declaring it, over the one it declared: a throw a button aims. |
|
|
656
|
+
| `Ui::request_frame_at` | `kui_request_frame_at` | `request_frame_at` | `requestFrameAt` | `request_frame_at` | Asks for a frame at a time on the frame clock — a toast's expiry, a sequence's beat — with nothing owed until then. |
|
|
657
|
+
| `Core::next_frame_at` | `kui_next_frame_at` | `next_frame_at` | `nextFrameAt` | *none: a script is a guest in the host's frame (ADR 0014): its env is the view's reading, and registering, driving, pacing and reading back are the host's* | The time a driver should next draw at for a frame asked at a time; infinity in C and Odin, null in Node, for none. |
|
|
658
|
+
| `Ui::now` | `kui_now` | `now` | `now` | `env.now`, a reading | The frame clock in seconds, the one the tweens read: what a view times its own deadlines by. |
|
|
647
659
|
| `Core::env` | `kui_env_set` and its four siblings, `ENV_FIELDS`' C column | `env_set` and its four siblings | `Ctx.setEnv` | *none: a script is a guest in the host's frame (ADR 0014): its env is the view's reading, and registering, driving, pacing and reading back are the host's* | The host facts written in (the `env` field); a `KuiWindow`'s runner writes its own. |
|
|
648
660
|
| `Ui::env` | *none: C is the host, so it writes the facts and has no reading (`ENV_FIELDS`)* | *none: Odin's doors are kui.h's (packages/odin): C has none, for the reason in C's cell* | `env` | `env`, the view's argument | The facts read back, `ENV_FIELDS` row for row. |
|
|
649
661
|
| `Core::handle_input` | `kui_input_cursor` … `kui_input_access`, one per `InputEvent` | `input_cursor` … `input_access`, one per input event; `click` and `press` for a drive | `Ctx.cursor` … `Ctx.access`, one per `InputEvent`; a `KuiWindow` refuses injection | *none: a script is a guest in the host's frame (ADR 0014): its env is the view's reading, and registering, driving, pacing and reading back are the host's* | Pointer, wheel, key, text, IME, assistive and OS file-drag input; a wheel gesture's latching is `scroll_gesture` (`kui_input_scroll_gesture`, `Ctx.scrollGesture`, backlog F107); `press` / `release` are a click by label (`kui_input_press`, `Ctx.press`); the file drag is `drag_files` / `drop_files` / `drag_cancel` (ADR 0031); a file dialog's answer is `answer_files` (`kui_input_files`, `Ctx.answerFiles`, backlog C51); the documents the OS asked the app to open are `InputEvent::Open` (`kui_input_open`, `Ctx.openDocuments`, backlog F124). |
|
|
@@ -656,4 +668,8 @@ its generator (`nu scripts/odin.nu gen --check`).
|
|
|
656
668
|
| `Core::audio_refused` | `kui_audio_refused` | `audio_refused` | `Ctx.audioRefused` | *none: a script is a guest in the host's frame (ADR 0014): its env is the view's reading, and registering, driving, pacing and reading back are the host's* | The device reports a play it would not take — a `refused` sound event and `playback-refused`. |
|
|
657
669
|
| `Launcher::size` | `width` / `height` in the `KuiRunConfig` `kui_run_with` takes | `width` / `height` in the `Run_Config` `kui.run` takes | `width` / `height` in `WindowOptions` | *none: a script is a guest in the host's frame (ADR 0014): its env is the view's reading, and registering, driving, pacing and reading back are the host's* | The window's opening size; `min_size` / `max_size` / `chrome` / `text_aa` / `diagnostics` / `frame_latency` / `backdrop` are the rest of the set, and each binding's form carries them all (`min_w`, `chrome`, `text_aa`, `diagnostics`, `frame_latency`, `backdrop` in C and Odin; `minWidth`, `chrome`, `textAa`, `diagnostics`, `frameLatency`, `backdrop` in Node). `Launcher::devtools` and `Launcher::core` are the two the others reach another way: `kui_set_devtools` / `setDevtools` on the context, and the context handed to `kui_run_with` *is* the core. |
|
|
658
670
|
| `Launcher::icon` | `kui_set_icon` | `set_icon` | `icon` in `WindowOptions` | *none: a script is a guest in the host's frame (ADR 0014): its env is the view's reading, and registering, driving, pacing and reading back are the host's* | The icon every window of the app is created with — RGBA pixels and their size — shown by Windows in the title bar, Alt-Tab and the taskbar and by X11's window manager; macOS (the bundle's `.icns`) and Wayland (the `.desktop` file's) have no window icon (backlog F86). `Launcher::icon_resource` is the Windows executable's own icon resource, which wins there — C's `resource` argument, Node's `icon.resource`. C's is a free function called before `kui_run`, for `kui_on_teardown`'s reason. |
|
|
671
|
+
| `Launcher::icon_bytes` | `kui_decode_image`, then `kui_set_icon` | `decode_image`, then `set_icon` | `kui.decodeImage`, then `icon` in `WindowOptions` | *none: a script is a guest in the host's frame (ADR 0014): its env is the view's reading, and registering, driving, pacing and reading back are the host's* | The icon from an image file's bytes, a PNG most often, through the runner's decoder. |
|
|
672
|
+
| `kui_native::decode_image` | `kui_decode_image` | `decode_image` | `kui.decodeImage` | *none: a script is a guest in the host's frame (ADR 0014): its env is the view's reading, and registering, driving, pacing and reading back are the host's* | PNG, JPEG, WebP or GIF bytes to straight RGBA with the runner's decoder, the shape `add_image` takes; C's frees with `kui_pixels_free`. |
|
|
673
|
+
| `kui_native::decode_animation` | `kui_decode_animation` | `decode_animation` | `kui.decodeAnimation` | *none: a script is a guest in the host's frame (ADR 0014): its env is the view's reading, and registering, driving, pacing and reading back are the host's* | Every frame of an animated GIF, APNG or WebP, each the whole canvas, with the seconds each shows and the loop count. |
|
|
674
|
+
| `Animation::at` | `kui_animation_at` | `animation_at` | `kui.animationAt` | *none: a script is a guest in the host's frame (ADR 0014): its env is the view's reading, and registering, driving, pacing and reading back are the host's* | Which frame of an animation a moment shows and when the next is due: what `update_image` and `request_frame_at` take to play a GIF on the frame clock. |
|
|
659
675
|
| `App::teardown` | `kui_on_teardown` | the `teardown` procedure `kui.run` takes | `KuiWindow.onTeardown` | *none: a script is a guest in the host's frame (ADR 0014): its env is the view's reading, and registering, driving, pacing and reading back are the host's* | The window going for good — its close button, Quit from the menu or the dock, a close command on it, a pumped runner ended — heard once, before `run` returns or the process exits, with nothing drawing: the place to keep what the app would lose with the window (backlog F74, the other two hosts under RG1). On macOS a Quit ends the process from inside the loop, so this is the only thing an app runs on ⌘Q — nothing after `run`, `kui_run` or `await runWindowed(...)` does, not even `process.on('exit')`. C's is a free function called before `kui_run`, with the run's `user`, since `kui_run`'s app is three arguments and not a struct. Node's is the window's door, called from inside the pump that saw the window go; `runWindowed` registers its config's `teardown(model)` there, and `createApp`'s `app.teardown()` runs the same one for a headless drive. |
|