kuinetic 0.1.3 → 0.2.0

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.
Files changed (125) hide show
  1. package/README.md +93 -10
  2. package/dist/esm/advanced/index.mjs +4727 -0
  3. package/dist/esm/chunk-AEQGC2KM.mjs +189 -0
  4. package/dist/esm/chunk-MQSNMYRP.mjs +9332 -0
  5. package/dist/esm/chunk-RVTYN6NZ.mjs +3446 -0
  6. package/dist/esm/chunk-U43BX5O2.mjs +2984 -0
  7. package/dist/esm/core/index.mjs +16 -9
  8. package/dist/esm/effects/index.mjs +19 -4
  9. package/dist/esm/index.mjs +17 -10
  10. package/dist/kuinetic.advanced.js +5070 -0
  11. package/dist/kuinetic.advanced.min.js +749 -0
  12. package/dist/kuinetic.all.js +15218 -5595
  13. package/dist/kuinetic.all.min.js +2 -0
  14. package/dist/kuinetic.css +2900 -360
  15. package/dist/kuinetic.js +15217 -5593
  16. package/dist/kuinetic.min.css +1 -0
  17. package/dist/kuinetic.min.js +7 -0
  18. package/dist/types/3d/angles.d.ts +30 -0
  19. package/dist/types/3d/flattening.d.ts +49 -0
  20. package/dist/types/3d/index.d.ts +36 -0
  21. package/dist/types/3d/model-3d.d.ts +115 -0
  22. package/dist/types/3d/register.d.ts +26 -0
  23. package/dist/types/3d/webgl.d.ts +24 -0
  24. package/dist/types/advanced/audio.d.ts +322 -0
  25. package/dist/types/advanced/base.d.ts +233 -0
  26. package/dist/types/advanced/camera-3d.d.ts +236 -0
  27. package/dist/types/advanced/fluid-cursor.d.ts +149 -0
  28. package/dist/types/advanced/gl-utils.d.ts +52 -0
  29. package/dist/types/advanced/glsl.d.ts +60 -0
  30. package/dist/types/advanced/index.d.ts +23 -0
  31. package/dist/types/advanced/particles.d.ts +108 -0
  32. package/dist/types/advanced/scenes.d.ts +207 -0
  33. package/dist/types/advanced/shaders.d.ts +972 -0
  34. package/dist/types/browser/boot.d.ts +72 -0
  35. package/dist/types/core/activation.d.ts +227 -2
  36. package/dist/types/core/animator.d.ts +446 -11
  37. package/dist/types/core/attrs.d.ts +9 -0
  38. package/dist/types/core/breakpoints.d.ts +260 -0
  39. package/dist/types/core/bundles.d.ts +71 -0
  40. package/dist/types/core/callback.d.ts +64 -0
  41. package/dist/types/core/capabilities.d.ts +47 -0
  42. package/dist/types/core/channels.d.ts +120 -2
  43. package/dist/types/core/cloak-selectors.d.ts +47 -0
  44. package/dist/types/core/compile.d.ts +207 -2
  45. package/dist/types/core/control.d.ts +218 -0
  46. package/dist/types/core/declarations.d.ts +128 -0
  47. package/dist/types/core/derived/aggregate.d.ts +43 -0
  48. package/dist/types/core/derived/book.d.ts +30 -0
  49. package/dist/types/core/derived/diagnostics.d.ts +51 -0
  50. package/dist/types/core/derived/group-gate.d.ts +62 -0
  51. package/dist/types/core/derived/install.d.ts +110 -0
  52. package/dist/types/core/derived/late-matches.d.ts +19 -0
  53. package/dist/types/core/derived/types.d.ts +108 -0
  54. package/dist/types/core/easing.d.ts +57 -0
  55. package/dist/types/core/element-config.d.ts +47 -6
  56. package/dist/types/core/element-size.d.ts +23 -0
  57. package/dist/types/core/event-sources.d.ts +39 -0
  58. package/dist/types/core/flip.d.ts +34 -1
  59. package/dist/types/core/gesture.d.ts +12 -0
  60. package/dist/types/core/host-facts.d.ts +155 -0
  61. package/dist/types/core/index.d.ts +7 -3
  62. package/dist/types/core/owned-styles.d.ts +129 -2
  63. package/dist/types/core/params.d.ts +48 -0
  64. package/dist/types/core/parse.d.ts +28 -1
  65. package/dist/types/core/register-into.d.ts +29 -0
  66. package/dist/types/core/registry.d.ts +10 -3
  67. package/dist/types/core/repeat.d.ts +91 -0
  68. package/dist/types/core/sequence.d.ts +126 -0
  69. package/dist/types/core/stagger-config.d.ts +199 -0
  70. package/dist/types/core/stagger-keys.d.ts +63 -0
  71. package/dist/types/core/stagger.d.ts +187 -6
  72. package/dist/types/core/style-plan.d.ts +21 -16
  73. package/dist/types/core/target.d.ts +128 -0
  74. package/dist/types/core/threshold-reachability.d.ts +45 -0
  75. package/dist/types/core/time-scale.d.ts +9 -0
  76. package/dist/types/core/toggle-actions.d.ts +141 -0
  77. package/dist/types/core/travel.d.ts +59 -0
  78. package/dist/types/core/types.d.ts +705 -13
  79. package/dist/types/core/unquoted-selectors.d.ts +53 -0
  80. package/dist/types/effects/carousel/drag.d.ts +80 -0
  81. package/dist/types/effects/carousel/index.d.ts +149 -0
  82. package/dist/types/effects/catalog/background-media.d.ts +152 -0
  83. package/dist/types/effects/catalog/discrete.d.ts +13 -0
  84. package/dist/types/effects/catalog/interaction-proximity.d.ts +24 -0
  85. package/dist/types/effects/catalog/interaction-reveal.d.ts +248 -0
  86. package/dist/types/effects/catalog/interaction-shared.d.ts +42 -0
  87. package/dist/types/effects/catalog/interaction-states.d.ts +68 -0
  88. package/dist/types/effects/catalog/materials.d.ts +67 -0
  89. package/dist/types/effects/catalog/media-shared.d.ts +3 -2
  90. package/dist/types/effects/catalog/numbers-shared.d.ts +8 -5
  91. package/dist/types/effects/catalog/shared.d.ts +2 -2
  92. package/dist/types/effects/catalog/text-shared.d.ts +92 -10
  93. package/dist/types/effects/catalog/transforms.d.ts +13 -0
  94. package/dist/types/effects/catalog/view-transitions.d.ts +13 -0
  95. package/dist/types/effects/forms/primitives.d.ts +15 -4
  96. package/dist/types/effects/gestures/index.d.ts +53 -0
  97. package/dist/types/effects/index.d.ts +5 -0
  98. package/dist/types/effects/layout/presets.d.ts +35 -0
  99. package/dist/types/effects/motion-path/index.d.ts +35 -0
  100. package/dist/types/effects/scroll-mechanics/params.d.ts +23 -0
  101. package/dist/types/effects/scroll-mechanics/presets.d.ts +51 -0
  102. package/dist/types/effects/scroll-mechanics/section-index.d.ts +47 -0
  103. package/dist/types/effects/shared.d.ts +162 -2
  104. package/dist/types/effects/step-index.d.ts +121 -0
  105. package/dist/types/effects/step-marking.d.ts +41 -33
  106. package/dist/types/effects/svg/icon-parts.d.ts +23 -0
  107. package/dist/types/effects/three-d/flip-parts.d.ts +46 -0
  108. package/dist/types/effects/tween/index.d.ts +57 -0
  109. package/dist/types/effects/tween/properties.d.ts +107 -0
  110. package/dist/types/effects/tween/waypoints.d.ts +106 -0
  111. package/dist/types/showcase/compare.d.ts +4 -0
  112. package/dist/types/showcase/device-frame.d.ts +3 -0
  113. package/dist/types/showcase/hotspots.d.ts +3 -0
  114. package/dist/types/showcase/index.d.ts +30 -0
  115. package/dist/types/showcase/lightbox.d.ts +3 -0
  116. package/dist/types/showcase/media-source.d.ts +13 -0
  117. package/dist/types/showcase/modal-shell.d.ts +22 -0
  118. package/dist/types/showcase/scroll-story.d.ts +4 -0
  119. package/dist/types/showcase/shared.d.ts +47 -0
  120. package/dist/types/showcase/slideshow.d.ts +3 -0
  121. package/dist/types/showcase/slow-mo.d.ts +5 -0
  122. package/package.json +14 -7
  123. package/dist/esm/chunk-5DON3UFQ.mjs +0 -4380
  124. package/dist/esm/chunk-JT4PZL3A.mjs +0 -773
  125. package/dist/esm/chunk-QUVFODSQ.mjs +0 -1278
