kuinetic 0.2.2 → 0.2.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. package/README.md +2 -2
  2. package/dist/esm/{chunk-K2M7O6W5.mjs → chunk-FASAEQDR.mjs} +32 -1
  3. package/dist/esm/{chunk-XDDJVANU.mjs → chunk-FYMZW5N2.mjs} +2301 -528
  4. package/dist/esm/{chunk-BBQ22D7A.mjs → chunk-MZQG2VMG.mjs} +202 -2
  5. package/dist/esm/core/index.mjs +21 -3
  6. package/dist/esm/effects/index.mjs +2 -2
  7. package/dist/esm/index.mjs +35 -4
  8. package/dist/esm/notes/index.mjs +1075 -0
  9. package/dist/kuinetic.all.js +2502 -501
  10. package/dist/kuinetic.all.min.js +2 -2
  11. package/dist/kuinetic.css +238 -65
  12. package/dist/kuinetic.js +2501 -500
  13. package/dist/kuinetic.min.css +1 -1
  14. package/dist/kuinetic.min.js +1 -1
  15. package/dist/kuinetic.notes.js +1 -0
  16. package/dist/types/3d/flattening.d.ts +8 -7
  17. package/dist/types/core/deck-viewer.d.ts +87 -0
  18. package/dist/types/core/describe.d.ts +174 -0
  19. package/dist/types/core/gesture.d.ts +10 -0
  20. package/dist/types/core/index.d.ts +6 -2
  21. package/dist/types/core/target.d.ts +28 -0
  22. package/dist/types/core/timing-contract.d.ts +61 -0
  23. package/dist/types/core/types.d.ts +59 -0
  24. package/dist/types/effects/auto-motion.d.ts +169 -0
  25. package/dist/types/effects/carousel/deck.d.ts +170 -0
  26. package/dist/types/effects/carousel/drag.d.ts +6 -0
  27. package/dist/types/effects/carousel/fit.d.ts +130 -0
  28. package/dist/types/effects/carousel/index.d.ts +6 -23
  29. package/dist/types/effects/carousel/stack.d.ts +77 -0
  30. package/dist/types/effects/catalog/interaction-reveal.d.ts +69 -9
  31. package/dist/types/effects/forms/primitives.d.ts +1 -2
  32. package/dist/types/effects/gestures/index.d.ts +3 -4
  33. package/dist/types/effects/shared.d.ts +3 -30
  34. package/dist/types/effects/step-index.d.ts +48 -10
  35. package/dist/types/effects/swipe-event.d.ts +75 -0
  36. package/dist/types/index.d.ts +26 -0
  37. package/dist/types/notes/catalog-core.d.ts +3 -0
  38. package/dist/types/notes/catalog.d.ts +3 -0
  39. package/dist/types/notes/index.d.ts +13 -0
  40. package/dist/types/notes/interaction-reveal.d.ts +3 -0
  41. package/dist/types/notes/interaction.d.ts +6 -0
  42. package/dist/types/notes/keys.d.ts +7 -0
  43. package/dist/types/notes/mechanics.d.ts +7 -0
  44. package/dist/types/notes/scroll-nav-forms.d.ts +6 -0
  45. package/dist/types/notes/shared.d.ts +6 -0
  46. package/dist/types/notes/showcase.d.ts +7 -0
  47. package/dist/types/notes/text.d.ts +3 -0
  48. package/dist/types/showcase/compare.d.ts +9 -0
  49. package/dist/types/showcase/index.d.ts +3 -0
  50. package/dist/types/showcase/lightbox.d.ts +23 -1
  51. package/dist/types/showcase/modal-shell.d.ts +11 -0
  52. package/package.json +7 -3
