kuinetic 0.1.4 → 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 (124) 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 +15147 -6039
  13. package/dist/kuinetic.all.min.js +2 -0
  14. package/dist/kuinetic.css +2801 -318
  15. package/dist/kuinetic.js +15147 -6038
  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 +699 -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/discrete.d.ts +13 -0
  83. package/dist/types/effects/catalog/interaction-proximity.d.ts +24 -0
  84. package/dist/types/effects/catalog/interaction-reveal.d.ts +248 -0
  85. package/dist/types/effects/catalog/interaction-shared.d.ts +42 -0
  86. package/dist/types/effects/catalog/interaction-states.d.ts +68 -0
  87. package/dist/types/effects/catalog/materials.d.ts +67 -0
  88. package/dist/types/effects/catalog/media-shared.d.ts +3 -2
  89. package/dist/types/effects/catalog/numbers-shared.d.ts +8 -5
  90. package/dist/types/effects/catalog/shared.d.ts +1 -1
  91. package/dist/types/effects/catalog/text-shared.d.ts +86 -10
  92. package/dist/types/effects/catalog/transforms.d.ts +13 -0
  93. package/dist/types/effects/catalog/view-transitions.d.ts +13 -0
  94. package/dist/types/effects/forms/primitives.d.ts +15 -4
  95. package/dist/types/effects/gestures/index.d.ts +53 -0
  96. package/dist/types/effects/index.d.ts +5 -0
  97. package/dist/types/effects/layout/presets.d.ts +35 -0
  98. package/dist/types/effects/motion-path/index.d.ts +35 -0
  99. package/dist/types/effects/scroll-mechanics/params.d.ts +23 -0
  100. package/dist/types/effects/scroll-mechanics/presets.d.ts +51 -0
  101. package/dist/types/effects/scroll-mechanics/section-index.d.ts +47 -0
  102. package/dist/types/effects/shared.d.ts +124 -4
  103. package/dist/types/effects/step-index.d.ts +121 -0
  104. package/dist/types/effects/step-marking.d.ts +41 -33
  105. package/dist/types/effects/svg/icon-parts.d.ts +23 -0
  106. package/dist/types/effects/three-d/flip-parts.d.ts +46 -0
  107. package/dist/types/effects/tween/index.d.ts +57 -0
  108. package/dist/types/effects/tween/properties.d.ts +107 -0
  109. package/dist/types/effects/tween/waypoints.d.ts +106 -0
  110. package/dist/types/showcase/compare.d.ts +4 -0
  111. package/dist/types/showcase/device-frame.d.ts +3 -0
  112. package/dist/types/showcase/hotspots.d.ts +3 -0
  113. package/dist/types/showcase/index.d.ts +30 -0
  114. package/dist/types/showcase/lightbox.d.ts +3 -0
  115. package/dist/types/showcase/media-source.d.ts +13 -0
  116. package/dist/types/showcase/modal-shell.d.ts +22 -0
  117. package/dist/types/showcase/scroll-story.d.ts +4 -0
  118. package/dist/types/showcase/shared.d.ts +47 -0
  119. package/dist/types/showcase/slideshow.d.ts +3 -0
  120. package/dist/types/showcase/slow-mo.d.ts +5 -0
  121. package/package.json +14 -7
  122. package/dist/esm/chunk-7WMNPIOZ.mjs +0 -4883
  123. package/dist/esm/chunk-JT4PZL3A.mjs +0 -773
  124. package/dist/esm/chunk-TJDDIQRG.mjs +0 -1290
@@ -1,9 +1,91 @@
1
1
  import type { Registry, ResolvedEffect } from './registry.js';
2
- import type { Activation, Channel, EffectSpec, ParsedValue, ReducedMotionPolicy, Timeline } from './types.js';
2
+ import type { SequenceStep } from './sequence.js';
3
+ import type { TargetScope } from './target.js';
4
+ import type { Activation, Channel, EffectSpec, EffectVariant, NamedActivation, ParameterSchema, ParsedValue, ReducedMotionPolicy, SegmentHoists, Timeline } from './types.js';
3
5
  export interface Entry {
4
6
  spec: EffectSpec;
5
7
  resolved: ResolvedEffect;
8
+ /**
9
+ * Per-spec refinement from `primitive.variantFor`, computed once in `resolveEntries` and carried
10
+ * on the entry so nothing downstream has to call it a second time. Absent for every primitive
11
+ * that is fully described without seeing an attribute, which is all but the generic tween.
12
+ */
13
+ variant?: EffectVariant;
14
+ /**
15
+ * Concrete milliseconds an `at:` position resolved to, for JS-rendered entries only.
16
+ *
17
+ * A CSS-rendered entry needs no such field: its position is a symbolic `calc()` the browser
18
+ * evaluates, which is both more accurate and re-evaluated when a stylesheet moves one of the
19
+ * durations underneath it (see `core/sequence.ts`). A JS-rendered one has no `animation-delay`
20
+ * to write to and needs a number, so the sequencer's numeric mirror is carried here and applied
21
+ * by `js-effect-preparer.ts` over whatever `readEffectTiming` read off the spec.
22
+ *
23
+ * Absent when the segment carries no `at:`, and when the position was refused — in both cases the
24
+ * effect keeps its own authored delay. There is no third case: a sequenced step always carries a
25
+ * real number, because the sequencer refuses a duration it cannot read rather than passing an
26
+ * unknown along.
27
+ */
28
+ sequencedDelayMs?: number;
29
+ /**
30
+ * Where the sequencer placed this segment, resolved once for the whole authored comma list
31
+ * before {@link compileTargets} partitions it by target — see that function's own comment for
32
+ * why the order matters. Always present once an entry leaves `compileTargets`; absent only on an
33
+ * `Entry` a test builds by hand without going through it.
34
+ */
35
+ step?: SequenceStep;
36
+ /**
37
+ * Selector this entry retargets to, lifted out of `spec.params` by `resolveEntries` for any
38
+ * primitive that does not declare a `target` parameter of its own. Undefined means "compiles on
39
+ * the host", which is every entry today and every entry whose primitive owns `target:` itself
40
+ * (see `liftTarget`'s comment for the current set — they read the key from `spec.params`,
41
+ * unchanged).
42
+ */
43
+ target?: string;
44
+ /** Which tree {@link target} is searched in. Only meaningful when `target` is set. */
45
+ scope?: TargetScope;
46
+ /**
47
+ * `animation-composition` for every track this entry compiles, when the composition resolver had
48
+ * to make it additive to let a same-channel neighbour survive — see {@link resolveComposition}.
49
+ *
50
+ * Absent means the CSS initial value, `replace`, which is what every entry compiled before this
51
+ * existed and what all but the handful of additively-resolved ones still compile to.
52
+ * `declarations.ts` omits the declaration entirely when no track sets it, the same way it already
53
+ * omits an all-`normal` `animation-direction`.
54
+ */
55
+ composite?: 'add';
6
56
  }