@@ -53,8 +53,35 @@ export declare const CHANNEL: {
53
53
  * the union documents the built-ins without closing the set.
54
54
  */
55
55
  export type Channel = (typeof CHANNEL)[keyof typeof CHANNEL] | (string & {});
56
- /** What starts the animation. Distinct from — and never a substitute for — a Timeline. */
57
- export type Activation = 'load' | 'enter' | 'hover' | 'focus' | 'click' | 'manual';
56
+ /**
57
+ * The activations the library gives a name of its own, because the DOM has no event for them or
58
+ * because the name bundles more than one event. See `core/activation.ts` for what each
59
+ * one binds.
60
+ */
61
+ export type NamedActivation = 'load' | 'enter' | 'leave' | 'hover' | 'unhover' | 'focus' | 'blur' | 'click' | 'manual';
62
+ /**
63
+ * What starts the animation. Distinct from — and never a substitute for — a Timeline.
64
+ *
65
+ * The open `string` arm is the whole point of `data-kui-on`: any DOM event type an author can pass
66
+ * to `addEventListener` — `input`, `submit`, `pointerleave`, `cart:updated` — starts an animation,
67
+ * and a `start/end` pair (`pointerenter/pointerleave`) plays it out again on the second one. The
68
+ * union documents the names the library adds on top of that without closing the set, the same
69
+ * shape and for the same reason as `Channel` above.
70
+ *
71
+ * A value may therefore be a `NamedActivation`, a raw event type, or a pair of either joined by
72
+ * `/`. Anything reading it for meaning must go through `resolveActivationSpec` rather than
73
+ * comparing strings — an equality test against `'load'` silently stops being true for
74
+ * `'load/pointerleave'`.
75
+ *
76
+ * Plain `string`, not `NamedActivation | (string & {})`. The latter spelling — which `Channel`
77
+ * above uses, and which preserves editor autocomplete for the documented names — is right where
78
+ * the set is genuinely a set that third parties extend. This is not that: an authored activation
79
+ * is an arbitrary event type by design, and pretending otherwise would put a hint in front of a
80
+ * value that has no closed vocabulary at all. The place a closed vocabulary *does* still apply is
81
+ * `Primitive.supportedActivations`, which is why that one is typed `NamedActivation[]` and keeps
82
+ * its autocomplete.
83
+ */
84
+ export type Activation = string;
58
85
  /**
59
86
  * What drives progress.
60
87
  *
@@ -82,21 +109,104 @@ export type PerfClass = 'compositor' | 'paint' | 'layout' | 'continuous' | 'dom-
82
109
  * reduce parallax, pinning, flashing or continuous ambient motion.
83
110
  */
84
111
  export type ReducedMotionPolicy = 'shorten' | 'crossfade' | 'disable';
85
- export type ParamType = 'length' | 'time' | 'number' | 'percentage' | 'angle' | 'color' | 'easing' | 'keyword'
112
+ /**
113
+ * Parameter types that name exactly one grammar.
114
+ *
115
+ * Kept separate from {@link UnionParamType} so the union members can be spelled as
116
+ * `'<a>|<b>'` and expanded back into this list by one table in `core/params.ts`, rather than
117
+ * every validation site learning which spellings are compound.
118
+ */
119
+ export type ScalarParamType = 'length' | 'time' | 'number' | 'percentage' | 'angle' | 'color' | 'easing' | 'keyword'
86
120
  /**
87
121
  * Free text — a CSS selector, a URL pattern. Consumed only by JS primitives and **never
88
122
  * written to a stylesheet**, which is what makes accepting arbitrary characters safe here.
89
123
  * `resolveParams` drops these on the CSS path; `readParams` keeps them for `prepare`.
90
124
  */
91
- | 'text';
92
- export interface ParamSpec {
93
- type: ParamType;
125
+ | 'text'
126
+ /**
127
+ * SVG path data, for `offset-path: path(...)`.
128
+ *
129
+ * Deliberately *not* `text`. `text` is the "arbitrary characters, never reaches CSS" escape
130
+ * hatch, and the whole point of a motion path is that the geometry reaches a stylesheet: the
131
+ * travel is a real CSS animation, not something JavaScript drives frame by frame. So this needs
132
+ * a type of its own, validated by an allowlist narrow enough to be safe in a declaration —
133
+ * command letters, digits, and separators, nothing else. See `validate` in `core/params.ts`,
134
+ * which also explains why an accepted value comes back wrapped in quotes.
135
+ */
136
+ | 'path';
137
+ /**
138
+ * Parameter types that accept two grammars from one parameter.
139
+ *
140
+ * CSS properties routinely take "a keyword or a value", or "a number or a percentage", and a
141
+ * parameter that models one has to accept both spellings without inventing a second parameter for
142
+ * the second spelling. `offset-rotate` is `[ auto | reverse ] || <angle>`; `opacity` is
143
+ * `<alpha-value>`, which is a number *or* a percentage and means the same thing either way.
144
+ *
145
+ * **This list is deliberately closed and deliberately short.** The alternative — a per-property
146
+ * table of CSS grammars, so any parameter can declare any combination — is a second CSS parser to
147
+ * own and keep correct, and every entry in it is a new way for an authored value to be accepted
148
+ * into a declaration nobody checked. Three unions cover the properties the catalog actually has.
149
+ * Adding a fourth should be a considered decision with a parameter that needs it, not a default.
150
+ *
151
+ * The member order is the order the grammars are tried, which matters only for
152
+ * {@link ScalarParamType} overlaps; today there are none.
153
+ */
154
+ export type UnionParamType =
155
+ /**
156
+ * `0.8` or `80%`, normalised to `0.8` before it reaches anything. Both spellings are legal CSS
157
+ * for an `<alpha-value>` and mean the same thing, so accepting only one of them is a papercut
158
+ * with no upside; normalising to the number means `minimum`/`maximum` still bound the parameter
159
+ * whichever way it was written, and a custom property holding it composes inside `calc()`.
160
+ */
161
+ 'number|percentage'
162
+ /**
163
+ * `24px` or `50%`. No normalisation is possible — a percentage resolves against a box this code
164
+ * has not measured and must not guess at — so both spellings reach the stylesheet as written.
165
+ *
166
+ * Note that `length`'s own grammar has accepted `%` since it was written, and narrowing it now
167
+ * would break authored pages, so this union is not currently wider than `length`. It exists
168
+ * because it *says so*: a parameter declared `'length|percentage'` is one where a percentage is
169
+ * a supported reading rather than an accident of the unit list, and a reader of the declaration
170
+ * can tell which without going to look at the regex.
171
+ */
172
+ | 'length|percentage'
173
+ /**
174
+ * `45deg` or one of a closed set of words — the `offset-rotate` shape. The angle half also takes
175
+ * the bare and `d`-suffixed spellings every `angle` parameter takes; the keyword half is
176
+ * {@link KeywordParamSpec.keywords}, which is closed exactly as it is for `'keyword'` itself.
177
+ */
178
+ | 'angle|keyword';
179
+ export type ParamType = ScalarParamType | UnionParamType;
180
+ /** Everything a parameter declares regardless of which grammar it accepts. */
181
+ interface ParamSpecBase {
94
182
  /** Used as the `var()` fallback in CSS. Never written to element.style — see design.md §7. */
95
183
  default: string;
96
- /** Custom property this parameter feeds, e.g. `--kui-reveal-distance`. */
184
+ /**
185
+ * Custom property this parameter feeds, e.g. `--kui-reveal-distance`.
186
+ *
187
+ * **This is not, on its own, an escape hatch.** It is easy to read the field as a promise that
188
+ * any parameter can be overridden from a page's own stylesheet by setting the custom property
189
+ * directly — that a closed `keywords` list is therefore only a convenience, with the raw
190
+ * property underneath it for anyone who needs a value the list does not have. That is true for
191
+ * exactly one of the three ways a parameter is consumed:
192
+ *
193
+ * - **Read by CSS.** A stylesheet somewhere does `var(--kui-reveal-distance, 24px)`. The
194
+ * cascade decides, so a page rule genuinely wins and the parameter is overridable. Most of
195
+ * the catalog is here.
196
+ * - **Read by JavaScript.** The property is written and then nothing reads it back: `prepare()`
197
+ * takes the *validated attribute string* through `readParams`, never `getComputedStyle`.
198
+ * `scramble-text`'s `charset` is the clearest case — `--kui-charset` is compiled and no
199
+ * `var()` anywhere consumes it, so setting it in a stylesheet changes nothing at all and says
200
+ * nothing about having changed nothing. There is **no** escape hatch for these, and widening
201
+ * one means widening its `keywords` list in this repository.
202
+ * - **`type: 'text'`.** `resolveParams` drops these before the stylesheet, so the property is
203
+ * never written in the first place and this field is inert.
204
+ *
205
+ * Which class a parameter is in is not derivable from the declaration; it depends on whether
206
+ * any shipped CSS reads the property. Do not tell an author to "just set the custom property"
207
+ * without checking that something does.
208
+ */
97
209
  cssProperty: string;
98
- /** For `keyword` params. */
99
- values?: readonly string[];
100
210
  /** Require a numeric parameter to convert to a finite JavaScript number. */
101
211
  finite?: boolean;
102
212
  /** Inclusive lower bound for numeric parameters. */
@@ -106,9 +216,52 @@ export interface ParamSpec {
106
216
  /** Require a numeric parameter to have no fractional part. */
107
217
  integer?: boolean;
108
218
  }
219
+ /**
220
+ * A parameter with a keyword half: either nothing but words (`'keyword'`), or words beside a
221
+ * value grammar (`'angle|keyword'`).
222
+ *
223
+ * `keywords` is **required**, not optional. A keyword parameter with no list accepts nothing at
224
+ * all and can only ever fall back to its default, which is a declaration bug that used to compile
225
+ * and then report `expected one of (none declared)` at runtime. Requiring the field moves that to
226
+ * the type checker.
227
+ */
228
+ export interface KeywordParamSpec extends ParamSpecBase {
229
+ type: 'keyword' | 'angle|keyword';
230
+ /**
231
+ * The complete set of words this parameter accepts. **Closed** — a value that is not in this
232
+ * list is rejected, and for `'angle|keyword'` is then tried against the angle grammar and
233
+ * nothing else.
234
+ *
235
+ * This field replaced `values`, which meant a closed set on `'keyword'` and *additive* extra
236
+ * literals on every other type. One key with two meanings validated nothing on the second
237
+ * reading while looking exactly like validation on the first: `{ type: 'number', values:
238
+ * ['80%'] }` accepted every number in existence and said nothing. The additive meaning has not
239
+ * been renamed, it has been removed — a parameter that wants "a value or a word" declares a
240
+ * union type, which is checkable, instead of smuggling the words past the type.
241
+ */
242
+ keywords: readonly string[];
243
+ }
244
+ /**
245
+ * A parameter whose grammar is entirely a value shape — a length, a time, a colour, a path.
246
+ *
247
+ * `keywords?: never` is the half of the split that does the work: it makes attaching a word list
248
+ * to a numeric parameter a compile error rather than a silent no-op.
249
+ */
250
+ export interface ValueParamSpec extends ParamSpecBase {
251
+ type: Exclude<ParamType, 'keyword' | 'angle|keyword'>;
252
+ keywords?: never;
253
+ }
254
+ /**
255
+ * One parameter's declaration.
256
+ *
257
+ * A discriminated union on `type` rather than one interface with optional fields, so that the
258
+ * relationship between the type and the word list is checked instead of documented.
259
+ */
260
+ export type ParamSpec = KeywordParamSpec | ValueParamSpec;
109
261
  export type ParameterSchema = Record<string, ParamSpec>;
262
+ import type { EffectGate } from './breakpoints.js';
110
263
  import type { PrepareContext } from './effect-context.js';
111
- import type { AttributeLedger, StyleLedger } from './owned-styles.js';
264
+ import type { AttributeLedger, LedgerSet, StyleLedger } from './owned-styles.js';
112
265
  export type { PrepareContext };
113
266
  export type Cleanup = () => void;
114
267
  /**
@@ -122,10 +275,43 @@ export type Cleanup = () => void;
122
275
  export interface EffectInstance {
123
276
  /** Start. Called by the animator once its gate opens, never by `prepare`. */
124
277
  activate(): void;
278
+ /**
279
+ * Runtime control over this instance's playhead, when the renderer has one to offer.
280
+ *
281
+ * **Optional on purpose, and its absence is the contract.** A `css-keyframes` instance is backed
282
+ * by real `Animation` objects with a `currentTime` and a `playbackRate`, so it can be paused,
283
+ * seeked and re-sped honestly. A JavaScript-rendered instance has no such object — `element
284
+ * .getAnimations()` returns `[]` for every one of them (see `play.ts`) — and it has no shared
285
+ * notion of progress either: a drag handler, a scroll spy and a count-up are all "JS effects"
286
+ * with nothing in common to seek.
287
+ *
288
+ * A shim that swallowed `pause()` and reported `progress: 0` would satisfy this interface and
289
+ * lie to every caller, which is worse than the gap it papers over. So the field is simply absent,
290
+ * and `control.ts` reports by name which effects on an element could not be reached.
291
+ */
292
+ readonly control?: InstanceControl;
125
293
  /** Stop where it is, leaving the element mid-effect. */
126
294
  cancel(): void;
127
295
  /** Jump to the end state immediately. */
128
296
  finish(): void;
297
+ /**
298
+ * Resume forward playback from wherever the effect currently sits.
299
+ *
300
+ * Optional, and deliberately so: only a renderer with a real playhead can honour it. A
301
+ * CSS-rendered effect has an `Animation` handle and simply sets `playbackRate` back to 1; a
302
+ * JS-rendered one has no playhead at all (`getAnimations()` returns `[]` for it — see
303
+ * `core/play.ts`), and there is no honest shim for "half-way through a `split-flap`, backwards".
304
+ * Leaving it undefined is how a primitive says so, and `animator.ts` warns by name rather than
305
+ * pretending — a knob that exists and does nothing is worse than a missing knob.
306
+ */
307
+ play?(): void;
308
+ /**
309
+ * Play backwards from wherever the effect currently sits, ending at the from-state.
310
+ *
311
+ * The exit half of a paired activation (`data-kui-on="pointerenter/pointerleave"`) is built on
312
+ * this. Optional for the same reason as `play` above.
313
+ */
314
+ reverse?(): void;
129
315
  /** Resolves when the effect completes. Resolves — never rejects — on cancel. */
130
316
  readonly finished: Promise<void>;
131
317
  /**
@@ -145,6 +331,38 @@ export interface EffectInstance {
145
331
  /** Release every listener, observer, subscription, and inserted node. */
146
332
  destroy(): void;
147
333
  }
334
+ /**
335
+ * Playback state of an element's effects.
336
+ *
337
+ * Deliberately *not* `AnimationPlayState`: the native union has a `pending` member describing an
338
+ * animation waiting on a style flush, which is an implementation detail of one renderer rather
339
+ * than something an author can act on, and it has no member for "nothing has started yet", which
340
+ * is the state every deferred effect sits in between install and activation.
341
+ */
342
+ export type PlaybackState = 'idle' | 'running' | 'paused' | 'finished';
343
+ /**
344
+ * One instance's playhead, as `control.ts` drives it.
345
+ *
346
+ * Progress is normalized 0..1 across the instance's whole span — from the instant the activation
347
+ * fired to the moment its last composed animation ends, authored delays included. Milliseconds
348
+ * would force the author to know each effect's duration to seek anywhere meaningful, and composed
349
+ * effects on one element rarely share one.
350
+ */
351
+ export interface InstanceControl {
352
+ /** Hold the playhead where it is. */
353
+ pause(): void;
354
+ /** Resume from wherever the playhead sits. */
355
+ resume(): void;
356
+ /** Flip direction and keep running. */
357
+ reverse(): void;
358
+ /** Move the playhead to `progress` (0..1), leaving the running/paused state alone. */
359
+ seek(progress: number): void;
360
+ /** Multiply playback speed. `1` is authored speed; a negative value runs backwards. */
361
+ rate(playbackRate: number): void;
362
+ /** Current position, 0..1. */
363
+ readonly progress: number;
364
+ readonly playState: PlaybackState;
365
+ }
148
366
  /**
149
367
  * An instance that does nothing, for effects with no work to do in the current environment.
150
368
  *
@@ -157,7 +375,7 @@ export declare function inertInstance(destroy?: Cleanup): EffectInstance;
157
375
  *
158
376
  * Separate from `EffectParams` because it is not a parameter: it is not declared in any
159
377
  * `ParameterSchema`, it means the same thing for every effect, and the CSS renderer already reads
160
- * it straight off the `EffectSpec` in `compile.pushTrack`. A JS-rendered primitive gets the same
378
+ * it straight off the `EffectSpec` in `declarations.ts`'s `pushTrack`. A JS-rendered primitive gets the same
161
379
  * three values here rather than having to declare look-alike parameters of its own.
162
380
  *
163
381
  * Every field is optional and `undefined` means *the author named none* — a primitive must be able
@@ -197,6 +415,63 @@ export interface EffectParams {
197
415
  */
198
416
  readonly timing: EffectTiming;
199
417
  }
418
+ /**
419
+ * A primitive's refinement of itself for one authored spec.
420
+ *
421
+ * Almost every primitive in the catalog is fully described before it ever meets an attribute:
422
+ * `fade-up 200ms` and `fade-up 900ms` claim the same channels and compile the same keyframe. The
423
+ * generic tween (`effects/tween`) is the exception this exists for — `tween x:100` writes
424
+ * `translate` and `tween opacity:0` writes `opacity`, so *which* channels it owns, and therefore
425
+ * whether it may compose with a neighbour at all, is a fact about the attribute rather than about
426
+ * the primitive.
427
+ *
428
+ * Neither static answer is honest. Declaring every channel a tween *could* write makes it collide
429
+ * with everything and compose with nothing; declaring none makes the conflict detector wave
430
+ * through two effects that both animate `opacity`, which is the one failure `core/channels.ts`
431
+ * exists to prevent. So the primitive is asked, once per spec, instead.
432
+ *
433
+ * Every field is optional, and an absent field means "use the primitive's own declaration". A
434
+ * primitive with no `variantFor` at all — which is all but two of them — is unaffected.
435
+ */
436
+ export interface EffectVariant {
437
+ /**
438
+ * Channels this spec writes, unioned with the primitive's own declared list.
439
+ *
440
+ * Unioned rather than replacing, so a variant can only ever *widen* what a primitive admits to
441
+ * touching. A refinement that could quietly drop a declared channel would be a way to opt out of
442
+ * conflict detection, which is exactly backwards.
443
+ */
444
+ channels?: Channel[];
445
+ /**
446
+ * Keyframe blocks to compile, one animation track each, replacing the preset's single
447
+ * `keyframes` name. Empty means this spec animates nothing and emits no `animation` declaration.
448
+ */
449
+ keyframes?: string[];
450
+ /**
451
+ * Authored parameter values to resolve in place of `spec.params`.
452
+ *
453
+ * For normalisation the schema cannot express — the tween reads `x:100` as `100px`, because a
454
+ * bare number is the spelling the syntax was designed around and `params.ts` is deliberately
455
+ * strict about units for everyone else. Still untrusted author input: it goes through
456
+ * `resolveParams` exactly as `spec.params` would have.
457
+ */
458
+ params?: Record<string, string>;
459
+ /**
460
+ * Extra parameter specs for keys this variant synthesised, merged *over* the primitive's own.
461
+ *
462
+ * One attribute can legitimately produce parameters the primitive could not have declared
463
+ * statically. `tween x:'0,100,40'` is three values for one key, and each of them needs its own
464
+ * custom property (`--kui-tween-x-1`, `-2`, `-3`) because a keyframe step can only read one — so
465
+ * `variantFor` expands the list into `params` and declares the specs for the expansion here.
466
+ *
467
+ * **This is not a way around validation, and must never become one.** The merged schema is what
468
+ * `resolveParams` validates `params` against, so every synthesised value is type-checked and
469
+ * escape-screened exactly as an authored one is; what this field changes is only *which* specs
470
+ * exist, never whether they are enforced. Merged over rather than replacing, so a variant cannot
471
+ * quietly drop the primitive's own declarations and admit a value the schema forbids.
472
+ */
473
+ schema?: ParameterSchema;
474
+ }
200
475
  /**
201
476
  * A primitive is an implementation. Presets are names that point at a primitive with different
202
477
  * default parameters — 48 of the entrance/exit names come from one primitive.
@@ -207,7 +482,16 @@ export interface Primitive {
207
482
  channels: Channel[];
208
483
  parameters: ParameterSchema;
209
484
  supportedTimelines: Timeline[];
210
- supportedActivations: Activation[];
485
+ /**
486
+ * Which activations this primitive is built to handle.
487
+ *
488
+ * `NamedActivation[]`, not `Activation[]`: the authored value is open (any DOM event starts an
489
+ * animation), but a primitive declaring what it supports is choosing from a closed vocabulary —
490
+ * it is saying "I work when observed" or "I work on a listener", not enumerating event types it
491
+ * has never heard of. `animator.ts` maps an authored half onto this vocabulary before checking
492
+ * it; see `SUPPORT_PROXIES` in `activation.ts`.
493
+ */
494
+ supportedActivations: NamedActivation[];
211
495
  /**
212
496
  * Activation used when the author specifies none.
213
497
  *
@@ -267,7 +551,117 @@ export interface Primitive {
267
551
  * `params` are validated and defaulted — never raw author input.
268
552
  */
269
553
  prepare?(el: Element, params: EffectParams, ctx: PrepareContext): EffectInstance;
554
+ /**
555
+ * Refine this primitive for one authored spec — see {@link EffectVariant}.
556
+ *
557
+ * Called once per spec by `compile`, *before* composition analysis, because the answer decides
558
+ * whether the spec's neighbours may compose with it at all. It is handed the raw `EffectSpec`
559
+ * (author input, unvalidated — `resolveParams` still runs afterwards on whatever it returns) and
560
+ * a warning sink, so a spec the primitive cannot make sense of can say so by name rather than
561
+ * compiling to silence.
562
+ *
563
+ * Must be pure and must not mutate `spec`: `compile` is a pure function and the same parsed
564
+ * value is compiled again on every rescan of the element.
565
+ */
566
+ variantFor?(spec: EffectSpec, warn: (message: string) => void): EffectVariant;
567
+ }
568
+ /**
569
+ * One property a preset transitions on its own host box — the `data-kui-fx` element itself, never
570
+ * a pseudo-element, sibling, or descendant. `declarations.ts`'s `pushTransitions` reads these to build
571
+ * the merged `--kui-transition` custom property `base.css`'s one `:where([data-kui-fx])` rule
572
+ * consumes; see that rule's comment for why the merge has to happen here rather than in CSS.
573
+ */
574
+ export interface TransitionSegment {
575
+ /**
576
+ * CSS property or registered custom property (e.g. `--kui-border-pct`) this segment eases.
577
+ * `registerPreset` rejects `all` and `none` here: either would swallow every other segment in
578
+ * the merged list, the same failure mode a shared `transition: all` shorthand has today.
579
+ */
580
+ property: string;
581
+ /** Literal, when the preset pins its own timing regardless of what the author writes. Omitted
582
+ * means "the primitive's namespaced `--kui-<id>-duration`, or the author's positional value" —
583
+ * the same precedence `pushTrack` already gives a compiled `animation-duration`. */
584
+ duration?: string;
585
+ /** Same precedence as `duration`, for the easing curve. */
586
+ easing?: string;
270
587
  }
