kuinetic 0.1.3 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (125) hide show
  1. package/README.md +93 -10
  2. package/dist/esm/advanced/index.mjs +4727 -0
  3. package/dist/esm/chunk-AEQGC2KM.mjs +189 -0
  4. package/dist/esm/chunk-MQSNMYRP.mjs +9332 -0
  5. package/dist/esm/chunk-RVTYN6NZ.mjs +3446 -0
  6. package/dist/esm/chunk-U43BX5O2.mjs +2984 -0
  7. package/dist/esm/core/index.mjs +16 -9
  8. package/dist/esm/effects/index.mjs +19 -4
  9. package/dist/esm/index.mjs +17 -10
  10. package/dist/kuinetic.advanced.js +5070 -0
  11. package/dist/kuinetic.advanced.min.js +749 -0
  12. package/dist/kuinetic.all.js +15218 -5595
  13. package/dist/kuinetic.all.min.js +2 -0
  14. package/dist/kuinetic.css +2900 -360
  15. package/dist/kuinetic.js +15217 -5593
  16. package/dist/kuinetic.min.css +1 -0
  17. package/dist/kuinetic.min.js +7 -0
  18. package/dist/types/3d/angles.d.ts +30 -0
  19. package/dist/types/3d/flattening.d.ts +49 -0
  20. package/dist/types/3d/index.d.ts +36 -0
  21. package/dist/types/3d/model-3d.d.ts +115 -0
  22. package/dist/types/3d/register.d.ts +26 -0
  23. package/dist/types/3d/webgl.d.ts +24 -0
  24. package/dist/types/advanced/audio.d.ts +322 -0
  25. package/dist/types/advanced/base.d.ts +233 -0
  26. package/dist/types/advanced/camera-3d.d.ts +236 -0
  27. package/dist/types/advanced/fluid-cursor.d.ts +149 -0
  28. package/dist/types/advanced/gl-utils.d.ts +52 -0
  29. package/dist/types/advanced/glsl.d.ts +60 -0
  30. package/dist/types/advanced/index.d.ts +23 -0
  31. package/dist/types/advanced/particles.d.ts +108 -0
  32. package/dist/types/advanced/scenes.d.ts +207 -0
  33. package/dist/types/advanced/shaders.d.ts +972 -0
  34. package/dist/types/browser/boot.d.ts +72 -0
  35. package/dist/types/core/activation.d.ts +227 -2
  36. package/dist/types/core/animator.d.ts +446 -11
  37. package/dist/types/core/attrs.d.ts +9 -0
  38. package/dist/types/core/breakpoints.d.ts +260 -0
  39. package/dist/types/core/bundles.d.ts +71 -0
  40. package/dist/types/core/callback.d.ts +64 -0
  41. package/dist/types/core/capabilities.d.ts +47 -0
  42. package/dist/types/core/channels.d.ts +120 -2
  43. package/dist/types/core/cloak-selectors.d.ts +47 -0
  44. package/dist/types/core/compile.d.ts +207 -2
  45. package/dist/types/core/control.d.ts +218 -0
  46. package/dist/types/core/declarations.d.ts +128 -0
  47. package/dist/types/core/derived/aggregate.d.ts +43 -0
  48. package/dist/types/core/derived/book.d.ts +30 -0
  49. package/dist/types/core/derived/diagnostics.d.ts +51 -0
  50. package/dist/types/core/derived/group-gate.d.ts +62 -0
  51. package/dist/types/core/derived/install.d.ts +110 -0
  52. package/dist/types/core/derived/late-matches.d.ts +19 -0
  53. package/dist/types/core/derived/types.d.ts +108 -0
  54. package/dist/types/core/easing.d.ts +57 -0
  55. package/dist/types/core/element-config.d.ts +47 -6
  56. package/dist/types/core/element-size.d.ts +23 -0
  57. package/dist/types/core/event-sources.d.ts +39 -0
  58. package/dist/types/core/flip.d.ts +34 -1
  59. package/dist/types/core/gesture.d.ts +12 -0
  60. package/dist/types/core/host-facts.d.ts +155 -0
  61. package/dist/types/core/index.d.ts +7 -3
  62. package/dist/types/core/owned-styles.d.ts +129 -2
  63. package/dist/types/core/params.d.ts +48 -0
  64. package/dist/types/core/parse.d.ts +28 -1
  65. package/dist/types/core/register-into.d.ts +29 -0
  66. package/dist/types/core/registry.d.ts +10 -3
  67. package/dist/types/core/repeat.d.ts +91 -0
  68. package/dist/types/core/sequence.d.ts +126 -0
  69. package/dist/types/core/stagger-config.d.ts +199 -0
  70. package/dist/types/core/stagger-keys.d.ts +63 -0
  71. package/dist/types/core/stagger.d.ts +187 -6
  72. package/dist/types/core/style-plan.d.ts +21 -16
  73. package/dist/types/core/target.d.ts +128 -0
  74. package/dist/types/core/threshold-reachability.d.ts +45 -0
  75. package/dist/types/core/time-scale.d.ts +9 -0
  76. package/dist/types/core/toggle-actions.d.ts +141 -0
  77. package/dist/types/core/travel.d.ts +59 -0
  78. package/dist/types/core/types.d.ts +705 -13
  79. package/dist/types/core/unquoted-selectors.d.ts +53 -0
  80. package/dist/types/effects/carousel/drag.d.ts +80 -0
  81. package/dist/types/effects/carousel/index.d.ts +149 -0
  82. package/dist/types/effects/catalog/background-media.d.ts +152 -0
  83. package/dist/types/effects/catalog/discrete.d.ts +13 -0
  84. package/dist/types/effects/catalog/interaction-proximity.d.ts +24 -0
  85. package/dist/types/effects/catalog/interaction-reveal.d.ts +248 -0
  86. package/dist/types/effects/catalog/interaction-shared.d.ts +42 -0
  87. package/dist/types/effects/catalog/interaction-states.d.ts +68 -0
  88. package/dist/types/effects/catalog/materials.d.ts +67 -0
  89. package/dist/types/effects/catalog/media-shared.d.ts +3 -2
  90. package/dist/types/effects/catalog/numbers-shared.d.ts +8 -5
  91. package/dist/types/effects/catalog/shared.d.ts +2 -2
  92. package/dist/types/effects/catalog/text-shared.d.ts +92 -10
  93. package/dist/types/effects/catalog/transforms.d.ts +13 -0
  94. package/dist/types/effects/catalog/view-transitions.d.ts +13 -0
  95. package/dist/types/effects/forms/primitives.d.ts +15 -4
  96. package/dist/types/effects/gestures/index.d.ts +53 -0
  97. package/dist/types/effects/index.d.ts +5 -0
  98. package/dist/types/effects/layout/presets.d.ts +35 -0
  99. package/dist/types/effects/motion-path/index.d.ts +35 -0
  100. package/dist/types/effects/scroll-mechanics/params.d.ts +23 -0
  101. package/dist/types/effects/scroll-mechanics/presets.d.ts +51 -0
  102. package/dist/types/effects/scroll-mechanics/section-index.d.ts +47 -0
  103. package/dist/types/effects/shared.d.ts +162 -2
  104. package/dist/types/effects/step-index.d.ts +121 -0
  105. package/dist/types/effects/step-marking.d.ts +41 -33
  106. package/dist/types/effects/svg/icon-parts.d.ts +23 -0
  107. package/dist/types/effects/three-d/flip-parts.d.ts +46 -0
  108. package/dist/types/effects/tween/index.d.ts +57 -0
  109. package/dist/types/effects/tween/properties.d.ts +107 -0
  110. package/dist/types/effects/tween/waypoints.d.ts +106 -0
  111. package/dist/types/showcase/compare.d.ts +4 -0
  112. package/dist/types/showcase/device-frame.d.ts +3 -0
  113. package/dist/types/showcase/hotspots.d.ts +3 -0
  114. package/dist/types/showcase/index.d.ts +30 -0
  115. package/dist/types/showcase/lightbox.d.ts +3 -0
  116. package/dist/types/showcase/media-source.d.ts +13 -0
  117. package/dist/types/showcase/modal-shell.d.ts +22 -0
  118. package/dist/types/showcase/scroll-story.d.ts +4 -0
  119. package/dist/types/showcase/shared.d.ts +47 -0
  120. package/dist/types/showcase/slideshow.d.ts +3 -0
  121. package/dist/types/showcase/slow-mo.d.ts +5 -0
  122. package/package.json +14 -7
  123. package/dist/esm/chunk-5DON3UFQ.mjs +0 -4380
  124. package/dist/esm/chunk-JT4PZL3A.mjs +0 -773
  125. package/dist/esm/chunk-QUVFODSQ.mjs +0 -1278
