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
|
@@ -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.
|
|
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.
|
|
14
|
-
*
|
|
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
|
|
27
|
-
*
|
|
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
|
|
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
|
|
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
|
|
126
|
-
*
|
|
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
|
|
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;
|