@@ -0,0 +1,77 @@
1
+ import type { Preset, Primitive } from '../../core/types.js';
2
+ import type { Face } from './deck.js';
3
+ /**
4
+ * A deck of cards receding diagonally into depth — the front card large and sharp, each one behind
5
+ * it stepped up and to the side, smaller, softer and fainter — that cycles forward: the front card
6
+ * leaves toward the viewer and the next one comes up to take its place.
7
+ *
8
+ * ## Why a primitive of its own, sharing the ring's deck
9
+ *
10
+ * The *index* is the ring's exactly: N slots, a live one, a signed place for every other, a real-
11
+ * valued position a drag moves between two places, `next:`/`prev:`/`jump:`, the keyboard, `spin:`
12
+ * and `autoplay:`. All of that is `deck.ts`, shared, so the two can never disagree about where a
13
+ * deck is. What differs is not a parameter of the ring's shape but a different shape:
14
+ *
15
+ * - **It is linear, not circular.** A ring spaces its slots by `arc / count` and has no front or
16
+ * back in the index; a stack has a front, and the slots behind it are *further*, not *around*.
17
+ * Not one of the ring's seven geometry parameters (`tilt`, `arc`, `radius`, `gap`, `perspective`,
18
+ * `facing`, `plane`) means anything for it, and none of its six means anything for a ring.
19
+ * - **It paints more channels.** Receding cards blur and fade, so a stack writes `filter` and
20
+ * `opacity` on its slots as well as `transform`. Declaring those on the ring primitive would have
21
+ * made every existing `carousel-3d` start colliding with any opacity or filter effect composed
22
+ * beside it — a behaviour change to four shipped names in order to add a fifth.
23
+ *
24
+ * So: one deck, two primitives, each declaring only its own geometry and its own channels.
25
+ *
26
+ * ## Which cards are showing
27
+ *
28
+ * The marker publishes circular offsets (`-N/2 … N/2`), which suits a ring. A stack reads them
29
+ * shifted — `mod(offset + 1, N) - 1`, in `carousel.css` — onto `-1 … N-2`: the live card at 0,
30
+ * `depth` cards visible behind it, and exactly one place *in front*, for the card that has just
31
+ * left. The wrap from the front to the back therefore happens between `-1` (faded out in front)
32
+ * and `N-2` (faded out behind), so a card being recycled never crosses the visible stack. With
33
+ * fewer than `depth + 3` cards the back slot is still visible when it arrives, and the recycled card
34
+ * fades in there as it lands, which is the honest limit of a short deck.
35
+ */
36
+ /**
37
+ * Whether a slot at this stack place is showing: in front of the fade-out behind the last visible
38
+ * card, and not yet gone out past the viewer.
39
+ *
40
+ * Pure and exported so the boundaries are assertable without a DOM. Mirrors the opacity ramp in
41
+ * `carousel.css`: a card that has left is gone by half a place in front, and the back of the stack
42
+ * fades out over the one place past `depth`.
43
+ *
44
+ * @param offset - The slot's circular offset, as the marker published it.
45
+ * @param drift - The deck's sub-step position past the live place.
46
+ * @param count - The deck's size.
47
+ * @param depth - How many cards show behind the front one.
48
+ * @returns `'front'` while any of the card shows, `'back'` once none of it does.
49
+ * @complexity O(1) time and space.
50
+ */
51
+ export declare function stackFace(offset: number, drift: number, count: number, depth: number): Face;
52
+ /**
53
+ * A circular offset re-read as a place in the stack: `0` in front, `1…N-2` behind, `-1` just left.
54
+ *
55
+ * The JavaScript twin of `carousel.css`'s `mod(offset + 1, N) - 1`; the two must agree, and this is
56
+ * the one the tests pin.
57
+ *
58
+ * @complexity O(1) time and space.
59
+ */
60
+ export declare function stackPlace(offset: number, count: number): number;
61
+ /**
62
+ * The stack primitive.
63
+ *
64
+ * `skew` for the `transform` shorthand (the scale and the diagonal step compose in one function
65
+ * list), `opacity` and `filter` for the recession, `discrete` for the host's `display: grid` — the
66
+ * same reasoning `SPATIAL_RING_PRIMITIVE` gives for its own pair, widened by what a stack paints.
67
+ * `reducedMotion: 'shorten'` for the same reason as the ring: the deck must keep working, and the
68
+ * policy layer already shortens the transition, while `deck.ts` starts any auto-motion paused.
69
+ */
70
+ export declare const SPATIAL_STACK_PRIMITIVE: Primitive;
71
+ /**
72
+ * `carousel-stack` — the family name first, the shape second, as `carousel-fade`/`carousel-slide`/
73
+ * `carousel-3d` already read. One name: the diagonal's direction is two signed lengths (`shift:`,
74
+ * `rise:`), so a left-hand or downward stack is a parameter, not a catalogue entry. `requiresOwnSubtree`
75
+ * and `phase: 'idle'` for the reasons `CAROUSEL_PRESETS` gives for the ring.
76
+ */
77
+ export declare const STACK_PRESETS: Preset[];
@@ -1,4 +1,57 @@
1
1
  import type { Preset, Primitive } from '../../core/types.js';
