@qxuken/kui 0.1.0-alpha.45 → 0.1.0-alpha.47
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 +240 -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 +376 -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/howto.md
CHANGED
|
@@ -37,6 +37,101 @@ because the removal is judged whole rather than half-animated, and the
|
|
|
37
37
|
[ADR 0012](docs/adr/0012-the-exit-budget.md) ·
|
|
38
38
|
[alpha.8](CHANGELOG.md#010-alpha8-2026-09-07)
|
|
39
39
|
|
|
40
|
+
### How do I throw a card left or right from a button?
|
|
41
|
+
|
|
42
|
+
Aim the exit in the handler that removes the card:
|
|
43
|
+
`ui.exit_with(key, Enter::from(400.0, 0.0).opacity(0.0))` in Rust
|
|
44
|
+
(`env.exit_with` in Lua, `kui_exit_with` in C, `ctx.exitWith(key, { dx:
|
|
45
|
+
400, opacity: 0 })` in Node), then stop declaring the card. The exit a
|
|
46
|
+
node leaves by is otherwise the one its last frame declared, so a Like
|
|
47
|
+
button pressed with the card at rest would send it the default way; the
|
|
48
|
+
named exit wins for the frame the card goes in, and lapses when that
|
|
49
|
+
frame finishes if the card stayed. It aims a node that declares an
|
|
50
|
+
`exit` and a `transition`, and plays with that transition — so give the
|
|
51
|
+
card a default `exit`, and a drag that already aims it by the lean on
|
|
52
|
+
every frame needs nothing more.
|
|
53
|
+
|
|
54
|
+
[`exit` row](props.md#container-props) ·
|
|
55
|
+
[`Ui::exit_with` door](props.md#doors)
|
|
56
|
+
|
|
57
|
+
### How do I tilt a card as it is dragged?
|
|
58
|
+
|
|
59
|
+
`rotate` on the card, in turns clockwise (`0.03` is a tilt, `0.25` a
|
|
60
|
+
quarter turn), read from the drag's `dx` in `on_event`; `scale` and
|
|
61
|
+
`pivotX` / `pivotY` (fractions of the box, the centre by default) sit
|
|
62
|
+
beside it. The turn is paint-only — the card keeps the room its upright
|
|
63
|
+
self takes — and everything under it turns with it: the photo it clips
|
|
64
|
+
inside its rounded corners, a badge floating on its corner, its text,
|
|
65
|
+
and its hit region, so it is grabbed on its tilted edge. Leave the
|
|
66
|
+
`transition` off while the pointer holds the card, so the tilt follows
|
|
67
|
+
at once, and put it back on release: the turn is a slot like `bg` or
|
|
68
|
+
`width`, so the spring brings it home. `enter: { scale: 0.8 }` settles
|
|
69
|
+
a chip in, `exit: { rotate: 0.1, scale: 0 }` spins one away, and a
|
|
70
|
+
spinner is a box with `keyframes: [{ rotate: 0 }, { rotate: 1 }]`. A
|
|
71
|
+
`path`'s own `rotate` is ADR 0041's and does not tween; put the path in
|
|
72
|
+
a box for one that does.
|
|
73
|
+
|
|
74
|
+
[`rotate` row](props.md#container-props) ·
|
|
75
|
+
[ADR 0043](docs/adr/0043-a-node-turns-about-its-pivot.md) ·
|
|
76
|
+
[`transform` example](../examples/rust/features/transform.rs)
|
|
77
|
+
|
|
78
|
+
### How do I make a node bob, drift or shake by itself?
|
|
79
|
+
|
|
80
|
+
Give it `keyframes` whose stops name `dx` / `dy`: logical px from where
|
|
81
|
+
layout put it, sampled off the cycle like any other slot, so the view
|
|
82
|
+
declares the stops once and never redraws for them. `[{ dy: 0 }, { dy:
|
|
83
|
+
-6 }]` with `repeat: 'alternate'` bobs it; stops that rise and fade drift
|
|
84
|
+
a sparkle up; a few small alternating `dx` stops on a short cycle shake
|
|
85
|
+
it. The node and everything under it are drawn and hit where the stop
|
|
86
|
+
puts them, and the offset adds to a `slide`, but the room it takes is its
|
|
87
|
+
place's: siblings do not move, and `onLayout` reports the layout rect
|
|
88
|
+
rather than a cycle that would post an event every frame. A cycle runs
|
|
89
|
+
for as long as the node is declared and asks for a frame every vsync,
|
|
90
|
+
unless `iterations` says how many times.
|
|
91
|
+
|
|
92
|
+
[`keyframes` row](props.md#container-props) ·
|
|
93
|
+
[`transition` example](../examples/rust/features/transition.rs)
|
|
94
|
+
|
|
95
|
+
### How do I play a keyframe animation once?
|
|
96
|
+
|
|
97
|
+
`iterations: 1` on the node with the `keyframes` (CSS's
|
|
98
|
+
`animation-iteration-count`). The cycle plays from the first frame the
|
|
99
|
+
node is declared with it, rests where it ended — the last stop, or the
|
|
100
|
+
first for `reverse`, or the start again for `2` alternating — and then
|
|
101
|
+
asks for nothing, so `animating()` goes quiet and a test's `advance` runs
|
|
102
|
+
it out. Before its `delay` it holds its first stop, so a row of stars
|
|
103
|
+
given `delay: i * 150` and `iterations: 1` pops in one after another
|
|
104
|
+
without showing their own values first. To play it again, drop the
|
|
105
|
+
stops for a frame and declare them again, or give the node a new key: a
|
|
106
|
+
finite cycle restarts whenever a frame went without it. A blinking caret
|
|
107
|
+
is still not keyframes (see the caret how-to): it is a cycle with no end.
|
|
108
|
+
|
|
109
|
+
[`iterations` row](props.md#container-props) ·
|
|
110
|
+
[`keyframes` row](props.md#container-props) ·
|
|
111
|
+
[`transition` example](../examples/rust/features/transition.rs)
|
|
112
|
+
|
|
113
|
+
### How do I show a photo or a GIF my app ships?
|
|
114
|
+
|
|
115
|
+
Decode it with the runner, which links the decoder it draws its
|
|
116
|
+
wallpaper with: `kui_native::decode_image(bytes)` turns PNG, JPEG, WebP
|
|
117
|
+
or GIF bytes into straight RGBA and a size, which `add_image` takes as it
|
|
118
|
+
is (`decodeImage` in Node, `kui_decode_image` in C, freed with
|
|
119
|
+
`kui_pixels_free`). No `image` dependency of the app's own. For an
|
|
120
|
+
animated GIF, APNG or WebP, `decode_animation` keeps every frame — the
|
|
121
|
+
whole canvas each, as a browser composites it — with the seconds each
|
|
122
|
+
shows. Play it on the frame clock: keep when it started, ask
|
|
123
|
+
`anim.at(ui.now() - started)` which frame shows and when the next
|
|
124
|
+
is due, `update_image_with` the pixels when the index moved, and
|
|
125
|
+
`request_frame_at(started + next)`. Nothing is owed between steps, so a
|
|
126
|
+
spinner at 80 ms a step costs twelve frames a second and an idle window
|
|
127
|
+
otherwise, and a paused one asks for nothing. A GIF frame that says 10 ms
|
|
128
|
+
or less shows for 100, the way browsers show it; a finite loop count
|
|
129
|
+
rests on the last frame. Lua is a guest and gets the handle from its
|
|
130
|
+
host.
|
|
131
|
+
|
|
132
|
+
[`decode` example](../examples/rust/features/decode.rs) ·
|
|
133
|
+
[`image` element](props.md#elements)
|
|
134
|
+
|
|
40
135
|
### How do I draw a connector between two boxes?
|
|
41
136
|
|
|
42
137
|
`<line from={[x, y]} to={[x, y]} width color/>` is one round-capped stroke,
|
|
@@ -1207,7 +1302,8 @@ blinks in the background. Headless the phase stays true;
|
|
|
1207
1302
|
`setCaretVisible(false)` / `kui_set_caret_visible` is how a test sees
|
|
1208
1303
|
the off phase drawn, and how a C host with its own window drives it
|
|
1209
1304
|
(`kui_has_caret`, `kui_caret_stamp` are the clock's inputs). Never use
|
|
1210
|
-
`keyframes` for this:
|
|
1305
|
+
`keyframes` for this: a blink has no end, and an endless cycle asks for a
|
|
1306
|
+
frame every vsync.
|
|
1211
1307
|
|
|
1212
1308
|
A caret that does not blink — the block of a modal editor's normal mode —
|
|
1213
1309
|
declares `caretSolid` (Lua `caret_solid = true`, C `KUI_VALUE_CARET_SOLID`
|
|
@@ -1700,7 +1796,48 @@ re-renders. `ctx.setTime` under a loop throws and names `advance` — the loop
|
|
|
1700
1796
|
owns the clock — and `startTime` in the options pins where that clock starts
|
|
1701
1797
|
so assertions on `tick.msg(now)` are exact.
|
|
1702
1798
|
|
|
1703
|
-
|
|
1799
|
+
Keep the app's own deadlines on that clock too. `now()` — `ui.now()` in
|
|
1800
|
+
Rust, `env.now` in Lua, `kui_now` in C, `ctx.now()` in Node — is the
|
|
1801
|
+
frame clock in seconds, the one `transition` and `keyframes` read (0
|
|
1802
|
+
before a driver sets it), so a toast due at `shownAt + 3` and the fade
|
|
1803
|
+
that takes it away agree, and `advance` or `Drive::advance` moves both.
|
|
1804
|
+
A view that reads `Instant::now()` or `Date.now()` keeps
|
|
1805
|
+
a second clock no test can move.
|
|
1806
|
+
|
|
1807
|
+
[alpha.8 `**What breaks.**`](CHANGELOG.md#010-alpha8-2026-09-07) ·
|
|
1808
|
+
[`now` in the env](props.md#env)
|
|
1809
|
+
|
|
1810
|
+
### How do I hide a toast after three seconds without a timer thread?
|
|
1811
|
+
|
|
1812
|
+
Keep the time it is due on the frame clock, and ask for a frame then:
|
|
1813
|
+
`ui.request_frame_at(shown_at + 3.0)` in Rust (`env.request_frame_at` in
|
|
1814
|
+
Lua, `kui_request_frame_at` in C, `ctx.requestFrameAt` in Node), read
|
|
1815
|
+
`now()` in the view, and stop drawing the toast once it is past. The
|
|
1816
|
+
runner sleeps to that time and runs the view, and nothing is owed in
|
|
1817
|
+
between, so `animating()` stays false and an idle app stays idle. The
|
|
1818
|
+
earliest time asked for wins and is kept until a frame reaches it, so a
|
|
1819
|
+
view may ask every frame or once. From a thread that is not the view's —
|
|
1820
|
+
a download finishing on its own schedule — `Waker::wake_at(instant)`
|
|
1821
|
+
says the same thing to every window. A test moves the clock past the
|
|
1822
|
+
time and draws; a host driving its own window reads
|
|
1823
|
+
`kui_next_frame_at` and sleeps to it.
|
|
1824
|
+
|
|
1825
|
+
[`now` in the env](props.md#env)
|
|
1826
|
+
|
|
1827
|
+
### How do I press "the button named Like" in a test?
|
|
1828
|
+
|
|
1829
|
+
Look it up by the name a reader hears: `Drive::key_named("Like")` in
|
|
1830
|
+
Rust (`Core::key_named` under it, `ctx.keyNamed` in Node, `kui_key_named`
|
|
1831
|
+
in C), then `click_key` it. The name is the node's `label` row, else the
|
|
1832
|
+
text inside a control, so a test that finds a button this way also checks
|
|
1833
|
+
that a screen reader can. `key_of` asks a different question: the *key
|
|
1834
|
+
label*, the name the view opened the node under (`with_keyed("like",
|
|
1835
|
+
..)`, a `key` prop), which a reader never hears and `texts_under` reads
|
|
1836
|
+
too. Two nodes with one name resolve to the first in tree order and raise
|
|
1837
|
+
`ambiguous-name` — the same two a reader cannot tell apart.
|
|
1838
|
+
|
|
1839
|
+
[`label` row](props.md#container-props) ·
|
|
1840
|
+
[`ambiguous-name`](props.md#warnings)
|
|
1704
1841
|
|
|
1705
1842
|
### How do I test the real window, not a headless core?
|
|
1706
1843
|
|
|
@@ -1938,7 +2075,8 @@ it again after an upgrade; it is generated, never edited.
|
|
|
1938
2075
|
Tell the launcher, once, and every window it creates carries it:
|
|
1939
2076
|
`kui_native::app("t").icon(rgba, w, h)` with straight RGBA pixels, row by row
|
|
1940
2077
|
— something a taskbar shrinks cleanly, 64 to 256 px, rendered from your
|
|
1941
|
-
drawing at build time or
|
|
2078
|
+
drawing at build time — or `.icon_bytes(include_bytes!("icon.png"))` for
|
|
2079
|
+
a PNG you ship, decoded by the runner — and, for a
|
|
1942
2080
|
Windows program, `.icon_resource(1)` too. A Windows program's icon is a
|
|
1943
2081
|
resource linked into its executable (a `1 ICON "app.ico"` line in its
|
|
1944
2082
|
`.rc`, compiled by `embed-resource` or the like in `build.rs`), which is
|
package/index.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type {
|
|
2
2
|
AppMsg,
|
|
3
3
|
BoxProps,
|
|
4
|
+
EnterProp,
|
|
4
5
|
GeneratedSpecProps,
|
|
5
6
|
KuiNode,
|
|
6
7
|
MenuItemInput,
|
|
@@ -805,6 +806,12 @@ export type WarningCode =
|
|
|
805
806
|
* declares, or pass the hex key an event carried. Two nodes with the *same*
|
|
806
807
|
* key are `duplicate-key`. */
|
|
807
808
|
| 'ambiguous-key'
|
|
809
|
+
/** A lookup by accessible name (`Core::key_named`, `ctx.keyNamed`,
|
|
810
|
+
* `kui_key_named`) found more than one node with that name in the last frame
|
|
811
|
+
* — two buttons both read as "Delete" — and used the first in tree order. A
|
|
812
|
+
* reader hears the same name twice too: give each a `label` that says which,
|
|
813
|
+
* or look the one meant up by its key label. */
|
|
814
|
+
| 'ambiguous-name'
|
|
808
815
|
/** A `focusRegion(name)` (`Core::focus_region`, `env.focus_region`,
|
|
809
816
|
* `kui_focus_region`) named a node the frame after it did not declare as a
|
|
810
817
|
* `focusRegion` — no node under the label, or a node without the row — so
|
|
@@ -1381,6 +1388,10 @@ export interface NodeInfo {
|
|
|
1381
1388
|
opacity: number;
|
|
1382
1389
|
/** Its `backdropBlur` radius in px, 0 for none (backlog F129). */
|
|
1383
1390
|
backdropBlur: number;
|
|
1391
|
+
/** Its turn in turns and its scale as drawn this frame, eased (ADR
|
|
1392
|
+
* 0043): 0 and 1 for a node that declares none. */
|
|
1393
|
+
rotate: number;
|
|
1394
|
+
scale: number;
|
|
1384
1395
|
/** A scroller's offset; `null` for a node that does not scroll. */
|
|
1385
1396
|
scroll: { x: number; y: number } | null;
|
|
1386
1397
|
/** Every handler it declared with the payload it would post: `click`,
|
|
@@ -3047,6 +3058,37 @@ export declare class Ctx {
|
|
|
3047
3058
|
* while the window has no keyboard. Always `true` headless.
|
|
3048
3059
|
*/
|
|
3049
3060
|
caretVisible(): boolean
|
|
3061
|
+
/**
|
|
3062
|
+
* The frame clock in seconds (backlog F134): the one the
|
|
3063
|
+
* tweens and cycles read, `setTime` or the loop's own, 0
|
|
3064
|
+
* before either. Read a deadline off it rather than off
|
|
3065
|
+
* `Date.now()`, so `advance` moves both.
|
|
3066
|
+
*/
|
|
3067
|
+
now(): number
|
|
3068
|
+
/**
|
|
3069
|
+
* Asks for a frame at `at` on the frame clock (backlog F135),
|
|
3070
|
+
* in seconds: a toast's expiry, a sequence's next beat. Nothing
|
|
3071
|
+
* is owed until then, so `animating()` stays false; a window
|
|
3072
|
+
* sleeps to it and draws, and the earliest time asked for wins.
|
|
3073
|
+
* A headless loop's `advance` past it draws the frame it ends
|
|
3074
|
+
* on, as it does for a `tick`.
|
|
3075
|
+
*/
|
|
3076
|
+
requestFrameAt(at: number): void
|
|
3077
|
+
/**
|
|
3078
|
+
* The exit the node `key` leaves by if it leaves in the frame
|
|
3079
|
+
* that finishes next (backlog F136), over the `exit` it
|
|
3080
|
+
* declared: `{dx: 400, opacity: 0}`, the same fields as
|
|
3081
|
+
* `exit`. A card a button throws aside is removed in the same
|
|
3082
|
+
* handler, with no frame drawn first to aim it. It aims a node
|
|
3083
|
+
* that declares an `exit`, with that node's transition, and is
|
|
3084
|
+
* forgotten when that frame finishes.
|
|
3085
|
+
*/
|
|
3086
|
+
exitWith(key: string, exit: EnterProp): void
|
|
3087
|
+
/**
|
|
3088
|
+
* The frame-clock time the next asked-for frame is due at, in
|
|
3089
|
+
* seconds; `null` when none was asked for.
|
|
3090
|
+
*/
|
|
3091
|
+
nextFrameAt(): number | null
|
|
3050
3092
|
/**
|
|
3051
3093
|
* Whether there is a caret to blink: a focused `<edit>`'s, or
|
|
3052
3094
|
* the `caret` a `line` under the focused sink declares — unless
|
|
@@ -3078,8 +3120,9 @@ export declare class Ctx {
|
|
|
3078
3120
|
*/
|
|
3079
3121
|
focus(key: string): void
|
|
3080
3122
|
/**
|
|
3081
|
-
* The hex key of the node a label names — the label a `key`
|
|
3082
|
-
* prop declared,
|
|
3123
|
+
* The hex key of the node a key label names — the label a `key`
|
|
3124
|
+
* prop declared, not the accessible name a reader hears
|
|
3125
|
+
* (`keyNamed`), resolved through the frame being built so
|
|
3083
3126
|
* far and then the last finished one — or null when no node
|
|
3084
3127
|
* declared it. The door for holding a key across frames;
|
|
3085
3128
|
* every call that takes a key takes the label too, so this
|
|
@@ -3090,6 +3133,16 @@ export declare class Ctx {
|
|
|
3090
3133
|
* plugin filling a slot is answered from its own nodes only.
|
|
3091
3134
|
*/
|
|
3092
3135
|
keyOf(label: string): string | null
|
|
3136
|
+
/**
|
|
3137
|
+
* The hex key of the first node in the last finished frame
|
|
3138
|
+
* whose accessible name is `name` (backlog F137) — its
|
|
3139
|
+
* `label` prop, else its own text, else a control's derived
|
|
3140
|
+
* name — so a test clicks "the button named Like" as a reader
|
|
3141
|
+
* would: `ctx.click(ctx.keyNamed('Like'))`. Not `keyOf`'s key
|
|
3142
|
+
* label, which a reader never hears. Null when no node has the
|
|
3143
|
+
* name; two raise `ambiguous-name`.
|
|
3144
|
+
*/
|
|
3145
|
+
keyNamed(name: string): string | null
|
|
3093
3146
|
blur(): void
|
|
3094
3147
|
/**
|
|
3095
3148
|
* What Tab does, as a call — for an `onKey` sink that binds Tab
|
|
@@ -3509,6 +3562,66 @@ export declare class Ctx {
|
|
|
3509
3562
|
setEditText(key: string, text: string): void
|
|
3510
3563
|
}
|
|
3511
3564
|
|
|
3565
|
+
/**
|
|
3566
|
+
* Decoded pixels (`decodeImage`): `width` by `height`, four bytes each
|
|
3567
|
+
* (RGBA), row by row from the top left, alpha not premultiplied — what
|
|
3568
|
+
* `addImage`, `updateImage` and a window's `icon` take.
|
|
3569
|
+
*/
|
|
3570
|
+
export interface DecodedImage {
|
|
3571
|
+
width: number
|
|
3572
|
+
height: number
|
|
3573
|
+
rgba: Buffer
|
|
3574
|
+
}
|
|
3575
|
+
|
|
3576
|
+
/**
|
|
3577
|
+
* Every frame of an animated image (`decodeAnimation`), each the whole
|
|
3578
|
+
* canvas, with the seconds each shows and how many times the sequence
|
|
3579
|
+
* plays (absent: for ever).
|
|
3580
|
+
*/
|
|
3581
|
+
export interface DecodedAnimation {
|
|
3582
|
+
width: number
|
|
3583
|
+
height: number
|
|
3584
|
+
frames: Array<Buffer>
|
|
3585
|
+
delays: Array<number>
|
|
3586
|
+
loops?: number
|
|
3587
|
+
}
|
|
3588
|
+
|
|
3589
|
+
/**
|
|
3590
|
+
* Which frame shows at a moment (`animationAt`), and the seconds after
|
|
3591
|
+
* the start the next is due — `Infinity` once a finite animation has
|
|
3592
|
+
* played out.
|
|
3593
|
+
*/
|
|
3594
|
+
export interface AnimationShowing {
|
|
3595
|
+
index: number
|
|
3596
|
+
next: number
|
|
3597
|
+
}
|
|
3598
|
+
|
|
3599
|
+
/**
|
|
3600
|
+
* Decodes PNG, JPEG, WebP or GIF bytes (an animated file's first frame)
|
|
3601
|
+
* with the runner's decoder (backlog F138), so an app that ships a photo
|
|
3602
|
+
* needs no image package: `ctx.addImage(d.width, d.height, d.rgba)`.
|
|
3603
|
+
* Throws, naming why, for bytes that are not an image or do not decode.
|
|
3604
|
+
*/
|
|
3605
|
+
export declare function decodeImage(bytes: Buffer): DecodedImage
|
|
3606
|
+
|
|
3607
|
+
/**
|
|
3608
|
+
* Decodes every frame of an animated GIF, PNG (APNG) or WebP, each the
|
|
3609
|
+
* whole canvas, and the seconds each shows; a GIF frame asking for 10 ms
|
|
3610
|
+
* or less shows for 100, as browsers show it. A still image is one frame
|
|
3611
|
+
* shown for ever. Play it on the frame clock with `animationAt`,
|
|
3612
|
+
* `updateImage` and `requestFrameAt`.
|
|
3613
|
+
*/
|
|
3614
|
+
export declare function decodeAnimation(bytes: Buffer): DecodedAnimation
|
|
3615
|
+
|
|
3616
|
+
/**
|
|
3617
|
+
* Which of the frames with these `delays` shows `elapsed` seconds after
|
|
3618
|
+
* the animation started, playing `loops` times (null or 0 for ever), and when
|
|
3619
|
+
* the next is due: `animationAt(gif.delays, gif.loops, ctx.now() -
|
|
3620
|
+
* started)`, then `updateImage` when the index moved and
|
|
3621
|
+
* `requestFrameAt(started + next)`.
|
|
3622
|
+
*/
|
|
3623
|
+
export declare function animationAt(delays: Array<number>, loops: number | undefined | null, elapsed: number): AnimationShowing
|
|
3624
|
+
|
|
3512
3625
|
/** Byte stride of one quad in the `quads()` buffer. */
|
|
3513
3626
|
export declare function quadStride(): number
|
|
3514
3627
|
|
|
@@ -4244,6 +4357,37 @@ export declare class KuiWindow {
|
|
|
4244
4357
|
* while the window has no keyboard. Always `true` headless.
|
|
4245
4358
|
*/
|
|
4246
4359
|
caretVisible(): boolean
|
|
4360
|
+
/**
|
|
4361
|
+
* The frame clock in seconds (backlog F134): the one the
|
|
4362
|
+
* tweens and cycles read, `setTime` or the loop's own, 0
|
|
4363
|
+
* before either. Read a deadline off it rather than off
|
|
4364
|
+
* `Date.now()`, so `advance` moves both.
|
|
4365
|
+
*/
|
|
4366
|
+
now(): number
|
|
4367
|
+
/**
|
|
4368
|
+
* Asks for a frame at `at` on the frame clock (backlog F135),
|
|
4369
|
+
* in seconds: a toast's expiry, a sequence's next beat. Nothing
|
|
4370
|
+
* is owed until then, so `animating()` stays false; a window
|
|
4371
|
+
* sleeps to it and draws, and the earliest time asked for wins.
|
|
4372
|
+
* A headless loop's `advance` past it draws the frame it ends
|
|
4373
|
+
* on, as it does for a `tick`.
|
|
4374
|
+
*/
|
|
4375
|
+
requestFrameAt(at: number): void
|
|
4376
|
+
/**
|
|
4377
|
+
* The exit the node `key` leaves by if it leaves in the frame
|
|
4378
|
+
* that finishes next (backlog F136), over the `exit` it
|
|
4379
|
+
* declared: `{dx: 400, opacity: 0}`, the same fields as
|
|
4380
|
+
* `exit`. A card a button throws aside is removed in the same
|
|
4381
|
+
* handler, with no frame drawn first to aim it. It aims a node
|
|
4382
|
+
* that declares an `exit`, with that node's transition, and is
|
|
4383
|
+
* forgotten when that frame finishes.
|
|
4384
|
+
*/
|
|
4385
|
+
exitWith(key: string, exit: EnterProp): void
|
|
4386
|
+
/**
|
|
4387
|
+
* The frame-clock time the next asked-for frame is due at, in
|
|
4388
|
+
* seconds; `null` when none was asked for.
|
|
4389
|
+
*/
|
|
4390
|
+
nextFrameAt(): number | null
|
|
4247
4391
|
/**
|
|
4248
4392
|
* Whether there is a caret to blink: a focused `<edit>`'s, or
|
|
4249
4393
|
* the `caret` a `line` under the focused sink declares — unless
|
|
@@ -4275,8 +4419,9 @@ export declare class KuiWindow {
|
|
|
4275
4419
|
*/
|
|
4276
4420
|
focus(key: string): void
|
|
4277
4421
|
/**
|
|
4278
|
-
* The hex key of the node a label names — the label a `key`
|
|
4279
|
-
* prop declared,
|
|
4422
|
+
* The hex key of the node a key label names — the label a `key`
|
|
4423
|
+
* prop declared, not the accessible name a reader hears
|
|
4424
|
+
* (`keyNamed`), resolved through the frame being built so
|
|
4280
4425
|
* far and then the last finished one — or null when no node
|
|
4281
4426
|
* declared it. The door for holding a key across frames;
|
|
4282
4427
|
* every call that takes a key takes the label too, so this
|
|
@@ -4287,6 +4432,16 @@ export declare class KuiWindow {
|
|
|
4287
4432
|
* plugin filling a slot is answered from its own nodes only.
|
|
4288
4433
|
*/
|
|
4289
4434
|
keyOf(label: string): string | null
|
|
4435
|
+
/**
|
|
4436
|
+
* The hex key of the first node in the last finished frame
|
|
4437
|
+
* whose accessible name is `name` (backlog F137) — its
|
|
4438
|
+
* `label` prop, else its own text, else a control's derived
|
|
4439
|
+
* name — so a test clicks "the button named Like" as a reader
|
|
4440
|
+
* would: `ctx.click(ctx.keyNamed('Like'))`. Not `keyOf`'s key
|
|
4441
|
+
* label, which a reader never hears. Null when no node has the
|
|
4442
|
+
* name; two raise `ambiguous-name`.
|
|
4443
|
+
*/
|
|
4444
|
+
keyNamed(name: string): string | null
|
|
4290
4445
|
blur(): void
|
|
4291
4446
|
/**
|
|
4292
4447
|
* What Tab does, as a call — for an `onKey` sink that binds Tab
|
package/index.js
CHANGED
|
@@ -3,7 +3,10 @@
|
|
|
3
3
|
import native from './native.cjs';
|
|
4
4
|
import { createEncoder } from './encoder.js';
|
|
5
5
|
|
|
6
|
-
export const {
|
|
6
|
+
export const {
|
|
7
|
+
Ctx, KuiWindow, RowHeights, quadStride, clipStride, protocol,
|
|
8
|
+
decodeImage, decodeAnimation, animationAt,
|
|
9
|
+
} = native;
|
|
7
10
|
export { createEncoder };
|
|
8
11
|
|
|
9
12
|
// The brand on what `update` (or a function `init`) returns to hand the loop
|
|
@@ -1412,6 +1415,14 @@ export function decodeClips(buffer) {
|
|
|
1412
1415
|
// Corner radii clockwise from the top-left: a clipping node with a
|
|
1413
1416
|
// radius rounds what it clips. All zero = a plain rect clip.
|
|
1414
1417
|
radii: [f[4], f[5], f[6], f[7]],
|
|
1418
|
+
// The turn every quad naming this entry is drawn through (ADR
|
|
1419
|
+
// 0043): angle in radians, scale, tx, ty — `[0, 1, 0, 0]`, the
|
|
1420
|
+
// identity, on a frame that turns nothing.
|
|
1421
|
+
transform: [f[8], f[9], f[10], f[11]],
|
|
1422
|
+
// A second clip in the quad's own space, before the turn, and its
|
|
1423
|
+
// radii: from clipping nodes inside a turned subtree.
|
|
1424
|
+
inner: [f[12], f[13], f[14], f[15]],
|
|
1425
|
+
innerRadii: [f[16], f[17], f[18], f[19]],
|
|
1415
1426
|
});
|
|
1416
1427
|
}
|
|
1417
1428
|
return clips;
|
package/jsx-runtime.d.ts
CHANGED
|
@@ -119,12 +119,20 @@ export type AlignProp = 'start' | 'center' | 'end';
|
|
|
119
119
|
* animate their amount only, in the form the prop itself declares. */
|
|
120
120
|
export interface KeyframeProp {
|
|
121
121
|
at?: number;
|
|
122
|
+
/** Logical px across from where layout put the node, at this stop (backlog F132): the node and its subtree are drawn and hit there. Left out, 0. */
|
|
123
|
+
dx?: number;
|
|
124
|
+
/** Logical px down from where layout put the node, at this stop: `[{ dy: 0 }, { dy: -6 }]` with `repeat: 'alternate'` bobs it. Left out, 0. */
|
|
125
|
+
dy?: number;
|
|
122
126
|
width?: SizingProp;
|
|
123
127
|
height?: SizingProp;
|
|
124
128
|
bg?: ColorProp;
|
|
125
129
|
radius?: number;
|
|
126
130
|
/** Group opacity, 0..1. */
|
|
127
131
|
opacity?: number;
|
|
132
|
+
/** A turn in turns clockwise (ADR 0043). */
|
|
133
|
+
rotate?: number;
|
|
134
|
+
/** A uniform scale about the node's pivot. */
|
|
135
|
+
scale?: number;
|
|
128
136
|
}
|
|
129
137
|
|
|
130
138
|
/** Where a node starts the first frame it is seen, for `enter`: the slots
|
|
@@ -141,6 +149,10 @@ export interface EnterProp {
|
|
|
141
149
|
radius?: number;
|
|
142
150
|
/** Group opacity, 0..1: `{ opacity: 0 }` fades the whole subtree in. */
|
|
143
151
|
opacity?: number;
|
|
152
|
+
/** A turn in turns clockwise (ADR 0043): `{ rotate: -0.02 }` swings in. */
|
|
153
|
+
rotate?: number;
|
|
154
|
+
/** A uniform scale about the node's pivot: `{ scale: 0.8 }` settles in. */
|
|
155
|
+
scale?: number;
|
|
144
156
|
}
|
|
145
157
|
|
|
146
158
|
/** A gradient painted over a box's `bg`, under its border and children
|
|
@@ -296,9 +308,9 @@ export interface GeneratedSpecProps {
|
|
|
296
308
|
dropBg?: ColorProp;
|
|
297
309
|
/** 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. */
|
|
298
310
|
easing?: 'easeOut' | 'linear' | 'easeIn' | 'easeInOut' | 'spring' | 'bouncy' | 'smooth' | 'snappy';
|
|
299
|
-
/** 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). */
|
|
311
|
+
/** 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). */
|
|
300
312
|
enter?: EnterProp;
|
|
301
|
-
/** 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. */
|
|
313
|
+
/** 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. */
|
|
302
314
|
exit?: EnterProp;
|
|
303
315
|
/** 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. */
|
|
304
316
|
expanded?: 'collapsed' | 'expanded';
|
|
@@ -324,13 +336,15 @@ export interface GeneratedSpecProps {
|
|
|
324
336
|
hoverable?: boolean;
|
|
325
337
|
/** 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. */
|
|
326
338
|
initialFocus?: boolean;
|
|
339
|
+
/** 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. */
|
|
340
|
+
iterations?: LengthProp;
|
|
327
341
|
/** 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. */
|
|
328
342
|
keepFocus?: boolean;
|
|
329
343
|
/** 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. */
|
|
330
344
|
keyUp?: boolean;
|
|
331
|
-
/** CSS-style stops `[{ at?, width?, height?, bg?, radius?, opacity? }, …]`: the slots they name cycle through them over `transition` ms,
|
|
345
|
+
/** 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. */
|
|
332
346
|
keyframes?: KeyframeProp[];
|
|
333
|
-
/** 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`). */
|
|
347
|
+
/** 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. */
|
|
334
348
|
label?: string;
|
|
335
349
|
/** 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. */
|
|
336
350
|
live?: 'off' | 'polite' | 'assertive';
|
|
@@ -378,6 +392,10 @@ export interface GeneratedSpecProps {
|
|
|
378
392
|
opacity?: LengthProp;
|
|
379
393
|
/** 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. */
|
|
380
394
|
overscroll?: 'auto' | 'contain';
|
|
395
|
+
/** 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`. */
|
|
396
|
+
pivotX?: LengthProp;
|
|
397
|
+
/** 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`. */
|
|
398
|
+
pivotY?: LengthProp;
|
|
381
399
|
/** 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. */
|
|
382
400
|
pixelSnap?: boolean;
|
|
383
401
|
/** Background while pressed (or while its hoverGroup is); implies hover tracking. */
|
|
@@ -396,10 +414,14 @@ export interface GeneratedSpecProps {
|
|
|
396
414
|
repeat?: 'normal' | 'reverse' | 'alternate' | 'alternateReverse';
|
|
397
415
|
/** 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. */
|
|
398
416
|
role?: 'none' | 'button' | 'checkbox' | 'radio' | 'switch' | 'slider' | 'tab' | 'tabList' | 'link' | 'heading' | 'list' | 'listItem' | 'image' | 'dialog' | 'group' | 'textInput' | 'multilineTextInput' | 'line' | 'radioGroup' | 'menu' | 'menuItem' | 'terminal';
|
|
417
|
+
/** 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. */
|
|
418
|
+
rotate?: LengthProp;
|
|
399
419
|
/** The width of a table's `rules` in logical px; 1 when unset. */
|
|
400
420
|
ruleWidth?: LengthProp;
|
|
401
421
|
/** 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. */
|
|
402
422
|
rules?: ColorProp;
|
|
423
|
+
/** 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. */
|
|
424
|
+
scale?: LengthProp;
|
|
403
425
|
/** 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`. */
|
|
404
426
|
scrollAxes?: 'both' | 'x' | 'y';
|
|
405
427
|
/** 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`. */
|
package/package.json
CHANGED
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|