kuinetic 0.1.4 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (124) hide show
  1. package/README.md +93 -10
  2. package/dist/esm/advanced/index.mjs +4727 -0
  3. package/dist/esm/chunk-AEQGC2KM.mjs +189 -0
  4. package/dist/esm/chunk-MQSNMYRP.mjs +9332 -0
  5. package/dist/esm/chunk-RVTYN6NZ.mjs +3446 -0
  6. package/dist/esm/chunk-U43BX5O2.mjs +2984 -0
  7. package/dist/esm/core/index.mjs +16 -9
  8. package/dist/esm/effects/index.mjs +19 -4
  9. package/dist/esm/index.mjs +17 -10
  10. package/dist/kuinetic.advanced.js +5070 -0
  11. package/dist/kuinetic.advanced.min.js +749 -0
  12. package/dist/kuinetic.all.js +15147 -6039
  13. package/dist/kuinetic.all.min.js +2 -0
  14. package/dist/kuinetic.css +2801 -318
  15. package/dist/kuinetic.js +15147 -6038
  16. package/dist/kuinetic.min.css +1 -0
  17. package/dist/kuinetic.min.js +7 -0
  18. package/dist/types/3d/angles.d.ts +30 -0
  19. package/dist/types/3d/flattening.d.ts +49 -0
  20. package/dist/types/3d/index.d.ts +36 -0
  21. package/dist/types/3d/model-3d.d.ts +115 -0
  22. package/dist/types/3d/register.d.ts +26 -0
  23. package/dist/types/3d/webgl.d.ts +24 -0
  24. package/dist/types/advanced/audio.d.ts +322 -0
  25. package/dist/types/advanced/base.d.ts +233 -0
  26. package/dist/types/advanced/camera-3d.d.ts +236 -0
  27. package/dist/types/advanced/fluid-cursor.d.ts +149 -0
  28. package/dist/types/advanced/gl-utils.d.ts +52 -0
  29. package/dist/types/advanced/glsl.d.ts +60 -0
  30. package/dist/types/advanced/index.d.ts +23 -0
  31. package/dist/types/advanced/particles.d.ts +108 -0
  32. package/dist/types/advanced/scenes.d.ts +207 -0
  33. package/dist/types/advanced/shaders.d.ts +972 -0
  34. package/dist/types/browser/boot.d.ts +72 -0
  35. package/dist/types/core/activation.d.ts +227 -2
  36. package/dist/types/core/animator.d.ts +446 -11
  37. package/dist/types/core/attrs.d.ts +9 -0
  38. package/dist/types/core/breakpoints.d.ts +260 -0
  39. package/dist/types/core/bundles.d.ts +71 -0
  40. package/dist/types/core/callback.d.ts +64 -0
  41. package/dist/types/core/capabilities.d.ts +47 -0
  42. package/dist/types/core/channels.d.ts +120 -2
  43. package/dist/types/core/cloak-selectors.d.ts +47 -0
  44. package/dist/types/core/compile.d.ts +207 -2
  45. package/dist/types/core/control.d.ts +218 -0
  46. package/dist/types/core/declarations.d.ts +128 -0
  47. package/dist/types/core/derived/aggregate.d.ts +43 -0
  48. package/dist/types/core/derived/book.d.ts +30 -0
  49. package/dist/types/core/derived/diagnostics.d.ts +51 -0
  50. package/dist/types/core/derived/group-gate.d.ts +62 -0
  51. package/dist/types/core/derived/install.d.ts +110 -0
  52. package/dist/types/core/derived/late-matches.d.ts +19 -0
  53. package/dist/types/core/derived/types.d.ts +108 -0
  54. package/dist/types/core/easing.d.ts +57 -0
  55. package/dist/types/core/element-config.d.ts +47 -6
  56. package/dist/types/core/element-size.d.ts +23 -0
  57. package/dist/types/core/event-sources.d.ts +39 -0
  58. package/dist/types/core/flip.d.ts +34 -1
  59. package/dist/types/core/gesture.d.ts +12 -0
  60. package/dist/types/core/host-facts.d.ts +155 -0
  61. package/dist/types/core/index.d.ts +7 -3
  62. package/dist/types/core/owned-styles.d.ts +129 -2
  63. package/dist/types/core/params.d.ts +48 -0
  64. package/dist/types/core/parse.d.ts +28 -1
  65. package/dist/types/core/register-into.d.ts +29 -0
  66. package/dist/types/core/registry.d.ts +10 -3
  67. package/dist/types/core/repeat.d.ts +91 -0
  68. package/dist/types/core/sequence.d.ts +126 -0
  69. package/dist/types/core/stagger-config.d.ts +199 -0
  70. package/dist/types/core/stagger-keys.d.ts +63 -0
  71. package/dist/types/core/stagger.d.ts +187 -6
  72. package/dist/types/core/style-plan.d.ts +21 -16
  73. package/dist/types/core/target.d.ts +128 -0
  74. package/dist/types/core/threshold-reachability.d.ts +45 -0
  75. package/dist/types/core/time-scale.d.ts +9 -0
  76. package/dist/types/core/toggle-actions.d.ts +141 -0
  77. package/dist/types/core/travel.d.ts +59 -0
  78. package/dist/types/core/types.d.ts +699 -13
  79. package/dist/types/core/unquoted-selectors.d.ts +53 -0
  80. package/dist/types/effects/carousel/drag.d.ts +80 -0
  81. package/dist/types/effects/carousel/index.d.ts +149 -0
  82. package/dist/types/effects/catalog/discrete.d.ts +13 -0
  83. package/dist/types/effects/catalog/interaction-proximity.d.ts +24 -0
  84. package/dist/types/effects/catalog/interaction-reveal.d.ts +248 -0
  85. package/dist/types/effects/catalog/interaction-shared.d.ts +42 -0
  86. package/dist/types/effects/catalog/interaction-states.d.ts +68 -0
  87. package/dist/types/effects/catalog/materials.d.ts +67 -0
  88. package/dist/types/effects/catalog/media-shared.d.ts +3 -2
  89. package/dist/types/effects/catalog/numbers-shared.d.ts +8 -5
  90. package/dist/types/effects/catalog/shared.d.ts +1 -1
  91. package/dist/types/effects/catalog/text-shared.d.ts +86 -10
  92. package/dist/types/effects/catalog/transforms.d.ts +13 -0
  93. package/dist/types/effects/catalog/view-transitions.d.ts +13 -0
  94. package/dist/types/effects/forms/primitives.d.ts +15 -4
  95. package/dist/types/effects/gestures/index.d.ts +53 -0
  96. package/dist/types/effects/index.d.ts +5 -0
  97. package/dist/types/effects/layout/presets.d.ts +35 -0
  98. package/dist/types/effects/motion-path/index.d.ts +35 -0
  99. package/dist/types/effects/scroll-mechanics/params.d.ts +23 -0
  100. package/dist/types/effects/scroll-mechanics/presets.d.ts +51 -0
  101. package/dist/types/effects/scroll-mechanics/section-index.d.ts +47 -0
  102. package/dist/types/effects/shared.d.ts +124 -4
  103. package/dist/types/effects/step-index.d.ts +121 -0
  104. package/dist/types/effects/step-marking.d.ts +41 -33
  105. package/dist/types/effects/svg/icon-parts.d.ts +23 -0
  106. package/dist/types/effects/three-d/flip-parts.d.ts +46 -0
  107. package/dist/types/effects/tween/index.d.ts +57 -0
  108. package/dist/types/effects/tween/properties.d.ts +107 -0
  109. package/dist/types/effects/tween/waypoints.d.ts +106 -0
  110. package/dist/types/showcase/compare.d.ts +4 -0
  111. package/dist/types/showcase/device-frame.d.ts +3 -0
  112. package/dist/types/showcase/hotspots.d.ts +3 -0
  113. package/dist/types/showcase/index.d.ts +30 -0
  114. package/dist/types/showcase/lightbox.d.ts +3 -0
  115. package/dist/types/showcase/media-source.d.ts +13 -0
  116. package/dist/types/showcase/modal-shell.d.ts +22 -0
  117. package/dist/types/showcase/scroll-story.d.ts +4 -0
  118. package/dist/types/showcase/shared.d.ts +47 -0
  119. package/dist/types/showcase/slideshow.d.ts +3 -0
  120. package/dist/types/showcase/slow-mo.d.ts +5 -0
  121. package/package.json +14 -7
  122. package/dist/esm/chunk-7WMNPIOZ.mjs +0 -4883
  123. package/dist/esm/chunk-JT4PZL3A.mjs +0 -773
  124. package/dist/esm/chunk-TJDDIQRG.mjs +0 -1290