2
+ /**
3
+ * ### `PLACEMENT` — `place:top|bottom|auto`, and the one piece of JavaScript it costs
4
+ *
5
+ * `label-swap`'s and `anchored-preview`'s doc comments both record why a placement *keyword* was
6
+ * once refused: CSS cannot branch on a custom property's value without `@container style()`, so
7
+ * the word has to be read by script. That is still true, and this is the script — kept to the one
8
+ * job CSS genuinely cannot do, so the geometry itself stays in the stylesheet.
9
+ *
10
+ * The script never writes a length. It stamps the *resolved side* as an attribute on the host
11
+ * (`data-kui-hint-place` / `data-kui-preview-place`), and `interaction.css` keys the geometry on
12
+ * `[data-kui-fx][data-kui-…-place='…']`: a compound on the host itself, no combinator, so the
13
+ * `INHERITED_STATE` contract above holds. The `[data-kui-fx]`
14
+ * half lifts the rule to two attributes of specificity, which is what lets an explicit side beat
15
+ * an `anchored-preview-left` preset rule regardless of source order.
16
+ *
17
+ * A fixed side is stamped once at activation. Measuring happens only when the part is about to be
18
+ * seen: on `pointerenter`/`focusin`, the two events that precede every state the stylesheet reveals
19
+ * on (`:hover`, `:focus-visible`, and the coarse-pointer `:active`, which a touch `pointerenter`
20
+ * also precedes). While shown, a passive capture-phase `scroll` and a `resize` listener re-measure
21
+ * at most once per frame, because the reader can scroll a hovered trigger into the viewport edge;
22
+ * both are removed the moment neither hover nor focus remains. Nothing runs per frame while the
23
+ * part is hidden. `auto` measures which side; every side, fixed or not, measures `SHIFT` below.
24
+ *
25
+ * The flip is along the preferred side's own axis only — top↔bottom, left↔right. Moving a
26
+ * left-anchored preview to the top is a different layout, not a correction of this one.
27
+ */
28
+ type Side = 'top' | 'bottom' | 'left' | 'right';
29
+ /**
30
+ * Pick the side a part should sit on, given the room around its trigger.
31
+ *
32
+ * The preferred side wins whenever the part fits there. When it does not, the opposite side is
33
+ * taken only if it has *more* room — not merely if it fits — so a part too big for either side
34
+ * lands where the least of it is clipped rather than flipping to a side that clips more.
35
+ *
36
+ * @param preferred - The side to try first.
37
+ * @param room - Free space, in CSS px, between each edge of the trigger and the viewport.
38
+ * @param extent - How far the part reaches from the trigger along its axis, gap included.
39
+ * @complexity O(1) time and space.
40
+ */
41
+ export declare function chooseSide(preferred: Side, room: Record<Side, number>, extent: number): Side;
42
+ /**
43
+ * How far to move a span on one axis so it sits inside `[margin, viewport - margin]`.
44
+ *
45
+ * A span longer than that room cannot fit either way, so it is aligned to the start edge — the
46
+ * reading edge, where its first words are.
47
+ *
48
+ * @param start - The span's start coordinate in the viewport, unshifted, in CSS px.
49
+ * @param size - Its length on the same axis.
50
+ * @param viewport - The viewport's length on that axis.
51
+ * @returns The signed offset to add, `0` when it already fits.
52
+ * @complexity O(1) time and space.
53
+ */
54
+ export declare function shiftWithin(start: number, size: number, viewport: number, margin?: number): number;
2
55
  /**
3
56
  * The masked label swap: two stacked copies of a label in a clipped box, one sliding out as the
4
57
  * other slides in.
@@ -148,15 +201,15 @@ export declare const HOVER_INTENT_PRESETS: Preset[];
148
201
  * That mechanism needs no feature detection and no fallback branch, because it already works in
149
202
  * every browser the rest of the catalog does.
150
203
  *
151
- * ### Why four preset names and not a `placement:` parameter
204
+ * ### Four preset names, and a `place:` parameter behind them
152
205
  *
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`).
206
+ * The four names came first, for the reasoning `label-swap`'s own doc comment gives for its axis:
207
+ * CSS cannot branch on a custom property's *value*, so a placement keyword has to be read by
208
+ * JavaScript. `place:auto` — flip to whichever side fits the viewport — is a measurement CSS cannot
209
+ * make at all, so that JavaScript now exists (`PLACEMENT`, above), and the names became four
210
+ * spellings of `place:` (each preset carries its side in `params`). They stay because they are how
211
+ * the rest of the catalog spells "one mechanism, several fixed shapes" (`masked-label-swap-x`,
212
+ * `card-flip-y`), and because the stylesheet still places them before any script has run.
160
213
  *
161
214
  * `anchored-preview` (no suffix) is `top`, matching `hover-intent`'s own default placement for the
162
215
  * same reason: a name tag or a tooltip reads naturally above its trigger unless told otherwise.
@@ -171,7 +224,13 @@ export declare const HOVER_INTENT_PRESETS: Preset[];
171
224
  * absolute; inset: var(--kui-anchored-preview-inset); translate: var(--kui-anchored-preview-travel)`
172
225
  * and nothing placement-specific. No rule anywhere carries a combinator after an fx compound, so
173
226
  * `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.
227
+ * derives and needs no `requiresOwnSubtree`.
228
+ *
229
+ * ### `target:` names the preview
230
+ *
231
+ * `data-kui="anchored-preview-bottom target:.preview-img"` needs no `data-kui-preview` in the
232
+ * markup: the effect stays on the trigger and the library stamps the marker on the match, then
233
+ * takes it off again on teardown — see `claimPart`.
175
234
  *
176
235
  * ### Channels
177
236
  *
@@ -246,3 +305,4 @@ export declare const SEARCH_EXPAND_PRESETS: Preset[];
246
305
  /** All four reveal-shaped families, for `interaction.ts`'s catalog-wide exports. */
247
306
  export declare const REVEAL_PRIMITIVES: Primitive[];
248
307
  export declare const REVEAL_PRESETS: Preset[];
308
+ export {};
@@ -15,8 +15,7 @@ export declare const VALIDATE_CHECK_PRIMITIVE: Primitive;
15
15
  export declare function computeStrength(value: string): number;
16
16
  export declare const STRENGTH_METER_PRIMITIVE: Primitive;
17
17
  export declare const RANGE_FILL_PRIMITIVE: Primitive;