@@ -0,0 +1,248 @@
1
+ import type { Preset, Primitive } from '../../core/types.js';
2
+ /**
3
+ * The masked label swap: two stacked copies of a label in a clipped box, one sliding out as the
4
+ * other slides in.
5
+ *
6
+ * ### Markup
7
+ *
8
+ * ```html
9
+ * <button data-kui="masked-label-swap">
10
+ * <span data-kui-swap="from">Download</span>
11
+ * <span data-kui-swap="to">Get the file</span>
12
+ * </button>
13
+ * ```
14
+ *
15
+ * Both copies are real elements with real text, not `::before`/`content` — a pseudo-element's
16
+ * generated content cannot be selected, cannot be copied, cannot hold markup, and reaches assistive
17
+ * technology inconsistently. For an effect whose entire subject is a *label*, that is the wrong
18
+ * trade at any price, and it would also have burned one of the two contended pseudo boxes (see
19
+ * `border-draw`'s channel note in `interaction.ts`).
20
+ *
21
+ * `from`/`to` rather than document order, so the pair reads the way a keyframe does and so an icon
22
+ * or a `<span class="sr-only">` sitting between the two copies cannot silently reassign the roles.
23
+ *
24
+ * ### Why one effect covers two patterns
25
+ *
26
+ * The "old number slides out, upgraded number slides in" case is the same mechanism with numerals
27
+ * in the two parts, so it needs no name of its own. What it does need is the `-y` axis, which is
28
+ * why vertical is the unsuffixed default: a price or a count changing reads as a vertical roll,
29
+ * where a call-to-action changing its wording reads better horizontally.
30
+ *
31
+ * ### Channels
32
+ *
33
+ * `discrete` because the host rule pins `display: inline-grid` to stack the two copies in one grid
34
+ * cell — the same reason `split-flap`, `feedback-spin` and six others declare it (`display` is
35
+ * tracked as one physical property regardless of the value written into it).
36
+ *
37
+ * `label-swap` is the box token for the marked parts, exactly parallel to `group-dim`'s `group`:
38
+ * the properties this effect actually animates live on a *child* box, so filing them under
39
+ * `translate` would make the compiler refuse `data-kui="lift, masked-label-swap"` — a button that
40
+ * rises on hover while its label swaps — even though the two never touch the same box. Two
41
+ * `masked-label-swap*` names on one host *are* refused, correctly: they share this primitive, so
42
+ * they collide on every channel it declares, and they would fight over one `--kui-label-swap-dx`.
43
+ */
44
+ export declare const LABEL_SWAP_PRIMITIVES: Primitive[];
45
+ /**
46
+ * Three axes, three names, one primitive — so all three share the `--kui-label-swap-*` timing
47
+ * namespace, the schema, the reduced-motion policy and the channel claim, and differ only in the
48
+ * two offset custom properties their own selector sets in `interaction.css`.
49
+ *
50
+ * No `transitions` field. `Preset.transitions` describes properties a preset eases *on its own host
51
+ * box*, and the compiler spends them on the one `--kui-transition` property `base.css` applies to
52
+ * the element carrying `data-kui-fx` — which here is the clip box, which does not move. The parts'
53
+ * `translate` transition is authored directly on their own rule, the same way `group-dim`'s
54
+ * children's is, and it is safe there for the same reason: nothing but this primitive can write to
55
+ * `[data-kui-swap]`, because a second effect that did would be on the `label-swap` channel and
56
+ * refused at compile time before it got there.
57
+ *
58
+ * No `requiresOwnSubtree` either, and that is load-bearing rather than an omission — see
59
+ * `INHERITED_STATE` above for why these rules deliberately carry no combinator, and what `target:`
60
+ * buys once they do not.
61
+ */
62
+ export declare const LABEL_SWAP_PRESETS: Preset[];
63
+ /**
64
+ * Hover intent: nothing happens until the pointer has *rested* for a second, and leaving early
65
+ * cancels it outright.
66
+ *
67
+ * ```html
68
+ * <button data-kui="hover-intent" aria-describedby="t1">Archive
69
+ * <span data-kui-hint id="t1" role="tooltip">Moves to Archive; nothing is deleted</span>
70
+ * </button>
71
+ * ```
72
+ *
73
+ * ### Why this is timing syntax and not a JavaScript primitive
74
+ *
75
+ * The question was live, and the answer turns on one fact about CSS transitions that is easy to
76
+ * assume wrongly: **a `transition-delay` is not a timer that fires regardless.** The transition's
77
+ * parameters are read from the element's *after-change* style, so when the pointer leaves before
78
+ * the delay elapses, the property reverts, the pending transition is cancelled before it ever
79
+ * started, and nothing at all was drawn. Cancel-on-early-leave — the half of hover intent that a
80
+ * plain delay supposedly cannot express — is native behaviour, not something a script has to
81
+ * supply. This file's `INHERITED_STATE` note explains the one extra step needed to get that
82
+ * asymmetric delay onto a *second* box.
83
+ *
84
+ * The claim that `delay:` "starts its clock on the trigger and never cancels" is true, but of the
85
+ * other tier: the library's own `on:hover` activation listens for `pointerenter`/`focusin` and has
86
+ * no un-trigger (`core/activation.ts`), so a JS-activated effect really does keep its appointment
87
+ * after the pointer has gone. That is a property of the activation, not of `delay:`, and the fix
88
+ * for it is not a new primitive here.
89
+ *
90
+ * So the only thing genuinely missing was something *to* delay: section I had twenty-five names
91
+ * that decorate the trigger and not one that reveals a companion, so `delay:` had nothing to be
92
+ * hover intent *about*. This preset is that missing subject, and it is thirty lines of stylesheet.
93
+ *
94
+ * A JavaScript primitive would be the right answer for a different feature — **trajectory** intent,
95
+ * the mega-menu heuristic that keeps a panel open while the pointer is moving *toward* it even
96
+ * though it has technically left. That needs pointer history and a predicted path; no amount of
97
+ * `transition-delay` expresses it, and it should be built when someone wants it rather than
98
+ * smuggled in as the implementation of this one.
99
+ *
100
+ * ### Channels
101
+ *
102
+ * `hint`, its own box token, for the reason `group-dim` gives for `group`: the properties animate a
103
+ * child box, so filing them under `opacity` would refuse `data-kui="fade-in, hover-intent"` — a
104
+ * card that fades itself in and reveals a hint on rest — even though the two never touch the same
105
+ * box.
106
+ *
107
+ * The hint is `pointer-events: none` in every state, deliberately. A revealed panel you can move
108
+ * the pointer *into* is a menu, not a hint: it needs the pointer to be able to leave the trigger
109
+ * without closing it, which is the trajectory problem above. Naming this one non-interactive keeps
110
+ * it honest about which of the two it is.
111
+ */
112
+ export declare const HOVER_INTENT_PRIMITIVES: Primitive[];
113
+ export declare const HOVER_INTENT_PRESETS: Preset[];
114
+ /**
115
+ * A name tag springing out from an avatar on hover, and the same mechanism used to hover a word
116
+ * and pop out an image beside it — one composite, four placements, not two effects.
117
+ *
118
+ * ### Markup
119
+ *
120
+ * ```html
121
+ * <span data-kui="anchored-preview" class="avatar-wrap" tabindex="0">
122
+ * <img class="avatar" src="jane.jpg" alt="">
123
+ * <span data-kui-preview class="name-tag">Jane Doe</span>
124
+ * </span>
125
+ *
126
+ * <span data-kui="anchored-preview-right" tabindex="0">a word
127
+ * <img data-kui-preview src="preview.jpg" alt="">
128
+ * </span>
129
+ * ```
130
+ *
131
+ * The trigger has to be its own focusable, hoverable element — an `<img>` cannot contain the
132
+ * preview, which is why the avatar case wraps both in a `<span tabindex="0">` rather than putting
133
+ * `data-kui` on the `<img>` itself. That is a real markup cost worth naming, and it is the same one
134
+ * `hover-intent`'s own doc comment already pays for a tooltip's trigger.
135
+ *
136
+ * ### Why not CSS anchor positioning
137
+ *
138
+ * This is the textbook use case `anchor()`/`position-anchor` was built for, and it was considered.
139
+ * The project's own planning notes record the owner dropping anchor positioning outright on
140
+ * 2026-08-26, for a reason that is decisive here specifically: it is the only one of the "four
141
+ * modern CSS techniques" listed there with a
142
+ * *harmful* fallback — an unsupported `anchor()` puts the element in the *wrong* place rather than
143
+ * a neutral one — and the two conditions set for reopening it (proof that an anchored position
144
+ * transitions rather than snaps when the anchor moves, and materially better support than the
145
+ * measured 84.12%) are both still open. So this reuses the mechanism `hover-intent` and
146
+ * `masked-label-swap` already ship instead: the trigger's state and geometry travel as *inherited
147
+ * custom properties*, computed once per placement and read by a genuinely generic standalone rule.
148
+ * That mechanism needs no feature detection and no fallback branch, because it already works in
149
+ * every browser the rest of the catalog does.
150
+ *
151
+ * ### Why four preset names and not a `placement:` parameter
152
+ *
153
+ * The same reasoning `label-swap`'s own doc comment gives for its axis: CSS cannot branch on a
154
+ * custom property's *value* without `@container style()`, which is not portable enough to build a
155
+ * shipped effect on. A `placement:` keyword would have to be read by JavaScript, turning a pure
156
+ * stylesheet effect into a JS one for the sake of one word. Four preset names cost nothing extra —
157
+ * they share this one primitive's schema, timing namespace, reduced-motion policy and channel
158
+ * claim — and it is how the rest of the catalog already spells "one mechanism, several fixed
159
+ * shapes" (`masked-label-swap-x`/`-diagonal`, `card-flip-x`/`-y`, `flip-in-x`/`-y`).
160
+ *
161
+ * `anchored-preview` (no suffix) is `top`, matching `hover-intent`'s own default placement for the
162
+ * same reason: a name tag or a tooltip reads naturally above its trigger unless told otherwise.
163
+ *
164
+ * ### How placement reaches a standalone child rule without a combinator
165
+ *
166
+ * Each placement preset's own base rule (no state involved) sets two custom properties on
167
+ * *itself* — `--kui-anchored-preview-inset` (the four-value `inset` shorthand for that side) and
168
+ * `--kui-anchored-preview-travel` (the `translate` value combining the constant centering offset on
169
+ * the cross-axis with the spring distance on the main axis, sign and axis already resolved). Both
170
+ * are precomputed per placement so `[data-kui-preview]` itself stays fully generic: `position:
171
+ * absolute; inset: var(--kui-anchored-preview-inset); translate: var(--kui-anchored-preview-travel)`
172
+ * and nothing placement-specific. No rule anywhere carries a combinator after an fx compound, so
173
+ * `anchored-preview` never enters the reaching-selector set `css-requires-own-subtree.test.ts`
174
+ * derives, needs no `requiresOwnSubtree`, and `target:` relocates all four cleanly.
175
+ *
176
+ * ### Channels
177
+ *
178
+ * `preview`, its own box token, for the reason `hint` and `label-swap` already state: the
179
+ * properties animate a child box, so filing them under `opacity`/`translate`/`scale` would refuse
180
+ * `data-kui="lift, anchored-preview"` — an avatar that lifts on hover while its name tag pops out —
181
+ * even though the two never touch the same box. No pseudo-element is used at all (the tag/image is
182
+ * real, selectable, copyable markup, the same reasoning `masked-label-swap`'s doc comment gives for
183
+ * its own two copies), so this effect has zero pseudo-element collision cost.
184
+ *
185
+ * The preview is `pointer-events: none` in every state, for the identical reason `hover-intent`'s
186
+ * hint is: a revealed panel the pointer can move *into* is a menu, which needs pointer-history
187
+ * "trajectory intent" rather than this mechanism, and should be built when someone wants it rather
188
+ * than smuggled in here.
189
+ */
190
+ export declare const ANCHORED_PREVIEW_PRIMITIVES: Primitive[];
191
+ export declare const ANCHORED_PREVIEW_PRESETS: Preset[];
192
+ /**
193
+ * A search icon that grows into a full input field.
194
+ *
195
+ * ### Markup
196
+ *
197
+ * ```html
198
+ * <label data-kui="search-expand">
199
+ * <svg aria-hidden="true">…</svg>
200
+ * <input data-kui-search-field type="search" placeholder="Search…">
201
+ * </label>
202
+ * ```
203
+ *
204
+ * The host is a `<label>`, not a `<div>`, and that is load-bearing rather than a styling
205
+ * preference: clicking anywhere in a `<label>` — including the icon — natively focuses the
206
+ * `<input>` it contains, with no JavaScript and no matching `for`/`id` pair, which is what lets a
207
+ * collapsed, icon-only control still be a single click away from typing.
208
+ *
209
+ * ### Why this is not a FLIP job
210
+ *
211
+ * A reviewer ruled on this ahead of time (see this task's brief): a width/clip/intrinsic-size
212
+ * transition is sufficient unless the field genuinely *relocates* between two layouts, and this one
213
+ * does not — it grows in place. So the host's own `inline-size` is an ordinary, explicit-length CSS
214
+ * transition (`Preset.transitions`, never a bare stylesheet `transition:` — seeing one there is
215
+ * itself a failure `css-composition-invariants.test.ts`'s "transition channel" suite asserts
216
+ * against), not a measure-before/measure-after/invert-and-play choreography. Both `collapsed` and
217
+ * `width` are explicit lengths for the same reason `interpolate-size: allow-keywords` is not used
218
+ * here at all: the project's own planning notes record that technique as deliberately deferred
219
+ * pending Safari support, and reaching for it on a brand new effect would be an unsanctioned first
220
+ * use of something the owner has already decided to wait on.
221
+ *
222
+ * ### How the input's fade reaches a standalone child rule without a combinator
223
+ *
224
+ * The same `INHERITED_STATE` mechanism `hover-intent` and `anchored-preview` (above) use, for the
225
+ * identical reason: `[data-kui-fx~='search-expand'] input` would be a combinator after the fx
226
+ * compound, forcing `requiresOwnSubtree` and making `target:` a no-op — and unlike `group-dim`,
227
+ * there is no reason to want that here. So the host's `:focus-visible`/`:has(:focus-visible)`/
228
+ * `:hover`/`:has(input:not(:placeholder-shown))` rules set `--kui-search-expand-shown` (0/1) and
229
+ * `--kui-search-expand-lag` (0ms at rest, the authored delay once expanded — the same enter-only
230
+ * asymmetry every hover-intent-shaped rule in this file uses), and the marked field reads both from
231
+ * a *standalone* `[data-kui-search-field]` selector with no fx
232
+ * prefix at all.
233
+ *
234
+ * ### Channels
235
+ *
236
+ * Three, because this primitive owns three distinct boxes' worth of properties: `expand` for the
237
+ * host's own `inline-size` (a channel of its own, the same reasoning `skew`/`border` document —
238
+ * no existing channel names this physical property, and folding it into `layout` would couple this
239
+ * effect's fate to `header-shrink`'s unrelated `padding-block`/`font-size` claim); `discrete`
240
+ * because the host pins `display: inline-flex` unconditionally, the same reason `split-flap` and
241
+ * `masked-label-swap` declare it; and `search-field` for the inherited signals that feed the child
242
+ * input's `opacity`, the same reasoning `hint`/`label-swap` give for their own child-box channels.
243
+ */
244
+ export declare const SEARCH_EXPAND_PRIMITIVES: Primitive[];
245
+ export declare const SEARCH_EXPAND_PRESETS: Preset[];
246
+ /** All four reveal-shaped families, for `interaction.ts`'s catalog-wide exports. */
247
+ export declare const REVEAL_PRIMITIVES: Primitive[];
248
+ export declare const REVEAL_PRESETS: Preset[];
@@ -3,7 +3,13 @@
3
3
  * `cursor-*` family) — kept separate from DOM/timer wiring so the position → transform arithmetic