@@ -0,0 +1,53 @@
1
+ import type { EffectSpec } from './types.js';
2
+ import type { Registry } from './registry.js';
3
+ /**
4
+ * Owned by phase 9b — catching the classic `target:` typo: `pop target:.a, .b` reads as three
5
+ * effect segments (`pop`, `.a`, `.b`), not one effect with a two-part selector, because a comma
6
+ * separates *segments* everywhere else in the grammar and `target:` is the one place a selector
7
+ * can legitimately contain one. An author who forgets the quotes (`target:'.a, .b'`) gets a stray
8
+ * segment named `.a` that is not a registered effect and looks exactly like a selector — this
9
+ * module finds that shape and 9b's diagnostics surface it via `data-kui-unmatched`.
10
+ */
11
+ /** One stray segment that looks like a continuation of the previous segment's `target:` selector. */
12
+ export interface UnquotedSelector {
13
+ /** The previous segment's `target:` value, as authored. */
14
+ previous: string;
15
+ /** The stray segment's name — the text between commas that isn't a registered effect. */
16
+ segment: string;
17
+ }
18
+ /**
19
+ * Find segments whose first token is not a registered effect name, that look like a selector
20
+ * fragment (start with `.`, `#`, `[`, `*`, `:`, or contain `>`, `~`, `+`), and whose *previous*
21
+ * segment carries a `target:` token — the shape an unquoted, comma-containing selector leaves
22
+ * behind once `parse.ts` has already split it on the comma.
23
+ *
24
+ * Pure: it only reads `specs` and `registry`, and never changes how anything is parsed or
25
+ * compiled — the fix is a warning, not a silent re-join, because re-joining would have to guess
26
+ * where the selector was supposed to end.
27
+ *
28
+ * Known limitation: only catches a stray segment whose *first token* looks like a selector.
29
+ * `pop target:.a, li > a` splits into a stray segment `li > a` whose first token is `li` — a
30
+ * plausible effect name — so it is not flagged, even though `>` later in the segment is still a
31
+ * combinator. Detecting that shape would mean scanning every segment for a `target:`-less
32
+ * combinator, not just the one right after a `target:`-bearing segment, which is out of scope
33
+ * here: this pass only looks at the segment immediately following one that carries `target:`.
34
+ *
35
+ * @param specs - The document's parsed effect segments, in authored order. Pre-lift: `target:`
36
+ * still lives in `spec.params.target` rather than having been pulled onto a separate field.
37
+ * @param registry - Where a segment's first token is looked up to decide if it names a real
38
+ * effect.
39
+ * @returns Every suspect segment found, in authored order.
40
+ * @complexity O(n) time in `specs.length`, each iteration doing an O(1) registry lookup; O(f)
41
+ * space in the number of findings.
42
+ * @overallScore 100
43
+ */
44
+ export declare function findUnquotedSelectors(specs: EffectSpec[], registry: Registry): UnquotedSelector[];
45
+ /**
46
+ * Render one {@link UnquotedSelector} finding as a warning string, naming both halves of the fix:
47
+ * quote the previous segment's selector so the whole comma list is one `target:` value.
48
+ *
49
+ * @param found - One finding from {@link findUnquotedSelectors}.
50
+ * @complexity O(1) time and space.
51
+ * @overallScore 100
52
+ */
53
+ export declare function unquotedSelectorWarning(found: UnquotedSelector): string;
@@ -0,0 +1,80 @@
1
+ import type { PrepareContext } from '../../core/effect-context.js';
2
+ import type { Cleanup } from '../../core/types.js';
3
+ export interface RingDragRequest {
4
+ el: Element;
5
+ ctx: PrepareContext;
6
+ /** `grab:false` still installs the keyboard half — that is accessibility, not decoration. */
7
+ enabled: boolean;
8
+ /** Pointer pixels per place on the ring. */
9
+ travelPx: number;
10
+ /** How many places the ring has *now*; asked per gesture, never captured. */
11
+ total(): number;
12
+ /** The ring's current continuous position, in places. */
13
+ positionOf(): number;
14
+ /** Move the ring to a continuous position and re-render. */
15
+ moveTo(position: number): void;
16
+ /** Publish or clear the dragging state the stylesheet suspends its transition on. */
17
+ setDragging(dragging: boolean): void;
18
+ }
19
+ /**
20
+ * Where a release coasts to, before snapping.
21
+ *
22
+ * Velocity is in pixels per second and the ring is measured in places, so the projection is
23
+ * `velocity / travel * seconds` — one conversion, in one place, in the unit the index uses.
24
+ *
25
+ * Capped, and the cap is not politeness. Uncapped, a hard flick on a phone (velocities of several
26
+ * thousand pixels per second are ordinary) projects a dozen places, and a ring that spins a dozen
27
+ * cards from one flick has stopped being a control and become a slot machine: the visitor cannot
28
+ * predict where it lands, so they cannot aim, so they flick again. Four places is far enough that a
29
+ * deliberate throw clearly differs from a nudge and close enough that the destination stays
30
+ * legible.
31
+ *
32
+ * Pure and exported so both halves — the conversion and the cap — are assertable without a pointer.
33
+ *
34
+ * @param position - Where the ring is at release, in places.
35
+ * @param velocityPxPerSec - Signed pointer velocity along the drag axis.
36
+ * @param travelPx - Pixels per place.
37
+ * @returns The un-snapped position the ring is heading for.
38
+ * @complexity O(1) time and space.
39
+ */
40
+ export declare function projectRelease(position: number, velocityPxPerSec: number, travelPx: number): number;
41
+ /**
42
+ * Which place a released ring settles on.
43
+ *
44
+ * Separate from {@link projectRelease} because they answer different questions and only one of them
45
+ * has an opinion: projection is physics, snapping is the contract that a ring always comes to rest
46
+ * facing a card. Splitting them is also what makes "a flick of exactly half a place still commits"
47
+ * testable without synthesising a gesture.
48
+ *
49
+ * `+ 0` is not decoration. `Math.round(-0.4)` is `-0`, and a ring settled at "minus place zero" is
50
+ * not a place — it is the same place spelled in a way that half of JavaScript disagrees with
51
+ * itself about. `-0 === 0` is true, so most arithmetic hides it; `Object.is(-0, 0)` is false, which
52
+ * is what `toBe` compares with, and `[-0].toString()` and `JSON.stringify(-0)` disagree with each
53
+ * other too. Adding zero normalises the sign at the one place a negative zero can be produced,
54
+ * rather than leaving every caller and every serialisation to find out for itself.
55
+ *
56
+ * @param projected - The coasting destination, in places.
57
+ * @returns The nearest whole place, never negative zero.
58
+ * @complexity O(1) time and space.
59
+ */
60
+ export declare function snapTo(projected: number): number;
61
+ /**
62
+ * Bring a settled position onto `[0, total)`.
63
+ *
64
+ * A ring has no ends, so nothing about the geometry needs this — `--kui-offset` is computed modulo
65
+ * the count and `render` rebases the published position on the live step every time. What needs it
66
+ * is arithmetic hygiene over a long session: `position` accumulates every drag ever made, and a
67
+ * number in the millions loses the fractional precision the sub-step rotation is carried in. Doing
68
+ * it at rest rather than mid-drag keeps the drag itself monotonic, so a gesture never jumps because
69
+ * it happened to cross the seam.
70
+ *
71
+ * @complexity O(1) time and space.
72
+ */
73
+ export declare function wrapPlace(place: number, total: number): number;
74
+ /**
75
+ * Install the grab and the keyboard on a ring.
76
+ *
77
+ * @returns Teardown for every listener and attribute this installs.
78
+ * @complexity O(1) per pointer event and per key; O(1) space.
79
+ */
80
+ export declare function createRingDrag(request: RingDragRequest): Cleanup;
@@ -0,0 +1,149 @@
1
+ import type { Preset, Primitive } from '../../core/types.js';
2
+ import type { Registry } from '../../core/registry.js';
3
+ /**
4
+ * Whether a slot at this ring angle has its own plane turned away from the camera.
5
+ *
6
+ * Pure and exported so the rule is assertable without a DOM or a layout. The angle is the slot's
7
+ * signed place on the ring in degrees, already including the live-step drift.
8
+ *
9
+ * `>=` rather than `>` at exactly a quarter turn: a slot edge-on to the camera has zero projected
10
+ * width, so it can only ever swallow a click that was aimed at something else. Calling it "back" is
11
+ * the answer that costs nothing and removes the boundary case from the hit-testing question.
12
+ *
13
+ * @param angleDeg - Signed degrees from the camera-facing position.
14
+ * @returns `'back'` when the slot's face points away, `'front'` otherwise.
15
+ * @complexity O(1) time and space.
16
+ */
17
+ export declare function faceAt(angleDeg: number): 'front' | 'back';
18
+ /**
19
+ * Bring an angle onto `(-180, 180]`, the range where "how far from facing me" is a magnitude.
20
+ *
21
+ * Without it a slot at `+350deg` — which is 10 degrees from front — reads as further from the
22
+ * camera than one at `+100deg`, and the whole back half of a large ring is mislabelled.
23
+ *
24
+ * @complexity O(1) time and space.
25
+ */
26
+ export declare function normaliseDegrees(angleDeg: number): number;
27
+ /**
28
+ * Where the ring's continuous position rounds to, and how far past it the ring currently sits.
29
+ *
30
+ * The pair the stylesheet needs and the reason both tokens are published rather than one: the
31
+ * integer decides which slot is *live* (and therefore what `--kui-offset` means for every other
32
+ * slot), and the remainder is the sub-step rotation on top of it. Re-snapping the integer whenever
33
+ * the remainder passes a half step is what keeps the remainder inside ±0.5, so the ring never has
34
+ * to travel back across the whole strip to wrap from the last slide to the first — the same
35
+ * property `circularOffset` gives the discrete case.
36
+ *
37
+ * @param position - Continuous ring position, in places. May be negative or beyond the count.
38
+ * @param total - How many places the ring has.
39
+ * @returns The live integer step, wrapped into range, and the signed remainder in `[-0.5, 0.5)`.
40
+ * @complexity O(1) time and space.
41
+ */
42
+ export declare function snapPosition(position: number, total: number): {
43
+ step: number;
44
+ drift: number;
45
+ };
46
+ /**
47
+ * An authored `<angle>` as a number of degrees.
48
+ *
49
+ * `params.ts` accepts every CSS angle unit and normalises only the bare/`d` spellings to `deg`, so
50
+ * a value reaching here can legitimately be `0.05turn` or `1.2rad`. The stylesheet does not care —
51
+ * it hands the token straight to `rotateY()` — but this file computes with it (to decide which
52
+ * slots face away), and a `Number('0.05turn')` is `NaN`.
53
+ *
54
+ * Falls back rather than throwing on an unrecognised unit for the reason every reader in this
55
+ * library does: the value has already been validated, so an unfamiliar spelling here means this
56
+ * function is behind `params.ts`, and a ring that spreads over the default arc is a better answer
57
+ * than one that throws during `prepare`.
58
+ *
59
+ * @param value - A validated CSS angle.
60
+ * @param fallbackDeg - Used when the unit is not one this function converts.
61
+ * @returns Degrees.
62
+ * @complexity O(n) time in value length; O(1) space.
63
+ */
64
+ export declare function degreesOf(value: string, fallbackDeg: number): number;
65
+ /**
66
+ * The ring primitive.
67
+ *
68
+ * `renderer: 'javascript'` and two channels together are the honest declaration: this file paints
69
+ * nothing itself — it publishes numbers — but `carousel.css` does, and both of the things it writes
70
+ * have to be declared or the conflict detector waves through a composition that silently loses one
71
+ * effect's output.
72
+ *
73
+ * `skew` is this catalog's name for "claims the whole `transform` shorthand" (see `CHANNEL.skew` in
74
+ * `core/types.ts`), which `carousel.css` writes on the elements this effect drives. The shorthand is
75
+ * unavoidable here: three-dimensional placement needs `rotateX`/`rotateY`/`translateZ` composed in a
76
+ * fixed order, and `rotate:`/`translate:` as individual properties are applied in the order the spec
77
+ * fixes, which is the reverse of the one this geometry needs.
78
+ *
79
+ * `discrete` is `display`, and it is here for exactly the reason `card-toggle` declares it
80
+ * (`effects/three-d/index.ts`): the unconditional host rule pins `display: grid` so every slot
81
+ * starts from the same cell instead of flowing down the page as block siblings. That has nothing to
82
+ * do with `catalog/discrete.ts`'s show/hide use of the same property, but `display` is tracked as
83
+ * one channel whatever value is written into it — the point of the channel model is the physical
84
+ * property, not the intent — so a ring composed with an `@starting-style` open/close effect is now
85
+ * correctly reported as a collision rather than quietly letting one of the two win.
86
+ *
87
+ * `reducedMotion: 'shorten'`, not `'disable'`, for exactly the reason `step-progress` carries the
88
+ * same note: under `disable` the animator never activates the instance, so no index is published,
89
+ * no slot is marked, and the arrows do nothing. A visitor who asked for less motion would get a
90
+ * dead widget rather than a calm one. Reduced motion suppresses the *travel* between places — which
91
+ * `base.css`'s policy layer already does to the transition `carousel.css` declares — never the
92
+ * function.
93
+ */
94
+ export declare const SPATIAL_RING_PRIMITIVE: Primitive;
95
+ export declare const CAROUSEL_PRIMITIVES: Primitive[];
96
+ /**
97
+ * Convex and concave are two names, not one parameter with two values.
98
+ *
99
+ * They are one sign in the maths — `translateZ(+r)` puts the camera outside the ring,
100
+ * `translateZ(-r)` puts it at the centre — and everything else about them differs. Outside, the far
101
+ * half of the ring is content: smaller, dimmer, and part of the composition, and a full 360-degree
102
+ * spread is right. Inside, the far half is *behind your head*, so a full spread hides most of the
103
+ * deck and the sensible default is a slice; the cards nearest the edges of that slice are the ones
104
+ * that need culling; and "next" moves the world past a fixed viewer rather than turning an object
105
+ * in front of one.
106
+ *
107
+ * A `curve:` keyword would have made all of that one name's problem to explain, with half its
108
+ * documentation prefixed by "unless". Two names each get their own defaults, their own rules in
109
+ * `carousel.css`, and their own paragraph.
110
+ *
111
+ * `requiresOwnSubtree` on all of them: every rule in `carousel.css` past the host rule reaches the
112
+ * slots, so a `target:` that relocated `data-kui-fx` onto one slot would leave those selectors
113
+ * looking for grandchildren nobody authored. (The primitive declares a `target` parameter of its
114
+ * own, so `compile.ts` never consults the flag — but `test/css-requires-own-subtree.test.ts`
115
+ * re-derives the reaching set from the stylesheet and does, and the fact it records is true.)
116
+ *
117
+ * `phase: 'idle'` on all of them too, and for the same reason `target` does not change the answer:
118
+ * `channelsFor`/`findConflicts` (`core/channels.ts`) reason about the channels *this preset
119
+ * declares* on the host it is authored on (`CHANNEL.skew` and `'discrete'`, from
120
+ * `SPATIAL_RING_PRIMITIVE`), not about which element in the subtree physically paints them — the
121
+ * compiler has no notion of "the primitive's own target moved the real work three nodes down," and
122
+ * does not need one here, because `requiresOwnSubtree` already keeps this preset off of anything
123
+ * `target:` could relocate.
124
+ *
125
+ * The phase question itself is the same one `scroll-mechanics/presets.ts` answers at length:
126
+ * `prepareSpatialRing` calls `render()` unconditionally during `prepare` — before any drag, before
127
+ * any control click — which stamps `data-kui-step`/`--kui-step-position`/the per-slot face
128
+ * attributes immediately, and the primitive's `defaultActivation` is `'load'`. So, like the scroll
129
+ * primitives and unlike `gestures/index.ts`'s `draggable` (silent until `pointerdown`), a ring never
130
+ * has a quiescent stretch for an entrance to release into — it claims `skew`/`discrete` from the
131
+ * first frame and holds them, drag or no drag, until `destroy()`. That is `EffectPhase`'s `idle`:
132
+ * "runs unbounded, so it never yields the channel at all." It buys nothing new by itself —
133
+ * `INDEPENDENT_PHASES` exempts only `entrance|state` and `exit|state`, so `idle` still collides with
134
+ * everything including another `idle`, which is correct: `carousel-3d, flip-in-x` really would fight
135
+ * over the `transform` shorthand (`CHANNEL.skew`) for the ring's whole lifetime, and there is no
136
+ * turn-taking to model. It replaces "nothing is known" with the actual, checked fact.
137
+ *
138
+ * None of the four declares `transitions`: nothing here renders through a CSS `transition:` on the
139
+ * host box for `phaseOf` to derive `state` from — the ring is `renderer: 'javascript'` throughout.
140
+ */
141
+ export declare const CAROUSEL_PRESETS: Preset[];
142
+ /**
143
+ * Register the spatial carousel.
144
+ *
145
+ * @param registry - Registry to populate.
146
+ * @returns The same registry, for chaining.
147
+ * @complexity O(n) time in the number of primitives and presets.
148
+ */
149
+ export declare function registerCarousel(registry: Registry): Registry;
@@ -0,0 +1,13 @@
1
+ import type { Preset, Primitive } from '../../core/types.js';
2
+ import type { Registry } from '../../core/registry.js';
3
+ export declare const DISCRETE_PRIMITIVES: Primitive[];
4
+ export declare const DISCRETE_PRESETS: Preset[];
5
+ /**
6
+ * Register the `@starting-style` discrete-open catalog (section J).
7
+ *
8
+ * @param registry - Registry to populate.
9
+ * @returns The same registry, for chaining.
10
+ * @complexity O(n) time in registered primitives and presets.
11
+ * @overallScore 100
12
+ */
13
+ export declare function registerDiscrete(registry: Registry): Registry;
@@ -0,0 +1,24 @@
1
+ import type { Preset, Primitive } from '../../core/types.js';
2
+ import type { Registry } from '../../core/registry.js';
3
+ export declare const PROXIMITY_FIELD_PRIMITIVES: Primitive[];
4
+ export declare const PROXIMITY_FIELD_PRESETS: Preset[];
5
+ export declare const PROXIMITY_GLOW_PRIMITIVES: Primitive[];
6
+ export declare const PROXIMITY_GLOW_PRESETS: Preset[];
7
+ /** Both halves of the family, for `registerInteractionProximity` below. */
8
+ export declare const INTERACTION_PROXIMITY_PRIMITIVES: Primitive[];
9
+ export declare const INTERACTION_PROXIMITY_PRESETS: Preset[];
10
+ /**
11
+ * Register the cross-element pointer-proximity family.
12
+ *
13
+ * A standalone top-level registration called directly from `catalog/index.ts`, the same shape
14
+ * `registerInteraction`'s own doc comment describes for `registerGestures`/`registerThreeD` —
15
+ * deliberately not folded into `interaction.ts`'s `INTERACTION_PRIMITIVES`/`_PRESETS`, which sits on
16
+ * that file's own 400-line lint ceiling and is being edited by another session concurrently with
17
+ * this one.
18
+ *
19
+ * @param registry - Registry to populate.
20
+ * @returns The same registry, for chaining.
21
+ * @complexity O(n) time in registered primitives and presets.
22
+ * @overallScore 100
23
+ */
24
+ export declare function registerInteractionProximity(registry: Registry): Registry;
@@ -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;