18
- export { nextStep, prevStep, clampStep, countSteps, delegateControls } from '../step-index.js';
19
- export type { ControlGroup } from '../step-index.js';
18
+ export { nextStep, prevStep, clampStep, countSteps } from '../step-index.js';
20
19
  /**
21
20
  * The only member of the family that is not `reducedMotion: 'disable'`, and the difference is the
22
21
  * whole widget.
@@ -41,10 +41,9 @@ import type { Registry } from '../../core/registry.js';
41
41
  * there via the same inline style until the next drag picks it up again. None of that is an
42
42
  * `entrance`: there is no hidden from-state and no `cloak`, just an idle rest value between
43
43
  * gestures.
44
- * - `swipeable` (`swipe`, `swipe-x`) and `pressable` (`long-press`) claim the primitive-level
45
- * `'state'` *channel* (a bucket for "writes an attribute, not a CSS property" — see
46
- * `gestures/primitives.ts`'s `channels: ['state']` — a name that collides with this same-named
47
- * `EffectPhase` value only in spelling, not in meaning). `long-press` is the clean case: it
44
+ * - `swipeable` (`swipe`, `swipe-x`, `swipe-y`) and `pressable` (`long-press`) write an attribute,
45
+ * not a CSS property, and their *channel* names it (`attributeChannel` in `core/types.ts`) —
46
+ * which is unrelated to this `state` *phase*. `long-press` is the clean case: it
48
47
  * flips `data-kui-pressed` on the hold and back off on release, precisely the `:active`-shaped
49
48
  * "held only while the visitor is doing something" case the `state` phase doc describes.
50
49
  * `swipe`/`swipe-x` are a discrete, already-over gesture by the time `data-kui-swipe` is
@@ -1,4 +1,5 @@
1
1
  import type { PrepareContext } from '../core/effect-context.js';
2
+ import type { TimingContract, TimingToken } from '../core/timing-contract.js';
2
3
  import type { Activation, EffectParams, NamedActivation, ParameterSchema, Primitive, ReducedMotionPolicy, Timeline } from '../core/types.js';
3
4
  /**
4
5
  * The one `delay` declaration, spread by every primitive that waits for a trigger before playing.
@@ -24,36 +25,8 @@ import type { Activation, EffectParams, NamedActivation, ParameterSchema, Primit
24
25
  * pick this up.
25
26
  */
26
27
  export declare const TRIGGER_DELAY_PARAM: ParameterSchema;
27
- /** The three positional timing tokens of the grammar: `data-kui="fade-up 600ms 200ms linear"`. */
28
- export type TimingToken = 'duration' | 'delay' | 'ease';
29
- /** All three, in grammar order. Also the `honours` list of a primitive that supports the lot. */
30
- export declare const ALL_TIMING_TOKENS: readonly TimingToken[];
31
- /**
32
- * What a JS-rendered primitive can actually *do* with authored timing.
33
- *
34
- * The problem this closes: the two spellings of a timing value fail differently, and one of them
35
- * fails silently. `pin-section delay:300ms` reaches `readParams`, is not in the schema, and warns
36
- * as an unknown parameter — the author is told. `pin-section 0ms 300ms` parses into
37
- * `spec.delay`, is lifted out to `params.timing` (see `core/js-effect-preparer.ts`), and is then
38
- * simply never read by a primitive that has no clock to shift. Nothing warns, nothing happens,
39
- * and the page gives the author no way to find out which of those two it was.
40
- *
41
- * So a primitive that cannot honour a token has to say so, and it has to say so about the
42
- * positional spelling too. Declaring the contract is how it does that.
43
- */
44
- export interface TimingContract {
45
- /**
46
- * Tokens this primitive genuinely acts on. Anything omitted warns by name when authored.
47
- * Omit the field entirely for a primitive that honours none of the three.
48
- */
49
- honours?: readonly TimingToken[];
50
- /**
51
- * Completes `"<id>" cannot honour <token>: <because>`. Write the *reason*, not the symptom —
52
- * "it tracks pointer position continuously, so there is no start moment to delay" tells an
53
- * author to stop looking for a spelling that works, where "unsupported" does not.
54
- */
55
- because: string;
56
- }
28
+ export { ALL_TIMING_TOKENS } from '../core/timing-contract.js';
29
+ export type { TimingContract, TimingToken } from '../core/timing-contract.js';
57
30
  /**
58
31
  * How long an effect waits after its trigger before it starts, in milliseconds.
59
32
  *
@@ -1,15 +1,29 @@
1
1
  import type { Cleanup, EffectParams } from '../core/types.js';
2
2
  import type { PrepareContext } from '../core/effect-context.js';
3
- import type { TargetScope } from '../core/target.js';
4
3
  export interface StepIndexOptions {
5
4
  el: Element;
6
5
  params: EffectParams;
7
6
  ctx: PrepareContext;
8
- scope: TargetScope;
7
+ /**
8
+ * The steps, already scoped by the caller's `scope:`. Controls take no scope — see
9
+ * `core/target.ts`'s `queryControls`.
10
+ */
9
11
  resolveSteps: () => Iterable<Element>;
10
12
  onRender?: (index: number, total: number) => void;