4
4
  * is assertable without a browser, the same separation `text-shared.ts` uses for its state
5
5
  * machines.
6
+ *
7
+ * `HOVER_TRANSITIONS` at the bottom is an unrelated second concern sharing this file only because
8
+ * `interaction.ts` hit its own 400-line lint ceiling — the same reason `css-invariants.test.ts`
9
+ * split into `css-scan.ts`/`channel-properties.ts`, not a claim that a lookup table belongs beside
10
+ * pointer-tracking arithmetic.
6
11
  */
12
+ import type { ParameterSchema, TransitionSegment } from '../../core/types.js';
7
13
  export interface TiltAngles {
8
14
  rotateX: number;
9
15
  rotateY: number;
@@ -54,3 +60,39 @@ export declare function parallaxOffset(point: LocalPoint, size: ElementSize, str
54
60
  * @overallScore 100
55
61
  */
56
62
  export declare function supportsFineHover(win: Window): boolean;
63
+ /**
64
+ * Transition segments for the five hover-family presets whose motion is a bare host-rule
65
+ * `transition:` in `interaction.css` (`lift`, `pop`, `lift-shadow`, `border-draw`, `border-glow`).
66
+ * The other eight — `shine-sweep`, `split-flap`, `beam-border`, `underline-slide`,
67
+ * `underline-center`, `icon-wiggle`, `icon-spin`, `icon-bounce` — animate via a keyframed
68
+ * `animation:` on `:hover`/`:focus-visible`, or transition a pseudo-element (`::after`) rather than
69
+ * the host box `Preset.transitions` describes, so they declare none here.
70
+ *
71
+ * A lookup keyed by primitive id, not a parallel array, so a typo in one list can't silently pair
72
+ * the wrong segments with the wrong preset — `HOVER_PRESETS` (`interaction.ts`) still derives its
73
+ * name/primitive pair from `HOVER_PRIMITIVES` the same way it always has.
74
+ */
75
+ export declare const HOVER_TRANSITIONS: Partial<Record<string, TransitionSegment[]>>;
76
+ /**
77
+ * The hover names whose motion is a host-level `animation:` in `interaction.css` rather than a
78
+ * transition or a pseudo-element rule — `Preset.delivery`'s one declared value, applied by
79
+ * `HOVER_PRESETS`.
80
+ *
81
+ * Beside {@link HOVER_TRANSITIONS} because it is the same shape of fact about the same family: a
82
+ * per-name detail of how that name's CSS is written, which `HOVER_PRIMITIVES` cannot carry because
83
+ * one primitive backs names on both sides of it.
84
+ *
85
+ * Written out rather than derived, because there is nothing in the effect record to derive it from:
86
+ * `HOVER_TRANSITIONS` does not separate them (`shine-sweep`, `beam-border`, `underline-slide` and
87
+ * `underline-center` are absent from it too, and are safe — they animate a pseudo-element that
88
+ * inline style cannot reach), and neither does the channel list, the renderer, or the parameter
89
+ * set. The fact lives in the stylesheet, so the stylesheet is what audits it:
90
+ * `test/css-composition-invariants.test.ts` derives the true set from `src/css/*.css` with
91
+ * `extractHostAnimationBindings` and fails in both directions, which is what stops this drifting the
92
+ * first time a fifth hover keyframe is added.
93
+ */
94
+ export declare const STYLESHEET_ANIMATED_HOVERS: string[];
95
+ export declare const BEAM_PARAMS: ParameterSchema;
96
+ export declare const SHINE_PARAMS: ParameterSchema;
97
+ export declare const COLOR_PARAMS: Record<string, ParameterSchema>;
98
+ export declare const BORDER_DRAW_PARAMS: ParameterSchema;
@@ -0,0 +1,68 @@
1
+ import type { Preset, Primitive } from '../../core/types.js';
2
+ /**
3
+ * The press state. One primitive, one preset today — the split is the composability the name
4
+ * promises.
5
+ *
6
+ * A future `glass-press`, `press-tilt`, or a brand's own pressed treatment is a second `Preset`
7
+ * row on this same primitive plus its own selector in `interaction.css`: it inherits the
8
+ * `--kui-press-duration/-delay/-ease` namespace (those are keyed on the *primitive* id by
9
+ * `registry.ts`'s `namespaceTiming`), the parameter schema, the reduced-motion policy, and the
10
+ * channel claim, and adds only the declarations that differ. That is the same
11
+ * one-primitive-many-names shape the rest of the catalog uses — 130 of the catalog's names are
12
+ * aliases of this kind — and it is why `press` is the primitive rather than `press-depth`.
13
+ *
14
+ * ### Channels: `scale` and `shadow`, and specifically not `translate`
15
+ *
16
+ * A channel claim is not documentation, it is a veto: `compile.ts`'s `resolveComposition` *drops*
17
+ * every effect after the first when two claims overlap, so an over-broad claim silently deletes
18
+ * an effect an author wrote. `translate` is the claim this primitive could plausibly have made and
19
+ * must not: `lift` owns `translate` and `data-kui="lift, press-depth"` — raise on hover, sink on
20
+ * press — is the single most useful pairing this effect has. Claiming `translate` would make the
21
+ * compiler refuse it and drop the press entirely.
22
+ *
23
+ * That is not a loophole; the sink genuinely does not need `translate`. A press reads as depth
24
+ * through the scale and through the shadow collapsing toward the surface, which is how the two
25
+ * dominant design systems build the same state. What it costs is real and worth naming:
26
+ * `lift-shadow` and `border-glow` both own `shadow`, so composing either with `press-depth` is
27
+ * refused. That refusal is correct rather than incidental — all three write `box-shadow` on the
28
+ * same box in overlapping states, and whichever rule came later in `interaction.css` would win the
29
+ * whole property.
30
+ */
31
+ export declare const PRESS_PRIMITIVES: Primitive[];
32
+ export declare const PRESS_PRESETS: Preset[];
33
+ /**
34
+ * Collective hover: the container recedes, except for the member you are pointing at.
35
+ *
36
+ * ### Why the name is `group-dim`
37
+ *
38
+ * The effect goes on the *container* — `data-kui="group-dim"` on the `<ul>`, the grid, the row —
39
+ * and reads whatever children are already there, so a page using it writes no structural CSS of
40
+ * its own. A name has to be true on the element it is written on, and that rules out the obvious
41
+ * `siblings-dim`/`dim-siblings` spelling: written on a `<ul>`, "siblings" names the `<ul>`'s own
42
+ * siblings, which are the one set of elements this effect never touches. `group-dim` names the
43
+ * container's contents, which is what actually dims.
44
+ *
45
+ * `group-` is also a prefix with room in it. This is the first collective effect in the catalog,
46
+ * and a `group-blur` or `group-desaturate` is the same mechanism with a different property; they
47
+ * would share this primitive's channel and read as one family in the catalog's group column.
48
+ *
49
+ * ### Channels: `group`, a channel of its own
50
+ *
51
+ * Every other channel in the model names properties on the element `data-kui-fx` sits on. This one
52
+ * paints its *children's* `opacity`, which is a different box, and filing it under `opacity` would
53
+ * make the compiler refuse `data-kui="fade-in, group-dim"` — a container that fades itself in and
54
+ * dims its children on hover — even though the two never touch the same box. Declaring the box set
55
+ * the effect actually owns is what keeps the veto pointed at the real collision: a second
56
+ * `group-*` effect on the same container.
57
+ *
58
+ * `requiresOwnSubtree` follows directly and is not optional. `target:` relocates `data-kui-fx` onto
59
+ * whatever a selector matches, and a rule that reaches `> *` compiles to silence when it lands on
60
+ * an element with a different shape beneath it. `compile.ts`'s `liftTarget` refuses to relocate a
61
+ * preset that declares this, and `css-requires-own-subtree.test.ts` re-derives the true set from
62
+ * the shipped stylesheets rather than trusting the flag.
63
+ */
64
+ export declare const GROUP_PRIMITIVES: Primitive[];
65
+ export declare const GROUP_PRESETS: Preset[];
66
+ /** Both state families, for `interaction.ts`'s catalog-wide exports. */
67
+ export declare const STATE_PRIMITIVES: Primitive[];
68
+ export declare const STATE_PRESETS: Preset[];
@@ -0,0 +1,67 @@
1
+ import type { Preset, Primitive } from '../../core/types.js';
2
+ import type { Registry } from '../../core/registry.js';
3
+ export declare const MATERIALS_PRIMITIVES: Primitive[];
4
+ /**
5
+ * The preset name and the primitive id are both `glass` — the same string in two different
6
+ * namespaces, which `registry.ts` keeps as separate maps (`resolve()` looks the authored name up
7
+ * in `presets`, then follows `preset.primitive` into `primitives`), so there is no collision to
8
+ * worry about. Plenty of the catalog already does this (`fade-open`, `cursor-follow`,
9
+ * `rotate-static`, and more) — a preset sharing its primitive's id is the common case, not the
10
+ * exception.
11
+ *
12
+ * Keeping them as two rows rather than one still buys composability: a distinctly-named preset —
13
+ * someone's own house style, say — is a second `Preset` row here plus its own selector in
14
+ * `glass.css`, inheriting the parameter schema, the channel claim, the reduced-motion policy and
15
+ * the perf class rather than restating them. `blur:` and `tint:` already cover the
16
+ * heavier-blur / different-tint variations that used to motivate that point, so those are
17
+ * `glass blur:32px` or `glass tint:navy`, not a second name — but the mechanism is still here for
18
+ * a preset that genuinely needs one. Same one-primitive-many-names shape the rest of the catalog
19
+ * uses, and the reason `press` — not `press-depth` — is the primitive next door.
20
+ *
21
+ * No `transitions` field. `Preset.transitions` describes properties this preset eases on its own
22
+ * host box, and nothing here eases: the surface is painted once and does not change. A composed
23
+ * `press-depth` brings its own `scale`/`box-shadow` segments, which is exactly the division of
24
+ * labour this family is built around.
25
+ *
26
+ * ### No `phase` field either, and this one took real thought
27
+ *
28
+ * None of the four {@link EffectPhase} values (`core/types.ts`) is honest about what `glass` does,
29
+ * and forcing one on would be worse than the unphased default it falls back to:
30
+ *
31
+ * - **Not `entrance`/`exit`.** Both describe a one-shot animation that starts from a hidden or
32
+ * displaced state and *releases* the channel the moment it finishes — that release is what makes
33
+ * `fade-up, lift` safe (see `channels.ts`'s `INDEPENDENT_PHASES`). `glass` never plays anything
34
+ * and never releases `background`/`backdrop`: `prepareGlass` above is a no-op, and the CSS rule
35
+ * in `glass.css` is an unconditional declaration with no keyframe at all. There is no from-state
36
+ * to hide and nothing to hand back to the cascade.
37
+ * - **Not `idle`.** `idle` is specifically an *unbounded animation loop* — a running clock that
38
+ * never yields, tracked in CSS as `--kui-fx-<name>-iterations: infinite` (see `ambient-tint`'s
39
+ * presets in `ambient.ts`, which also claim `background` and are exactly this). `glass` has no
40
+ * clock and no iteration count; it is not looping, it simply is.
41
+ * - **Not `state`.** `state` means the channel is held by a condition the *visitor* changes
42
+ * (`:hover`, `[aria-expanded]`) and released once that condition goes away. `glass`'s hold is
43
+ * unconditional from the moment it activates (`defaultActivation: 'load'`) and never releases —
44
+ * there is no visitor action that ends it.
45
+ *
46
+ * So `glass` holds `background`/`backdrop` exactly the way `idle` does — forever, with no turn to
47
+ * hand off — without being an animation, which is the one fact every {@link EffectPhase} value
48
+ * assumes. Declaring `idle` anyway was the tempting shortcut, since it happens to be inert against
49
+ * today's `INDEPENDENT_PHASES` table (`idle` is not a member of either exempted pair, so it would
50
+ * conflict with everything exactly as unphased does) — but "inert today" is not the same as
51
+ * "true", and a false claim sitting on the record is exactly what costs someone a real bug the day
52
+ * that table grows a third exemption. Leaving `phase` undeclared is the honest answer *and* the
53
+ * correct one operationally: an unphased claim conflicts with any other claimant on
54
+ * `background`/`backdrop`, which is exactly right, because anything else painting on that channel
55
+ * *forever* — another material, an `idle` ambient loop like `scanline` — is a genuine, permanent
56
+ * fight over the same property, not two effects taking turns. See
57
+ * `test/catalog-phase-materials.test.ts` for the compiled proof.
58
+ */
59
+ export declare const MATERIALS_PRESETS: Preset[];
60
+ /**
61
+ * Register catalog section S (materials) into a registry.
62
+ *
63
+ * @param registry - Registry to populate.
64
+ * @returns The same registry, for chaining.
65
+ * @complexity O(n) time in registered primitives and presets; O(1) extra space.
66
+ */
67
+ export declare function registerMaterials(registry: Registry): Registry;
@@ -35,9 +35,10 @@ export declare function slatOrder(index: number, count: number, from: SlatFrom):
35
35
  * Resolve the authored `angle:` to degrees, falling back to whatever `axis:` names.
36
36
  *
37
37
  * `angle:` is the general parameter and `axis:` the two-keyword shorthand that predates it, so an
38
- * authored angle wins and an absent one reads the axis. Both `45` and `45deg` are accepted, as are
38
+ * authored angle wins and an absent one reads the axis. `45`, `45d` and `45deg` are accepted, as are
39
39
  * `turn` and `rad`, because an author who writes an angle should not have to remember which of the
40
- * three this particular parameter takes.
40
+ * three this particular parameter takes. `core/params.ts` takes the same three spellings for every
41
+ * other `type: 'angle'` parameter.
41
42
  *
42
43
  * Normalised to `[0, 180)`: a band at 200° is the same set of bands as one at 20°, only numbered
43
44
  * from the other end, and collapsing that here means the geometry below never has to think about
@@ -10,8 +10,10 @@ export interface CountLayers {
10
10
  /** `aria-hidden` node the ticking display writes to on every frame. */
11
11
  decorative: HTMLElement;
12
12
  /**
13
- * Visually-hidden twin. The caller must write this exactly once, on completion — never
14
- * mid-tick — so a screen reader is told the final value and nothing in between.
13
+ * Visually-hidden twin. Holds the final value from the moment it is installed and never
14
+ * changes again — the number is content, not a status update, so there is nothing to announce
15
+ * mid-count (design.md §12's "no aria-live spam from counters"). The caller writes it once, at
16
+ * install, from the same `to`/`format` the tween itself will settle on.
15
17
  */
16
18
  srOnly: HTMLElement;
17
19
  /**
@@ -23,12 +25,13 @@ export interface CountLayers {
23
25
  }
24
26
  /**
25
27
  * Replace an element's content with an `aria-hidden` ticking display plus a visually-hidden twin,
26
- * both empty until the caller populates them, so a counter's mid-flight text is never read aloud
27
- * and its `aria-live` region changes exactly once.
28
+ * both empty until the caller populates them. The decorative layer is free to tick every frame;
29
+ * the SR-only twin is meant to be written once, with the counter's final value, and never touched
30
+ * again — a counter is content, not a live status, so it carries no `aria-live`.
28
31
  *
29
32
  * @param el - Element whose content is being taken over. Assumed to hold no meaningful children.
30
33
  * @param doc - Document to create nodes in, rather than the ambient global.
31
- * @returns The decorative node to tick, the SR-only node to finalize once, and a cleanup.
34
+ * @returns The decorative node to tick, the SR-only node to populate once, and a cleanup.
32
35
  * @complexity O(1) time and space.
33
36
  * @overallScore 100
34
37
  */
@@ -1,2 +1,2 @@
1
- /** Re-exported for same-directory catalog siblings; the canonical definition lives in `effects/shared.ts` — neutral ground for every CSS-keyframe category, not just `catalog/`. */
2
- export { cssPrimitive } from '../shared.js';
1
+ /** Re-exported for same-directory catalog siblings; the canonical definitions live in `effects/shared.ts` — neutral ground for every effect category, not just `catalog/`. */
2
+ export { cssPrimitive, TIMELINE_AGNOSTIC, TRIGGER_DELAY_PARAM, withTimingContract, } from '../shared.js';
@@ -1,9 +1,30 @@
1
- import type { Cleanup, EffectParams } from '../../core/types.js';
1
+ import type { Cleanup, EffectParams, EffectSpec, EffectVariant, ParameterSchema } from '../../core/types.js';
2
2
  /** How `split-text` breaks a string into decorative pieces. */
3
3
  export type SplitUnit = 'chars' | 'words' | 'lines';
4
+ /**
5
+ * Selector for the elements a content-mutating text/number effect must never remove from the DOM:
6
+ * a link, a form control, anything focusable or already interactive. Every primitive that takes
7
+ * over an element's text (`installSplitLayers`, `installCountLayers`, `word-cycler`) flattens
8
+ * markup into plain text or a captured-and-replaced subtree, and a link caught inside that is gone
9
+ * from the page for as long as the effect runs — for a one-shot effect that never settles back,
10
+ * permanently. Losing a link costs SEO; losing a focusable control is worse, because it costs a
11
+ * keyboard user access to it.
12
+ */
13
+ export declare const INTERACTIVE_DESCENDANT = "a[href], button, input, select, textarea, [tabindex], [contenteditable], details, summary, iframe, audio[controls], video[controls]";
14
+ /**
15
+ * Whether `el` contains an interactive descendant a content-mutating effect would otherwise
16
+ * remove or flatten.
17
+ *
18
+ * @param el - Host element the effect is about to take over.
19
+ * @complexity O(n) time in descendant count (native `querySelector`); O(1) space.
20
+ * @overallScore 100
21
+ */
22
+ export declare function hasInteractiveDescendant(el: Element): boolean;
4
23
  export interface SplitLayers {
5
24
  /** `aria-hidden` container the caller populates with decorative markup or text. */
6
25
  decorative: HTMLElement;
26
+ /** The accessible reading copy while the decorative pieces are animating. */
27
+ srOnly: HTMLElement;
7
28
  /** The element's text at the moment splitting began, trimmed for display. */
8
29
  originalText: string;
9
30
  /**
@@ -19,7 +40,9 @@ export interface SplitLayers {
19
40
  * screen reader ever seeing an incomplete or garbled read of the content.
20
41
  *
21
42
  * @param el - Element whose text is being taken over. Assumed to hold plain text (no children) —
22
- * every DOM-surgery primitive in this module shares that constraint.
43
+ * every DOM-surgery primitive in this module shares that constraint. Callers are expected to
44
+ * have already checked {@link hasInteractiveDescendant} and bailed out if it is true; this
45
+ * function itself does not guard, so a caller that skips the check still flattens a link.
23
46
  * @param doc - Document to create nodes in, rather than the ambient global, so callers can point
24
47
  * this at a test document.
25
48
  * @returns The decorative layer to populate, the captured text, and a cleanup.
@@ -117,29 +140,39 @@ export declare function createStepRunner(win: Window, options: StepRunOptions):
117
140
  * wrapping the space itself, the same way `appendWordSpans` already leaves inter-word whitespace
118
141
  * unwrapped.
119
142
  *
143
+ * Per the CSS Text line-breaking spec, browsers insert a line-break opportunity between any two
144
+ * adjacent atomic inline-level boxes, even without whitespace between them — so a bare run of
145
+ * `.kui-split-item` spans could wrap mid-word. Each word's char-spans are nested inside a shared
146
+ * `.kui-split-word` wrapper (itself `display: inline-block`) so the only break opportunities left
147
+ * are between words, same as the source text.
148
+ *
120
149
  * @complexity O(n) time and space in grapheme count.
121
150
  * @overallScore 100
122
151
  */
123
152
  export declare function appendCharSpans(container: Element, doc: Document, text: string): HTMLElement[];
124
153
  /**
125
- * Split text into one span per word, leaving the whitespace and punctuation between them as plain
126
- * text nodes so natural line-wrapping and spacing survive untouched.
154
+ * Split text into animated words. Opening punctuation joins the next word; other punctuation
155
+ * joins the preceding word. A punctuation run without a word gets its own item. Only whitespace
156
+ * stays outside the items, preserving natural wrapping and spacing.
127
157
  *
128
158
  * @complexity O(n) time and space in segment count.
129
159
  * @overallScore 100
130
160
  */
131
161
  export declare function appendWordSpans(container: Element, doc: Document, text: string): HTMLElement[];
132
162
  /**
133
- * Split text into one span per visual line.
134
- *
135
- * Lines are not a text property — they only exist after wrapping — so this measures word spans
136
- * already laid out in the live DOM, then regroups their nodes (words and the whitespace between
137
- * them) into per-line containers.
138
- *
163
+ * Split text into measured visual lines.
139
164
  * @complexity O(n) time and space in word-span count.
140
165
  * @overallScore 100
141
166
  */
142
167
  export declare function appendLineSpans(container: Element, doc: Document, text: string): HTMLElement[];
168
+ /**
169
+ * Put existing words and whitespace back into natural flow, measure, and wrap the new lines.
170
+ * The offsetTop reads in wrapMeasuredLines finish before it writes any line wrappers.
171
+ *
172
+ * @complexity O(n) time and space in word-span count.
173
+ * @overallScore 100
174
+ */
175
+ export declare function rewrapLineSpans(container: Element, doc: Document): HTMLElement[];
143
176
  /**
144
177
  * Split text by the requested unit.
145
178
  *
@@ -181,3 +214,52 @@ export declare const SCRAMBLE_CHARSETS: Record<string, string>;
181
214
  * @overallScore 100
182
215
  */
183
216
  export declare function scrambledFrame(graphemes: string[], resolved: number, charset: string, random: () => number): string;
217
+ /**
218
+ * The generic variable-font axis: `data-kui="var-axis axis:GRAD from:0 to:150"`.
219
+ *
220
+ * `var-weight`/`var-width`/`var-slant` cover the three axes CSS gave a high-level property to.
221
+ * Everything else a real type system exposes — Fraunces' `SOFT` and `WONK`, Recursive's `CASL`,
222
+ * `MONO` and `CRSV`, Roboto Flex's `GRAD` and `XOPQ`, `opsz` on almost everything modern — has no
223
+ * property of its own and can only be reached through `font-variation-settings`. Three hardcoded
224
+ * names cannot grow to meet that; a tag parameter can.
225
+ *
226
+ * `from`/`to` are deliberately unbounded numbers. Axis ranges are a property of the *font*, not of
227
+ * CSS — `wght` is 1–1000, `opsz` is typically 8–144, `GRAD` runs −200 to 150, `CASL` is 0–1 — so
228
+ * any `minimum`/`maximum` this schema could state would be wrong for most axes and would reject
229
+ * legitimate values. The renderer already handles the out-of-range case correctly: CSS Fonts 4
230
+ * clamps an axis value to the range the font declares, so an over-large `to:` reads as "all the way
231
+ * to that axis's maximum" rather than as a broken declaration.
232
+ *
233
+ * The defaults are weight-shaped (100 → 900) because `wght` is far and away the most common axis
234
+ * and a bare `data-kui="var-axis"` should do something recognisable. On a font with no `wght` axis
235
+ * that is a no-op, which is also what an unsupported axis should look like.
236
+ */
237
+ export declare const varAxisParams: ParameterSchema;
238
+ /**
239
+ * Turn an authored `axis:GRAD` into the quoted CSS string the keyframe needs, and into a schema
240
+ * that accepts exactly that one value.
241
+ *
242
+ * This is the whole reason `var-axis` can be generic while every parameter in the library is
243
+ * validated against a closed grammar. `ParamSpec` has no "four-character font tag" type, and the
244
+ * types that *are* open (`text`) are dropped before the stylesheet precisely because they are open
245
+ * — so a tag cannot reach CSS as an ordinary parameter at all. What `variantFor` adds is the
246
+ * ability to decide the grammar *per spec*: the tag is checked against {@link AXIS_TAG} here, then
247
+ * declared as the sole legal keyword, so `resolveParams` still runs its full escape screen over a
248
+ * value it can only accept if it is identical to the one already vetted. Two independent gates, and
249
+ * neither is loosened — this narrows the accepted set to one string rather than widening it.
250
+ *
251
+ * The `tween` primitive uses the same mechanism for the same class of reason: one authored key
252
+ * (`x:'0,100,40'`) expanding into parameters the primitive could not have declared statically. See
253
+ * `EffectVariant.schema` in `core/types.ts`.
254
+ *
255
+ * A rejected tag is *dropped*, not substituted. Rewriting it to the default would animate the
256
+ * wrong axis under a name the author did not write, which is the silent-wrong-behaviour outcome
257
+ * this codebase treats as worse than doing nothing; dropping it leaves the `var()` fallback in
258
+ * force and the warning is the only account of what happened.
259
+ *
260
+ * @param spec - The authored spec, untouched — `compile` recompiles the same value on every rescan.
261
+ * @param warn - Diagnostic sink, so a mistyped tag is named rather than silently ignored.
262
+ * @returns A variant carrying the quoted tag and its one-value schema, or an empty refinement.
263
+ * @complexity O(p) time and space in the spec's parameter count, for the copy.
264
+ */
265
+ export declare function varAxisVariant(spec: EffectSpec, warn: (message: string) => void): EffectVariant;