57
+ /**
58
+ * Channels one entry actually writes — the primitive's declaration, widened by any variant.
59
+ *
60
+ * Read by conflict detection *and* by the plan's channel union, which must agree: a `tween x:100`
61
+ * that is checked for collisions on `translate` but reports no channel to `style-plan.ts` would
62
+ * compose correctly and then skip the individual-transform fallback that same channel exists to
63
+ * trigger.
64
+ *
65
+ * @complexity O(c) time and space in the entry's channel count.
66
+ * @overallScore 100
67
+ */
68
+ export declare function channelsFor(entry: Entry): Channel[];
69
+ /**
70
+ * Authored parameter values in force for one entry — `spec.params`, or a variant's normalisation
71
+ * of them. Exported because the JS-effect path reads them too, and the two must not disagree about
72
+ * what the author wrote.
73
+ *
74
+ * @complexity O(1) time and space.
75
+ * @overallScore 100
76
+ */
77
+ export declare function authoredParams(entry: Entry): Record<string, string>;
78
+ /**
79
+ * The parameter specs one entry's values are validated against — the primitive's declaration,
80
+ * extended by any its variant synthesised for keys the primitive could not declare statically.
81
+ *
82
+ * Merged rather than replaced: see `EffectVariant.schema`. The primitive's own specs stay in force,
83
+ * so a variant can add parameters but never relax one.
84
+ *
85
+ * @complexity O(p) time and space in the schema size, and only when a variant declares one.
86
+ * @overallScore 100
87
+ */
88
+ export declare function schemaFor(entry: Entry): ParameterSchema;
7
89
  export interface CompiledPlan {
8
90
  /** Effect names to stamp into `data-kui-fx` for CSS hooks and debugging. */
9
91
  fxNames: string[];
@@ -11,6 +93,18 @@ export interface CompiledPlan {
11
93
  vars: Record<string, string>;
12
94
  /** Longhand animation declarations, compiled as parallel lists so effects compose. */
13
95
  declarations: Record<string, string>;
96
+ /**
97
+ * The bare `@keyframes` idents behind those declarations, one per track, in the same order.
98
+ *
99
+ * Carried separately because `animation-name` is no longer a list of idents that can be recovered
100
+ * by splitting it: a gated segment compiles to `var(--kui-above-md, kui-in-up)` (see
101
+ * `core/breakpoints.ts`), and `String.split(',')` shreds that into `var(--kui-above-md` and
102
+ * `kui-in-up)`. `animator.ts` hands this list to `createCssInstance`, which matches it against
103
+ * `getAnimations()` to decide which handles it owns — so a re-parse that produced `kui-in-up)`
104
+ * would own nothing, settle its completion promise immediately, and strand `data-kui-state` on
105
+ * `finished` while the animation was still visibly running.
106
+ */
107
+ keyframeNames: string[];
14
108
  /** Effects whose renderer needs JS setup. */
15
109
  jsEffects: Entry[];
16
110
  /** Names that are not registered. Must NOT be stamped, or the element is never rescanned. */
@@ -20,7 +114,7 @@ export interface CompiledPlan {
20
114
  /** Activation preferred by the composed primitives when the author named none. */
21
115
  defaultActivation?: Activation;
22
116
  /** Activations every composed primitive supports, for enforcement by the animator. */
23
- supportedActivations: Activation[];
117
+ supportedActivations: NamedActivation[];
24
118
  /**
25
119
  * Timelines every composed primitive supports. Empty means none — `style-plan.ts` must not
26
120
  * apply a native `view()`/`scroll()` timeline the author's effect doesn't declare support for,
@@ -30,6 +124,19 @@ export interface CompiledPlan {
30
124
  supportedTimelines: Timeline[];
31
125
  /** Union of channels every composed effect writes to, so callers can react to what actually moves. */
32
126
  channels: Channel[];
127
+ /**
128
+ * Merged `transition` shorthand value for the composed effects' declared {@link TransitionSegment}s,
129
+ * or absent when none of them transitions anything. Consumed by the one
130
+ * `:where([data-kui-fx])` rule in `base.css` through the ledger, the same way `vars`/`declarations`
131
+ * are.
132
+ *
133
+ * Deliberately NOT folded into `declarations`: `style-plan.ts` reads that field's emptiness as
134
+ * "this element has a CSS animation" and would gate every one of these ten presets — all
135
+ * `renderer: 'javascript'` — behind `animation-play-state: paused`, pausing an animation that
136
+ * does not exist. Deliberately NOT folded into `vars` either: that field means "author parameter
137
+ * overrides", and several tests assert its exact contents with `toEqual`.
138
+ */
139
+ transition?: string;
33
140
  warnings: string[];
34
141
  }
35
142
  /**
@@ -47,3 +154,101 @@ export interface CompiledPlan {
47
154
  * @overallScore 100
48
155
  */
49
156
  export declare function compile(parsed: ParsedValue, registry: Registry, timeline: Timeline): CompiledPlan;
157
+ /**
158
+ * One `target:`-partitioned group of a compiled `data-kui` attribute — the plan for the segments
159
+ * that share one `(scope, selector)` pair, plus which pair that is.
160
+ *
161
+ * `selector: ''` is the host: the element the attribute is authored on, always present, always
162
+ * `CompiledDocument.targets[0]`. Every other entry names a `target:` a primitive did not claim for
163
+ * itself — see `Entry.target`'s own comment for which primitives those are.
164
+ */
165
+ export interface CompiledTarget {
166
+ /** `''` for the host group. */
167
+ selector: string;
168
+ scope: TargetScope;
169
+ /**
170
+ * Segment-scoped hoists merged across this group's own member entries, or absent when none of
171
+ * them carried one — see `SegmentHoists` and `scopeHoists`. Not yet consumed at runtime (that is
172
+ * Phase 2's job, via `element-config.ts`'s `resolveGroupConfig`); documented here as the shape a
173
+ * derived host's own `ElementConfig` will be built from.
174
+ */
175
+ hoists?: SegmentHoists;
176
+ /** This group's lifted specs (target/scope stripped, hoists kept), authored order — what a
177
+ * multi-group union recompiles from (2b `compileUnion`). */
178
+ specs: EffectSpec[];
179
+ plan: CompiledPlan;
180
+ }
181
+ /**
182
+ * The full compilation of one authored `data-kui` value, before it is narrowed to a single plan.
183
+ *
184
+ * `warnings` here are the ones about the authored comma list as a whole rather than about any one
185
+ * target group: unknown effect names, the `at:` sequencer's own diagnostics, a refused container
186
+ * gate, a retarget the preset would not survive, and the hoisted `rm:`'s one-way-ratchet refusal.
187
+ * A group's own composition/parameter warnings live on `CompiledTarget.plan.warnings` instead,
188
+ * exactly where they always have.
189
+ *
190
+ * The two lists are disjoint, and that is load-bearing rather than tidy: `animator.ts` reports this
191
+ * one and then every group's, so a warning reachable through both would be printed twice — which is
192
+ * exactly what a shared array reference used to do to all of them.
193
+ */
194
+ export interface CompiledDocument {
195
+ targets: CompiledTarget[];
196
+ warnings: string[];
197
+ /** 9b: stray segments that look like an unquoted selector list — surfaced by 5b in
198
+ * `data-kui-unmatched`. */
199
+ suspectSelectors?: string[];
200
+ }
201
+ /**
202
+ * Compile a parsed `data-kui` value into one plan per `target:`/`scope:` group.
203
+ *
204
+ * The host group (selector `''`) is always present and always first, whether or not the author
205
+ * targeted anything — `install` in `animator.ts` loops every group the same way, and a page that
206
+ * never uses `target:` compiles to exactly the one group it always has.
207
+ *
208
+ * A fixed order, not to be reshuffled:
209
+ *
210
+ * 1. **Resolve and lift**, once, over the whole authored list (`resolveEntries`). Every entry gets
211
+ * a primitive; every entry whose primitive does not own `target:` itself gets `.target`/`.scope`
212
+ * pulled off its params here, before anything downstream can see them.
213
+ * 2. **Sanitize container gates** (`refuseContainerGate`), before composition sees them — see that
214
+ * function's own comment for why a stripped gate must not still count as "these can never
215
+ * collide".
216
+ * 3. **Sequence**, once, over the whole *unpartitioned* list. `at:` positions a segment against its
217
+ * neighbours in the authored comma list — `fade-up target:h1, slide-left target:p at:-200ms` is
218
+ * one author's clearly-linked pair, and partitioning first would make them neighbourless.
219
+ * 4. **Partition, then compose and build, per group.** Channel conflicts are only real within a
220
+ * group — two effects that land on different elements cannot collide — so `findConflicts`
221
+ * (inside `resolveComposition`) has to run after the split, not before it.
222
+ *
223
+ * @param parsed - Output of `parse`.
224
+ * @param registry - Effect catalog to resolve names against.
225
+ * @param timeline - Element-scoped timeline, used to warn on unsupported combinations.
226
+ * @complexity O(e * p) time in composed effects and their parameters; O(e) space.
227
+ * @overallScore 100
228
+ */
229
+ export declare function compileTargets(parsed: ParsedValue, registry: Registry, timeline: Timeline): CompiledDocument;
230
+ /**
231
+ * Fold a targeted segment's provisional hoists (`spec.hoists`, from `parse.ts`) back onto the
232
+ * element-wide `ParsedValue` for any spec whose primitive is not itself a `target:`-declaring one
233
+ * (or is unknown) — see `types.ts`'s `SegmentHoists`.
234
+ *
235
+ * `parseSegment` cannot know, at parse time, whether a targeted segment's own primitive declares
236
+ * a `target` parameter — that requires the registry, which only `compile.ts` has. A segment whose
237
+ * primitive *does* declare `target` (the nine listed on `liftTarget`'s comment) never becomes a
238
+ * group at all: `target:` there means "this primitive's own participants", so its hoists were
239
+ * always meant to be element-wide, same as before this feature existed. An unknown name is folded
240
+ * the same way, for the same reason `resolveEntries` already treats it as staying on the host:
241
+ * there is no group to scope anything to.
242
+ *
243
+ * Called from `animator.ts`'s `process()`, right after `bundles.expand` and before
244
+ * `resolveConfig` — `resolveConfig` reads `parsed.activation`/`timeline`/etc. and must see the
245
+ * folded answer, not a value still sitting on a declaring spec's `hoists`.
246
+ *
247
+ * @param parsed - Output of `parse`/`bundles.expand`.
248
+ * @param registry - Effect catalog, needed to tell a declaring primitive from a retargeting one.
249
+ * @returns `parsed` unchanged (by reference) when nothing needed folding — the identity path for
250
+ * every attribute with no `target:` at all — or a new `ParsedValue` with the fold applied.
251
+ * @complexity O(s * k) time in spec count and the nine hoist keys; O(s) space.
252
+ * @overallScore 100
253
+ */
254
+ export declare function scopeHoists(parsed: ParsedValue, registry: Registry): ParsedValue;
@@ -0,0 +1,218 @@
1
+ import type { Animator } from './animator.js';
2
+ import type { StyleLedger } from './owned-styles.js';
3
+ import type { Target } from './play.js';
4
+ import type { Activation, InstanceControl, PlaybackState, Timeline } from './types.js';
5
+ /**
6
+ * Control backed by the real `Animation` objects a compiled CSS effect produces.
7
+ *
8
+ * @param animations - Reader for the element's *owned* animation handles. A reader rather than an
9
+ * array because the set changes: `instances.ts` restarts an animation by rewriting
10
+ * `animation-name`, which replaces the handles wholesale, and a captured array would then be
11
+ * driving objects the element no longer has.
12
+ * @param ledger - The instance's style ledger, so a play-state write unwinds on teardown.
13
+ * @complexity O(a) per call in owned animations; O(1) space.
14
+ * @overallScore 100
15
+ */
16
+ export declare function createCssControl(animations: () => Animation[], ledger: StyleLedger): InstanceControl;
17
+ /**
18
+ * A handle over one selection's playheads.
19
+ *
20
+ * Every mutator returns the handle so calls chain — `control('.hero').timeScale(0.25).seek(0.5)`
21
+ * reads the way the equivalent GSAP line does. Chaining is the reason these are methods rather
22
+ * than settable properties.
23
+ */
24
+ export interface ControlHandle {
25
+ /** The elements this handle resolved to, in document order. */
26
+ readonly elements: Element[];
27
+ /**
28
+ * Effect names in the selection whose playhead no control call can reach — JavaScript-rendered
29
+ * effects, and anything driven by scroll rather than a clock.
30
+ *
31
+ * Readable so a caller can branch on it rather than discovering the gap by watching nothing
32
+ * happen. Every entry has also already been reported through the animator's reporter.
33
+ *
34
+ * "Playhead" is the operative word, and it is `pause`, `play`, `seek`, `timeScale` and `progress`
35
+ * that it governs. `reverse()` is the one method here that is not a playhead call — it asks the
36
+ * animator which way the element is travelling — so it answers to the animator's directional test
37
+ * instead, and a scroll-driven element is refused for its own reason rather than this one.
38
+ */
39
+ readonly uncontrolled: string[];
40
+ pause(): ControlHandle;
41
+ play(): ControlHandle;
42
+ /**
43
+ * Play the selection backwards, ending at the from-state each element started from.
44
+ *
45
+ * Idempotent: calling it twice is one exit, not an exit and an entrance, on exactly the rule that
46
+ * makes two `pointerleave`s in a row one exit. It is also *recorded* — the animator knows the
47
+ * element is travelling backwards, so a subsequent paired-activation exit does not start a second
48
+ * reverse, and the element settles at `data-kui-state="ready"` with a `kui:reverse-finish`.
49
+ *
50
+ * To travel forwards again, activate the element (`play()`, `kui.activate()`, or the entrance
51
+ * half of its activation): the animator turns a reversing playhead around rather than restarting
52
+ * it. `play()` on this handle is the narrower thing its name says elsewhere in this file — the
53
+ * counterpart to `pause()` — and does not change direction.
54
+ */
55
+ reverse(): ControlHandle;
56
+ /** Move every playhead to `progress` (0..1). Values outside the range are clamped. */
57
+ seek(progress: number): ControlHandle;
58
+ /** Multiply playback speed. `1` is authored speed; a negative value runs backwards. */
59
+ timeScale(rate: number): ControlHandle;
60
+ /**
61
+ * Position of the *least* advanced element in the selection, 0..1.
62
+ *
63
+ * The minimum rather than the maximum or the first, so `progress === 1` means every element is
64
+ * done — the reading an author actually gates on. `0` for a selection with nothing controllable.
65
+ */
66
+ readonly progress: number;
67
+ readonly state: PlaybackState;
68
+ }
69
+ export interface ControlRequest {
70
+ animator: Animator;
71
+ root: ParentNode;
72
+ target: Target;
73
+ }
74
+ /**
75
+ * Build a control handle for a selection.
76
+ *
77
+ * Warnings are emitted once here, at construction, rather than on each call: an author who asks
78
+ * for control over an effect that has none should be told immediately, including when they only
79
+ * meant to *read* `progress` — and repeating the same sentence on every frame of a `seek` loop
80
+ * would bury it. The default reporter is silent, exactly as it is for an unknown effect name or an
81
+ * unsupported activation; `consoleReporter()` makes all of them loud together, which is the
82
+ * existing contract rather than a new one invented here.
83
+ *
84
+ * @param request - Animator, root, and target selection. Mirrors `play`'s request shape.
85
+ * @returns A handle whose mutators chain.
86
+ * @complexity O(n) time in selected elements and their instances; O(n) space.
87
+ * @overallScore 100
88
+ */
89
+ export declare function control(request: ControlRequest): ControlHandle;
90
+ /**
91
+ * Lifecycle events.
92
+ *
93
+ * Before this module the library dispatched nothing at all — an author could start an animation
94
+ * from an attribute and then had no way whatsoever to learn that it had finished. Every escape
95
+ * hatch on offer (poll `data-kui-state`, race a `setTimeout` against the authored duration, wrap
96
+ * the element and watch it with a `MutationObserver`) is worse than the thing it replaces, and the
97
+ * last one is actively broken here — see the `MutationObserver` trap in
98
+ * `docs/implementation-outline-gsap-parity.md` §4.5.
99
+ *
100
+ * A `CustomEvent` on the animated element is the one answer that does not cost the author a
101
+ * library import. `addEventListener('kui:finish', ...)` is plain DOM, works from a framework's
102
+ * template, works from another script that has never heard of this library, and — because these
103
+ * bubble — works from one delegated listener on `document` for a whole page of animations. That is
104
+ * the same bet the attribute grammar makes: the author writes no library-specific JavaScript.
105
+ */
106
+ /**
107
+ * Event names, in one place so the namespace is a single rename before publish — the same
108
+ * discipline `attrs.ts` applies to attribute names, and for the same reason.
109
+ *
110
+ * `kui:` rather than `kui-`: a colon cannot appear in an HTML `on*` content attribute, so there is
111
+ * no chance of an author writing `onkui-finish="..."` in markup, discovering it silently does
112
+ * nothing, and blaming the library. It also reads unambiguously as a namespace separator and
113
+ * cannot collide with any current or future native event name, which a bare `kui-` prefix cannot
114
+ * promise.
115
+ */
116
+ export declare const KUI_EVENT: {
117
+ /** The element's effects have started. Fires once per activation. */
118
+ readonly start: "kui:start";
119
+ /** Every finite effect on the element has completed. */
120
+ readonly finish: "kui:finish";
121
+ /**
122
+ * The element has finished playing *backwards* and is sitting at its from-state again.
123
+ *
124
+ * A separate name rather than a second `kui:finish` with a different `reason`, because the
125
+ * commonest thing an author does with `kui:finish` is chain the next reveal off it, and a
126
+ * listener that has to remember to re-check `event.detail.reason` before doing so is a trap
127
+ * rather than an API — the one time they forget, a hover-out plays the next section in. Filtering
128
+ * by *name* is what `addEventListener` is for, and it is what a delegated document-level listener
129
+ * can do without reading the detail at all.
130
+ *
131
+ * The exit half of a paired activation (`data-kui-on="pointerenter/pointerleave"`) settles here,
132
+ * and so does a programmatic `control(el).reverse()`. Before this existed, a completed reverse
133
+ * dispatched nothing whatsoever, which left "the element is back where it started" the one
134
+ * lifecycle moment an author could only discover by polling `data-kui-state`.
135
+ */
136
+ readonly reverseFinish: "kui:reverse-finish";
137
+ /** The element's effects were torn down or cancelled before completing. */
138
+ readonly cancel: "kui:cancel";
139
+ };
140
+ export type LifecycleEventType = (typeof KUI_EVENT)[keyof typeof KUI_EVENT];
141
+ /**
142
+ * Why the event fired.
143
+ *
144
+ * Carried because the *same* event name legitimately arrives from more than one route, and an
145
+ * author chaining animations needs to tell them apart. `finish` in particular fires both when an
146
+ * animation genuinely ran to its end and when reduced motion meant it never ran at all — see
147
+ * `'reduced-motion'` below.
148
+ */
149
+ export type LifecycleReason =
150
+ /** `start`: an activation fired and at least one instance began. */
151
+ 'activated'
152
+ /** `finish`: every finite instance resolved. */
153
+ | 'complete'
154
+ /**
155
+ * `reverse-finish`: every finite instance finished running backwards, so the element is back at
156
+ * the from-state it was in before it was ever activated — `data-kui-state` reads `ready` again.
157
+ *
158
+ * One reason on one event name today, unlike `finish`, which carries two. It is carried anyway
159
+ * because the two routes into a reverse (a paired activation's exit half, and a programmatic
160
+ * `control().reverse()`) are already distinguishable from `detail.activation`, and a listener
161
+ * that branches on `reason` for every other lifecycle event should not have to special-case this
162
+ * one for having none.
163
+ */
164
+ | 'reversed'
165
+ /**
166
+ * `finish`: the effect declared `reducedMotion: 'disable'` and the visitor asked for reduced
167
+ * motion, so it was never started and the element is already at its final state.
168
+ *
169
+ * This is the one case where `finish` arrives with no preceding `start`, and it is deliberate.
170
+ * The alternative — staying silent — strands every author who uses these events to chain work,
171
+ * because their second step would simply never run for the visitors who most need the page to
172
+ * stay usable. Firing a synthetic `start` first would be the worse lie: nothing started.
173
+ */
174
+ | 'reduced-motion'
175
+ /** `cancel`: the element's effects were torn down — `reset()`, `destroy()`, or DOM removal. */
176
+ | 'reset'
177
+ /** `cancel`: an explicit `cancel()` on the element or on a `play()` handle. */
178
+ | 'cancelled';
179
+ /**
180
+ * What every lifecycle event carries.
181
+ *
182
+ * `effects` is the same normalized name list `data-kui-fx` holds, so a delegated listener can tell
183
+ * *which* effect just finished without re-reading the element's attributes — which matters
184
+ * precisely because delegation is the case this is for: one listener on `document` seeing events
185
+ * from hundreds of elements needs the identity in the event, not in a DOM read per event.
186
+ */
187
+ export interface LifecycleDetail {
188
+ /** Normalized effect names — the contents of `data-kui-fx`. */
189
+ effects: string[];
190
+ activation: Activation;
191
+ timeline: Timeline;
192
+ reason: LifecycleReason;
193
+ /** Set when the event fires on a derived match: its host. */
194
+ host?: Element;
195
+ }
196
+ /** A lifecycle event, narrowed so `event.detail` is typed at the listener. */
197
+ export type LifecycleEvent = CustomEvent<LifecycleDetail>;
198
+ /**
199
+ * Dispatch one lifecycle event on the animated element.
200
+ *
201
+ * `bubbles` is required by the design (delegation is the point) and `composed` follows from it: an
202
+ * animated element inside a shadow root whose event stopped at the shadow boundary would be
203
+ * invisible to exactly the document-level listener this exists to serve. Not `cancelable` — there
204
+ * is nothing for a listener to prevent; by the time any of these fire the decision has been made.
205
+ *
206
+ * The constructor is taken from the element's own window rather than the global one, so an
207
+ * animator driving a document in an `<iframe>` dispatches an event that element's own listeners
208
+ * recognise via `instanceof`. Falling back to the global keeps a jsdom-style environment working
209
+ * where `ownerDocument.defaultView` can be null, and the `typeof` guard keeps this module import-
210
+ * safe in a DOM-less process, the same rule `capabilities.ts` and `animator.ts` already follow.
211
+ *
212
+ * @param el - The animated element. The event's target and the element authors listen on.
213
+ * @param type - One of `KUI_EVENT`.
214
+ * @param detail - Identity and reason, readable as `event.detail`.
215
+ * @complexity O(n) time in the number of listeners on the propagation path; O(1) space.
216
+ * @overallScore 100
217
+ */
218
+ export declare function emitLifecycle(el: Element, type: LifecycleEventType, detail: LifecycleDetail): void;
@@ -0,0 +1,128 @@
1
+ import type { Entry } from './compile.js';
2
+ import type { SequenceStep } from './sequence.js';
3
+ import type { Timeline } from './types.js';
4
+ /**
5
+ * The CSS-emission half of the compiler: how one resolved entry becomes longhand declarations.
6
+ *
7
+ * `compile.ts` decides *what* animates — which effects resolve, which `target:` group they belong
8
+ * to, whether they may compose, what the reduced-motion policy is. This file decides *how that is
9
+ * spelled in CSS*: the parallel `animation-*` value lists that let composed effects share one
10
+ * declaration, and the merged `transition` shorthand that does the same job for the ten presets
11
+ * that transition rather than keyframe. Nothing here reads a registry, a parse tree, or a target
12
+ * group; nothing in `compile.ts` writes a CSS value string any more.
13
+ *
14
+ * The `Entry` import is deliberately type-only, so the module graph edge exists at type level and
15
+ * nowhere at runtime — `.dependency-cruiser.cjs` resolves the graph with `tsPreCompilationDeps:
16
+ * false` precisely because `import type` is erased by `verbatimModuleSyntax` and a mutual type
17
+ * reference is not an initialisation-order hazard. `Entry` stays in `compile.ts` because that is
18
+ * where entries are built and where every other consumer already imports it from.
19
+ */
20
+ export interface AnimationTracks {
21
+ /** What is written to `animation-name` — an ident, or a `var()` around one when gated. */
22
+ names: string[];
23
+ /** The ident inside it, for `CompiledPlan.keyframeNames`. Same length, same order. */
24
+ keyframes: string[];
25
+ durations: string[];
26
+ /**
27
+ * Where each track *starts*, as an unwrapped `calc()` sum. Left unwrapped because
28
+ * {@link declarationsFor} is what finalizes it: on a pin timeline it folds the scrub head into
29
+ * the same expression, and `calc(calc(a + b) - c)` buries the arithmetic an author has to read
30
+ * in a devtools panel.
31
+ */
32
+ delays: string[];
33
+ /**
34
+ * Where each track *ends* — its start plus its whole playback — as an unwrapped sum.
35
+ *
36
+ * Read only to size the pin scrub head, which has to span the element's longest track rather
37
+ * than each track's own playback. Accumulated for every timeline because it costs one string
38
+ * concatenation and knowing the timeline here would buy nothing else.
39
+ */
40
+ ends: string[];
41
+ easings: string[];
42
+ iterationCounts: string[];
43
+ /**
44
+ * One `animation-direction` per track. Almost always every entry is `normal`, which is the CSS
45
+ * initial value — see {@link declarationsFor} for why that case emits no declaration at all.
46
+ */
47
+ directions: string[];
48
+ /**
49
+ * One `animation-composition` per track — `replace` for all but the few the composition resolver
50
+ * had to sum, and handled exactly like {@link directions}: an all-`replace` list is the CSS
51
+ * initial value and emits no declaration at all.
52
+ *
53
+ * Per track and not per element for the reason `iterationCounts` is: CSS repeats a shorter value
54
+ * list to match the longest one across every `animation-*` longhand in the group, so a single
55
+ * `add` written for one effect would be handed to every neighbour composed beside it — including
56
+ * the ones whose keyframes write `opacity`, where adding to the underlying 1 deletes the fade.
57
+ */
58
+ compositions: string[];
59
+ }
60
+ /**
61
+ * A fresh accumulator for one plan's worth of tracks.
62
+ *
63
+ * @complexity O(1) time and space.
64
+ * @overallScore 100
65
+ */
66
+ export declare function emptyTracks(): AnimationTracks;
67
+ /**
68
+ * Append one effect to the parallel animation lists, as one track per keyframe block it compiles.
69
+ *
70
+ * Separate rules cannot both contribute an `animation` declaration — the cascade discards one —
71
+ * so composition is expressed as parallel longhand value lists on a single declaration.
72
+ *
73
+ * Usually one block, so usually one track. A variant naming several (`keyframesFor`) gets the same
74
+ * timing repeated across all of them, which is the point: `tween x:100 opacity:0 800ms` is *one*
75
+ * effect the author gave one duration, rendered as two tracks only because CSS has no way to write
76
+ * two unrelated properties from one keyframe without also writing everything in between.
77
+ *
78
+ * @param step - Where the sequencer placed this segment. For a segment with no `at:` this is the
79
+ * segment's own delay, so the compiled output is unchanged from before sequencing existed.
80
+ * @complexity O(k) time in the entry's keyframe count; O(k) space in the tracks.
81
+ * @overallScore 100
82
+ */
83
+ export declare function pushTrack(tracks: AnimationTracks, entry: Entry, step: SequenceStep): void;
84
+ /**
85
+ * Append one entry's declared {@link TransitionSegment}s to the element's merged transition list.
86
+ *
87
+ * Reuses `durationExpression`/`easingValue` — the same two functions `pushTrack` resolves an
88
+ * `animation-duration`/`-timing-function` through — so `data-kui="lift 400ms"` reaches a
89
+ * transitioned property and a keyframed one through one code path that cannot disagree with
90
+ * itself. The delay is a per-*preset* custom-property slot (`--kui-tx-delay-<name>`) rather than a
91
+ * shared `animation-delay`-style list: the CSS state rule (`:hover`, `[aria-expanded]`) that
92
+ * actually triggers the transition writes that slot directly (see `interaction.css`), and distinct
93
+ * property names per preset are what let two composed presets carry independent delays without a
94
+ * second clobber one level down.
95
+ *
96
+ * Mutates `segments`/`owners` rather than returning a value, the same accumulator shape `pushTrack`
97
+ * already uses for `tracks` — a per-entry return would need concatenating at every call site for no
98
+ * benefit, since every caller already owns one shared list for the whole composed entry set.
99
+ *
100
+ * @param segments - Accumulator of `"property duration easing delay"` strings, mutated in place.
101
+ * @param owners - property → the preset name that most recently claimed it, mutated in place, kept
102
+ * only to name both presets in the duplicate-property warning below.
103
+ * @param entry - The composed entry to read `preset.transitions` from.
104
+ * @param warnings - Diagnostic sink. Two presets transitioning the same property compose — the
105
+ * channel model does not forbid it, and CSS resolves the shorthand's last occurrence
106
+ * deterministically — but the author is owed a name for which one wins.
107
+ * @complexity O(t) time and space in the preset's declared transition segment count.
108
+ * @overallScore 100
109
+ */
110
+ export declare function pushTransitions(segments: string[], owners: Map<string, string>, entry: Entry, warnings: string[]): void;
111
+ /**
112
+ * Collapse the parallel track lists into the `animation-*` longhands they compile to.
113
+ *
114
+ * Six of them unconditionally, and `animation-direction` only when some track actually alternates.
115
+ * That last one is not tidiness: `normal` is the CSS initial value, so emitting a list of them
116
+ * would be a no-op declaration written onto every animated element on every page for the sake of
117
+ * the rare one that uses `yoyo:`. Omitting it is what keeps an attribute written before `yoyo:`
118
+ * existed compiling byte-for-byte identically. When it *is* emitted it carries one value per
119
+ * track, the full length — a shorter list would be repeated by the browser to match
120
+ * `animation-name` and would silently alternate a neighbour that never asked to.
121
+ *
122
+ * The delays arrive unwrapped and are finalized here, because the pin scrub head is the one term
123
+ * in them that cannot be computed per track — see {@link pinnedDelays}.
124
+ *
125
+ * @complexity O(t) time and space in the track count.
126
+ * @overallScore 100
127
+ */
128
+ export declare function declarationsFor(tracks: AnimationTracks, timeline: Timeline): Record<string, string>;
@@ -0,0 +1,43 @@
1
+ import type { CompiledTarget } from '../compile.js';
2
+ import type { AnimatorPort, DerivedInstallContext } from './types.js';
3
+ /**
4
+ * Recompute an aggregate host's `data-kui-state` from its derived matches' own states (D-A):
5
+ * `'running'` (+ host `kui:start`('activated')) when any derived match is running; `'finished'`
6
+ * (+ host `kui:finish`('complete', or `'reduced-motion'` if the host never ran any match; no event
7
+ * at all if every match was cancelled)) once every match has settled to `'finished'`/`'failed'`;
8
+ * back to `'ready'` (no event) if every match has been reset to `'ready'`. Idempotent, and a no-op
9
+ * for any host whose own state is not `aggregate: true`, or that `port.stateOf` does not (yet)
10
+ * know about.
11
+ *
12
+ * Checked in this order because the cases overlap: "any running" must win over "all failed" (a
13
+ * mix of running and failed is still running), and "all failed" must win over "all finished |
14
+ * failed" (a `failed`-only host reports `'failed'`, never a `kui:finish`). Any other mix — some
15
+ * settled, some still `ready` — leaves the host exactly where it was, per D-A: an aggregate host
16
+ * stays `running` until its last off-screen match finishes, however long that takes.
17
+ *
18
+ * @param port - The animator's narrow window.
19
+ * @param host - The candidate aggregate host.
20
+ * @complexity O(m) time in the host's derived match count; O(1) space.
21
+ * @overallScore 100
22
+ */
23
+ export declare function syncAggregate(port: AnimatorPort, host: Element): void;
24
+ /**
25
+ * Compile one union group for a match claimed by two or more `target:` groups of the same host, so
26
+ * `resolveComposition`'s conflict detection runs over the combined spec list exactly as it would
27
+ * for a hand-written comma list on that element — a match must never see two groups' effects as
28
+ * unrelated just because they arrived through different selectors. Cached in `port.book.unions`,
29
+ * keyed by the sorted, `\u0001`-joined `${scope} ${selector}` list, so a second match claimed by
30
+ * the same group combination reuses the compile. Folds the host's own `rm:` policy in (a union
31
+ * belongs to no single authored segment to read it from). Reports its own warnings once per host,
32
+ * not once per match.
33
+ *
34
+ * @param port - The animator's narrow window.
35
+ * @param context - The host's derived-install context — what the union recompiles the timeline
36
+ * against.
37
+ * @param targets - Every group claiming the match, in authored group order.
38
+ * @returns The compiled union target.
39
+ * @complexity O(e * p) time in the combined spec/parameter count on a cache miss, O(1) on a hit;
40
+ * O(e) space for the freshly compiled target.
41
+ * @overallScore 100
42
+ */
43
+ export declare function compileUnion(port: AnimatorPort, context: DerivedInstallContext, targets: CompiledTarget[]): CompiledTarget;
@@ -0,0 +1,30 @@
1
+ import type { DerivedBook } from './types.js';
2
+ /**
3
+ * A fresh, empty derived-host book for one `Animator`.
4
+ *
5
+ * All WeakMaps, so a removed element takes its own entries with it rather than leaking for the
6
+ * animator's lifetime — the same discipline `animator.ts`'s own `states`/`currentRun`/`settleArmed`
7
+ * already follow.
8
+ *
9
+ * @complexity O(1) time and space.
10
+ * @overallScore 100
11
+ */
12
+ export declare function createDerivedBook(): DerivedBook;
13
+ /**
14
+ * A host's derived matches as a fresh array, in document order.
15
+ *
16
+ * A copy rather than the live `Set`, so a caller may release matches while iterating without the
17
+ * iteration itself observing the mutation.
18
+ *
19
+ * @returns `[]` for a host with no derived matches (or that is not a host at all).
20
+ * @complexity O(n) time and space in the host's derived match count.
21
+ * @overallScore 100
22
+ */
23
+ export declare function derivedOf(book: DerivedBook, el: Element): Element[];
24
+ /**
25
+ * The host that installed a derived match, if `el` is one.
26
+ *
27
+ * @complexity O(1) time and space.
28
+ * @overallScore 100
29
+ */
30
+ export declare function hostOf(book: DerivedBook, el: Element): Element | undefined;