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,155 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The facts that belong to the *element* rather than to any one effect on it.
|
|
3
|
+
*
|
|
4
|
+
* Only `reducedMotion` is still a true single-element fact after the plan's D-B.4 reversal: `rm:`
|
|
5
|
+
* is one author decision (or one folded default) that applies however many `target:` groups the
|
|
6
|
+
* attribute compiles into, so {@link mergeHostFacts} still folds the strictest declared policy
|
|
7
|
+
* across every group and writes the merged answer back onto all of them.
|
|
8
|
+
*
|
|
9
|
+
* `supportedActivations`/`supportedTimelines`/`channels`/`defaultActivation` are **not** folded
|
|
10
|
+
* here any more — a `target:` group is a derived host in its own right (each match will get its
|
|
11
|
+
* own trigger, timeline and events once Phase 2 lands), so pretending its capabilities are the
|
|
12
|
+
* *document's* capabilities was the bug: `parallax-scale target:video timeline:view` alongside a
|
|
13
|
+
* `flip-card` host used to intersect the two primitives' `supportedTimelines` down to `[]` and warn
|
|
14
|
+
* a mismatch that was never really there. `compile.ts`'s `compileTargets` now resolves each of
|
|
15
|
+
* those per group, from that group's own composed entries, and writes them straight onto that
|
|
16
|
+
* group's own plan — see this plan's design doc, section D-B.4, for the full reasoning.
|
|
17
|
+
*
|
|
18
|
+
* Split out of `compile.ts` when that file reached its own 400-line lint ceiling, the same way
|
|
19
|
+
* `test/css-composition-invariants.test.ts` was split out of `css-invariants.test.ts`. The seam is
|
|
20
|
+
* not arbitrary: everything here folds a list of already-computed facts down to one value, and
|
|
21
|
+
* nothing here knows what an effect, a primitive or a registry is. `resolveDefaultActivation` takes
|
|
22
|
+
* {@link ActivationClaim}s rather than compiled entries for exactly that reason — the same
|
|
23
|
+
* structural-input argument `channels.ts` makes for `ChannelClaim`, and it is what keeps the
|
|
24
|
+
* type-only import of `CompiledPlan` below from becoming a runtime cycle.
|
|
25
|
+
*/
|
|
26
|
+
import type { CompiledPlan } from './compile.js';
|
|
27
|
+
import type { Activation, EffectPhase, ReducedMotionPolicy } from './types.js';
|
|
28
|
+
/** What {@link resolveDefaultActivation} needs to know about one composed effect. */
|
|
29
|
+
export interface ActivationClaim {
|
|
30
|
+
/** When this effect holds its channels — `compile.ts`'s `phaseOf`, already derived. */
|
|
31
|
+
phase?: EffectPhase;
|
|
32
|
+
/** The activation its primitive prefers, or absent when it expresses no preference. */
|
|
33
|
+
defaultActivation?: Activation;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Which activation the composed effects prefer, when the author named none.
|
|
37
|
+
*
|
|
38
|
+
* An element has exactly one activation binding — `animator.ts`'s `resolveActivation` returns a
|
|
39
|
+
* single `Activation` and every effect on the element is wired to it — so a comma list whose halves
|
|
40
|
+
* want different triggers cannot have both. What this decides is which one loses.
|
|
41
|
+
*
|
|
42
|
+
* It used to be first-wins: `plan.defaultActivation ??= primitive.defaultActivation`, folded in
|
|
43
|
+
* authoring order in `buildPlan` and again across every `target:` group's already-folded answer in
|
|
44
|
+
* `mergeHostFacts` (the D1 model — one document-wide answer written onto every group, since D-B.4
|
|
45
|
+
* reversed that: each group now calls this function once, over only its own composed entries, and
|
|
46
|
+
* keeps its own answer). `??=` is the right operator for "a default fills in when nothing is set"
|
|
47
|
+
* and the wrong one for a *merge*, because there is no sense in which the first name in a comma
|
|
48
|
+
* list is the authoritative one. The consequence was that composing an entrance with a behaviour
|
|
49
|
+
* changed **when the entrance itself fired**: `lift` declares `defaultActivation: 'load'`, so
|
|
50
|
+
* `data-kui="fade-up, lift"` — the pair the whole phase axis was added to allow — bound the element
|
|
51
|
+
* on `load` and fired the reveal before it was ever scrolled to. `back-in-down, drag` did the same.
|
|
52
|
+
* Measured across the catalog, 4,545 composing pairs took their activation from the non-entrance
|
|
53
|
+
* half.
|
|
54
|
+
*
|
|
55
|
+
* The rule is that **an entrance names the trigger**, and it is the lesser of two unavoidable harms
|
|
56
|
+
* rather than a preference. Either choice compromises something:
|
|
57
|
+
*
|
|
58
|
+
* - Binding on the behaviour's `load` spends the reveal on a below-the-fold element while nobody is
|
|
59
|
+
* looking. The entrance is destroyed outright and cannot be recovered.
|
|
60
|
+
* - Binding on the entrance's `enter` wires the behaviour up when the element scrolls into view
|
|
61
|
+
* instead of at load. It is late, and it still works — nobody drags, hovers or clicks an element
|
|
62
|
+
* they cannot see.
|
|
63
|
+
*
|
|
64
|
+
* `?? 'enter'` on the found entrance, not its raw declaration: almost no entrance declares an
|
|
65
|
+
* activation (the catalog's entrances leave it undefined and let `element-config.ts`'s `'enter'`
|
|
66
|
+
* fall through), so returning `undefined` here would hand the decision straight back to the
|
|
67
|
+
* first-wins fallback and change nothing at all. Materialising `'enter'` is what makes the
|
|
68
|
+
* entrance's silence a position. It is materialised *only* when something else declared a value to
|
|
69
|
+
* beat — a lone `fade-up` still compiles `defaultActivation: undefined`, exactly as before.
|
|
70
|
+
*
|
|
71
|
+
* ## No warning, and that was measured rather than assumed
|
|
72
|
+
*
|
|
73
|
+
* Silently picking one is what caused this, so a diagnostic is the obvious accompaniment. Two
|
|
74
|
+
* definitions were tried against the catalog and both are noise, not signal:
|
|
75
|
+
*
|
|
76
|
+
* - "the resolved activation is not in some composed primitive's `supportedActivations`" — 6,098
|
|
77
|
+
* pairs, including `fade-up, lift`, because `lift` declares `supportedActivations: ['load']`.
|
|
78
|
+
* - "two entries each declare an activation and the values differ" — 4,882 pairs, again including
|
|
79
|
+
* the flagship pairs.
|
|
80
|
+
*
|
|
81
|
+
* Disagreement is the *normal* state of this catalog: 120 of 290 names declare `load`, 21 declare
|
|
82
|
+
* `enter`, 124 declare nothing. A warning that fires on one composing pair in seven trains authors
|
|
83
|
+
* to ignore the reporter, which costs more than the thing it reports. `on:` remains the spelling
|
|
84
|
+
* for an author who wants the other answer, and it still wins outright — `animator.ts` only reads
|
|
85
|
+
* this field when `config.activationAuthored` is false.
|
|
86
|
+
*
|
|
87
|
+
* @param claims - Every effect that survived composition, for one `target:` group (`compile.ts`'s
|
|
88
|
+
* `compileTargets` calls this once per group, not once over the whole document — see D-B.4).
|
|
89
|
+
* Survivors rather than authored segments: an effect the resolver dropped is not going to run, so
|
|
90
|
+
* letting it name the trigger would bind the element for a corpse.
|
|
91
|
+
* @complexity O(n) time in the claim count; O(n) space.
|
|
92
|
+
* @overallScore 100
|
|
93
|
+
*/
|
|
94
|
+
export declare function resolveDefaultActivation(claims: ActivationClaim[]): Activation | undefined;
|
|
95
|
+
/**
|
|
96
|
+
* Fold the strictest declared `reducedMotion` across every `target:` group and write the merged
|
|
97
|
+
* answer back onto every group's plan.
|
|
98
|
+
*
|
|
99
|
+
* `rm:` is one author decision (or one folded default among the composed primitives) for the whole
|
|
100
|
+
* attribute, even when its effects are split across several `target:` groups — `fade-up target:h1
|
|
101
|
+
* rm:disable, pin target:.x` cannot honour two different reduced-motion policies on one page load
|
|
102
|
+
* decided from two different subsets of the same author's intent. Every other cross-group fact this
|
|
103
|
+
* function used to merge (`supportedActivations`/`supportedTimelines`/`channels`/
|
|
104
|
+
* `defaultActivation`) is decided per group instead now — see this module's own doc comment for why
|
|
105
|
+
* — so this is the one field left.
|
|
106
|
+
*
|
|
107
|
+
* Mutates the plans in place rather than returning a new list: `buildPlan` already built each one,
|
|
108
|
+
* and threading a copy through here for one field write would cost more than it clarifies.
|
|
109
|
+
*
|
|
110
|
+
* @complexity O(g) time in the group count; O(1) space.
|
|
111
|
+
* @overallScore 100
|
|
112
|
+
*/
|
|
113
|
+
export declare function mergeHostFacts(targets: {
|
|
114
|
+
plan: CompiledPlan;
|
|
115
|
+
}[]): void;
|
|
116
|
+
/**
|
|
117
|
+
* Fold an authored `rm:` into the policy the composed primitives declared.
|
|
118
|
+
*
|
|
119
|
+
* `rm:` is the one thing an author can say about reduced motion, and it is deliberately a
|
|
120
|
+
* *one-way ratchet*: it can only make the policy stricter, never weaker.
|
|
121
|
+
*
|
|
122
|
+
* The rule is not invented for this key — it is {@link strictestPolicy}'s, applied one more time.
|
|
123
|
+
* That function already encodes "if any effect must not run, none of the list should", and an
|
|
124
|
+
* author key that could overrule it would make the whole fold advisory: `parallax` declares
|
|
125
|
+
* `disable` because parallax is a documented vestibular trigger, not because the library is being
|
|
126
|
+
* cautious on the author's behalf, and `rm:shorten` on it would hand a visitor who has explicitly
|
|
127
|
+
* asked their operating system for less motion exactly the motion they asked not to receive. The
|
|
128
|
+
* useful direction is the other one and it stays open: `rm:disable` on a spinning logo whose
|
|
129
|
+
* primitive only claims `shorten` is a real request the library previously had no spelling for.
|
|
130
|
+
*
|
|
131
|
+
* A weakening attempt warns by name rather than being ignored, because the author wrote a value
|
|
132
|
+
* and is otherwise owed an explanation for why the page does not behave as they asked.
|
|
133
|
+
*
|
|
134
|
+
* @param declared - Strictest policy among the composed primitives.
|
|
135
|
+
* @param authored - The hoisted `rm:` value, already validated by `parse.ts`.
|
|
136
|
+
* @complexity O(1) time and space.
|
|
137
|
+
* @overallScore 100
|
|
138
|
+
*/
|
|
139
|
+
export declare function resolvedPolicy(declared: ReducedMotionPolicy, authored: ReducedMotionPolicy | undefined, warnings: string[]): ReducedMotionPolicy;
|
|
140
|
+
/**
|
|
141
|
+
* Narrow a running capability intersection by one more primitive's support list.
|
|
142
|
+
*
|
|
143
|
+
* `undefined` means no primitive has contributed yet; `[]` means the composed primitives share
|
|
144
|
+
* nothing. Collapsing those two states into "is the array empty?" — which is what the previous
|
|
145
|
+
* `length ? filter : copy` form did — made an intersection that had legitimately emptied out
|
|
146
|
+
* repopulate from the next effect: `fade-up, parallax-scale, scroll-progress-ring timeline:view`
|
|
147
|
+
* emptied on the second effect and came back as `['scroll', 'view']` on the third, so
|
|
148
|
+
* `style-plan.ts` applied `view()` to `fade-up`, the exact mismatch `supportedTimelines` was
|
|
149
|
+
* added to prevent. Emptiness is a real answer here and must survive the rest of the list.
|
|
150
|
+
*
|
|
151
|
+
* @complexity O(a * b) time in the two list lengths — both are single-digit; O(a) space.
|
|
152
|
+
* @overallScore 100
|
|
153
|
+
*/
|
|
154
|
+
export declare function intersect<T>(accumulated: T[] | undefined, supported: T[]): T[];
|
|
155
|
+
export declare function strictestPolicy(a: ReducedMotionPolicy, b: ReducedMotionPolicy): ReducedMotionPolicy;
|
|
@@ -1,9 +1,13 @@
|
|
|
1
|
-
export { createActivationBinder } from './activation.js';
|
|
2
|
-
export type { ActivationBinder, ActivationBinderOptions } from './activation.js';
|
|
1
|
+
export { createActivationBinder, resolveActivationSpec } from './activation.js';
|
|
2
|
+
export type { ActivationBinder, ActivationBinderOptions, ActivationRequest, ActivationSpec, ActivationTrigger, } from './activation.js';
|
|
3
3
|
export { Animator, createAnimator, ATTR } from './animator.js';
|
|
4
4
|
export type { AnimatorOptions } from './animator.js';
|
|
5
5
|
export { detect } from './capabilities.js';
|
|
6
6
|
export type { Capabilities } from './capabilities.js';
|
|
7
|
+
export { control } from './control.js';
|
|
8
|
+
export type { ControlHandle } from './control.js';
|
|
9
|
+
export { KUI_EVENT } from './control.js';
|
|
10
|
+
export type { LifecycleDetail, LifecycleEvent, LifecycleEventType, LifecycleReason, } from './control.js';
|
|
7
11
|
export { play, resolveTargets, toAttributeValue } from './play.js';
|
|
8
12
|
export type { PlaybackHandle, PlayOptions, Target } from './play.js';
|
|
9
13
|
export { Registry } from './registry.js';
|
|
@@ -11,4 +15,4 @@ export type { ResolvedEffect } from './registry.js';
|
|
|
11
15
|
export { collectingReporter, consoleReporter, silentReporter } from './reporter.js';
|
|
12
16
|
export type { CollectingReporter, Reporter } from './reporter.js';
|
|
13
17
|
export { CHANNEL, inertInstance } from './types.js';
|
|
14
|
-
export type { Activation, Channel, Cleanup, EffectInstance, EffectParams, ParamSpec, ParameterSchema, PerfClass, PrepareContext, Preset, Primitive, ReducedMotionPolicy, Timeline, } from './types.js';
|
|
18
|
+
export type { Activation, Channel, Cleanup, EffectInstance, EffectParams, EffectVariant, InstanceControl, NamedActivation, ParamSpec, ParameterSchema, PerfClass, PlaybackState, PrepareContext, Preset, Primitive, ReducedMotionPolicy, Timeline, } from './types.js';
|
|
@@ -20,6 +20,19 @@ export interface StyleLedger {
|
|
|
20
20
|
restore(): void;
|
|
21
21
|
/** Properties currently owned. Diagnostics and leak assertions. */
|
|
22
22
|
owned(): string[];
|
|
23
|
+
/**
|
|
24
|
+
* The value this ledger will restore `property` to — whatever was there immediately before its
|
|
25
|
+
* first `set()` or `claim()` — or `undefined` if this ledger has never captured that property.
|
|
26
|
+
*
|
|
27
|
+
* `restore()` can put a value back, but until now nothing could ask what it remembered without
|
|
28
|
+
* writing it: a caller that needs the *value itself* mid-effect (a "resting state" to fall back
|
|
29
|
+
* to when its own live input settles at a neutral point, say) had to keep a private snapshot of
|
|
30
|
+
* its own, taken at its own moment, which is exactly the two-sources-of-truth bug this ledger
|
|
31
|
+
* exists to prevent everywhere else. `undefined` here means only "never captured" — a property
|
|
32
|
+
* that *was* captured but held no value beforehand reads back as `''`, matching what a live
|
|
33
|
+
* `getPropertyValue` call would have returned for it at that same instant.
|
|
34
|
+
*/
|
|
35
|
+
peek(property: string): string | undefined;
|
|
23
36
|
}
|
|
24
37
|
/**
|
|
25
38
|
* Create a ledger over one element's inline style.
|
|
@@ -36,9 +49,123 @@ export interface AttributeLedger {
|
|
|
36
49
|
}
|
|
37
50
|
/**
|
|
38
51
|
* The same discipline for attributes, so a library-stamped attribute never clobbers an authored
|
|
39
|
-
* one permanently.
|
|
52
|
+
* one permanently. The concrete ledger also exposes `remove`, which records the original value
|
|
53
|
+
* before clearing a boolean attribute such as a slideshow slide's `inert`.
|
|
40
54
|
*
|
|
41
55
|
* @complexity O(1) per write; O(n) to restore.
|
|
42
56
|
* @overallScore 100
|
|
43
57
|
*/
|
|
44
|
-
export declare function createAttributeLedger(el: Element): AttributeLedger
|
|
58
|
+
export declare function createAttributeLedger(el: Element): AttributeLedger & {
|
|
59
|
+
remove(name: string): void;
|
|
60
|
+
};
|
|
61
|
+
/**
|
|
62
|
+
* One owner's claim on the shared per-element ledgers.
|
|
63
|
+
*
|
|
64
|
+
* Deliberately not "the registry" — there is exactly one registry, module-wide, and a claim is a
|
|
65
|
+
* handle onto it. Two claims over one element see one capture and two refcounts.
|
|
66
|
+
*/
|
|
67
|
+
export interface StyleClaim {
|
|
68
|
+
/**
|
|
69
|
+
* This element's shared ledger, claimed for this owner.
|
|
70
|
+
*
|
|
71
|
+
* The object handed back is this claim's own view of it: `set`/`claim`/`owned`/`peek` read and
|
|
72
|
+
* write the shared ledger, and `restore()` means *"this owner is done"* rather than *"unwind the
|
|
73
|
+
* element now"*. That distinction is what lets a caller holding only a `StyleLedger` — every
|
|
74
|
+
* primitive, via `PrepareContext.style` — participate in the refcount without knowing it exists.
|
|
75
|
+
*/
|
|
76
|
+
style(el: Element): StyleLedger;
|
|
77
|
+
/** Drop this owner's claim on `el`, restoring it when no other owner holds it. */
|
|
78
|
+
release(el: Element): void;
|
|
79
|
+
/** Elements this claim currently holds, in the order it first asked for them. A snapshot. */
|
|
80
|
+
elements(): Element[];
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Open one owner's claim on the shared inline-style ledgers.
|
|
84
|
+
*
|
|
85
|
+
* **Writing is claiming; reading is not.** A handle outlives its own `restore()` — a primitive is
|
|
86
|
+
* handed `PrepareContext.style` once at prepare and keeps it for the element's whole life, a
|
|
87
|
+
* controller cancels and re-activates, a `LedgerSet` is restored and the element is entered again
|
|
88
|
+
* — so a handle that closed over one entry would, after release, write straight into a ledger the
|
|
89
|
+
* registry had already deleted and restored. The value landed on the element, the claim was empty,
|
|
90
|
+
* and nothing anywhere would ever put it back. So `set` and `claim` rejoin. `owned` and `peek`
|
|
91
|
+
* deliberately do not: asking what this owner will restore must not make it an owner, and a
|
|
92
|
+
* released handle honestly answers "nothing".
|
|
93
|
+
*
|
|
94
|
+
* @returns A claim that hands out ref-counted handles and releases them one element at a time.
|
|
95
|
+
* @complexity O(1) per lookup and per release; O(p) in properties written, on the last release.
|
|
96
|
+
* @overallScore 100
|
|
97
|
+
*/
|
|
98
|
+
export declare function createStyleClaim(): StyleClaim;
|
|
99
|
+
/**
|
|
100
|
+
* One owner's claim on the shared per-element *attribute* ledgers.
|
|
101
|
+
*
|
|
102
|
+
* The same model and the same reason. `animator.ts`'s `installMatch` stamps `data-kui-fx` and
|
|
103
|
+
* `data-kui-rm` onto every element a group's `target:` resolves to, and under `scope:page` two
|
|
104
|
+
* authored hosts can resolve to the same element — at which point the second `LedgerSet` captured
|
|
105
|
+
* the first one's stamp as the author's attribute and put *that* back on teardown.
|
|
106
|
+
*/
|
|
107
|
+
export interface AttributeClaim {
|
|
108
|
+
/** This element's shared attribute ledger, claimed for this owner. */
|
|
109
|
+
attributes(el: Element): AttributeLedger;
|
|
110
|
+
/** Drop this owner's claim on `el`, restoring it when no other owner holds it. */
|
|
111
|
+
release(el: Element): void;
|
|
112
|
+
/** Elements this claim currently holds, in the order it first asked for them. A snapshot. */
|
|
113
|
+
elements(): Element[];
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Open one owner's claim on the shared attribute ledgers.
|
|
117
|
+
*
|
|
118
|
+
* @complexity O(1) per lookup and per release; O(n) in attributes written, on the last release.
|
|
119
|
+
* @overallScore 100
|
|
120
|
+
*/
|
|
121
|
+
export declare function createAttributeClaim(): AttributeClaim;
|
|
122
|
+
/**
|
|
123
|
+
* Every element one authored `data-kui` wrote to, and the ledgers that unwind them.
|
|
124
|
+
*
|
|
125
|
+
* The host owns the lifecycle — one `InstanceState`, one gate, one event stream — but the *writes*
|
|
126
|
+
* do not all land on the host. `target:` names elements the effect animates instead of the element
|
|
127
|
+
* the attribute sits on, and each of those needs its own pair of ledgers: `createStyleLedger`
|
|
128
|
+
* closes over one element's `style` object and one snapshot of whether that element had a `style`
|
|
129
|
+
* attribute to begin with, so it cannot be shared and cannot be reconstructed later.
|
|
130
|
+
*
|
|
131
|
+
* A set rather than an array of pairs because the same element is reached repeatedly — once per
|
|
132
|
+
* effect in a composed attribute, once more by the stagger pass — and a second ledger over an
|
|
133
|
+
* element the first has already written to would snapshot *this library's* values as the author's
|
|
134
|
+
* own and restore to them. Memoising per element is what makes "what was there before" mean before
|
|
135
|
+
* this instance existed, rather than before this particular call.
|
|
136
|
+
*
|
|
137
|
+
* Both kinds go one level further out than that, through {@link createStyleClaim} and
|
|
138
|
+
* {@link createAttributeClaim}: memoising *within* one set is not enough once two sets reach the
|
|
139
|
+
* same element — two authored hosts whose `target:` lands on one node, a stagger group and the
|
|
140
|
+
* child's own state, a core effect and an advanced controller. The set is one owner of a shared,
|
|
141
|
+
* ref-counted capture, so `restore()` here means "this host is finished with the element", and the
|
|
142
|
+
* element is unwound by whichever owner is last.
|
|
143
|
+
*/
|
|
144
|
+
export interface LedgerSet {
|
|
145
|
+
/** This element's inline-style ledger, created on first ask. */
|
|
146
|
+
style(el: Element): StyleLedger;
|
|
147
|
+
/** This element's attribute ledger, created on first ask. */
|
|
148
|
+
attributes(el: Element): AttributeLedger;
|
|
149
|
+
/** Unwind every element this set ever handed out a ledger for. Host last — see below. */
|
|
150
|
+
restore(): void;
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Open a ledger set over one authored host.
|
|
154
|
+
*
|
|
155
|
+
* **Restore is host-last, and that ordering is load-bearing.** The host carries `data-kui-state`,
|
|
156
|
+
* which is the cloak layer's per-element release key: `html[data-kui-cloak] [data-kui][data-kui-reveal]:not([data-kui-state])`
|
|
157
|
+
* holds a subtree at `opacity: 0`, and the attribute's presence is what lets go. Restoring the host
|
|
158
|
+
* first removes that key while the elements underneath still carry library styles, reopening the
|
|
159
|
+
* cloak over a half-unwound subtree. Doing it last means the page is already back to the author's
|
|
160
|
+
* markup at the instant it becomes visible again.
|
|
161
|
+
*
|
|
162
|
+
* Style before attributes within each element, matching the order `release()` has always used: a
|
|
163
|
+
* primitive's teardown may write styles, and the attributes are what CSS keys on to decide whether
|
|
164
|
+
* those styles mean anything.
|
|
165
|
+
*
|
|
166
|
+
* @param host - The authored element. Always present in the set, always restored last.
|
|
167
|
+
* @returns A set that hands out memoised ledgers and unwinds all of them together.
|
|
168
|
+
* @complexity O(1) per lookup; O(n) space and O(n) time to restore, in elements written to.
|
|
169
|
+
* @overallScore 100
|
|
170
|
+
*/
|
|
171
|
+
export declare function createLedgerSet(host: Element): LedgerSet;
|
|
@@ -15,6 +15,54 @@ export interface ValidationResult {
|
|
|
15
15
|
* @overallScore 100
|
|
16
16
|
*/
|
|
17
17
|
export declare function validate(raw: string, spec: ParamSpec): ValidationResult;
|
|
18
|
+
/**
|
|
19
|
+
* Expand decimal notation lexically, optionally shifting the decimal point.
|
|
20
|
+
*
|
|
21
|
+
* Lexical rather than arithmetic on purpose. `Number('1.1') / 100` is `0.011000000000000001` — a
|
|
22
|
+
* binary floating-point artefact that would be written verbatim into a custom property and shown
|
|
23
|
+
* to anyone inspecting the element, making a correct value look broken. Shifting the digits
|
|
24
|
+
* instead is exact for every input, and it neither rounds a tiny scale to zero nor rounds a large
|
|
25
|
+
* integer to its nearest double.
|
|
26
|
+
*
|
|
27
|
+
* The length cap matches the core value budget and bounds the allocation even for `1e999999999`,
|
|
28
|
+
* which would otherwise ask for a gigabyte of zeroes. An unrepresentable result returns
|
|
29
|
+
* `undefined` and leaves the caller holding the original input for ordinary validation to reject.
|
|
30
|
+
*
|
|
31
|
+
* Lives in the core because two callers need the same answer: this module's `'number|percentage'`
|
|
32
|
+
* normalisation, and `effects/tween/properties.ts`'s `withImpliedUnit`, which has accepted
|
|
33
|
+
* `opacity:50%` on the tween since before the union type existed. Two copies would be two places
|
|
34
|
+
* for `50%` to stop meaning `0.5`.
|
|
35
|
+
*
|
|
36
|
+
* @param raw - A candidate number in CSS's spelling, including `+` and exponent forms.
|
|
37
|
+
* @param shift - Decimal places to move the point by; `-2` divides by one hundred.
|
|
38
|
+
* @returns The plain decimal spelling, or `undefined` when the input is not a number or the
|
|
39
|
+
* result would exceed the value budget.
|
|
40
|
+
* @complexity O(n) time and space in the digit count, bounded by the cap.
|
|
41
|
+
* @overallScore 100
|
|
42
|
+
*/
|
|
43
|
+
export declare function decimalNumber(raw: string, shift?: number): string | undefined;
|
|
44
|
+
/**
|
|
45
|
+
* The escape screen on its own, for a value that has no type to be validated against.
|
|
46
|
+
*
|
|
47
|
+
* Every authored value normally reaches `style.setProperty` through {@link validate}, which runs
|
|
48
|
+
* this screen and then a type match. One value cannot take the second half: `data-kui-stagger`'s
|
|
49
|
+
* step (and its `cascade:` spelling inside `data-kui`) has always been passed through verbatim so
|
|
50
|
+
* that `var(--speed)` and `calc(90ms * 2)` work, and narrowing it to a `<time>` literal now would
|
|
51
|
+
* break every group written against that promise. That left it as the one authored string in the
|
|
52
|
+
* library with no screen at all — and `data-kui` is explicitly not assumed to be site-owner text
|
|
53
|
+
* (see the module doc above; a CMS field or a comment can reach it).
|
|
54
|
+
*
|
|
55
|
+
* So the half that costs the expression forms nothing is available separately. It is deliberately
|
|
56
|
+
* *not* a type check: anything shaped like a length, a time, a `calc()` or a `var()` passes, and
|
|
57
|
+
* only the characters and functions that can escape a declaration or reach the network are
|
|
58
|
+
* refused.
|
|
59
|
+
*
|
|
60
|
+
* @param value - Author-supplied text bound for a custom property.
|
|
61
|
+
* @returns Whether it is safe to write as-is.
|
|
62
|
+
* @complexity O(n) time in value length; O(1) space.
|
|
63
|
+
* @overallScore 100
|
|
64
|
+
*/
|
|
65
|
+
export declare function isSafeCssValue(value: string): boolean;
|
|
18
66
|
/**
|
|
19
67
|
* Whether an authored value stays on the page's own origin wherever a `src`-shaped `text`
|
|
20
68
|
* parameter is actually turned into a network request.
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ParsedValue } from './types.js';
|
|
1
|
+
import type { Activation, ParsedValue } from './types.js';
|
|
2
2
|
/**
|
|
3
3
|
* Split on a delimiter, ignoring delimiters nested inside parentheses or quotes.
|
|
4
4
|
*
|
|
@@ -11,6 +11,25 @@ import type { ParsedValue } from './types.js';
|
|
|
11
11
|
* @overallScore 100
|
|
12
12
|
*/
|
|
13
13
|
export declare function splitTopLevel(input: string, delimiter: ',' | ' ', warnings?: string[]): string[];
|
|
14
|
+
/** The parsed longhand activation and its optional foreign event source. */
|
|
15
|
+
export interface ParsedActivationAttribute {
|
|
16
|
+
activation?: Activation;
|
|
17
|
+
from?: string;
|
|
18
|
+
warnings: string[];
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Parse the `data-kui-on` grammar: an activation followed by optional `key:value` refinements.
|
|
22
|
+
*
|
|
23
|
+
* `from:` deliberately lives only in this longhand grammar. It cannot be a general `data-kui`
|
|
24
|
+
* hoist because `from:` remains a per-effect parameter on existing primitives such as `count-up`.
|
|
25
|
+
* Quoting keeps a selector containing whitespace in one token, e.g.
|
|
26
|
+
* `submit from:"#signup form"`.
|
|
27
|
+
*
|
|
28
|
+
* @param input - Raw `data-kui-on` attribute value.
|
|
29
|
+
* @returns The activation/source pair, or no pair when any token is malformed.
|
|
30
|
+
* @complexity O(n) time in input length; O(n) space for tokenisation.
|
|
31
|
+
*/
|
|
32
|
+
export declare function parseActivationAttribute(input: string | null): ParsedActivationAttribute;
|
|
14
33
|
/**
|
|
15
34
|
* Parse a `data-kui` attribute value.
|
|
16
35
|
*
|
|
@@ -20,3 +39,11 @@ export declare function splitTopLevel(input: string, delimiter: ',' | ' ', warni
|
|
|
20
39
|
* @overallScore 100
|
|
21
40
|
*/
|
|
22
41
|
export declare function parse(input: string): ParsedValue;
|
|
42
|
+
/**
|
|
43
|
+
* Write a hoisted value once. A second, differing value across segments is an authoring mistake;
|
|
44
|
+
* first one wins so behaviour stays deterministic.
|
|
45
|
+
*
|
|
46
|
+
* @complexity O(1) time, O(1) space.
|
|
47
|
+
* @overallScore 100
|
|
48
|
+
*/
|
|
49
|
+
export declare function assignOnce<K extends 'activation' | 'timeline' | 'threshold' | 'actions' | 'cascade' | 'spread' | 'order' | 'cols' | 'along' | 'rm' | 'func'>(result: ParsedValue, key: K, value: ParsedValue[K], label: string): void;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* How the showcase module is handed a registry.
|
|
3
|
+
*
|
|
4
|
+
* A near-copy of `registerInto`/`asRegistry`/`hasRegistrarShape` from `src/3d/register.ts`
|
|
5
|
+
* (itself a copy of `src/advanced/base.ts:519-568`) — deliberately copied rather than imported.
|
|
6
|
+
* `src/showcase/` ships in core today but is designed to split into its own `<script>` bundle and
|
|
7
|
+
* subpath later (see `.claude/showcase-run/showcase-plan.md`'s "Exact steps to split"), and importing a
|
|
8
|
+
* sibling tier's helper would mean the split could not happen without also shipping whichever tier
|
|
9
|
+
* that helper lived in. This file is the third copy of the same nine lines; the two originals stay
|
|
10
|
+
* the reference implementation, and if the shape gate's rule ever changes, all three move together.
|
|
11
|
+
* `test/showcase-seam.test.ts` covers this copy the same way `test/3d-register-gate.test.ts` mirrors
|
|
12
|
+
* `src/advanced/__tests__/register-gate.test.ts`, so a drift shows up as a failing test rather than
|
|
13
|
+
* a browser bug.
|
|
14
|
+
*/
|
|
15
|
+
import type { Preset, Primitive } from './types.js';
|
|
16
|
+
import type { Registry } from './registry.js';
|
|
17
|
+
import type { Animator } from './animator.js';
|
|
18
|
+
/**
|
|
19
|
+
* Register one module's primitives and presets onto a `Registry`, an `Animator`, or anything
|
|
20
|
+
* carrying either shape.
|
|
21
|
+
*
|
|
22
|
+
* @param target - The registry, the animator, or a host exposing one as `.registry`.
|
|
23
|
+
* @param primitives - One primitive or a list of them.
|
|
24
|
+
* @param presets - One preset or a list of them.
|
|
25
|
+
* @param name - Spelled into the thrown message, so it names the function the consumer called.
|
|
26
|
+
* @returns `target`, so callers can chain.
|
|
27
|
+
* @complexity O(n) in primitives plus presets.
|
|
28
|
+
*/
|
|
29
|
+
export declare function registerInto(target: unknown, primitives: Primitive | Primitive[], presets: Preset | Preset[], name?: string): Registry | Animator;
|
|
@@ -6,9 +6,16 @@ export interface ResolvedEffect {
|
|
|
6
6
|
/**
|
|
7
7
|
* Name → primitive alias table.
|
|
8
8
|
*
|
|
9
|
-
* The catalog's
|
|
10
|
-
* parameter defaults. Presets are therefore data rows, not
|
|
11
|
-
* release costs a table entry. See docs/catalog.md.
|
|
9
|
+
* The catalog's 302 names come from 159 primitives; 143 of the names are aliases — the
|
|
10
|
+
* same primitive with different parameter defaults. Presets are therefore data rows, not
|
|
11
|
+
* code, and adding a name in a later release costs a table entry. See docs/catalog.md.
|
|
12
|
+
*
|
|
13
|
+
* Those two numbers are derived, never incremented by hand: `createRegistry().names().length`,
|
|
14
|
+
* and the count of distinct `resolve(name).primitive.id` across them. Do not add to a
|
|
15
|
+
* remembered total — the figures above were stale by 31 names and 21 primitives when last
|
|
16
|
+
* checked, because someone did. Note they count *primitives*, which is not the same thing as
|
|
17
|
+
* the "primitive families" docs/catalog.md tallies; that grouping is curatorial and has no
|
|
18
|
+
* registry equivalent.
|
|
12
19
|
*/
|
|
13
20
|
export declare class Registry {
|
|
14
21
|
private primitives;
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import type { EffectSpec, Renderer, Timeline } from './types.js';
|
|
2
|
+
export type PlaybackKey = 'repeat' | 'yoyo';
|
|
3
|
+
/**
|
|
4
|
+
* Whether a `key:value` key is one this module owns, so `parse.ts` can dispatch with one lookup
|
|
5
|
+
* rather than a growing `||` chain against its own complexity ceiling.
|
|
6
|
+
*
|
|
7
|
+
* @complexity O(1) time and space.
|
|
8
|
+
* @overallScore 100
|
|
9
|
+
*/
|
|
10
|
+
export declare function isPlaybackKey(key: string): key is PlaybackKey;
|
|
11
|
+
/** Where a playback token came from, so the diagnostics can quote it back. */
|
|
12
|
+
export interface PlaybackContext {
|
|
13
|
+
segment: string;
|
|
14
|
+
warn: (message: string) => void;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Validate and lift one `repeat:`/`yoyo:` token onto the spec being built.
|
|
18
|
+
*
|
|
19
|
+
* Validated here, at parse time, for the reason `rm:` is: both are closed value sets with no
|
|
20
|
+
* expression forms, so there is nothing a later stage could know that this one does not, and the
|
|
21
|
+
* author is owed the diagnostic next to every other grammar diagnostic. The *timeline*-dependent
|
|
22
|
+
* refusals are the opposite case and live in {@link resolvePlayback}, which runs where the
|
|
23
|
+
* element's timeline and renderer are known.
|
|
24
|
+
*
|
|
25
|
+
* Every rejection is a warning plus no value, never a warning plus a half-applied one — the same
|
|
26
|
+
* fail-open `applyGate` uses. A mistyped count leaves the effect playing exactly once, which is
|
|
27
|
+
* what it did before the author added the key.
|
|
28
|
+
*
|
|
29
|
+
* @complexity O(n) time in the value's length; O(1) space.
|
|
30
|
+
* @overallScore 100
|
|
31
|
+
*/
|
|
32
|
+
export declare function applyPlayback(spec: EffectSpec, key: PlaybackKey, value: string, context: PlaybackContext): void;
|
|
33
|
+
/** What {@link resolvePlayback} needs to know about one compiled segment. */
|
|
34
|
+
export interface PlaybackInput {
|
|
35
|
+
/** Preset name. Only ever used in warnings, which have to name what the author wrote. */
|
|
36
|
+
name: string;
|
|
37
|
+
renderer: Renderer;
|
|
38
|
+
/** Whether the preset's from-state is one the visitor must not see. See `Preset.cloak`. */
|
|
39
|
+
cloak?: boolean;
|
|
40
|
+
timeline: Timeline;
|
|
41
|
+
repeat?: string;
|
|
42
|
+
yoyo?: boolean;
|
|
43
|
+
}
|
|
44
|
+
/** The playback settings that survived, plus anything the author needs told. */
|
|
45
|
+
export interface PlaybackResolution {
|
|
46
|
+
repeat?: string;
|
|
47
|
+
yoyo?: boolean;
|
|
48
|
+
warnings: string[];
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Decide what an authored `repeat:`/`yoyo:` actually compiles to, given the renderer and timeline.
|
|
52
|
+
*
|
|
53
|
+
* Refusals here **drop the modifier and keep the effect**, which is the same fail-open
|
|
54
|
+
* `refuseContainerGate` uses one function away in `compile.ts`: the effect still runs, it simply
|
|
55
|
+
* runs the way it would have without the key, and the author is told by name. It is the right
|
|
56
|
+
* direction for this feature specifically because the failure mode of *keeping* an impossible
|
|
57
|
+
* repeat is not "nothing happens" but a visibly broken element — see the progress-timeline case.
|
|
58
|
+
*
|
|
59
|
+
* @complexity O(1) time; O(1) space beyond the returned warnings.
|
|
60
|
+
* @overallScore 100
|
|
61
|
+
*/
|
|
62
|
+
export declare function resolvePlayback(input: PlaybackInput): PlaybackResolution;
|
|
63
|
+
/**
|
|
64
|
+
* The finite number of plays a validated `repeat:` asks for.
|
|
65
|
+
*
|
|
66
|
+
* @returns The count, or `undefined` for `infinite` and for an unwritten `repeat:` — the two cases
|
|
67
|
+
* where no caller may do arithmetic with it.
|
|
68
|
+
* @complexity O(n) time in the value's length; O(1) space.
|
|
69
|
+
* @overallScore 100
|
|
70
|
+
*/
|
|
71
|
+
export declare function repeatCount(repeat: string | undefined): number | undefined;
|
|
72
|
+
/** Whether a validated `repeat:` asks for a loop with no end. */
|
|
73
|
+
export declare function isInfiniteRepeat(repeat: string | undefined): boolean;
|
|
74
|
+
/** The value a track's `animation-direction` takes. `normal` is the CSS initial value. */
|
|
75
|
+
export declare function directionValue(yoyo: boolean | undefined): string;
|
|
76
|
+
/**
|
|
77
|
+
* A CSS time expression scaled from one iteration to the whole playback.
|
|
78
|
+
*
|
|
79
|
+
* Used by both consumers of a repeat that has to do arithmetic — the `at:` sequencer's "after the
|
|
80
|
+
* previous one ends", and the pin scrub head in `declarations.ts` — so the two cannot disagree
|
|
81
|
+
* about how long a repeated effect takes.
|
|
82
|
+
*
|
|
83
|
+
* Returns `duration` untouched for an unwritten or single repeat, which is what makes every
|
|
84
|
+
* attribute written before this parameter existed compile byte-for-byte identically. Never called
|
|
85
|
+
* with `infinite`: both callers refuse that case by name first, because `calc(600ms * infinite)`
|
|
86
|
+
* is not an expression and a browser drops the whole declaration rather than reporting it.
|
|
87
|
+
*
|
|
88
|
+
* @complexity O(n) time in the expression's length; O(1) space.
|
|
89
|
+
* @overallScore 100
|
|
90
|
+
*/
|
|
91
|
+
export declare function playbackExpression(duration: string, repeat: string | undefined): string;
|