11
13
  clickFallback?: boolean;
12
14
  name?: string;
15
+ /**
16
+ * Further control groups bound through the same delegated listener as `next:`/`prev:`/`jump:` —
17
+ * a deck's `pause:` (`auto-motion.ts`). Like the three, naming one retires the click-the-container
18
+ * fallback: a pause button inside the deck would otherwise also advance it on every press.
19
+ */
20
+ controls?: readonly ControlGroup[];
21
+ /**
22
+ * Called before a *person* moves the index — a named control or the container click — and never
23
+ * for `goTo`/`next`/`prev` called from code. How a deck's autoplay yields to the visitor without
24
+ * mistaking its own timer for one.
25
+ */
26
+ onInput?: () => void;
13
27
  }
14
28
  export interface StepIndex {
15
29
  goTo(index: number): void;
@@ -80,7 +94,28 @@ export interface ControlGroup {
80
94
  run: (node: Element, position: number) => void;
81
95
  }
82
96
  /**
83
- * Bind every named control group with a *single* delegated listener on the scope root.
97
+ * Read one control parameter into a group, or `null` when it is not authored or not usable.
98
+ *
99
+ * The one place a control selector is resolved, for every deck: `next:`/`prev:`/`jump:` on the
100
+ * step deck and the spatial decks, and `pause:` (`auto-motion.ts`). Where it is then *searched* is
101
+ * {@link queryControls}' rule — inside the host first, the page only when nothing inside matches —
102
+ * and deliberately not `scope:`, which is the steps' alone (see that function for why).
103
+ *
104
+ * Registered even when nothing matches yet, with a warning: a control the page renders after setup
105
+ * is still reached, because {@link delegateControls} re-queries on every press.
106
+ *
107
+ * @param label - The effect name an author would recognise, for the warning.
108
+ * @complexity O(n) in one setup query's matches; O(1) space beyond the group.
109
+ * @overallScore 100
110
+ */
111
+ export declare function bindControl(request: {
112
+ el: Element;
113
+ ctx: PrepareContext;
114
+ params: EffectParams;
115
+ label: string;
116
+ }, param: string, run: ControlGroup['run']): ControlGroup | null;
117
+ /**
118
+ * Bind every named control group with a *single* delegated listener.
84
119
  *
85
120
  * One listener per matched node was the obvious form and it had a hole with no warning attached:
86
121
  * the nodes are matched once, at prepare time, so a dot rendered afterwards was inert forever. The
@@ -90,23 +125,26 @@ export interface ControlGroup {
90
125
  * the selector at the moment of the press, which is the same rule `resolveSteps` and `countSteps`
91
126
  * already follow, and it collapses teardown from N removals to one.
92
127
  *
93
- * The root is the *scope* root, not simply the host. Under the page scope `step-progress` has
94
- * always resolved with, a control may legitimately sit outside the element it drives — arrows in a
95
- * section header above the deck — and a listener on the host would never see those clicks.
128
+ * Two listeners, each owning half the presses. A control may sit outside the element it drives —
129
+ * arrows in a section header, one pause button for a band of rings — which {@link queryControls}
130
+ * reaches by falling back to the page, so the document listens for presses *outside* the host. The
131
+ * host listens for presses inside itself, which keeps a detached or shadow-hosted deck working and
132
+ * bounds the walk below at the host: with no control inside, a wrapper *around* the deck that
133
+ * matches `next:` is a page match, and an unbounded walk from a press on a slide would climb into
134
+ * it and step the deck on every press. Each press is handled by exactly one of the two.
96
135
  *
97
136
  * Exported for `effects/carousel` alongside `countSteps`: `next:`/`prev:`/`jump:` mean the same
98
137
  * thing on a ring as they do on a bar, and every subtlety in the walk below (the `:scope` root, the
99
- * per-press re-query, the `scope:self` containment) was found once and should not be found again.
138
+ * per-press re-query, the containment at the host) was found once and should not be found again.
100
139
  *
101
- * @returns The teardown for the one listener.
102
- * @complexity O(g × (m + d)) per click — one scoped query and one ancestor walk per named group,
140
+ * @returns The teardown for both listeners.
141
+ * @complexity O(g × (m + d)) per click — at most two queries and one ancestor walk per named group,
103
142
  * in that group's matches `m` and the pressed node's depth `d`; O(m) space for the largest group.
104
143
  * @overallScore 100
105
144
  */
106
145
  export declare function delegateControls(request: {
107
146
  el: Element;
108
147
  ctx: PrepareContext;
109
- scope: TargetScope;
110
148
  groups: ControlGroup[];
111
149
  }): Cleanup;
112
150
  /**
@@ -0,0 +1,75 @@
1
+ import type { Cleanup } from '../core/types.js';
2
+ import type { Direction } from '../core/gesture.js';
3
+ /**
4
+ * How a swipe reaches a deck it does not sit on.
5
+ *
6
+ * A swipe either shares the deck's element (`data-kui="swipe-y, carousel"` — the two write
7
+ * different attributes, so the channel check lets them compose) or sits on a wrapper around it.
8
+ * Either way the last step — "a left flick means next" — was left to every page, as a
9
+ * `MutationObserver` on `data-kui-swipe` that each one wrote slightly differently.
10
+ *
11
+ * ## Why an event from the swipe, heard by the deck
12
+ *
13
+ * The two alternatives both put the knowledge in the wrong place:
14
+ *
15
+ * - **The swipe steps the deck.** `swipeable` would have to find decks below it and call into
16
+ * their index, which makes the gesture primitive know what a carousel is, and makes every future
17
+ * stepper (a tab set, a gallery) an edit to the gesture module.
18
+ * - **A deck parameter naming its swipe** (`swipe:'.shell'`). A second selector to keep in sync
19
+ * with markup that already says it — the deck is *inside* the swipe — and a deck and its wrapper
20
+ * cannot drift apart if nothing names the other.
21
+ *
22
+ * An event inverts it cleanly: the swipe announces "left, here" and knows nothing about decks; a
23
+ * deck listens and decides whether the swipe was about it. It bubbles, so a page can still hear it
24
+ * without a `MutationObserver`, and the attribute stays exactly as it was for CSS.
25
+ *
26
+ * The deck listens on its document rather than on an ancestor because it cannot know which ancestor
27
+ * will be the swipe, and because the two start in either order — a listener on the document is
28
+ * already there whichever element's effect prepares first.
29
+ */
30
+ /** The event a swipe dispatches on its own element. `detail.direction` is the flick. */
31
+ export declare const SWIPE_EVENT = "kui:swipe";
32
+ /** The payload of {@link SWIPE_EVENT}. */
33
+ export interface SwipeDetail {
34
+ direction: Direction;
35
+ }
36
+ /**
37
+ * Announce a recognised swipe from `el`.
38
+ *
39
+ * Built with the element's own realm's `CustomEvent`, so a deck in an iframe hears a swipe
40
+ * dispatched there.
41
+ *
42
+ * @complexity O(depth) for the bubble; O(1) space.
43
+ */
44
+ export declare function announceSwipe(win: Window, el: Element, direction: Direction): void;
45
+ /**
46
+ * Which way a swipe moves a deck: the content follows the finger, as on any native pager.
47
+ *
48
+ * A flick left or up drags the next slide in (`1`); right or down brings the previous one back
49
+ * (`-1`). Pure and exported so the mapping is assertable without a gesture.
50
+ *
51
+ * @complexity O(1) time and space.
52
+ */
53
+ export declare function swipeStep(direction: Direction): 1 | -1;
54
+ /**
55
+ * Whether a swipe announced by `source` is about the deck `el`.
56
+ *
57
+ * It is when the swipe is on the deck itself, or contains it with no other deck between them. The
58
+ * self case is checked first because the walk below starts at the deck's parent and so would never
59
+ * meet a swipe on the deck: it would climb to the root and refuse at any outer deck. The second half is
60
+ * what keeps a swipe on a page of decks-within-decks from stepping every level at once: an inner
61
+ * deck (a gallery inside one slide of an outer one) is reached by its outer deck first, and the
62
+ * outer deck is the one the wrapper was put around. A deck is recognised by `data-kui-step`, the
63
+ * attribute every stepper in this library publishes on its own host.
64
+ *
65
+ * @complexity O(d) in the depth between the deck and the swipe; O(1) space.
66
+ */
67
+ export declare function swipeReaches(source: Element, el: Element): boolean;
68
+ /**
69
+ * Step a deck whenever a swipe around it is announced.
70
+ *
71
+ * @param step - Called with `1` for next and `-1` for previous.
72
+ * @returns Removes the listener.
73
+ * @complexity O(1) to install; O(d) per swipe (see {@link swipeReaches}).
74
+ */
75
+ export declare function stepOnSwipe(doc: Document, el: Element, step: (direction: 1 | -1) => void): Cleanup;
@@ -1,5 +1,7 @@
1
1
  import { Animator } from './core/animator.js';
2
2
  import type { AnimatorOptions } from './core/animator.js';
3
+ import type { DescribedStep, EffectDescription, ElementDescription, ParamNotes } from './core/describe.js';
4
+ import type { Registry } from './core/registry.js';
3
5
  export * from './core/index.js';
4
6
  export { COMBOS, PRESETS, PRIMITIVES, createRegistry } from './effects/index.js';
5
7
  /**
@@ -14,3 +16,27 @@ export { COMBOS, PRESETS, PRIMITIVES, createRegistry } from './effects/index.js'
14
16
  */
15
17
  export declare function kuinetic(options?: AnimatorOptions): Animator;
16
18
  export default kuinetic;
19
+ export interface DescribeOptions {
20
+ /** Defaults to the bundled catalog. Pass `window.__kuinetic.registry` to include tier effects. */
21
+ registry?: Registry;
22
+ /** The plain-words table from the separate notes entry (`kuineticNotes.PARAM_NOTES`). */
23
+ notes?: ParamNotes;
24
+ }
25
+ /**
26
+ * Every parameter a reader could write for one effect name — see `core/describe.ts`.
27
+ *
28
+ * kuinetic.describe('hover-intent', { notes: kuineticNotes.PARAM_NOTES })
29
+ *
30
+ * @returns `undefined` when the name is not registered.
31
+ */
32
+ export declare function describe(name: string, options?: DescribeOptions): EffectDescription | undefined;
33
+ /**
34
+ * {@link describe} for every step of a whole `data-kui` value, parsed exactly as the runtime
35
+ * parses it: `describeSteps('fade-up 600ms, lift')`. Each step also says what it `written`.
36
+ */
37
+ export declare function describeSteps(value: string, options?: DescribeOptions): DescribedStep[];
38
+ /**
39
+ * {@link describeSteps}, plus the reserved keys (`on:`, `timeline:`, `at:` …) and which of the
40
+ * element-wide ones this value sets: `describeElement('fade-up on:click, lift')`.
41
+ */
42
+ export declare function describeElement(value: string, options?: DescribeOptions): ElementDescription;
@@ -0,0 +1,3 @@
1
+ import type { ParamNotes } from '../core/describe.js';
2
+ /** `effects/catalog/core.ts` and `effects/catalog/feedback.ts` (entrance, scroll-linked and feedback effects). */
3
+ export declare const CATALOG_CORE_NOTES: ParamNotes;
@@ -0,0 +1,3 @@
1
+ import type { ParamNotes } from '../core/describe.js';
2
+ /** `effects/catalog/ambient.ts`, `media.ts`, `materials.ts`, `transforms.ts`, `view-transitions.ts`. */
3
+ export declare const CATALOG_NOTES: ParamNotes;
@@ -0,0 +1,13 @@
1
+ import type { ParamNotes } from '../core/describe.js';
2
+ /**
3
+ * The plain-words note for every parameter in the bundled catalog, read by `describe()`.
4
+ *
5
+ * A separate entry (`kuinetic/notes`, and `kuinetic.notes.js` for a script tag) rather than a
6
+ * field on each `ParamSpec`, because this is prose only documentation pages read: inline, it would
7
+ * ship to every visitor of every site using the library. It imports types only, so it carries no
8
+ * runtime code either.
9
+ *
10
+ * Kept in step with the registry by `test/param-notes.test.ts`: every registered parameter must
11
+ * resolve a note, and every key here must name a parameter that exists.
12
+ */
13
+ export declare const PARAM_NOTES: ParamNotes;
@@ -0,0 +1,3 @@
1
+ import type { ParamNotes } from '../core/describe.js';
2
+ /** `effects/catalog/interaction-reveal.ts`. */
3
+ export declare const INTERACTION_REVEAL_NOTES: ParamNotes;
@@ -0,0 +1,6 @@
1
+ import type { ParamNotes } from '../core/describe.js';
2
+ /**
3
+ * `effects/catalog/interaction.ts`, `interaction-reveal.ts` (all but hover-intent, noted in
4
+ * `notes/interaction-reveal.ts`), `interaction-states.ts` and `interaction-proximity.ts`.
5
+ */
6
+ export declare const INTERACTION_NOTES: ParamNotes;
@@ -0,0 +1,7 @@
1
+ import type { ParamNotes } from '../core/describe.js';
2
+ /**
3
+ * The reserved `data-kui` keys — the ones the parser keeps for itself rather than handing to an
4
+ * effect. Keyed `'data-kui.<key>'` (`KEY_NOTE_OWNER` in `core/describe.ts`), so no effect name can
5
+ * shadow them. The first block applies to the whole element; the second to the step it is in.
6
+ */
7
+ export declare const KEY_NOTES: ParamNotes;
@@ -0,0 +1,7 @@
1
+ import type { ParamNotes } from '../core/describe.js';
2
+ /**
3
+ * `effects/layout/primitives.ts`, `effects/three-d/index.ts`, `effects/gestures/primitives.ts`,
4
+ * `effects/svg/index.ts`, `effects/motion-path/index.ts`, `effects/tween/*` and the `blur`, `scale`,
5
+ * `rotate`, `skew`, `progress` primitives in `effects/catalog/core.ts`.
6
+ */
7
+ export declare const MECHANICS_NOTES: ParamNotes;
@@ -0,0 +1,6 @@
1
+ import type { ParamNotes } from '../core/describe.js';
2
+ /**
3
+ * `effects/navigation/index.ts`, `effects/scroll-mechanics/primitives.ts` (+ `scroll-spy.ts`,
4
+ * `params.ts`), `effects/forms/primitives.ts`.
5
+ */
6
+ export declare const SCROLL_NAV_FORMS_NOTES: ParamNotes;
@@ -0,0 +1,6 @@
1
+ import type { ParamNotes } from '../core/describe.js';
2
+ /**
3
+ * Parameters that mean the same thing on every primitive that declares them. A primitive whose
4
+ * `duration` means something else (one cycle of a loop, say) overrides it under its own key.
5
+ */
6
+ export declare const SHARED_NOTES: ParamNotes;
@@ -0,0 +1,7 @@
1
+ import type { ParamNotes } from '../core/describe.js';
2
+ /**
3
+ * `showcase/*` (compare, device-frame, hotspots, lightbox, scroll-story, slideshow, slow-mo) and
4
+ * the step decks in `effects/carousel/*` and `effects/step-index.ts` (spatial-ring, spatial-stack,
5
+ * step-progress).
6
+ */
7
+ export declare const SHOWCASE_NOTES: ParamNotes;
@@ -0,0 +1,3 @@
1
+ import type { ParamNotes } from '../core/describe.js';
2
+ /** `effects/catalog/text.ts`, `effects/catalog/numbers.ts`, `effects/catalog/discrete.ts` (this batch's families). */
3
+ export declare const TEXT_NOTES: ParamNotes;
@@ -1,4 +1,13 @@
1
1
  import type { Preset, Primitive } from '../core/types.js';
2
2
  export declare const COMPARE_AXIS_ATTR = "data-kui-compare-axis";
3
+ /** Stamped only on a frame of three or more media: the number of dividers. Absent for a pair. */
4
+ export declare const COMPARE_DIVIDERS_ATTR = "data-kui-compare-dividers";
5
+ /** Below this a sweep is a flicker between the two images, not a comparison. */
6
+ export declare const MIN_SWEEP_MS = 250;
7
+ /**
8
+ * The longest step one frame may take. A tab returning from the background hands the next frame a
9
+ * gap of however long it was hidden; integrating that whole gap would jump the divider.
10
+ */
11
+ export declare const MAX_FRAME_MS = 100;
3
12
  export declare const COMPARE_PRIMITIVE: Primitive;
4
13
  export declare const COMPARE_PRESETS: Preset[];
@@ -23,6 +23,9 @@ export declare const SHOWCASE_PRESETS: Preset[];
23
23
  * `kuinetic.showcase.js` tag will hand a page's existing animator tomorrow. See
24
24
  * `src/core/register-into.ts`'s docblock for why identity cannot survive that split and shape can.
25
25
  *
26
+ * Also provides the deck viewer (`core/deck-viewer.ts`), the one capability decks in `src/effects/`
27
+ * reach in this module without importing it.
28
+ *
26
29
  * @param target - The registry, the animator, or a host exposing one as `.registry`.
27
30
  * @returns `target`, so callers can chain.
28
31
  * @complexity O(n) time in showcase primitives plus presets; O(1) extra space.
@@ -1,3 +1,25 @@
1
- import type { Preset, Primitive } from '../core/types.js';
1
+ import type { DeckViewerRequest } from '../core/deck-viewer.js';
2
+ import type { Cleanup, Preset, Primitive } from '../core/types.js';
3
+ /**
4
+ * Click-to-view for a deck: `carousel-3d lightbox:true` and every other deck with the parameter.
5
+ * Provided to the decks through `core/deck-viewer.ts` by `registerShowcase`.
6
+ *
7
+ * Every card of the deck becomes one gallery, opened at the card clicked, through the same
8
+ * `galleryContent` the `lightbox` effect opens — buttons, keys, swipe, `loop`, the media rules and
9
+ * the one shared modal shell, unforked.
10
+ *
11
+ * What counts as a click is the deck's to say, and it already says it. The listener is on the host
12
+ * in the *bubble* phase, and a deck's drag consumes the click that ends a drag in the *capture*
13
+ * phase on the same host (`carousel/drag.ts`'s `guardClicks`), with `stopPropagation` — so a drag
14
+ * never reaches this listener, and nothing here re-derives a threshold. (That a tap's click lands
15
+ * on the card at all, rather than on the host, rests on the drag taking pointer capture only once a
16
+ * press becomes a drag — `capturePointer: 'drag'` in `createRingDrag`.) A card turned away from
17
+ * the viewer is `pointer-events: none` (`carousel.css`) and cannot be the target; the
18
+ * `data-kui-ring-face` check repeats that for an engine whose hit-testing ignores it.
19
+ *
20
+ * @returns Removes the listener and releases this deck's hold on the shared modal.
21
+ * @complexity O(n) per click in the deck's cards; O(1) space between clicks.
22
+ */
23
+ export declare function attachDeckLightbox(request: DeckViewerRequest): Cleanup;
2
24
  export declare const LIGHTBOX_PRIMITIVE: Primitive;
3
25
  export declare const LIGHTBOX_PRESETS: Preset[];
@@ -7,6 +7,17 @@ export interface ModalContent {
7
7
  reducedMotion: boolean;
8
8
  onKey?: (event: KeyboardEvent) => void;
9
9
  onClose?: () => void;
10
+ /**
11
+ * Whether a click on `target` lands on the content and keeps the viewer open. Omitted, anything
12
+ * inside the content node counts; a viewer whose node fills the screen narrows it to its media.
13
+ */
14
+ inside?: (target: Element) => boolean;
15
+ /**
16
+ * The element that opened this content — a deck's card, a `lightbox` trigger. The shell announces
17
+ * `kui:viewer` from it on open and on close (`core/deck-viewer.ts`), which is how a deck it sits in
18
+ * pauses its own motion for exactly as long as the viewer shows one of its cards.
19
+ */
20
+ opener?: Element;
10
21
  }
11
22
  export interface ModalShell {
12
23
  open(content: ModalContent): void;