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.
- package/README.md +93 -10
- package/dist/esm/advanced/index.mjs +4727 -0
- package/dist/esm/chunk-AEQGC2KM.mjs +189 -0
- package/dist/esm/chunk-MQSNMYRP.mjs +9332 -0
- package/dist/esm/chunk-RVTYN6NZ.mjs +3446 -0
- package/dist/esm/chunk-U43BX5O2.mjs +2984 -0
- package/dist/esm/core/index.mjs +16 -9
- package/dist/esm/effects/index.mjs +19 -4
- package/dist/esm/index.mjs +17 -10
- package/dist/kuinetic.advanced.js +5070 -0
- package/dist/kuinetic.advanced.min.js +749 -0
- package/dist/kuinetic.all.js +15218 -5595
- package/dist/kuinetic.all.min.js +2 -0
- package/dist/kuinetic.css +2900 -360
- package/dist/kuinetic.js +15217 -5593
- package/dist/kuinetic.min.css +1 -0
- package/dist/kuinetic.min.js +7 -0
- package/dist/types/3d/angles.d.ts +30 -0
- package/dist/types/3d/flattening.d.ts +49 -0
- package/dist/types/3d/index.d.ts +36 -0
- package/dist/types/3d/model-3d.d.ts +115 -0
- package/dist/types/3d/register.d.ts +26 -0
- package/dist/types/3d/webgl.d.ts +24 -0
- package/dist/types/advanced/audio.d.ts +322 -0
- package/dist/types/advanced/base.d.ts +233 -0
- package/dist/types/advanced/camera-3d.d.ts +236 -0
- package/dist/types/advanced/fluid-cursor.d.ts +149 -0
- package/dist/types/advanced/gl-utils.d.ts +52 -0
- package/dist/types/advanced/glsl.d.ts +60 -0
- package/dist/types/advanced/index.d.ts +23 -0
- package/dist/types/advanced/particles.d.ts +108 -0
- package/dist/types/advanced/scenes.d.ts +207 -0
- package/dist/types/advanced/shaders.d.ts +972 -0
- package/dist/types/browser/boot.d.ts +72 -0
- package/dist/types/core/activation.d.ts +227 -2
- package/dist/types/core/animator.d.ts +446 -11
- package/dist/types/core/attrs.d.ts +9 -0
- package/dist/types/core/breakpoints.d.ts +260 -0
- package/dist/types/core/bundles.d.ts +71 -0
- package/dist/types/core/callback.d.ts +64 -0
- package/dist/types/core/capabilities.d.ts +47 -0
- package/dist/types/core/channels.d.ts +120 -2
- package/dist/types/core/cloak-selectors.d.ts +47 -0
- package/dist/types/core/compile.d.ts +207 -2
- package/dist/types/core/control.d.ts +218 -0
- package/dist/types/core/declarations.d.ts +128 -0
- package/dist/types/core/derived/aggregate.d.ts +43 -0
- package/dist/types/core/derived/book.d.ts +30 -0
- package/dist/types/core/derived/diagnostics.d.ts +51 -0
- package/dist/types/core/derived/group-gate.d.ts +62 -0
- package/dist/types/core/derived/install.d.ts +110 -0
- package/dist/types/core/derived/late-matches.d.ts +19 -0
- package/dist/types/core/derived/types.d.ts +108 -0
- package/dist/types/core/easing.d.ts +57 -0
- package/dist/types/core/element-config.d.ts +47 -6
- package/dist/types/core/element-size.d.ts +23 -0
- package/dist/types/core/event-sources.d.ts +39 -0
- package/dist/types/core/flip.d.ts +34 -1
- package/dist/types/core/gesture.d.ts +12 -0
- package/dist/types/core/host-facts.d.ts +155 -0
- package/dist/types/core/index.d.ts +7 -3
- package/dist/types/core/owned-styles.d.ts +129 -2
- package/dist/types/core/params.d.ts +48 -0
- package/dist/types/core/parse.d.ts +28 -1
- package/dist/types/core/register-into.d.ts +29 -0
- package/dist/types/core/registry.d.ts +10 -3
- package/dist/types/core/repeat.d.ts +91 -0
- package/dist/types/core/sequence.d.ts +126 -0
- package/dist/types/core/stagger-config.d.ts +199 -0
- package/dist/types/core/stagger-keys.d.ts +63 -0
- package/dist/types/core/stagger.d.ts +187 -6
- package/dist/types/core/style-plan.d.ts +21 -16
- package/dist/types/core/target.d.ts +128 -0
- package/dist/types/core/threshold-reachability.d.ts +45 -0
- package/dist/types/core/time-scale.d.ts +9 -0
- package/dist/types/core/toggle-actions.d.ts +141 -0
- package/dist/types/core/travel.d.ts +59 -0
- package/dist/types/core/types.d.ts +705 -13
- package/dist/types/core/unquoted-selectors.d.ts +53 -0
- package/dist/types/effects/carousel/drag.d.ts +80 -0
- package/dist/types/effects/carousel/index.d.ts +149 -0
- package/dist/types/effects/catalog/background-media.d.ts +152 -0
- package/dist/types/effects/catalog/discrete.d.ts +13 -0
- package/dist/types/effects/catalog/interaction-proximity.d.ts +24 -0
- package/dist/types/effects/catalog/interaction-reveal.d.ts +248 -0
- package/dist/types/effects/catalog/interaction-shared.d.ts +42 -0
- package/dist/types/effects/catalog/interaction-states.d.ts +68 -0
- package/dist/types/effects/catalog/materials.d.ts +67 -0
- package/dist/types/effects/catalog/media-shared.d.ts +3 -2
- package/dist/types/effects/catalog/numbers-shared.d.ts +8 -5
- package/dist/types/effects/catalog/shared.d.ts +2 -2
- package/dist/types/effects/catalog/text-shared.d.ts +92 -10
- package/dist/types/effects/catalog/transforms.d.ts +13 -0
- package/dist/types/effects/catalog/view-transitions.d.ts +13 -0
- package/dist/types/effects/forms/primitives.d.ts +15 -4
- package/dist/types/effects/gestures/index.d.ts +53 -0
- package/dist/types/effects/index.d.ts +5 -0
- package/dist/types/effects/layout/presets.d.ts +35 -0
- package/dist/types/effects/motion-path/index.d.ts +35 -0
- package/dist/types/effects/scroll-mechanics/params.d.ts +23 -0
- package/dist/types/effects/scroll-mechanics/presets.d.ts +51 -0
- package/dist/types/effects/scroll-mechanics/section-index.d.ts +47 -0
- package/dist/types/effects/shared.d.ts +162 -2
- package/dist/types/effects/step-index.d.ts +121 -0
- package/dist/types/effects/step-marking.d.ts +41 -33
- package/dist/types/effects/svg/icon-parts.d.ts +23 -0
- package/dist/types/effects/three-d/flip-parts.d.ts +46 -0
- package/dist/types/effects/tween/index.d.ts +57 -0
- package/dist/types/effects/tween/properties.d.ts +107 -0
- package/dist/types/effects/tween/waypoints.d.ts +106 -0
- package/dist/types/showcase/compare.d.ts +4 -0
- package/dist/types/showcase/device-frame.d.ts +3 -0
- package/dist/types/showcase/hotspots.d.ts +3 -0
- package/dist/types/showcase/index.d.ts +30 -0
- package/dist/types/showcase/lightbox.d.ts +3 -0
- package/dist/types/showcase/media-source.d.ts +13 -0
- package/dist/types/showcase/modal-shell.d.ts +22 -0
- package/dist/types/showcase/scroll-story.d.ts +4 -0
- package/dist/types/showcase/shared.d.ts +47 -0
- package/dist/types/showcase/slideshow.d.ts +3 -0
- package/dist/types/showcase/slow-mo.d.ts +5 -0
- package/package.json +14 -7
- package/dist/esm/chunk-5DON3UFQ.mjs +0 -4380
- package/dist/esm/chunk-JT4PZL3A.mjs +0 -773
- 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
|
-
/**
|
|
57
|
-
|
|
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
|
-
|
|
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
|
-
|
|
93
|
-
|
|
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
|
-
/**
|
|
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 `
|
|
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
|
-
|
|
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
|
-
/**
|
|
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
|
}
|