kuinetic 0.1.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/LICENSE +21 -0
- package/README.md +99 -0
- package/dist/esm/chunk-LWS4OSLX.mjs +730 -0
- package/dist/esm/chunk-QZIJ7WZI.mjs +3312 -0
- package/dist/esm/chunk-R3TGDJKA.mjs +1264 -0
- package/dist/esm/core/index.mjs +34 -0
- package/dist/esm/effects/index.mjs +27 -0
- package/dist/esm/index.mjs +52 -0
- package/dist/kuinetic.all.js +5291 -0
- package/dist/kuinetic.css +2338 -0
- package/dist/kuinetic.js +5281 -0
- package/dist/types/core/activation.d.ts +21 -0
- package/dist/types/core/animator.d.ts +216 -0
- package/dist/types/core/attrs.d.ts +15 -0
- package/dist/types/core/capabilities.d.ts +19 -0
- package/dist/types/core/channels.d.ts +25 -0
- package/dist/types/core/compile.d.ts +49 -0
- package/dist/types/core/dom-watcher.d.ts +35 -0
- package/dist/types/core/effect-context.d.ts +49 -0
- package/dist/types/core/element-config.d.ts +52 -0
- package/dist/types/core/flip.d.ts +80 -0
- package/dist/types/core/gesture.d.ts +99 -0
- package/dist/types/core/index.d.ts +14 -0
- package/dist/types/core/instances.d.ts +98 -0
- package/dist/types/core/js-effect-preparer.d.ts +46 -0
- package/dist/types/core/js-params.d.ts +124 -0
- package/dist/types/core/owned-styles.d.ts +44 -0
- package/dist/types/core/params.d.ts +65 -0
- package/dist/types/core/parse.d.ts +22 -0
- package/dist/types/core/path-morph.d.ts +83 -0
- package/dist/types/core/play.d.ts +48 -0
- package/dist/types/core/registry.d.ts +38 -0
- package/dist/types/core/reporter.d.ts +35 -0
- package/dist/types/core/scroll-scheduler.d.ts +153 -0
- package/dist/types/core/spring.d.ts +83 -0
- package/dist/types/core/stagger.d.ts +19 -0
- package/dist/types/core/style-plan.d.ts +56 -0
- package/dist/types/core/types.d.ts +239 -0
- package/dist/types/effects/catalog/ambient.d.ts +18 -0
- package/dist/types/effects/catalog/core.d.ts +19 -0
- package/dist/types/effects/catalog/feedback.d.ts +21 -0
- package/dist/types/effects/catalog/index.d.ts +21 -0
- package/dist/types/effects/catalog/interaction-shared.d.ts +56 -0
- package/dist/types/effects/catalog/interaction.d.ts +22 -0
- package/dist/types/effects/catalog/media.d.ts +13 -0
- package/dist/types/effects/catalog/numbers-shared.d.ts +103 -0
- package/dist/types/effects/catalog/numbers.d.ts +31 -0
- package/dist/types/effects/catalog/shared.d.ts +3 -0
- package/dist/types/effects/catalog/text-shared.d.ts +180 -0
- package/dist/types/effects/catalog/text.d.ts +17 -0
- package/dist/types/effects/forms/index.d.ts +14 -0
- package/dist/types/effects/forms/primitives.d.ts +32 -0
- package/dist/types/effects/gestures/index.d.ts +19 -0
- package/dist/types/effects/gestures/primitives.d.ts +2 -0
- package/dist/types/effects/index.d.ts +19 -0
- package/dist/types/effects/layout/index.d.ts +12 -0
- package/dist/types/effects/layout/presets.d.ts +9 -0
- package/dist/types/effects/layout/primitives.d.ts +2 -0
- package/dist/types/effects/navigation/index.d.ts +30 -0
- package/dist/types/effects/scroll-mechanics/index.d.ts +14 -0
- package/dist/types/effects/scroll-mechanics/presets.d.ts +6 -0
- package/dist/types/effects/scroll-mechanics/primitives.d.ts +2 -0
- package/dist/types/effects/scroll-mechanics/tracker.d.ts +55 -0
- package/dist/types/effects/shared.d.ts +26 -0
- package/dist/types/effects/svg/index.d.ts +13 -0
- package/dist/types/effects/three-d/index.d.ts +20 -0
- package/dist/types/index.d.ts +16 -0
- package/package.json +78 -0
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export { createActivationBinder } from './activation.js';
|
|
2
|
+
export type { ActivationBinder, ActivationBinderOptions } from './activation.js';
|
|
3
|
+
export { Animator, createAnimator, ATTR } from './animator.js';
|
|
4
|
+
export type { AnimatorOptions } from './animator.js';
|
|
5
|
+
export { detect } from './capabilities.js';
|
|
6
|
+
export type { Capabilities } from './capabilities.js';
|
|
7
|
+
export { play, resolveTargets, toAttributeValue } from './play.js';
|
|
8
|
+
export type { PlaybackHandle, PlayOptions, Target } from './play.js';
|
|
9
|
+
export { Registry } from './registry.js';
|
|
10
|
+
export type { ResolvedEffect } from './registry.js';
|
|
11
|
+
export { collectingReporter, consoleReporter, silentReporter } from './reporter.js';
|
|
12
|
+
export type { CollectingReporter, Reporter } from './reporter.js';
|
|
13
|
+
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';
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import type { StyleLedger } from './owned-styles.js';
|
|
2
|
+
import type { Cleanup, EffectInstance } from './types.js';
|
|
3
|
+
/**
|
|
4
|
+
* Wrap a CSS-rendered effect.
|
|
5
|
+
*
|
|
6
|
+
* Gating is `animation-play-state` rather than a class toggle: `animation-fill-mode: both`
|
|
7
|
+
* already holds the from-state, so there is no flash between compilation and activation.
|
|
8
|
+
*
|
|
9
|
+
* Repeat activation of the same instance and a fresh instance's first activation both can meet a
|
|
10
|
+
* browser-level animation already sitting `finished`, but they need opposite responses. A second
|
|
11
|
+
* `activate()` on the same instance is a click-gated toggle (a card flip): reversing playback is
|
|
12
|
+
* the correct repeat — the property write only matters on the paused-to-running edge, so writing
|
|
13
|
+
* the same value again is a no-op the browser silently ignores, which is what made two-state
|
|
14
|
+
* effects look permanently stuck. A fresh instance's *first* activation meeting a `finished`
|
|
15
|
+
* animation instead means the browser never actually tore down the previous one (see
|
|
16
|
+
* `restartCssAnimation`) — replay wants that to run forward from the start, not reverse, so it
|
|
17
|
+
* gets the restart path instead of `.reverse()`.
|
|
18
|
+
*
|
|
19
|
+
* @param el - Element carrying the compiled animation.
|
|
20
|
+
* @param ledger - Ledger owning the play-state write.
|
|
21
|
+
* @param animationNames - Exact keyframe names emitted by the compiled plan.
|
|
22
|
+
* @returns A lifecycle handle over the element's owned CSS animations.
|
|
23
|
+
* @complexity O(a) per call in the number of running animations; O(1) space.
|
|
24
|
+
* @overallScore 100
|
|
25
|
+
*/
|
|
26
|
+
export declare function createCssInstance(el: Element, ledger: StyleLedger, animationNames: readonly string[]): EffectInstance;
|
|
27
|
+
/**
|
|
28
|
+
* What a *finite* JS primitive's setup returns instead of a bare teardown.
|
|
29
|
+
*
|
|
30
|
+
* A continuous primitive — a drag handler, a pin, an ambient loop — has no end, so it returns a
|
|
31
|
+
* plain `Cleanup` and keeps the immediately-resolved `finished` every caller composing
|
|
32
|
+
* `Promise.all` over an element's instances already relies on. Only an effect that genuinely
|
|
33
|
+
* completes needs to say so, and only that effect pays for it.
|
|
34
|
+
*/
|
|
35
|
+
export interface TimedSetup {
|
|
36
|
+
cleanup: Cleanup;
|
|
37
|
+
/**
|
|
38
|
+
* Resolves when the effect's own work is done. A setup that loops forever should hand over a
|
|
39
|
+
* promise that never resolves — the same contract an `animation-iteration-count: infinite` CSS
|
|
40
|
+
* effect already has, since its `Animation.finished` never resolves either.
|
|
41
|
+
*/
|
|
42
|
+
finished: Promise<void>;
|
|
43
|
+
/** Jump to the end state, for `EffectInstance.finish()`. */
|
|
44
|
+
finish?: () => void;
|
|
45
|
+
}
|
|
46
|
+
/** A deferred setup's return: a teardown, or a teardown plus the effect's own completion. */
|
|
47
|
+
export type SetupResult = Cleanup | TimedSetup;
|
|
48
|
+
/**
|
|
49
|
+
* Turn setup-that-also-starts into a properly gated instance.
|
|
50
|
+
*
|
|
51
|
+
* Most JS primitives are naturally written as "wire it up and go" — subscribe to the scheduler,
|
|
52
|
+
* attach listeners, write the first style. Deferring that whole body until `activate()` is what
|
|
53
|
+
* makes them obey `on:enter`, `on:click`, `manual`, and `reducedMotion: 'disable'` without each
|
|
54
|
+
* primitive having to implement gating itself.
|
|
55
|
+
*
|
|
56
|
+
* @param setup - Runs on activation; returns its own teardown, optionally with its completion.
|
|
57
|
+
* @returns An instance that does nothing until activated.
|
|
58
|
+
* @complexity O(1) beyond the setup itself.
|
|
59
|
+
* @overallScore 100
|
|
60
|
+
*/
|
|
61
|
+
export declare function deferredInstance(setup: () => SetupResult): EffectInstance;
|
|
62
|
+
/**
|
|
63
|
+
* Wrap a `(el, params, ctx) => SetupResult`-shaped setup function as a deferred
|
|
64
|
+
* `Primitive['prepare']`.
|
|
65
|
+
*
|
|
66
|
+
* Every JS primitive's `prepare` is `deferredInstance(() => setup(...args))` — this names that
|
|
67
|
+
* composition once instead of re-deriving it at each of the library's fourteen call sites.
|
|
68
|
+
*
|
|
69
|
+
* @complexity O(1) time and space beyond the wrapped call.
|
|
70
|
+
* @overallScore 100
|
|
71
|
+
*/
|
|
72
|
+
export declare function deferPrepare<Args extends unknown[]>(setup: (...args: Args) => SetupResult): (...args: Args) => EffectInstance;
|
|
73
|
+
export interface JsInstanceHooks {
|
|
74
|
+
/** Start the effect. Never called before the animator's gate opens. */
|
|
75
|
+
activate(): void;
|
|
76
|
+
cancel?(): void;
|
|
77
|
+
finish?(): void;
|
|
78
|
+
/** Release listeners, observers, subscriptions, and inserted nodes. */
|
|
79
|
+
destroy: Cleanup;
|
|
80
|
+
/**
|
|
81
|
+
* Resolve when the effect completes. Continuous effects may simply never resolve.
|
|
82
|
+
*
|
|
83
|
+
* Read on every access rather than captured once, so a setup that only learns its own completion
|
|
84
|
+
* at activation time can swap in a real promise — the animator and `play()` both read
|
|
85
|
+
* `finished` after `activate()`, never before.
|
|
86
|
+
*/
|
|
87
|
+
finished?(): Promise<void>;
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Build a JS-rendered instance from a primitive's hooks, filling in the optional operations.
|
|
91
|
+
*
|
|
92
|
+
* Keeps primitives free of lifecycle boilerplate while still guaranteeing the animator every
|
|
93
|
+
* operation it needs.
|
|
94
|
+
*
|
|
95
|
+
* @complexity O(1) time and space.
|
|
96
|
+
* @overallScore 100
|
|
97
|
+
*/
|
|
98
|
+
export declare function createJsInstance(hooks: JsInstanceHooks): EffectInstance;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import type { Capabilities } from './capabilities.js';
|
|
2
|
+
import type { CompiledPlan } from './compile.js';
|
|
3
|
+
import type { StyleLedger } from './owned-styles.js';
|
|
4
|
+
import type { Reporter } from './reporter.js';
|
|
5
|
+
import type { ScrollRoot, ScrollScheduler } from './scroll-scheduler.js';
|
|
6
|
+
import type { EffectInstance } from './types.js';
|
|
7
|
+
/** Everything `JsEffectPreparer.prepare` needs, grouped so the call site reads as one request. */
|
|
8
|
+
export interface PrepareJsEffectsRequest {
|
|
9
|
+
el: Element;
|
|
10
|
+
plan: CompiledPlan;
|
|
11
|
+
signal: AbortSignal;
|
|
12
|
+
ledger: StyleLedger;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Wires up an element's JS-rendered effects and hands each one the context it needs to run.
|
|
16
|
+
*
|
|
17
|
+
* Extracted from `Animator` as an injected collaborator — same shape as `binder` or `scheduler` —
|
|
18
|
+
* because this pair only ever reads already-injected environment collaborators
|
|
19
|
+
* (`scheduler`/`rootResolver`/`capabilities`/`reporter`/`respectReducedMotion`), never the
|
|
20
|
+
* animator's own lifecycle state.
|
|
21
|
+
*/
|
|
22
|
+
export interface JsEffectPreparer {
|
|
23
|
+
/**
|
|
24
|
+
* Run each JS-rendered effect's setup, isolating failures so one broken effect cannot abort the
|
|
25
|
+
* rest of the element.
|
|
26
|
+
*
|
|
27
|
+
* @returns Instances for every effect that initialised successfully.
|
|
28
|
+
* @complexity O(e) time in JS-rendered effects; O(e) space.
|
|
29
|
+
* @overallScore 100
|
|
30
|
+
*/
|
|
31
|
+
prepare(request: PrepareJsEffectsRequest): EffectInstance[];
|
|
32
|
+
}
|
|
33
|
+
export interface JsEffectPreparerOptions {
|
|
34
|
+
scheduler: ScrollScheduler;
|
|
35
|
+
rootResolver: (el: Element) => ScrollRoot;
|
|
36
|
+
capabilities: Capabilities;
|
|
37
|
+
reporter: Reporter;
|
|
38
|
+
respectReducedMotion: boolean;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Build a `JsEffectPreparer` closed over one animator's collaborators.
|
|
42
|
+
*
|
|
43
|
+
* @complexity O(1) time and space beyond the closure it returns.
|
|
44
|
+
* @overallScore 100
|
|
45
|
+
*/
|
|
46
|
+
export declare function createJsEffectPreparer(options: JsEffectPreparerOptions): JsEffectPreparer;
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import type { EffectParams, EffectSpec, EffectTiming, ParameterSchema } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Parameter access for JS-rendered primitives.
|
|
4
|
+
*
|
|
5
|
+
* `core/params.resolveParams` exists for the CSS path: it returns values keyed by *custom
|
|
6
|
+
* property* and deliberately omits defaults, because defaults belong in the stylesheet's `var()`
|
|
7
|
+
* fallback. A JS primitive needs the opposite shape — keyed by parameter name, defaults filled,
|
|
8
|
+
* and converted to numbers it can compute with. This module is that second reader. It reuses the
|
|
9
|
+
* same `validate` so untrusted author strings are screened once, by one implementation.
|
|
10
|
+
*
|
|
11
|
+
* See docs/v2-core-requests.md: `Animator.prepareJsEffects` currently hands `prepare` the raw,
|
|
12
|
+
* unvalidated `spec.params`, so every JS primitive must run this itself.
|
|
13
|
+
*/
|
|
14
|
+
/** Lengths resolve against the environment, so the environment is passed in rather than read. */
|
|
15
|
+
export interface LengthBasis {
|
|
16
|
+
viewportWidth: number;
|
|
17
|
+
viewportHeight: number;
|
|
18
|
+
/** Basis for `%` values — what "100%" means for this particular parameter. */
|
|
19
|
+
percentBasis: number;
|
|
20
|
+
/** Basis for `em`; `rem` uses `rootFontSize`. */
|
|
21
|
+
fontSize: number;
|
|
22
|
+
rootFontSize: number;
|
|
23
|
+
}
|
|
24
|
+
/** A basis usable when a parameter is known to carry only absolute units. */
|
|
25
|
+
export declare const ABSOLUTE_BASIS: LengthBasis;
|
|
26
|
+
/**
|
|
27
|
+
* Validate authored parameters against a schema and fill in every declared default.
|
|
28
|
+
*
|
|
29
|
+
* Unlike the CSS path this returns *every* declared parameter, because a JS primitive branches on
|
|
30
|
+
* values it must always have. Rejected values fall back to the declared default and warn, so one
|
|
31
|
+
* bad attribute can never leave a primitive holding an unvalidated string.
|
|
32
|
+
*
|
|
33
|
+
* @param authored - Raw, untrusted values from the attribute.
|
|
34
|
+
* @param schema - The primitive's declared parameters.
|
|
35
|
+
* @param warn - Diagnostic sink, called once per rejected or unknown parameter.
|
|
36
|
+
* @returns Every declared parameter name mapped to a validated string value.
|
|
37
|
+
* @complexity O(p * n) time in parameter count and value length; O(p) space.
|
|
38
|
+
* @overallScore 100
|
|
39
|
+
*/
|
|
40
|
+
export declare function readParams(authored: Record<string, string>, schema: ParameterSchema, warn: (message: string) => void): Record<string, string>;
|
|
41
|
+
/**
|
|
42
|
+
* Convert a validated CSS time to milliseconds.
|
|
43
|
+
*
|
|
44
|
+
* @param value - A value already accepted as `type: 'time'`, e.g. `400ms` or `1.5s`.
|
|
45
|
+
* @param fallback - Returned when the value is not a plain time (a `calc()`, for instance).
|
|
46
|
+
* @returns Milliseconds.
|
|
47
|
+
* @complexity O(n) time in value length; O(1) space.
|
|
48
|
+
* @overallScore 100
|
|
49
|
+
*/
|
|
50
|
+
export declare function toMilliseconds(value: string, fallback?: number): number;
|
|
51
|
+
/**
|
|
52
|
+
* Convert a validated CSS length to pixels.
|
|
53
|
+
*
|
|
54
|
+
* Viewport- and percentage-relative units need context, which is why the basis is a parameter
|
|
55
|
+
* rather than something this module reads off `window`: the same helper then resolves a length
|
|
56
|
+
* against a nested scroll container as easily as against the viewport, and tests need no layout.
|
|
57
|
+
*
|
|
58
|
+
* `calc()` is intentionally not evaluated — a second CSS expression engine is not worth owning.
|
|
59
|
+
* Such values return the fallback, which is visible and debuggable rather than silently wrong.
|
|
60
|
+
*
|
|
61
|
+
* @param value - A value already accepted as `type: 'length'`.
|
|
62
|
+
* @param basis - Viewport, font, and percentage context.
|
|
63
|
+
* @param fallback - Returned for `calc()` and unrecognised units.
|
|
64
|
+
* @returns Pixels.
|
|
65
|
+
* @complexity O(n) time in value length; O(1) space.
|
|
66
|
+
* @overallScore 100
|
|
67
|
+
*/
|
|
68
|
+
export declare function toPixels(value: string, basis: LengthBasis, fallback?: number): number;
|
|
69
|
+
/**
|
|
70
|
+
* Convert a validated numeric parameter.
|
|
71
|
+
*
|
|
72
|
+
* @param value - A value already accepted as `type: 'number'` or `type: 'percentage'`.
|
|
73
|
+
* @param fallback - Returned when the value is not a bare number or percentage.
|
|
74
|
+
* @returns The number; percentages are returned as a 0–1 ratio.
|
|
75
|
+
* @complexity O(n) time in value length; O(1) space.
|
|
76
|
+
* @overallScore 100
|
|
77
|
+
*/
|
|
78
|
+
export declare function toNumber(value: string, fallback?: number): number;
|
|
79
|
+
/** Whether a keyword parameter is set to its enabling value. */
|
|
80
|
+
export declare function isEnabled(value: string, enabling?: string): boolean;
|
|
81
|
+
/**
|
|
82
|
+
* Read one effect segment's positional timing into the shape JS primitives consume.
|
|
83
|
+
*
|
|
84
|
+
* The CSS renderer hands these straight to `animation-duration`/`-delay`/`-timing-function`, where
|
|
85
|
+
* the browser does the screening. Nothing screened them on the JS path, so they are converted to
|
|
86
|
+
* numbers (and the easing validated) here instead of anywhere a primitive might forget.
|
|
87
|
+
*
|
|
88
|
+
* @param spec - The parsed effect segment.
|
|
89
|
+
* @param warn - Diagnostic sink, called once per rejected value.
|
|
90
|
+
* @complexity O(n) time in value length; O(1) space.
|
|
91
|
+
* @overallScore 100
|
|
92
|
+
*/
|
|
93
|
+
export declare function readEffectTiming(spec: Pick<EffectSpec, 'duration' | 'delay' | 'easing'>, warn: (message: string) => void): EffectTiming;
|
|
94
|
+
/**
|
|
95
|
+
* How long an unstepped effect should run, preferring the segment's positional time over the
|
|
96
|
+
* same-named parameter.
|
|
97
|
+
*
|
|
98
|
+
* `count 3s` and `count duration:3s` are two spellings of one intent, and the positional one is
|
|
99
|
+
* what `play()` emits, so it cannot be the spelling that gets dropped. `stepMsFor` is the
|
|
100
|
+
* equivalent for effects whose authored duration divides across a tick count instead.
|
|
101
|
+
*
|
|
102
|
+
* @param fallback - The primitive's own default, used when the author wrote neither spelling.
|
|
103
|
+
* @complexity O(1) time and space.
|
|
104
|
+
* @overallScore 100
|
|
105
|
+
*/
|
|
106
|
+
export declare function effectDurationMs(params: EffectParams, fallback: number): number;
|
|
107
|
+
/**
|
|
108
|
+
* Wrap a validated record in the reader primitives consume.
|
|
109
|
+
*
|
|
110
|
+
* @param values - Output of `readParams`; every declared parameter is present.
|
|
111
|
+
* @param timing - Author timing for the segment; empty when none was written.
|
|
112
|
+
* @returns A reader with per-type accessors.
|
|
113
|
+
* @complexity O(1) per accessor call; O(1) space beyond the record.
|
|
114
|
+
* @overallScore 100
|
|
115
|
+
*/
|
|
116
|
+
export declare function createParams(values: Record<string, string>, timing?: EffectTiming): EffectParams;
|
|
117
|
+
/**
|
|
118
|
+
* Validate authored parameters and wrap them in a reader — the single entry point the animator
|
|
119
|
+
* uses for JS-rendered effects.
|
|
120
|
+
*
|
|
121
|
+
* @complexity O(p * n) time in parameter count and value length; O(p) space.
|
|
122
|
+
* @overallScore 100
|
|
123
|
+
*/
|
|
124
|
+
export declare function readEffectParams(authored: Record<string, string>, schema: ParameterSchema, warn: (message: string) => void, timing?: EffectTiming): EffectParams;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Owned-write ledger.
|
|
3
|
+
*
|
|
4
|
+
* The runtime writes inline custom properties, animation longhands, and timeline properties, but
|
|
5
|
+
* previously tore down by removing three attributes. Recompiling `fade-up distance:80px` into
|
|
6
|
+
* `zoom-in` therefore left `--kui-distance` behind, and destroying an animator left every
|
|
7
|
+
* animation installed.
|
|
8
|
+
*
|
|
9
|
+
* A ledger records what this library wrote *and what was there before*, so teardown restores the
|
|
10
|
+
* consumer's own inline styles rather than deleting them. That distinction matters: several JS
|
|
11
|
+
* primitives were previously clearing author-set `translate` and `scroll-snap-align` values they
|
|
12
|
+
* did not own.
|
|
13
|
+
*/
|
|
14
|
+
export interface StyleLedger {
|
|
15
|
+
/** Write a property, remembering the value it replaced. */
|
|
16
|
+
set(property: string, value: string): void;
|
|
17
|
+
/** Record a property this ledger will restore, without writing anything yet. */
|
|
18
|
+
claim(property: string): void;
|
|
19
|
+
/** Restore every recorded property to the value it had before this ledger touched it. */
|
|
20
|
+
restore(): void;
|
|
21
|
+
/** Properties currently owned. Diagnostics and leak assertions. */
|
|
22
|
+
owned(): string[];
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Create a ledger over one element's inline style.
|
|
26
|
+
*
|
|
27
|
+
* @param el - Element whose inline style is managed.
|
|
28
|
+
* @returns A ledger that can restore the element to its pre-effect inline state.
|
|
29
|
+
* @complexity O(1) per write; O(n) space and O(n) time to restore, in properties written.
|
|
30
|
+
* @overallScore 100
|
|
31
|
+
*/
|
|
32
|
+
export declare function createStyleLedger(el: Element): StyleLedger;
|
|
33
|
+
export interface AttributeLedger {
|
|
34
|
+
set(name: string, value: string): void;
|
|
35
|
+
restore(): void;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* The same discipline for attributes, so a library-stamped attribute never clobbers an authored
|
|
39
|
+
* one permanently.
|
|
40
|
+
*
|
|
41
|
+
* @complexity O(1) per write; O(n) to restore.
|
|
42
|
+
* @overallScore 100
|
|
43
|
+
*/
|
|
44
|
+
export declare function createAttributeLedger(el: Element): AttributeLedger;
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import type { ParamSpec, ParameterSchema, ResolvedParams } from './types.js';
|
|
2
|
+
export interface ValidationResult {
|
|
3
|
+
value: string;
|
|
4
|
+
ok: boolean;
|
|
5
|
+
reason?: string;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Validate one authored parameter value against its declared type.
|
|
9
|
+
*
|
|
10
|
+
* @param raw - Author-supplied value, untrusted.
|
|
11
|
+
* @param spec - The parameter's declared type, default, and target custom property.
|
|
12
|
+
* @returns The accepted value, or the declared default with a reason when rejected. Never throws
|
|
13
|
+
* and never returns an unvalidated string.
|
|
14
|
+
* @complexity O(n) time in value length; O(1) space.
|
|
15
|
+
* @overallScore 100
|
|
16
|
+
*/
|
|
17
|
+
export declare function validate(raw: string, spec: ParamSpec): ValidationResult;
|
|
18
|
+
/**
|
|
19
|
+
* Whether an authored value stays on the page's own origin wherever a `src`-shaped `text`
|
|
20
|
+
* parameter is actually turned into a network request.
|
|
21
|
+
*
|
|
22
|
+
* `type: 'text'` is deliberately shape-free — see the module doc above — because it also carries
|
|
23
|
+
* CSS selectors and other non-URL strings that have no notion of "origin" at all. So this is not
|
|
24
|
+
* part of `validate()`: a `text` value that is a URL pattern (media-scrub's frame `src`) is safe
|
|
25
|
+
* to accept lexically, but a *consumer of that value* must call this before ever assigning it to
|
|
26
|
+
* something that fetches, such as `<img>.src`.
|
|
27
|
+
*
|
|
28
|
+
* The threat: `data-kui` content is not always authored by the site owner — a CMS field, a
|
|
29
|
+
* comment, anything not trusted the way hand-written markup is — so an unconstrained `src`
|
|
30
|
+
* pattern turns the visitor's own browser into a same-origin-cookie-free but still
|
|
31
|
+
* attacker-directed request tool: exfiltration via path/query, third-party tracking pixels, or
|
|
32
|
+
* probing hosts on the victim's internal network that are unreachable from outside it. A
|
|
33
|
+
* Content-Security-Policy would mitigate this, but the library should not depend on the consumer
|
|
34
|
+
* having one.
|
|
35
|
+
*
|
|
36
|
+
* Only relative and root-relative paths pass. That is narrower than "any same-origin URL": a
|
|
37
|
+
* fully-qualified `https://this-very-site/…` is rejected too, on purpose, because nothing this
|
|
38
|
+
* library ships needs one — a root-relative path reaches the same resource — and accepting it
|
|
39
|
+
* would mean re-deriving "is this really the page's own origin" from `location` inside what is
|
|
40
|
+
* otherwise pure string validation, with all the parsing edge cases (`this-site.com.evil.com`,
|
|
41
|
+
* userinfo tricks, IDN lookalikes) that comparison invites. Rejecting every scheme uniformly,
|
|
42
|
+
* regardless of which host follows it, has no such edge cases.
|
|
43
|
+
*
|
|
44
|
+
* @param value - Author-supplied value already accepted by {@link validate} as `type: 'text'`.
|
|
45
|
+
* @returns Whether every request this value can produce is confined to the page's own origin.
|
|
46
|
+
* @complexity O(n) time in value length; O(1) space.
|
|
47
|
+
* @overallScore 100
|
|
48
|
+
*/
|
|
49
|
+
export declare function isSameOriginPath(value: string): boolean;
|
|
50
|
+
/**
|
|
51
|
+
* Resolve authored parameters against a schema.
|
|
52
|
+
*
|
|
53
|
+
* Only values the author explicitly supplied are returned. Defaults are deliberately excluded:
|
|
54
|
+
* they live in the CSS `var()` fallback. Writing them to `element.style` would give inline custom
|
|
55
|
+
* properties precedence over consumer stylesheets and break the promise that a site's own CSS
|
|
56
|
+
* wins without `!important`.
|
|
57
|
+
*
|
|
58
|
+
* @param authored - Raw parameter values from the attribute or options object.
|
|
59
|
+
* @param schema - The primitive's declared parameters.
|
|
60
|
+
* @param warn - Diagnostic sink; called once per rejected or unknown parameter.
|
|
61
|
+
* @returns Custom property names mapped to validated values.
|
|
62
|
+
* @complexity O(p * n) time in parameter count and value length; O(p) space.
|
|
63
|
+
* @overallScore 100
|
|
64
|
+
*/
|
|
65
|
+
export declare function resolveParams(authored: Record<string, string>, schema: ParameterSchema, warn: (message: string) => void): ResolvedParams;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { ParsedValue } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Split on a delimiter, ignoring delimiters nested inside parentheses or quotes.
|
|
4
|
+
*
|
|
5
|
+
* @param input - Raw attribute text.
|
|
6
|
+
* @param delimiter - `','` for effect segments, `' '` for tokens within a segment.
|
|
7
|
+
* @param warnings - Sink for a diagnostic when a quote or `(` is never closed. Optional so the
|
|
8
|
+
* two-argument call every existing caller and test uses keeps working unchanged.
|
|
9
|
+
* @returns Trimmed, non-empty parts.
|
|
10
|
+
* @complexity O(n) time in input length; O(n) space for the parts.
|
|
11
|
+
* @overallScore 100
|
|
12
|
+
*/
|
|
13
|
+
export declare function splitTopLevel(input: string, delimiter: ',' | ' ', warnings?: string[]): string[];
|
|
14
|
+
/**
|
|
15
|
+
* Parse a `data-kui` attribute value.
|
|
16
|
+
*
|
|
17
|
+
* @param input - Raw attribute text; empty or whitespace yields an empty spec list.
|
|
18
|
+
* @returns Effect specs plus any hoisted element-scoped settings and warnings.
|
|
19
|
+
* @complexity O(n) time in input length; O(e) space in the number of effect segments.
|
|
20
|
+
* @overallScore 100
|
|
21
|
+
*/
|
|
22
|
+
export declare function parse(input: string): ParsedValue;
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SVG path interpolation.
|
|
3
|
+
*
|
|
4
|
+
* Two `d` strings cannot be interpolated directly: they differ in command type and count, and
|
|
5
|
+
* `1px` of `C` is not `1px` of `L`. The fix is normalisation — convert everything to absolute
|
|
6
|
+
* cubic segments, then split the longer path's segments until both have the same count. After
|
|
7
|
+
* that, morphing is per-number lerp.
|
|
8
|
+
*
|
|
9
|
+
* Scope is deliberately bounded. `M L H V C Z` are supported; `A S Q T` are not, and a path using
|
|
10
|
+
* them reports a reason rather than producing a plausible-looking wrong shape. Owning a complete
|
|
11
|
+
* SVG path engine is not worth it for two effect names.
|
|
12
|
+
*/
|
|
13
|
+
export interface Point {
|
|
14
|
+
x: number;
|
|
15
|
+
y: number;
|
|
16
|
+
}
|
|
17
|
+
/** One cubic segment: start point, two controls, end point. */
|
|
18
|
+
export interface Cubic {
|
|
19
|
+
from: Point;
|
|
20
|
+
c1: Point;
|
|
21
|
+
c2: Point;
|
|
22
|
+
to: Point;
|
|
23
|
+
}
|
|
24
|
+
export interface ParseResult {
|
|
25
|
+
segments: Cubic[];
|
|
26
|
+
/** Present when the path could not be normalised; the caller should warn and not morph. */
|
|
27
|
+
reason?: string;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Parse a path's `d` attribute into absolute cubic segments.
|
|
31
|
+
*
|
|
32
|
+
* @param d - The `d` attribute value.
|
|
33
|
+
* @returns Segments, or a `reason` when the path uses unsupported commands.
|
|
34
|
+
* @complexity O(n) time in the length of `d`; O(s) space in segment count.
|
|
35
|
+
* @overallScore 100
|
|
36
|
+
*/
|
|
37
|
+
export declare function parsePath(d: string): ParseResult;
|
|
38
|
+
/**
|
|
39
|
+
* Split a cubic at `t` using de Casteljau, preserving the exact curve.
|
|
40
|
+
*
|
|
41
|
+
* Splitting rather than padding is what keeps a normalised path visually identical to the
|
|
42
|
+
* original — a duplicated zero-length segment would create a visible kink under interpolation.
|
|
43
|
+
*
|
|
44
|
+
* @complexity O(1) time and space.
|
|
45
|
+
* @overallScore 100
|
|
46
|
+
*/
|
|
47
|
+
export declare function splitCubic(segment: Cubic, t: number): [Cubic, Cubic];
|
|
48
|
+
/**
|
|
49
|
+
* Grow a segment list to `target` segments by repeatedly halving the longest one.
|
|
50
|
+
*
|
|
51
|
+
* @param segments - Source segments; not mutated.
|
|
52
|
+
* @param target - Desired count, which must be at least the source count.
|
|
53
|
+
* @returns A list of exactly `target` segments describing the same curve.
|
|
54
|
+
* @complexity O((target - n) * n) time; O(target) space. Both paths are parsed once per morph
|
|
55
|
+
* setup, not per frame, so the quadratic term never reaches the frame budget.
|
|
56
|
+
* @overallScore 100
|
|
57
|
+
*/
|
|
58
|
+
export declare function normaliseCount(segments: Cubic[], target: number): Cubic[];
|
|
59
|
+
/**
|
|
60
|
+
* Serialise cubic segments back to a `d` string.
|
|
61
|
+
*
|
|
62
|
+
* @complexity O(n) time and space in segment count.
|
|
63
|
+
* @overallScore 100
|
|
64
|
+
*/
|
|
65
|
+
export declare function toPathData(segments: Cubic[]): string;
|
|
66
|
+
export interface Morph {
|
|
67
|
+
/** Path data at `t` in [0, 1]. */
|
|
68
|
+
at(t: number): string;
|
|
69
|
+
segmentCount: number;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Build an interpolator between two path strings.
|
|
73
|
+
*
|
|
74
|
+
* @param fromPath - Starting `d`.
|
|
75
|
+
* @param toPath - Ending `d`.
|
|
76
|
+
* @returns A morph, or a `reason` when either path cannot be normalised.
|
|
77
|
+
* @complexity O(n log n)-ish setup in segment count; O(n) per `at` call.
|
|
78
|
+
* @overallScore 100
|
|
79
|
+
*/
|
|
80
|
+
export declare function createMorph(fromPath: string, toPath: string): {
|
|
81
|
+
morph?: Morph;
|
|
82
|
+
reason?: string;
|
|
83
|
+
};
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import type { Animator } from './animator.js';
|
|
2
|
+
export type Target = string | Element | Iterable<Element>;
|
|
3
|
+
export interface PlayOptions {
|
|
4
|
+
duration?: string | number;
|
|
5
|
+
delay?: string | number;
|
|
6
|
+
ease?: string;
|
|
7
|
+
stagger?: string | number;
|
|
8
|
+
/** Any effect parameter, e.g. `{ distance: '40px' }`. */
|
|
9
|
+
[param: string]: string | number | undefined;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* A handle, not a bare promise. `finished` resolving and the animation being cancellable are
|
|
13
|
+
* different concerns, and a selection-level `.stop()` cannot express which run it stops.
|
|
14
|
+
*/
|
|
15
|
+
export interface PlaybackHandle {
|
|
16
|
+
readonly elements: Element[];
|
|
17
|
+
/** Resolves when every selected element finishes. Resolves (never rejects) on cancel. */
|
|
18
|
+
readonly finished: Promise<void>;
|
|
19
|
+
cancel(): void;
|
|
20
|
+
finish(): void;
|
|
21
|
+
}
|
|
22
|
+
export declare function resolveTargets(target: Target, root: ParentNode): Element[];
|
|
23
|
+
/**
|
|
24
|
+
* Options object → the same attribute string an author would write. One execution path.
|
|
25
|
+
*
|
|
26
|
+
* @complexity O(n) time in the number of options; O(n) space for the parts.
|
|
27
|
+
* @overallScore 100
|
|
28
|
+
*/
|
|
29
|
+
export declare function toAttributeValue(effect: string, options?: PlayOptions): string;
|
|
30
|
+
export interface PlayRequest {
|
|
31
|
+
animator: Animator;
|
|
32
|
+
root: ParentNode;
|
|
33
|
+
target: Target;
|
|
34
|
+
effect: string;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Play an effect on a selection, returning a handle rather than a bare promise.
|
|
38
|
+
*
|
|
39
|
+
* Options are compiled into the same attribute string an author would write, so the declarative
|
|
40
|
+
* and programmatic surfaces share one execution path instead of drifting apart.
|
|
41
|
+
*
|
|
42
|
+
* @param request - Animator, root, target selection, and effect name.
|
|
43
|
+
* @param options - Timing and effect parameters.
|
|
44
|
+
* @returns A handle exposing `finished`, `cancel`, and `finish`.
|
|
45
|
+
* @complexity O(n) time in the number of selected elements; O(n) space.
|
|
46
|
+
* @overallScore 100
|
|
47
|
+
*/
|
|
48
|
+
export declare function play(request: PlayRequest, options?: PlayOptions): PlaybackHandle;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { Preset, Primitive } from './types.js';
|
|
2
|
+
export interface ResolvedEffect {
|
|
3
|
+
preset: Preset;
|
|
4
|
+
primitive: Primitive;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Name → primitive alias table.
|
|
8
|
+
*
|
|
9
|
+
* The catalog's ~237 names come from 29 primitives; 48 of them are one primitive with different
|
|
10
|
+
* parameter defaults. Presets are therefore data rows, not code, and adding a name in a later
|
|
11
|
+
* release costs a table entry. See docs/catalog.md.
|
|
12
|
+
*/
|
|
13
|
+
export declare class Registry {
|
|
14
|
+
private primitives;
|
|
15
|
+
private presets;
|
|
16
|
+
/** Sorted-name key → preset that renders that combination as one tested keyframe. */
|
|
17
|
+
private combos;
|
|
18
|
+
registerPrimitive(primitive: Primitive): this;
|
|
19
|
+
registerPreset(preset: Preset): this;
|
|
20
|
+
registerPresets(presets: Preset[]): this;
|
|
21
|
+
registerPrimitives(primitives: Primitive[]): this;
|
|
22
|
+
/**
|
|
23
|
+
* Declare that a set of effect names has a purpose-built single-keyframe implementation.
|
|
24
|
+
* Checked before channel conflict analysis, so `fade-up` + `blur-in` can resolve to the
|
|
25
|
+
* tested `fade-blur-up` rather than being rejected for both writing `opacity`.
|
|
26
|
+
*/
|
|
27
|
+
registerCombo(names: string[], presetName: string): this;
|
|
28
|
+
resolve(name: string): ResolvedEffect | undefined;
|
|
29
|
+
findCombo(names: string[]): ResolvedEffect | undefined;
|
|
30
|
+
has(name: string): boolean;
|
|
31
|
+
/** All registered effect names, for docs generation and dev-mode "did you mean" hints. */
|
|
32
|
+
names(): string[];
|
|
33
|
+
getPrimitive(id: string): Primitive | undefined;
|
|
34
|
+
}
|
|
35
|
+
/** Custom property a primitive's timing parameter writes to. */
|
|
36
|
+
export declare function timingProperty(primitiveId: string, name: string): string;
|
|
37
|
+
/** Levenshtein-lite suggestion for unknown effect names. Dev-mode ergonomics only. */
|
|
38
|
+
export declare function suggest(name: string, candidates: string[]): string | undefined;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Diagnostic sink.
|
|
3
|
+
*
|
|
4
|
+
* Injected rather than calling `console` directly so warnings are assertable in tests and
|
|
5
|
+
* silenceable in production without a build-time flag.
|
|
6
|
+
*/
|
|
7
|
+
export interface Reporter {
|
|
8
|
+
warn(message: string, subject?: unknown): void;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Reporter that writes to the console with a library prefix.
|
|
12
|
+
*
|
|
13
|
+
* @returns A reporter suitable for development builds.
|
|
14
|
+
* @complexity O(1) time, O(1) space.
|
|
15
|
+
* @overallScore 100
|
|
16
|
+
*/
|
|
17
|
+
export declare function consoleReporter(): Reporter;
|
|
18
|
+
/**
|
|
19
|
+
* Reporter that discards everything. The production default.
|
|
20
|
+
*
|
|
21
|
+
* @complexity O(1) time, O(1) space.
|
|
22
|
+
* @overallScore 100
|
|
23
|
+
*/
|
|
24
|
+
export declare function silentReporter(): Reporter;
|
|
25
|
+
export interface CollectingReporter extends Reporter {
|
|
26
|
+
readonly messages: string[];
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Reporter that records messages for assertion.
|
|
30
|
+
*
|
|
31
|
+
* @returns A reporter whose `messages` array accumulates every warning in order.
|
|
32
|
+
* @complexity O(1) amortised per warning; O(n) space in the number of warnings.
|
|
33
|
+
* @overallScore 100
|
|
34
|
+
*/
|
|
35
|
+
export declare function collectingReporter(): CollectingReporter;
|