588
+ /**
589
+ * *When* an effect owns the channels it claims — the axis `core/channels.ts` needs to tell a real
590
+ * collision apart from two effects that merely name the same CSS property.
591
+ *
592
+ * Channels alone answer "do these two write `translate`?" and the answer for `fade-up, lift` is
593
+ * yes, which is why the compiler used to drop `lift`. It is the wrong question. `fade-up` is a
594
+ * from-only `@keyframes` — `kui-in-up` declares a `from` block and no `to`, so its endpoint is the
595
+ * *underlying* value the cascade supplies, re-evaluated every frame — and `lift` is a
596
+ * `:hover`-scoped normal declaration with a transition. The entrance owns `translate` while it
597
+ * plays and hands it straight back afterwards, which is precisely the layering an author asking for
598
+ * "fade it in, then let it lift on hover" wants.
599
+ *
600
+ * The four values are the four ways an effect can hold a channel over time:
601
+ *
602
+ * - `entrance` — plays once on arrival from a hidden/displaced from-state, then yields the channel
603
+ * to whatever the cascade says. Every `cloak: true` preset in the catalog.
604
+ * - `exit` — the mirror: starts at the rest state and plays once to a departed one.
605
+ * - `idle` — runs unbounded, so it never yields the channel at all. Ambient loops, and anything an
606
+ * author promoted with `repeat:infinite`.
607
+ * - `state` — held by a CSS state on the element (`:hover`, `[aria-expanded]`, a
608
+ * `data-kui-step-state`), so it changes only when the visitor does something.
609
+ *
610
+ * **This has to be declared, not derived** — the same conclusion `cloak` above reached, for the
611
+ * same reason and after the same measurement. Phase cannot come from the `Primitive`: a primitive
612
+ * carries no iteration count (that is a per-*preset* custom property, see
613
+ * `declarations.ts`'s `iterationCountProperty`), and entrances and exits share primitives outright
614
+ * — `fade-in` and `fade-out` are both `reveal`. A census of the 37 primitives backing two or more
615
+ * presets found exactly two whose presets would disagree about phase (`path-draw`, six finite draws
616
+ * beside an infinite `gradient-stroke`; `text-marquee`, an infinite `marquee` beside a
617
+ * scroll-linked one), so preset granularity is where the fact actually lives.
618
+ *
619
+ * `entrance` is specifically "plays once and *releases* the channel". That is what makes the pairing
620
+ * safe, and it is narrower than "is an entrance": fifteen of the catalog's fifty-four entrances
621
+ * close their keyframe block with an explicit `to`, so their `animation-fill-mode: both` pins the
622
+ * property for good and a composed hover would silently do nothing. `test/composition-phase.test.ts`
623
+ * asserts the released half against the shipped stylesheets, the way `css-invariants.test.ts`
624
+ * already does for `requiresOwnSubtree`.
625
+ *
626
+ * Undeclared is a real fifth state and not a synonym for any value: `compile.ts`'s `phaseOf` derives
627
+ * `state` from `transitions` — sound by construction, a transition has no clock and emits no
628
+ * animation track — and otherwise leaves the phase unknown, which conflicts with everything exactly
629
+ * as the library behaved before this field existed. Nothing is guessed from `cloak`; see `phaseOf`
630
+ * for the measurement that ruled it out.
631
+ */
632
+ export type EffectPhase = 'entrance' | 'exit' | 'idle' | 'state';
633
+ /**
634
+ * *How* a preset's motion reaches the element — the axis {@link EffectPhase} was mistaken for.
635
+ *
636
+ * `phase` answers **when** an effect holds a channel, and the `entrance|state` composition
637
+ * exemption was built on it. Three of four auditors in the 2026-09-08 catalog review arrived
638
+ * independently at the same correction: when is not the question that decides whether two effects
639
+ * can coexist. *How each one's motion is delivered* is, because delivery decides which of them the
640
+ * cascade lets win. Five mechanisms exist in this catalog:
641
+ *
642
+ * - **host transition / normal declaration** — sits in the cascade beneath a composed entrance, so
643
+ * a from-only keyframe resolves its missing endpoint against it. This is the one the exemption
644
+ * was actually reasoning about, and it is safe.
645
+ * - **compiler-owned inline animation** — `compile.ts` writes `animation-name` and its longhands to
646
+ * `element.style`. Two of these on one channel replace each other by list order, which is what
647
+ * `channels.ts` already models.
648
+ * - **stylesheet-owned host animation** — the motion is `[data-kui-fx~='name']:hover { animation: … }`
649
+ * in `src/css/*.css`, on the host box. **This is the one that needs declaring**, because an inline
650
+ * `animation-name` written by *any* composed neighbour outranks that author-rule shorthand
651
+ * outright, whatever channels either side claims. `fade-up, icon-spin` compiled with zero
652
+ * warnings and `icon-spin` could never run.
653
+ * - **pseudo-element** — `::before`/`::after`. Inline style on the host cannot reach it, which is
654
+ * why `shine-sweep` and `beam-border` are unaffected and why `pseudo-before`/`sweep` model that
655
+ * box as a channel instead.
656
+ * - **JS-driven** — the primitive writes inline style itself at runtime.
657
+ *
658
+ * Only the third has a name here, deliberately, and it is the same argument
659
+ * `channels.ts`'s `CHANNEL_COMPOSITION` makes for being an allowlist with one member: a value the
660
+ * compiler does not act on is documentation wearing a type's clothes. The other four are either
661
+ * already modelled (inline animation is `renderer: 'css-keyframes'`; a pseudo-element box is a
662
+ * channel) or need no rule at all, so naming them here would invite a declaration nothing reads.
663
+ */
664
+ export type DeliveryMechanism = 'stylesheet-animation';
271
665
  export interface Preset {
272
666
  name: string;
273
667
  primitive: string;
@@ -275,6 +669,19 @@ export interface Preset {
275
669
  params?: Record<string, string>;
276
670
  /** CSS `@keyframes` name this preset animates, when renderer is `css-keyframes`. */
277
671
  keyframes?: string;
672
+ /**
673
+ * Properties this preset transitions on its own host box, merged by `compile.ts` into one
674
+ * `--kui-transition` custom property so composed presets never fight over a bare `transition:`
675
+ * shorthand the way two rules once did (`data-kui="lift, border-glow"` used to compile
676
+ * `transition-property: box-shadow` only — `border-glow`'s rule replaced `lift`'s by source
677
+ * order, and `lift` snapped instead of easing).
678
+ *
679
+ * Per-*preset*, not per-primitive, for the same reason `keyframes`/`cloak` are: `plus-to-minus`
680
+ * and `hamburger-to-x` share the `icon-toggle` primitive, but only `plus-to-minus`'s control
681
+ * itself rotates — the other two only move their `.kui-bar` children, a different box this field
682
+ * deliberately does not describe (see `base.css`'s consuming rule for the host-box boundary).
683
+ */
684
+ transitions?: TransitionSegment[];
278
685
  /**
279
686
  * This effect begins from a state the visitor must not see — invisible, displaced, unsplit, or
280
687
  * un-assembled — so painting the element at its rest state before the runtime installs that
@@ -294,6 +701,67 @@ export interface Preset {
294
701
  * hide and cloaking it would blank an element that should be visible until it leaves.
295
702
  */
296
703
  cloak?: boolean;
704
+ /**
705
+ * When this name holds the channels its primitive claims — see {@link EffectPhase} for what the
706
+ * four values mean and why the fact is per-preset rather than per-primitive.
707
+ *
708
+ * Optional, and omitting it is not a shortcut for "entrance". `compile.ts`'s `phaseOf` derives
709
+ * `state` from `transitions` and otherwise treats the preset as unphased, which composes exactly
710
+ * as the library did before this field existed. Declaring it is how a name opts *into* composing
711
+ * with a neighbour on a shared channel; it is never how one opts out.
712
+ *
713
+ * So everything except the seventeen presets carrying `transitions` declares its own phase or
714
+ * composes as it always did. None of the three remaining values has a signal the compiler can
715
+ * read: an `idle` loop keeps its `infinite` iteration count in CSS as
716
+ * `--kui-fx-<name>-iterations`, an `exit` is structurally identical to an entrance at this layer
717
+ * (same primitive, no cloak), and an `entrance` only qualifies if its keyframes leave the channel
718
+ * open at the end, which is a fact about the stylesheet rather than about this record.
719
+ */
720
+ phase?: EffectPhase;
721
+ /**
722
+ * How this name's motion reaches the element, when that is something the compiler has to act on —
723
+ * see {@link DeliveryMechanism} for the full five-mechanism picture and why only one of them is
724
+ * spelled here.
725
+ *
726
+ * Declared per *preset*, the same granularity as {@link cloak} and {@link phase}, because the
727
+ * stylesheet rule it describes is keyed on the preset name (`[data-kui-fx~='icon-spin']:hover`)
728
+ * and one primitive backs names on both sides of it — `HOVER_PRIMITIVES` holds `icon-spin`
729
+ * beside `lift`, whose motion is a transition.
730
+ *
731
+ * Absent means "nothing the composition resolver needs to know", which is every preset but four.
732
+ * That is a hand-written set of names and it is not allowed to stay one on trust:
733
+ * `test/css-composition-invariants.test.ts` derives the true set from `src/css/*.css` with
734
+ * `extractHostAnimationBindings` and asserts it *both* ways, so a fifth hover keyframe added to a
735
+ * stylesheet without this declaration fails a test instead of shipping a silently dead effect —
736
+ * the same shape `css-invariants.test.ts` already uses to police {@link requiresOwnSubtree}.
737
+ */
738
+ delivery?: DeliveryMechanism;
739
+ /**
740
+ * This preset's CSS reaches past the `data-kui-fx`-stamped element — to a child, a sibling, or a
741
+ * descendant it assumes exists — rather than animating the fx element itself. `target:` may not
742
+ * relocate it: a relocated `data-kui-fx` would sit on the *matched* descendant instead of the
743
+ * host, and the reaching selector (`[data-kui-fx~='card-flip-x'] > .kui-card-face`, say) would
744
+ * then look for a grandchild the retargeted element was never authored with, compiling to
745
+ * silence rather than a visible effect.
746
+ *
747
+ * Declared per *preset*, the same granularity as {@link cloak}: whether a name's CSS reaches past
748
+ * itself is a fact only its author knows, and a primitive can back names on both sides of this —
749
+ * `step-progress` and `media-scrub` already resolve their own `target:` internally and never
750
+ * reach this flag at all, because `compile.ts` only consults it for a preset whose primitive does
751
+ * *not* declare a `target` parameter of its own.
752
+ *
753
+ * `test/css-invariants.test.ts` derives the true set from `src/css/*.css` and asserts every name
754
+ * on it declares this, so a future selector added without the flag fails a test instead of
755
+ * silently compiling to an empty subtree in production.
756
+ */
757
+ requiresOwnSubtree?: boolean;
758
+ /**
759
+ * This preset's CSS establishes a 3D rendering context (`preserve-3d`/`perspective`) on the
760
+ * `data-kui-fx` element — nesting another such effect inside it breaks (memory:
761
+ * no-nested-3d). Derived from `three-d.css` by `test/target-establishes3d.test.ts` (5a) so it
762
+ * cannot drift.
763
+ */
764
+ establishes3d?: boolean;
297
765
  }
298
766
  export type ResolvedParams = Record<string, string>;
299
767
  /** One effect segment parsed out of a `data-kui` value. */
@@ -302,15 +770,159 @@ export interface EffectSpec {
302
770
  duration?: string;
303
771
  delay?: string;
304
772
  easing?: string;
773
+ /**
774
+ * Authored `at:` position — where this segment starts relative to the one before it in the comma
775
+ * list. Untrusted and unparsed; `core/sequence.ts` owns the grammar and every diagnostic.
776
+ *
777
+ * Beside `delay` rather than inside `params` for the same reason `delay` itself is: it is not a
778
+ * parameter. No `ParameterSchema` declares it, it means the same thing for every effect in the
779
+ * catalog, and `resolveParams` would reject it as unknown on all 255 of them. It is also not
780
+ * hoisted to `ParsedValue` the way `on:`/`timeline:` are, because a position is per-segment by
781
+ * definition — the whole point is that each segment can sit somewhere different.
782
+ */
783
+ at?: string;
784
+ /**
785
+ * Authored condition, on either or both of two independent axes: viewport (`above:md`,
786
+ * `below:lg`, or both for a band) and container (`wide:md`, `narrow:lg`, or both for a band,
787
+ * measured against the nearest `data-kui-container` ancestor). Absent when the segment is
788
+ * unconditional, which is every segment written before the viewport half of this existed.
789
+ *
790
+ * Beside `at` rather than inside `params` for the same reason `at` is: it is not a parameter. No
791
+ * `ParameterSchema` declares it, it means the same thing for every effect in the catalog, and
792
+ * `resolveParams` would reject it as unknown on all of them. And not hoisted to `ParsedValue` the
793
+ * way `on:`/`timeline:` are, because the point of a gate is that each segment can carry a
794
+ * different one — `fade-up below:md, parallax-y above:md` is the case this feature exists for, and
795
+ * an element-scoped gate could not express it at all.
796
+ *
797
+ * Already validated by the time it lands here: `parse.ts` refuses a name that is not on the
798
+ * scale, and refuses a band that can never match, on either axis. `core/breakpoints.ts` owns all
799
+ * of it. The container half is further narrowed by `compile.ts`'s `refuseContainerGate`: a
800
+ * `wide`/`narrow` on a JavaScript-rendered primitive is stripped (with a warning) before the plan
801
+ * is built, because there is no JS mirror for it the way `above`/`below` have one.
802
+ */
803
+ gate?: EffectGate;
804
+ /**
805
+ * Authored `repeat:` — how many times this segment plays, as the CSS
806
+ * `animation-iteration-count` value it compiles to: a non-negative number, or `infinite`.
807
+ *
808
+ * Beside `at` and `gate` rather than inside `params` for the reason both of those are: it is not
809
+ * a parameter. No `ParameterSchema` declares it, it means the same thing for every effect in the
810
+ * catalog, and `resolveParams` would reject it as unknown on all of them. And not hoisted to
811
+ * `ParsedValue` the way `on:`/`timeline:` are, because `declarations.ts` writes
812
+ * `animation-iteration-count` as a per-*track* list precisely so a composed one-shot effect does
813
+ * not inherit its neighbour's loop — an element-scoped repeat would undo that in the grammar.
814
+ *
815
+ * Already validated by the time it lands here (`core/repeat.ts` owns the value grammar), but not
816
+ * yet reconciled with the element's timeline or renderer: `compile.ts`'s `refusePlayback` strips
817
+ * a repeat the compiled output cannot honour, with a warning, before the plan is built.
818
+ */
819
+ repeat?: string;
820
+ /**
821
+ * Authored `yoyo:` — whether this segment alternates direction between iterations, compiling to
822
+ * `animation-direction: alternate`. Spelled `yoyo` and not `direction` because `direction` is
823
+ * already a parameter on the split-text primitive; see `core/repeat.ts`'s module comment.
824
+ */
825
+ yoyo?: boolean;
305
826
  params: Record<string, string>;
827
+ /**
828
+ * Segment-scoped hoists — see {@link SegmentHoists}. Present only when this segment carries a
829
+ * `target:` token and wrote at least one of these keys; `compile.ts`'s `scopeHoists` consumes
830
+ * and clears it (for a declaring/unknown name) before anything else reads `EffectSpec`.
831
+ */
832
+ hoists?: SegmentHoists;
306
833
  }
834
+ /**
835
+ * Element-scoped hoists recorded on a *targeted* segment rather than folded element-wide — D-B in
836
+ * the plan. `parseSegment` cannot yet know whether the segment's primitive declares its own
837
+ * `target` parameter (resolved later, in `compile.ts`), so it provisionally routes these here for
838
+ * any segment carrying a `target:` token; `compile.ts`'s `scopeHoists` folds them back onto the
839
+ * element-wide `ParsedValue` for a declaring primitive or an unknown name, where there is no group
840
+ * to scope them to.
841
+ *
842
+ * `rm:`/`func:` excluded on purpose — they stay element-wide always (D-B.3): one element has one
843
+ * reduced-motion policy and one completion callback, whatever it targets.
844
+ */
845
+ export type SegmentHoists = Pick<ParsedValue, 'activation' | 'actions' | 'timeline' | 'threshold' | 'cascade' | 'spread' | 'order' | 'cols' | 'along'>;
846
+ /**
847
+ * The keys {@link SegmentHoists} carries, as a runtime-iterable tuple — the one place enumerating
848
+ * them, so `compile.ts` and `bundles.ts` cannot drift into two different lists.
849
+ *
850
+ * NOTE: these are the `ParsedValue` *field* names, not the surface grammar words — `on:` writes
851
+ * the field `activation`. `parse.ts` needs the surface-word spelling instead and keeps its own
852
+ * local set for that (see its own comment).
853
+ */
854
+ export declare const SEGMENT_HOIST_KEYS: readonly ["activation", "actions", "timeline", "threshold", "cascade", "spread", "order", "cols", "along"];
855
+ export type SegmentHoistKey = (typeof SEGMENT_HOIST_KEYS)[number];
307
856
  /** The full parse of one element's `data-kui` attribute. */
308
857
  export interface ParsedValue {
309
858
  specs: EffectSpec[];
310
- /** Hoisted from reserved `on:` / `timeline:` / `threshold:` keys. Element-scoped. */
859
+ /**
860
+ * Hoisted from the reserved `on:` / `actions:` / `timeline:` / `threshold:` / `cascade:` /
861
+ * `spread:` / `order:` / `cols:` / `along:` / `rm:` / `func:` keys. Element-scoped: one element
862
+ * has one activation, one timeline, one stagger group, one reduced-motion policy and one
863
+ * completion — so none of these can sensibly differ between the comma-separated segments of a
864
+ * single attribute, and all of them are lifted out of the per-effect `params`.
865
+ */
311
866
  activation?: Activation;
867
+ /**
868
+ * What to do at each of the scroll trigger's four crossings, verbatim —
869
+ * `play/pause/resume/reset`, in the order enter, leave, enter-back, leave-back.
870
+ *
871
+ * A refinement of `activation` rather than a peer of it: without one of the observed activations
872
+ * there are no crossings to act on, and `toggle-actions.ts` says so rather than binding verbs
873
+ * nothing will ever reach.
874
+ */
875
+ actions?: string;
312
876
  timeline?: string;
313
877
  threshold?: string;
878
+ /**
879
+ * Stagger step for this element's animated children — the `data-kui` spelling of
880
+ * `data-kui-stagger`'s positional step. Raw text, deliberately unvalidated here: `stagger.ts`
881
+ * owns the grammar for both spellings and has always passed the step through verbatim so
882
+ * `var(--speed)` and `calc(90ms * 2)` keep working.
883
+ */
884
+ cascade?: string;
885
+ /**
886
+ * Total stagger budget for this element's animated children — GSAP's `stagger.amount` to
887
+ * `cascade`'s `stagger.each`. The whole group finishes within this time however many children it
888
+ * has, so `stagger.ts` divides it by the group's largest rank to get the per-item step.
889
+ *
890
+ * Raw text and unvalidated for the same reason `cascade` is: it ends up inside a `calc()` written
891
+ * to `--kui-stagger`, and `var(--speed)` divides exactly as well as `600ms` does.
892
+ */
893
+ spread?: string;
894
+ /** Where the stagger wave starts — `data-kui-stagger`'s `from:`, under a name `data-kui` can hold. */
895
+ order?: string;
896
+ /**
897
+ * The stagger group's column count, or `auto` to measure it — what turns `order:` from a rank
898
+ * over DOM index into a rank over distance through a real 2D layout.
899
+ *
900
+ * Raw text, validated in `stagger.ts` beside the `order:` it modifies: the two are one
901
+ * diagnostic, and splitting them across two modules would mean an author reading half a message.
902
+ */
903
+ cols?: string;
904
+ /**
905
+ * Restrict a grid stagger to one axis — `x` or `y`. Spelled `along:` rather than GSAP's `axis:`
906
+ * because `axis` is already a parameter on four primitives and a hoisted key never reaches
907
+ * `spec.params`; `data-kui-stagger` accepts both words.
908
+ */
909
+ along?: string;
910
+ /**
911
+ * Author-chosen reduced-motion policy, folded into the primitives' own by `compile.ts`.
912
+ *
913
+ * Validated at parse time (unlike `cascade`/`order`) because it is a closed three-value set with
914
+ * no expression forms, so there is nothing a later stage could know that `parse.ts` does not.
915
+ */
916
+ rm?: ReducedMotionPolicy;
917
+ /**
918
+ * Name of a global function to call when the element's effects finish — the no-build spelling of
919
+ * `addEventListener('kui:finish', fn)`, and registered as exactly that listener.
920
+ *
921
+ * Raw text, deliberately unvalidated at parse time: whether a name resolves depends on script
922
+ * order at runtime, which the parser cannot see. `core/callback.ts` owns the lookup and carries
923
+ * the note on why this key must not be built from untrusted input.
924
+ */
925
+ func?: string;
314
926
  warnings: string[];
315
927
  }
316
928
  /** Runtime truth. Attributes are for CSS and debugging; they make a poor state machine. */
@@ -320,12 +932,92 @@ export interface InstanceState {
320
932
  specs: EffectSpec[];
321
933
  activation: Activation;
322
934
  timeline: Timeline;
935
+ /**
936
+ * Normalized effect names — the same list stamped into `data-kui-fx`.
937
+ *
938
+ * Retained rather than re-read off the attribute because both readers need it after teardown has
939
+ * already restored the attribute: a `kui:cancel` event fires *after* `release()` has unwound the
940
+ * ledgers (so listeners see the author's own markup, not the library's), and by then the
941
+ * attribute is gone.
942
+ */
943
+ fxNames: string[];
944
+ /**
945
+ * Names of the composed effects rendered in JavaScript.
946
+ *
947
+ * Kept so `control.ts` can say *which* effects a control call could not reach instead of the
948
+ * useless "some effect on this element". The names, not the instances: an instance whose
949
+ * `prepare` threw is never constructed, and an author who wrote the name still deserves to be
950
+ * told the name they wrote.
951
+ */
952
+ jsEffectNames: string[];
953
+ /**
954
+ * Whether progress here is driven by scroll position rather than a clock — `timeline: pin`'s
955
+ * paused-plus-negative-delay scrub, or a native `view()`/`scroll()` timeline.
956
+ *
957
+ * `control.ts` refuses to act on these. Writing `animation-play-state: running` onto a scrubbed
958
+ * animation hands it back to the document timeline and it plays forward in wall-clock time on
959
+ * top of the seek (see `style-plan.ts`'s `Gate`), and seeking one is pointless anyway — the
960
+ * scroll scheduler rewrites `--kui-progress` on the very next frame and overwrites the seek.
961
+ */
962
+ progressDriven: boolean;
323
963
  /** One handle per renderer in play; the animator gates them uniformly. */
324
964
  instances: EffectInstance[];
325
965
  /** Inline properties this element's effects wrote, and what they replaced. */
326
966
  ledger: StyleLedger;
327
967
  attributes: AttributeLedger;
968
+ /**
969
+ * Every element these effects wrote to, host included, and the ledgers that unwind them.
970
+ *
971
+ * `ledger`/`attributes` above are this set's entries *for the host*, kept as named fields because
972
+ * that is what every reader in `animator.ts` asks for and the host is the only element a
973
+ * lifecycle write ever lands on: `data-kui-state` is stamped on the authored element and nowhere
974
+ * else. The set exists for the writes that are not lifecycle — inline properties, `data-kui-fx`,
975
+ * `--kui-i` — which `target:` relocates onto elements the host merely names.
976
+ *
977
+ * One element in the set is exactly the old singular behaviour, which is why this can be true
978
+ * before anything is retargeted.
979
+ */
980
+ ledgers: LedgerSet;
328
981
  /** Aborted on release, detaching bindings and primitive listeners. */
329
982
  controller: AbortController;
983
+ /**
984
+ * Releases a one-shot `enter` binding, set only for that activation and cleared once spent.
985
+ * Present so `Animator.activate` can disarm an observer a programmatic activation just made
986
+ * redundant; a toggle activation (`hover`/`focus`/`click`) deliberately leaves this undefined.
987
+ */
988
+ releaseActivation?: Cleanup;
989
+ /**
990
+ * Which way the effects are currently playing. Only meaningful while `status === 'running'`.
991
+ *
992
+ * Runtime truth, not an attribute, for the reason at the top of this interface: a reversing
993
+ * element is still `running`, and `data-kui-state` has no vocabulary for the difference. It is
994
+ * here because two decisions need it and neither can be re-derived from the DOM — `activate`
995
+ * must turn a reversing element back around instead of being swallowed by its own re-entrancy
996
+ * guard, and the `finished` handler that writes the final state must not let a stale promise
997
+ * from the run it superseded write over the run in flight.
998
+ */
999
+ direction?: 'forward' | 'reverse';
1000
+ /**
1001
+ * Whether this element's effects were cancelled rather than allowed to complete.
1002
+ *
1003
+ * Cancelling an instance resolves its `finished` promise (the "resolves, never rejects" contract
1004
+ * above), so the animator's completion handler still runs and still writes
1005
+ * `data-kui-state="finished"` — behaviour that predates lifecycle events and is left alone. What
1006
+ * it must *not* do is also dispatch `kui:finish`, which would tell an author chaining work that
1007
+ * an animation they explicitly cancelled had run to its end.
1008
+ */
1009
+ cancelled?: boolean;
330
1010
  status: 'pending' | 'ready' | 'running' | 'finished' | 'failed';
1011
+ /**
1012
+ * Derived state only: the authored element whose `target:` group installed this one. Lifecycle
1013
+ * events carry it as `detail.host`.
1014
+ */
1015
+ host?: Element;
1016
+ /**
1017
+ * Host only: it has no own group; its status mirrors its derived matches' (`syncAggregate`),
1018
+ * and `activateOne` never starts it.
1019
+ */
1020
+ aggregate?: boolean;
1021
+ /** Derived state of a grouped target: the element that owns its activation binding. */
1022
+ gateOwner?: Element;
331
1023
  }