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,260 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Viewport gates — the `above:` / `below:` keys in the `data-kui` grammar.
|
|
3
|
+
*
|
|
4
|
+
* The gap this closes: there was no way to say "fade-up on desktop, nothing on mobile". `matchMedia`
|
|
5
|
+
* appeared exactly once in the whole source (`capabilities.ts`, for `prefers-reduced-motion`), and
|
|
6
|
+
* GSAP's answer — `gsap.matchMedia()` — is a runtime that also has to tear an animation down when a
|
|
7
|
+
* breakpoint changes underneath it.
|
|
8
|
+
*
|
|
9
|
+
* **A breakpoint is a media query, so the gate is compiled into CSS rather than evaluated in JS.**
|
|
10
|
+
* `gatedAnimationName` wraps a track's keyframe ident in a `var()` whose custom property is
|
|
11
|
+
* declared `none` outside the gate and guaranteed-invalid inside it (see the `kui.tokens` block in
|
|
12
|
+
* `base.css`). The browser re-resolves that on every resize with no script running, so there is
|
|
13
|
+
* nothing to observe, nothing to tear down, and nothing to get out of sync — the whole class of
|
|
14
|
+
* problem `gsap.matchMedia()` exists to manage simply does not arise. The cost is that the
|
|
15
|
+
* vocabulary has to be *closed*: a static stylesheet cannot hold a width the author invents, so
|
|
16
|
+
* `above:900px` is refused and only the five names below are accepted.
|
|
17
|
+
*
|
|
18
|
+
* ## Why these five names
|
|
19
|
+
*
|
|
20
|
+
* They are Tailwind v4's default scale, unchanged. This repo already builds `demo/tailwind.css`
|
|
21
|
+
* from its own `tailwindcss` dependency and every demo page authors against `sm`/`md`/`lg`, so
|
|
22
|
+
* adopting that scale means the library is matching a scale the project already uses rather than
|
|
23
|
+
* inventing a sixth one it would then have to own forever. `rem` rather than `px` for the reason
|
|
24
|
+
* Tailwind moved: the breakpoints then track the visitor's root font size, so a reader who has
|
|
25
|
+
* scaled their text up crosses into the "narrow" treatment at the point their layout actually
|
|
26
|
+
* reflows.
|
|
27
|
+
*
|
|
28
|
+
* ## Why `above` is inclusive and `below` is not
|
|
29
|
+
*
|
|
30
|
+
* `above:md` is `width >= md` and `below:md` is `width < md`, so the pair tiles the axis with
|
|
31
|
+
* neither a gap nor an overlap: `data-kui="fade-up below:md, parallax above:md"` runs exactly one
|
|
32
|
+
* of the two at every possible viewport width, including at exactly 768px. Splitting the boundary
|
|
33
|
+
* any other way makes one width either run both effects or run none, which is the classic
|
|
34
|
+
* `min-width`/`max-width` off-by-one — and is why this does *not* spell itself `min:`/`max:`, since
|
|
35
|
+
* CSS's own `max-width` is inclusive and would reintroduce it.
|
|
36
|
+
*
|
|
37
|
+
* ## Why `min-width` and not the range syntax
|
|
38
|
+
*
|
|
39
|
+
* `(width >= 48rem)` is Media Queries 4 and reads better, but a browser that does not parse it
|
|
40
|
+
* drops the whole block — which here means `--kui-above-md` keeps its `none` default and every
|
|
41
|
+
* `above:` gate on the page is silently off forever. `(min-width: 48rem)` has no such cliff. The
|
|
42
|
+
* shipped stylesheet and this module must agree exactly, so both use the same spelling.
|
|
43
|
+
*/
|
|
44
|
+
/**
|
|
45
|
+
* The scale. Ordered narrowest to widest — `breakpointRank` depends on the declaration order, and
|
|
46
|
+
* `parse.ts` uses that ordering to refuse a band that can never match.
|
|
47
|
+
*/
|
|
48
|
+
export declare const BREAKPOINTS: {
|
|
49
|
+
readonly sm: "40rem";
|
|
50
|
+
readonly md: "48rem";
|
|
51
|
+
readonly lg: "64rem";
|
|
52
|
+
readonly xl: "80rem";
|
|
53
|
+
readonly '2xl': "96rem";
|
|
54
|
+
};
|
|
55
|
+
export type Breakpoint = keyof typeof BREAKPOINTS;
|
|
56
|
+
/** In scale order, for `breakpointRank` and for naming the accepted values in a diagnostic. */
|
|
57
|
+
export declare const BREAKPOINT_NAMES: Breakpoint[];
|
|
58
|
+
/**
|
|
59
|
+
* A segment's condition, lifted onto the spec by `parse.ts`. Two independent axes:
|
|
60
|
+
*
|
|
61
|
+
* - `above`/`below` — the **viewport** condition, `data-kui="fade-up above:md"`. Compiled into
|
|
62
|
+
* `:root`-scoped custom properties in `base.css` (see `gatedAnimationName`), so the browser
|
|
63
|
+
* re-decides on every resize with no script involved.
|
|
64
|
+
* - `wide`/`narrow` — the **container** condition, `data-kui="fade-up wide:md"`, decided against
|
|
65
|
+
* the element's nearest `data-kui-container` ancestor rather than the viewport. Neither axis
|
|
66
|
+
* subsumes the other: `above:md` answers "is the window at least 768px", `wide:md` answers "is
|
|
67
|
+
* the box I sit in at least 768px", and a component reused at more than one width on the same
|
|
68
|
+
* page needs the second question. `wide`/`narrow` compile the same way `above`/`below` do
|
|
69
|
+
* (`gatedAnimationName` wraps both axes), with one deliberate asymmetry in their CSS
|
|
70
|
+
* defaults — see the `@container` block in `base.css` for why.
|
|
71
|
+
*
|
|
72
|
+
* All four keys may be present at once, each pair forming a band the same way: `above:md
|
|
73
|
+
* below:xl` runs only between the two, and `wide:md narrow:xl` does the same against the
|
|
74
|
+
* container. That falls out of the CSS for free — the `var()`s nest arbitrarily deep — so bands
|
|
75
|
+
* are supported rather than refused on either axis.
|
|
76
|
+
*/
|
|
77
|
+
export interface EffectGate {
|
|
78
|
+
above?: Breakpoint;
|
|
79
|
+
below?: Breakpoint;
|
|
80
|
+
wide?: Breakpoint;
|
|
81
|
+
narrow?: Breakpoint;
|
|
82
|
+
}
|
|
83
|
+
export type GateDirection = keyof EffectGate;
|
|
84
|
+
/**
|
|
85
|
+
* The other direction on this one's axis — `above` pairs with `below`, `wide` pairs with `narrow`.
|
|
86
|
+
* Used wherever a check has to reason about a *band* rather than a single direction, so a new axis
|
|
87
|
+
* only ever needs an entry in {@link GATE_AXES} rather than a new branch at every call site.
|
|
88
|
+
*
|
|
89
|
+
* @complexity O(1) time (two axes, checked in a fixed order); O(1) space.
|
|
90
|
+
* @overallScore 100
|
|
91
|
+
*/
|
|
92
|
+
export declare function axisOf(direction: GateDirection): readonly [GateDirection, GateDirection];
|
|
93
|
+
/**
|
|
94
|
+
* Whether a token names a breakpoint on the scale.
|
|
95
|
+
*
|
|
96
|
+
* `Object.hasOwn`, not `value in BREAKPOINTS` and not a truthiness test on the lookup: the value
|
|
97
|
+
* comes straight off an author-written attribute, so `above:constructor` and `above:__proto__`
|
|
98
|
+
* both resolve to an inherited value on `Object.prototype` and would be accepted as breakpoints.
|
|
99
|
+
* The same trap `parse.ts` documents on its `HOISTS` table.
|
|
100
|
+
*
|
|
101
|
+
* @complexity O(1) time and space.
|
|
102
|
+
* @overallScore 100
|
|
103
|
+
*/
|
|
104
|
+
export declare function isBreakpoint(value: string): value is Breakpoint;
|
|
105
|
+
/**
|
|
106
|
+
* Position on the scale, narrowest first.
|
|
107
|
+
*
|
|
108
|
+
* @complexity O(b) time in the scale's length — five; O(1) space.
|
|
109
|
+
* @overallScore 100
|
|
110
|
+
*/
|
|
111
|
+
export declare function breakpointRank(name: Breakpoint): number;
|
|
112
|
+
/**
|
|
113
|
+
* The custom property one direction/breakpoint pair is switched by. Shared by both axes — `above`/
|
|
114
|
+
* `below` name a `@media`-declared switch, `wide`/`narrow` name an `@container`-declared one — the
|
|
115
|
+
* spelling is identical either way, so one function serves both rather than a `containerProperty`
|
|
116
|
+
* duplicate of it.
|
|
117
|
+
*
|
|
118
|
+
* This name is a contract with `src/css/base.css`, which declares all twenty (ten per axis).
|
|
119
|
+
* Changing the spelling here without changing it there compiles a `var()` that resolves to
|
|
120
|
+
* nothing, and a `var()` that resolves to nothing falls through to its fallback — so every gate
|
|
121
|
+
* would silently be *on* everywhere, which is the failure a reader would be least likely to
|
|
122
|
+
* notice. `test/breakpoints.test.ts` asserts the two agree.
|
|
123
|
+
*
|
|
124
|
+
* @complexity O(1) time and space.
|
|
125
|
+
* @overallScore 100
|
|
126
|
+
*/
|
|
127
|
+
export declare function gateProperty(direction: GateDirection, breakpoint: Breakpoint): string;
|
|
128
|
+
/**
|
|
129
|
+
* The media query one breakpoint is switched at. Deliberately only ever `min-width`: the `below`
|
|
130
|
+
* half is the *negation* of this same query rather than a `max-width` of its own, so the two halves
|
|
131
|
+
* cannot drift apart at the boundary. See the header note on inclusivity.
|
|
132
|
+
*
|
|
133
|
+
* @complexity O(1) time and space.
|
|
134
|
+
* @overallScore 100
|
|
135
|
+
*/
|
|
136
|
+
export declare function breakpointQuery(breakpoint: Breakpoint): string;
|
|
137
|
+
/**
|
|
138
|
+
* A track's `animation-name` value, gated.
|
|
139
|
+
*
|
|
140
|
+
* `kui-in-up` becomes `var(--kui-above-md, kui-in-up)`. Outside the gate that property holds
|
|
141
|
+
* `none`, so the track is a named-nothing entry in the composed `animation-name` list: its
|
|
142
|
+
* duration, delay, easing and fill-mode entries all still sit at the same index and are simply
|
|
143
|
+
* ignored, which is what keeps a *composed* element's parallel longhand lists aligned. Inside the
|
|
144
|
+
* gate the property is the guaranteed-invalid value and `var()` falls through to the real ident.
|
|
145
|
+
*
|
|
146
|
+
* A gated-off track leaves nothing behind: with no animation there is no `animation-fill-mode:
|
|
147
|
+
* both` holding a from-state, so the element paints at its ordinary rest state. That is the
|
|
148
|
+
* property that makes "nothing on mobile" safe — the zero-area presets that have historically
|
|
149
|
+
* stranded elements did so through a from-state that never got released, and there is no
|
|
150
|
+
* from-state here to release.
|
|
151
|
+
*
|
|
152
|
+
* A band nests the two, and the order matters only for readability: `above` outermost reads as
|
|
153
|
+
* "not wide enough → off; otherwise, too wide → off; otherwise run". `wide`/`narrow` nest the same
|
|
154
|
+
* way, innermost, so all four conditions the author wrote — viewport band and container band
|
|
155
|
+
* together — AND together through the same chain of `var()` fallbacks: any one of them resolving
|
|
156
|
+
* to `none` neutralises the whole expression, regardless of which axis it came from.
|
|
157
|
+
*
|
|
158
|
+
* @complexity O(1) time and space.
|
|
159
|
+
* @overallScore 100
|
|
160
|
+
*/
|
|
161
|
+
export declare function gatedAnimationName(keyframes: string, gate: EffectGate | undefined): string;
|
|
162
|
+
/**
|
|
163
|
+
* Whether a gate is currently satisfied — the JavaScript mirror of the CSS above.
|
|
164
|
+
*
|
|
165
|
+
* Needed *only* for JavaScript-rendered segments. A `css-keyframes` segment is gated by the
|
|
166
|
+
* compiled `var()` and never consults this; a JS-rendered one emits no `animation-name` at all, so
|
|
167
|
+
* there is nothing for a stylesheet to neutralise and the decision has to be made here. That is
|
|
168
|
+
* the whole of the JS fallback, and it is a stated limit rather than a hidden one: gating is free
|
|
169
|
+
* for the large majority of the catalog that compiles to keyframes, and costs one `matchMedia`
|
|
170
|
+
* read plus one `change` listener for the rest.
|
|
171
|
+
*
|
|
172
|
+
* **Fails open.** No `matchMedia` — a server render, a bare Node runtime, jsdom, which has none —
|
|
173
|
+
* means the effect runs. The alternative is an environment with no viewport deciding that every
|
|
174
|
+
* gated effect is off, which is a silent no-op, and "never a silent no-op" is the one promise the
|
|
175
|
+
* grammar makes everywhere else.
|
|
176
|
+
*
|
|
177
|
+
* Reads only the viewport axis (`above`/`below`). The container axis (`wide`/`narrow`) has no
|
|
178
|
+
* equivalent here on purpose — there is no `matchContainer()`, and building one is a
|
|
179
|
+
* `ResizeObserver` per container plus a re-entrancy-safe notify path for one attribute. A
|
|
180
|
+
* JavaScript-rendered primitive refuses a container gate at compile time instead of faking it (see
|
|
181
|
+
* `refuseContainerGate` in `compile.ts`), so a gate reaching this function has already had `wide`/
|
|
182
|
+
* `narrow` stripped if it ever had them — there is nothing left for this function to ignore.
|
|
183
|
+
*
|
|
184
|
+
* @param gate - The segment's condition; `undefined` is an ungated segment and always matches.
|
|
185
|
+
* @param win - Window to query. Injected rather than reached for, exactly as
|
|
186
|
+
* `effects/catalog/interaction-shared.ts` injects it for its `(hover: hover)` probe.
|
|
187
|
+
* @complexity O(1) time and space — at most two media queries.
|
|
188
|
+
* @overallScore 100
|
|
189
|
+
*/
|
|
190
|
+
export declare function gateMatches(gate: EffectGate | undefined, win: Window | undefined): boolean;
|
|
191
|
+
/**
|
|
192
|
+
* Whether two gates can ever be satisfied at the same time, on both axes at once.
|
|
193
|
+
*
|
|
194
|
+
* Read by `channels.ts`: two effects that are never live together cannot collide on a channel, so
|
|
195
|
+
* `fade-up below:md, parallax-y above:md` composes even though both own `translate`. Without this
|
|
196
|
+
* the compiler would refuse that pair — the exact list the gate exists to make expressible — and
|
|
197
|
+
* silently drop its second half at every width. `wide:md` and `above:md` are independent
|
|
198
|
+
* conditions — a wide container inside a narrow viewport is ordinary layout — so overlap requires
|
|
199
|
+
* agreement on **both** axes: two gates that are disjoint on either one alone can never be live
|
|
200
|
+
* together, however the other axis compares.
|
|
201
|
+
*
|
|
202
|
+
* Each direction is a half-open interval over the scale's *ranks*, which is exact because the only
|
|
203
|
+
* boundaries that exist are the five breakpoints themselves: `above:X`/`wide:X` starts at `rank(X)`
|
|
204
|
+
* and an absent upper bound starts below the narrowest name; `below:Y`/`narrow:Y` ends at `rank(Y)`
|
|
205
|
+
* and an absent lower bound runs past the widest. Half-open is what makes the boundary come out
|
|
206
|
+
* right — `[below:md)` ends exactly where `[above:md)` begins, so the two touch without
|
|
207
|
+
* overlapping, which is the same property that makes a complementary pair tile an axis.
|
|
208
|
+
*
|
|
209
|
+
* @complexity O(b) time in the scale's length; O(1) space.
|
|
210
|
+
* @overallScore 100
|
|
211
|
+
*/
|
|
212
|
+
export declare function gatesOverlap(a: EffectGate | undefined, b: EffectGate | undefined): boolean;
|
|
213
|
+
/**
|
|
214
|
+
* Breakpoints a set of gates depends on, deduplicated. Empty for an ungated list.
|
|
215
|
+
*
|
|
216
|
+
* Only ever called on `EffectGate`s already filtered to JavaScript-rendered effects
|
|
217
|
+
* (`Animator.applyViewportGates`), and those never carry `wide`/`narrow` — `refuseContainerGate`
|
|
218
|
+
* in `compile.ts` strips both before a gate reaches `plan.jsEffects`. Reading only `above`/`below`
|
|
219
|
+
* here is therefore not a gap, it mirrors `gateMatches`'s own scope.
|
|
220
|
+
*
|
|
221
|
+
* @complexity O(g) time in the gate count; O(b) space, bounded by the scale's five names.
|
|
222
|
+
* @overallScore 100
|
|
223
|
+
*/
|
|
224
|
+
export declare function breakpointsIn(gates: readonly EffectGate[]): Breakpoint[];
|
|
225
|
+
export interface GateWatcher {
|
|
226
|
+
/**
|
|
227
|
+
* Re-notify about `el` whenever one of `breakpoints` is crossed. An empty list unwatches, so a
|
|
228
|
+
* caller can pass whatever it computed without branching first.
|
|
229
|
+
*/
|
|
230
|
+
watch(el: Element, breakpoints: readonly Breakpoint[]): void;
|
|
231
|
+
unwatch(el: Element): void;
|
|
232
|
+
destroy(): void;
|
|
233
|
+
}
|
|
234
|
+
/**
|
|
235
|
+
* Watch the breakpoints a page's JavaScript-rendered gates actually name.
|
|
236
|
+
*
|
|
237
|
+
* Only JS-rendered gates ever reach here (see `gateMatches`), so on a page whose gates are all on
|
|
238
|
+
* CSS effects — the common case — nothing is ever watched and no listener is ever bound. The cost
|
|
239
|
+
* of this feature is genuinely zero until a `pin-section below:md` asks for it.
|
|
240
|
+
*
|
|
241
|
+
* The reaction to a crossing is deliberately "tell the caller about the element" rather than
|
|
242
|
+
* anything cleverer: the animator already has exact teardown (`release`) and exact reinstall
|
|
243
|
+
* (`process`), both used on every attribute edit, so a breakpoint change reuses the path that is
|
|
244
|
+
* already exercised rather than inventing a second, partial one. That is also what makes the
|
|
245
|
+
* transition correct at the boundary — a JS effect that was running is destroyed through its own
|
|
246
|
+
* `EffectInstance.destroy()` and its style writes unwound by the ledger before the new plan is
|
|
247
|
+
* installed, so nothing is left half-applied.
|
|
248
|
+
*
|
|
249
|
+
* A single `Map` of element → breakpoints rather than a reverse index per breakpoint: the map only
|
|
250
|
+
* ever holds elements with a gated JS effect, so a linear scan on a crossing is a handful of
|
|
251
|
+
* entries, and a second index would be more state to keep consistent than the scan costs.
|
|
252
|
+
*
|
|
253
|
+
* @param win - Window to bind against; `undefined`, or one with no `matchMedia`, yields a watcher
|
|
254
|
+
* that accepts every call and does nothing. Install-time gating still applies in that case; only
|
|
255
|
+
* live re-evaluation is dropped.
|
|
256
|
+
* @param onChange - Called with each affected element when a watched breakpoint is crossed.
|
|
257
|
+
* @complexity O(1) per `watch`/`unwatch`; O(w) per crossing in the number of watched elements.
|
|
258
|
+
* @overallScore 100
|
|
259
|
+
*/
|
|
260
|
+
export declare function createGateWatcher(win: Window | undefined, onChange: (el: Element) => void): GateWatcher;
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import type { Registry } from './registry.js';
|
|
2
|
+
import type { Reporter } from './reporter.js';
|
|
3
|
+
import type { ParsedValue } from './types.js';
|
|
4
|
+
/**
|
|
5
|
+
* Author-defined bundles for one animator root.
|
|
6
|
+
*
|
|
7
|
+
* A class rather than a closure for the same reason {@link Registry} is one: it is a table with a
|
|
8
|
+
* handful of operations on it, and the operations read better named than nested.
|
|
9
|
+
*/
|
|
10
|
+
export declare class BundleTable {
|
|
11
|
+
private readonly registry;
|
|
12
|
+
private readonly bundles;
|
|
13
|
+
/**
|
|
14
|
+
* @param registry - Consulted for name collisions only; a bundle never enters it.
|
|
15
|
+
*/
|
|
16
|
+
constructor(registry: Registry);
|
|
17
|
+
/**
|
|
18
|
+
* Register every definition in a subtree, including the subtree root itself.
|
|
19
|
+
*
|
|
20
|
+
* @returns How many *new* names were registered, so the caller can decide whether anything it
|
|
21
|
+
* already compiled deserves a second chance.
|
|
22
|
+
* @complexity O(n) time in the subtree's element count; O(d) space in definition count.
|
|
23
|
+
* @overallScore 100
|
|
24
|
+
*/
|
|
25
|
+
collect(root: ParentNode, reporter: Reporter): number;
|
|
26
|
+
/**
|
|
27
|
+
* Rewrite bundle references into the segments they stand for.
|
|
28
|
+
*
|
|
29
|
+
* @returns A new `ParsedValue`; the argument is returned untouched when nothing is defined.
|
|
30
|
+
* @complexity O(s) time in the total segment count across the bundles reached; O(s) space.
|
|
31
|
+
* @overallScore 100
|
|
32
|
+
*/
|
|
33
|
+
expand(parsed: ParsedValue): ParsedValue;
|
|
34
|
+
/**
|
|
35
|
+
* Register one definition element.
|
|
36
|
+
*
|
|
37
|
+
* @returns Whether this call added a name the table did not have.
|
|
38
|
+
* @complexity O(n) time in the body's length; O(e) space in its segment count.
|
|
39
|
+
* @overallScore 100
|
|
40
|
+
*/
|
|
41
|
+
private define;
|
|
42
|
+
/**
|
|
43
|
+
* Refuse a name that cannot work, with the reason.
|
|
44
|
+
*
|
|
45
|
+
* @returns The diagnostic, or `undefined` when the name is usable.
|
|
46
|
+
* @complexity O(n) time in the name's length; O(1) space.
|
|
47
|
+
* @overallScore 100
|
|
48
|
+
*/
|
|
49
|
+
private nameProblem;
|
|
50
|
+
/**
|
|
51
|
+
* Expand one comma list, recursing through bundles that name other bundles.
|
|
52
|
+
*
|
|
53
|
+
* @param trail - Bundle names currently being expanded, innermost last. Both the cycle guard and
|
|
54
|
+
* its diagnostic read it, so it is a list rather than a set.
|
|
55
|
+
* @complexity O(s) time in the total segment count across the bundles reached; O(s) space.
|
|
56
|
+
* @overallScore 100
|
|
57
|
+
*/
|
|
58
|
+
private expandList;
|
|
59
|
+
/**
|
|
60
|
+
* Name an unregistered effect that is one typo away from a bundle.
|
|
61
|
+
*
|
|
62
|
+
* `compile.ts` already reports every unknown name, with a suggestion drawn from the catalog — a
|
|
63
|
+
* misspelled *bundle* is invisible in that list, so this adds the one fact that message cannot
|
|
64
|
+
* carry. Only for names the registry does not know, or a real effect one edit away from a badly
|
|
65
|
+
* chosen bundle name would be reported as a mistake.
|
|
66
|
+
*
|
|
67
|
+
* @complexity O(b·n) time in bundle count and name length; O(n) space.
|
|
68
|
+
* @overallScore 100
|
|
69
|
+
*/
|
|
70
|
+
private noteNearMiss;
|
|
71
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import type { Reporter } from './reporter.js';
|
|
2
|
+
/**
|
|
3
|
+
* `func:` — a named global function, called when the element's effects finish.
|
|
4
|
+
*
|
|
5
|
+
* ## What this is, and what it is not
|
|
6
|
+
*
|
|
7
|
+
* It is **sugar for one line of DOM**, and it is implemented as literally that line:
|
|
8
|
+
*
|
|
9
|
+
* ```js
|
|
10
|
+
* el.addEventListener('kui:finish', window[name])
|
|
11
|
+
* ```
|
|
12
|
+
*
|
|
13
|
+
* Nothing here invents a new moment for a callback to fire at. `animator.ts` already dispatches
|
|
14
|
+
* `kui:finish` at exactly one place, and this module registers an ordinary listener for it — so
|
|
15
|
+
* `func:` cannot drift out of step with the event, because it *is* the event. That also means the
|
|
16
|
+
* function receives the same `LifecycleEvent` a hand-written listener receives, with `this` bound to
|
|
17
|
+
* the same element, so moving code between the two spellings is a copy-paste rather than a rewrite.
|
|
18
|
+
*
|
|
19
|
+
* It is **not** the recommended API. `addEventListener('kui:finish', fn)` works with bundlers and ES
|
|
20
|
+
* modules, allows several listeners on one element, and needs no global — and every project with a
|
|
21
|
+
* build step should use it. `func:` exists for the no-build site: a page with a `<script>` block, an
|
|
22
|
+
* attribute, and no module graph to hang a listener off. That audience is real, and telling them to
|
|
23
|
+
* "just import the library and add a listener" is telling them to adopt a toolchain to run one
|
|
24
|
+
* function.
|
|
25
|
+
*
|
|
26
|
+
* ## The one risk an integrator has to own
|
|
27
|
+
*
|
|
28
|
+
* `func:` turns an attribute value into a function call, so **whoever controls the attribute
|
|
29
|
+
* controls which global function runs**. On a hand-authored page that is the author, and there is no
|
|
30
|
+
* gap. On a page where `data-kui` is assembled from a CMS field, a URL parameter, or any other input
|
|
31
|
+
* an end user can influence, it is *them* — and a value they choose can invoke any function the page
|
|
32
|
+
* happens to have put on `window`, with the animated element as `this`.
|
|
33
|
+
*
|
|
34
|
+
* The lookup below is deliberately as narrow as it can be while still doing the job: an own-property
|
|
35
|
+
* read on the element's own window, a `typeof === 'function'` check before calling, no dotted path
|
|
36
|
+
* traversal, and no `eval`/`Function` anywhere — so the value can *name* a function but can never
|
|
37
|
+
* *be* one. That closes code injection. It does not, and cannot, close "call any function by name",
|
|
38
|
+
* because that is the feature. If `data-kui` on your site is ever built from untrusted input, use
|
|
39
|
+
* the event API for the callback and keep `func:` out of the template.
|
|
40
|
+
*/
|
|
41
|
+
/** Everything `bindCallback` needs, grouped so the call site reads as one request. */
|
|
42
|
+
export interface CallbackRequest {
|
|
43
|
+
/** The host element. The one lifecycle events are dispatched on, and the callback's `this`. */
|
|
44
|
+
el: Element;
|
|
45
|
+
/** The authored `func:` value — a bare global name, unvalidated by `parse.ts`. */
|
|
46
|
+
name: string;
|
|
47
|
+
/** Diagnostic sink. Silent by default, exactly as it is for an unknown effect name. */
|
|
48
|
+
reporter: Reporter;
|
|
49
|
+
/** The instance's teardown signal, so the listener detaches with everything else. */
|
|
50
|
+
signal: AbortSignal;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Register an author's named global as a `kui:finish` listener on one element.
|
|
54
|
+
*
|
|
55
|
+
* The name is resolved **at fire time, not here**, and that is deliberate: a no-build page routinely
|
|
56
|
+
* defines its functions in a `<script>` that runs after the library has scanned, and resolving
|
|
57
|
+
* eagerly would reject every one of those with a warning that was wrong by the time it printed.
|
|
58
|
+
* The cost is that a typo stays silent until the animation completes — at which point it warns,
|
|
59
|
+
* naming the value, which is the same deal every other diagnostic in the grammar offers.
|
|
60
|
+
*
|
|
61
|
+
* @complexity O(1) time and space to register; O(1) per event beyond the author's own function.
|
|
62
|
+
* @overallScore 100
|
|
63
|
+
*/
|
|
64
|
+
export declare function bindCallback(request: CallbackRequest): void;
|
|
@@ -14,6 +14,53 @@ export interface Capabilities {
|
|
|
14
14
|
viewTransitions: boolean;
|
|
15
15
|
intersectionObserver: boolean;
|
|
16
16
|
reducedMotion: boolean;
|
|
17
|
+
/**
|
|
18
|
+
* CSS Motion Path — `offset-path`, and with it `offset-distance`/`offset-rotate`/`offset-anchor`.
|
|
19
|
+
*
|
|
20
|
+
* Probed with a `path()` value specifically, not with `offset-path` alone: Chrome shipped
|
|
21
|
+
* `offset-path: path()` years before it accepted a `<basic-shape>` there, so "does this property
|
|
22
|
+
* parse" and "does this property parse the thing we emit" are different questions, in the same
|
|
23
|
+
* way `view()` and `scroll()` are two probes above rather than one.
|
|
24
|
+
*/
|
|
25
|
+
motionPath: boolean;
|
|
17
26
|
}
|
|
27
|
+
/**
|
|
28
|
+
* Build a `Capabilities` from only the fields a caller actually has an opinion about.
|
|
29
|
+
*
|
|
30
|
+
* `Capabilities` is a closed record of required booleans, so before this existed every object
|
|
31
|
+
* literal constructing one had to name every field. Adding `motionPath` was therefore O(number of
|
|
32
|
+
* construction sites) rather than O(1), and it broke three test harnesses in a single merge — none
|
|
33
|
+
* of which had any interest in motion paths, and all of which failed to compile purely for not
|
|
34
|
+
* mentioning one. Spreading a complete default makes a new field additive everywhere instead.
|
|
35
|
+
*
|
|
36
|
+
* Deliberately *not* used by `detect()`, even though `detect()` is a construction site too. Its
|
|
37
|
+
* literal is the detection logic: keeping it exhaustive is what makes the compiler demand a real
|
|
38
|
+
* probe for each new capability. Route it through here and adding a field would type-check the
|
|
39
|
+
* moment it was given a default, and ship as permanently `false` in every browser — the feature
|
|
40
|
+
* dead in production, with no test able to notice. So the two defaults differ on purpose: a
|
|
41
|
+
* caller stating a fixed environment gets "absent unless said otherwise", and production gets no
|
|
42
|
+
* constant default at all, because the honest answer there is always "go and probe it". Adding a
|
|
43
|
+
* capability still means editing one file — this one — in two adjacent places the compiler names.
|
|
44
|
+
*
|
|
45
|
+
* There is deliberately no `allCapabilities()` counterpart. A harness that wants a fully modern
|
|
46
|
+
* browser lists its `true`s, because the alternative is a test opting in to every capability
|
|
47
|
+
* invented after it was written.
|
|
48
|
+
*
|
|
49
|
+
* @param overrides - Fields this caller has actually decided; everything else comes back absent.
|
|
50
|
+
* @returns A fully-populated capability record.
|
|
51
|
+
* @complexity O(c) time and space in the number of capability fields — fixed and single-digit.
|
|
52
|
+
* @overallScore 100
|
|
53
|
+
*/
|
|
54
|
+
export declare function defaultCapabilities(overrides?: Partial<Capabilities>): Capabilities;
|
|
18
55
|
export declare function detect(force?: boolean): Capabilities;
|
|
19
56
|
export declare function resetCapabilities(): void;
|
|
57
|
+
/**
|
|
58
|
+
* Diagnostics for composed channels this environment cannot render.
|
|
59
|
+
*
|
|
60
|
+
* @param channels - Channels the compiled effects write to (`CompiledPlan.channels`).
|
|
61
|
+
* @param capabilities - The environment's detected capabilities.
|
|
62
|
+
* @returns One message per unsupported channel; empty in a browser that supports them all.
|
|
63
|
+
* @complexity O(r) time in the requirement table's length — single digits; O(1) space per message.
|
|
64
|
+
* @overallScore 100
|
|
65
|
+
*/
|
|
66
|
+
export declare function unsupportedChannelWarnings(channels: readonly string[], capabilities: Capabilities): string[];
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { EffectGate } from './breakpoints.js';
|
|
2
|
+
import type { Channel, DeliveryMechanism, EffectPhase } from './types.js';
|
|
2
3
|
/**
|
|
3
4
|
* Composition safety.
|
|
4
5
|
*
|
|
@@ -9,17 +10,134 @@ import type { Channel } from './types.js';
|
|
|
9
10
|
*
|
|
10
11
|
* Hence channels. An effect declares the property groups it owns; a comma list is only safe to
|
|
11
12
|
* compile when those sets are pairwise disjoint. See docs/design.md §4.
|
|
13
|
+
*
|
|
14
|
+
* "Pairwise disjoint" gained a second dimension when viewport gates arrived: two effects that can
|
|
15
|
+
* never be live at the same viewport width cannot collide however many channels they share. That
|
|
16
|
+
* is not a nicety — `fade-up below:md, parallax-y above:md` is the flagship case the gate exists
|
|
17
|
+
* for, both halves own `translate`, and without the width check the compiler would refuse the pair
|
|
18
|
+
* and drop the second half at every width.
|
|
19
|
+
*
|
|
20
|
+
* And a third: *when* each effect holds the channel. Disjointness in space and time are the same
|
|
21
|
+
* kind of exemption — `fade-up, lift` share `translate` and share every width, but an entrance and
|
|
22
|
+
* a hover state are not fighting over the property, they are taking turns with it. See
|
|
23
|
+
* {@link EffectPhase} for why that is a declared fact rather than a derived one, and
|
|
24
|
+
* {@link independentPhases} for which pairs qualify.
|
|
12
25
|
*/
|
|
13
26
|
export interface ChannelClaim {
|
|
14
27
|
name: string;
|
|
15
28
|
channels: Channel[];
|
|
29
|
+
/** Viewport condition, if the segment carries one. Absent means "live at every width". */
|
|
30
|
+
gate?: EffectGate;
|
|
31
|
+
/**
|
|
32
|
+
* When this segment holds its channels, if that is known. Absent means unphased — the preset
|
|
33
|
+
* declares no phase and none could be derived — and an unphased claim collides with everything,
|
|
34
|
+
* itself included, which is exactly how every claim behaved before this field existed.
|
|
35
|
+
*/
|
|
36
|
+
phase?: EffectPhase;
|
|
37
|
+
/**
|
|
38
|
+
* Whether the caller can put `animation-composition: add` on whatever this segment compiles to.
|
|
39
|
+
* Asked of the caller rather than worked out here because the answer is about the *renderer* —
|
|
40
|
+
* how many keyframe blocks the segment produces, and whether it produces any at all — which is
|
|
41
|
+
* `compile.ts`'s knowledge. What is additive about a *channel* stays here, in
|
|
42
|
+
* {@link CHANNEL_COMPOSITION}.
|
|
43
|
+
*/
|
|
44
|
+
additive?: boolean;
|
|
45
|
+
/**
|
|
46
|
+
* How this segment's motion reaches the element, when that is something composition has to act on
|
|
47
|
+
* — see `types.ts`'s {@link DeliveryMechanism}. Read only by {@link deliveryClobbers}, never by
|
|
48
|
+
* {@link findConflicts}: it describes a fight over a *property* rather than over a channel.
|
|
49
|
+
*/
|
|
50
|
+
delivery?: DeliveryMechanism;
|
|
51
|
+
/**
|
|
52
|
+
* Whether this segment writes `animation-name` to `element.style`. Asked of the caller for the
|
|
53
|
+
* same reason {@link additive} is — it is a fact about the renderer, which is `compile.ts`'s
|
|
54
|
+
* knowledge, not this module's.
|
|
55
|
+
*/
|
|
56
|
+
inlineAnimation?: boolean;
|
|
16
57
|
}
|
|
17
58
|
export interface Conflict {
|
|
18
59
|
channel: Channel;
|
|
19
60
|
effects: [string, string];
|
|
20
61
|
}
|
|
21
|
-
/**
|
|
62
|
+
/**
|
|
63
|
+
* Which channels may be composed by *adding* two animations' output rather than replacing it, and
|
|
64
|
+
* which must stay one-writer-only.
|
|
65
|
+
*
|
|
66
|
+
* Deliberately an allowlist with one member. `animation-composition: add` looks like a general
|
|
67
|
+
* answer to this whole problem and is not: addition in Web Animations L1 is defined per property
|
|
68
|
+
* type, and a property with no additive definition — anything discrete, and every value CSS
|
|
69
|
+
* resolves as a keyword — falls back toward replacement *silently*. Applying `add` across the
|
|
70
|
+
* board would convert a loud, detectable conflict into a quiet browser-dependent last-writer
|
|
71
|
+
* result, which is strictly worse than the drop it was meant to fix. The channel set includes
|
|
72
|
+
* `clip`, `background`, `text`, display-like channels and, through `Channel`'s open `string` arm,
|
|
73
|
+
* whatever a third-party primitive registers — none of which anyone has tested.
|
|
74
|
+
*
|
|
75
|
+
* So: `translate` is here because a translation is a vector with a well-defined sum and a neutral
|
|
76
|
+
* `0px 0px 0px`, and nothing else is here until someone measures it in a browser and adds it.
|
|
77
|
+
*/
|
|
78
|
+
export declare const CHANNEL_COMPOSITION: Partial<Record<Channel, 'add'>>;
|
|
79
|
+
/** Whether every channel a claim writes can survive being made additive. */
|
|
80
|
+
export declare function additiveChannels(channels: readonly Channel[]): boolean;
|
|
81
|
+
/**
|
|
82
|
+
* Rescue a surviving clash by summing the claims that caused it instead of letting one replace the
|
|
83
|
+
* other.
|
|
84
|
+
*
|
|
85
|
+
* The last resort, and deliberately the narrowest of the three exemptions the detector offers — a
|
|
86
|
+
* gate says the two are never live together, a phase says they take turns, and this one admits they
|
|
87
|
+
* genuinely overlap and asks the browser to blend them.
|
|
88
|
+
*
|
|
89
|
+
* All-or-nothing over the whole contested set. A partly-additive list is the worst outcome
|
|
90
|
+
* available: the author is told nothing, and one of the two effects is quietly replaced anyway.
|
|
91
|
+
*
|
|
92
|
+
* @returns Indices of the claims to compile additively, or `undefined` when the rescue does not
|
|
93
|
+
* apply and the caller should refuse the list.
|
|
94
|
+
* @complexity O(n * c) time in claims and their channels; O(n + c) space.
|
|
95
|
+
* @overallScore 100
|
|
96
|
+
*/
|
|
97
|
+
export declare function additiveResolution(claims: ChannelClaim[], conflicts: Conflict[]): Set<number> | undefined;
|
|
98
|
+
/**
|
|
99
|
+
* Every pair of effects claiming the same channel at widths where both can be live, in phases where
|
|
100
|
+
* both can hold it. Empty array means the list composes.
|
|
101
|
+
*
|
|
102
|
+
* Each channel keeps a *list* of prior claimants rather than a single owner, and a claim is
|
|
103
|
+
* reported against the first one it actually overlaps. With no gates in play that first one is
|
|
104
|
+
* always the first claim, so the output is exactly what a single-owner map produced. With gates it
|
|
105
|
+
* is the difference between catching a real collision and inventing one: in `fade-up below:md,
|
|
106
|
+
* parallax-y above:md, slide-up above:md` the genuine clash is the second pair, and a map holding
|
|
107
|
+
* only `fade-up` would have compared both later effects against a segment neither can coexist with
|
|
108
|
+
* and waved the list through.
|
|
109
|
+
*
|
|
110
|
+
* @complexity O(n^2) worst case in same-channel claimants; a comma list is a handful of segments.
|
|
111
|
+
* @overallScore 100
|
|
112
|
+
*/
|
|
22
113
|
export declare function findConflicts(claims: ChannelClaim[]): Conflict[];
|
|
114
|
+
/**
|
|
115
|
+
* Clashes over *how* each segment's motion is delivered, which {@link findConflicts} cannot see.
|
|
116
|
+
*
|
|
117
|
+
* That function asks which property groups two segments write, and for two compiled animation
|
|
118
|
+
* tracks it is the right question. It is the wrong one here. A preset whose motion is
|
|
119
|
+
* `[data-kui-fx~='icon-spin']:hover { animation: … }` in `src/css/interaction.css` loses the
|
|
120
|
+
* `animation` property to *any* composed neighbour that writes `animation-name` inline, because an
|
|
121
|
+
* inline declaration outranks an author rule outright. It does not matter that `icon-spin` writes
|
|
122
|
+
* `rotate` and `fade-up` writes `opacity`/`translate` — the two never shared a channel, the
|
|
123
|
+
* detector correctly reported nothing, and `fade-up, icon-spin` composed clean with the hover
|
|
124
|
+
* silently dead. 548 such pairs, measured, and pre-existing rather than introduced by the phase
|
|
125
|
+
* axis: see `types.ts`'s {@link DeliveryMechanism} and the 2026-09-08 catalog review.
|
|
126
|
+
*
|
|
127
|
+
* There is no rescue and deliberately none offered. A gate does not help, unlike in
|
|
128
|
+
* {@link findConflicts}: `gatedAnimationName` compiles an out-of-band track to
|
|
129
|
+
* `animation-name: var(--kui-above-md, kui-in-up)` and `base.css` declares that property `none`
|
|
130
|
+
* outside the gate — `none` is still an inline value, still outranks the stylesheet rule, and
|
|
131
|
+
* leaves the hover just as dead below the breakpoint as above it. Neither does
|
|
132
|
+
* {@link additiveResolution}: `animation-composition` blends two tracks that both run, and the
|
|
133
|
+
* whole failure here is a rule that never becomes active.
|
|
134
|
+
*
|
|
135
|
+
* @returns One sentence per clobbered claim, in the shape {@link describeConflicts} produces, or an
|
|
136
|
+
* empty array when nothing in the list writes an inline animation.
|
|
137
|
+
* @complexity O(n) time in the claim count; O(n) space.
|
|
138
|
+
* @overallScore 100
|
|
139
|
+
*/
|
|
140
|
+
export declare function deliveryClobbers(claims: ChannelClaim[]): string[];
|
|
23
141
|
export declare function describeConflicts(conflicts: Conflict[]): string;
|
|
24
142
|
/** Union of all claimed channels, for debugging and perf accounting. */
|
|
25
143
|
export declare function claimedChannels(claims: ChannelClaim[]): Channel[];
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Owned by phase 9a — the pre-JS cloak selector strings, pulled out of
|
|
3
|
+
* `scripts/generate-preset-css.mjs` so the generator can grow the comma-form handling 9a needs
|
|
4
|
+
* (`cloak: true`/`cloak:'.a, .b'`-style selector lists, per the owner decision) without hand-editing
|
|
5
|
+
* the bundled generator script directly.
|
|
6
|
+
*
|
|
7
|
+
* `~=` matches a whole whitespace-separated token, but a `data-kui` value's segments are
|
|
8
|
+
* comma-separated, not space-separated — `data-kui="text-reveal-mask,\nfade-up"` puts the comma
|
|
9
|
+
* straight up against the previous word, so the actual whitespace token is `text-reveal-mask,`, not
|
|
10
|
+
* `text-reveal-mask`. An author can glue that comma to either side (or both, mid-list), so each name
|
|
11
|
+
* or gate token needs one `[data-kui~=…]` per comma form, grouped in a single `:is()` so the cloak
|
|
12
|
+
* still matches whichever form the author wrote. `:is()` takes the specificity of its most specific
|
|
13
|
+
* argument; every form here has identical specificity, so the grouped selector keeps HEAD's
|
|
14
|
+
* specificity exactly. A browser old enough to reject `:is()` drops the whole rule and fails open —
|
|
15
|
+
* not cloaking is exactly today's (pre-9a) behaviour, same fail-open the bare-comma case already had.
|
|
16
|
+
*/
|
|
17
|
+
/**
|
|
18
|
+
* The four ways one `data-kui` token can sit relative to its neighbours' commas: alone, followed by
|
|
19
|
+
* a glued comma, preceded by one, or both (mid-list with no surrounding spaces).
|
|
20
|
+
*
|
|
21
|
+
* @param token - The preset name or gate token, as written in `data-kui`.
|
|
22
|
+
* @returns The four literal token forms, in `[data-kui~=…]` match order.
|
|
23
|
+
* @complexity O(1) time and space.
|
|
24
|
+
* @overallScore 100
|
|
25
|
+
*/
|
|
26
|
+
export declare function cloakTokenForms(token: string): string[];
|
|
27
|
+
/**
|
|
28
|
+
* The pre-JS cloak selector for one preset name — matched while `<html data-kui-cloak>` is still
|
|
29
|
+
* present, so an element carrying that preset stays hidden until the animator's own `data-kui-*`
|
|
30
|
+
* state attribute lands and the ordinary ready-gate rules take over.
|
|
31
|
+
*
|
|
32
|
+
* @param name - The preset name, as written in `data-kui`.
|
|
33
|
+
* @returns One selector line (no trailing comma or newline).
|
|
34
|
+
* @complexity O(1) time and space.
|
|
35
|
+
* @overallScore 100
|
|
36
|
+
*/
|
|
37
|
+
export declare function cloakSelector(name: string): string;
|
|
38
|
+
/**
|
|
39
|
+
* The gate-release selector for one gate token (e.g. `above:md`) — the selector half of
|
|
40
|
+
* `gateReleaseRules`'s per-token block, without the trailing ` {` a caller appends itself.
|
|
41
|
+
*
|
|
42
|
+
* @param token - The gate token, as written in `data-kui`.
|
|
43
|
+
* @returns One selector line (no trailing ` {` or newline).
|
|
44
|
+
* @complexity O(1) time and space.
|
|
45
|
+
* @overallScore 100
|
|
46
|
+
*/
|
|
47
|
+
export declare function gateReleaseSelector(token: string): string;
|