kuinetic 0.1.2 → 0.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,99 @@
1
+ import type { PrepareContext } from '../core/effect-context.js';
2
+ import type { Cleanup } from '../core/types.js';
3
+ /**
4
+ * Marking the children of a stepped effect, shared by every primitive that has an index.
5
+ *
6
+ * Two primitives publish `data-kui-step` — `scroll-progress` (scroll-driven, backs
7
+ * `scrollytelling-step`) and `step-progress` (click-driven, forms) — and the attribute alone puts
8
+ * all the work on the author. Selecting "the child matching the current index" is the one thing CSS
9
+ * cannot express, so a page had to enumerate it:
10
+ *
11
+ * ```css
12
+ * [data-kui-step='0'] li:nth-child(1),
13
+ * [data-kui-step='1'] li:nth-child(2),
14
+ * [data-kui-step='2'] li:nth-child(3),
15
+ * [data-kui-step='3'] li:nth-child(4) { ... }
16
+ * ```
17
+ *
18
+ * Four selectors for four steps, doubled for every property group, and every one of them wrong the
19
+ * moment a fifth step is added or an element is inserted above the list. `demo/scroll.html` carried
20
+ * eight such selectors for one demo.
21
+ *
22
+ * Marking the children instead collapses that to `li[data-kui-step-state='active']`, which does not
23
+ * change when the step count does. Three values rather than a boolean, because a stepper wants
24
+ * "everything up to here" (`:not([data-kui-step-state='after'])`) as often as it wants the live one.
25
+ */
26
+ /** The attribute this module owns. Never write it from anywhere else. */
27
+ export declare const STEP_STATE_ATTR = "data-kui-step-state";
28
+ export type StepState = 'before' | 'active' | 'after';
29
+ /**
30
+ * Classify a selector as unusable, dangerously broad, or fine.
31
+ *
32
+ * `matches` against the two roots catches every selector that reaches the whole document — `*`,
33
+ * `html`, `body` and compounds like `*, a` with one rule and no bespoke parser — while leaving a
34
+ * deliberately scoped wildcard such as `.spy-nav > *` working, which a syntactic ban on `*` would
35
+ * not. `matches` throws on invalid syntax exactly as `querySelectorAll` does, so the same call
36
+ * still answers the validity question.
37
+ *
38
+ * @complexity O(1) time and space.
39
+ * @overallScore 100
40
+ */
41
+ export declare function selectorBreadth(selector: string, doc: Document): 'invalid' | 'document-wide' | 'ok';
42
+ /**
43
+ * Resolve a `target:` selector once at setup, rejecting both the unusable and the over-broad.
44
+ *
45
+ * Two distinct failures, one warning channel. An invalid selector thrown from inside the shared
46
+ * scheduler's frame callback would skip every other subscriber on that root, not just this one. An
47
+ * over-broad one is worse for being silent: `target:*` is perfectly valid syntax, so a syntax-only
48
+ * check passed it straight through to stamp an attribute onto every element in the document, on
49
+ * every frame.
50
+ *
51
+ * Rejecting rather than silently narrowing is the honest response, because there is no narrower
52
+ * selector that could be meant. A selector reaching `<html>` or `<body>` is not naming a set of
53
+ * steps or a nav link, it is naming the page, and guessing which of its thousands of descendants
54
+ * the author meant would be worse than saying so.
55
+ *
56
+ * @param selector - The authored `target:` value; empty is the no-op default, not an error.
57
+ * @param ctx - Prepare context, for `doc` and the warning channel.
58
+ * @param effect - Effect name, so the warning says which attribute to go and fix.
59
+ * @returns The selector, or `''` when it must be ignored.
60
+ * @complexity O(1) time and space; `matches` walks the selector, not the document.
61
+ * @overallScore 100
62
+ */
63
+ export declare function resolveTarget(selector: string, ctx: PrepareContext, effect: string): string;
64
+ /**
65
+ * A set of step elements plus the ledgers that let their original attributes survive teardown.
66
+ *
67
+ * One ledger per element ever written, rather than a bare Set of touched nodes: a Set records
68
+ * *which* elements were stamped but not *what they held first*, so teardown would remove a
69
+ * `data-kui-step-state` the consumer had authored themselves. Same reasoning as `scroll-spy`'s
70
+ * link ledgers and the inline-style ledger they were both modelled on.
71
+ */
72
+ export interface StepMarker {
73
+ /** Stamp `before`/`active`/`after` across the tracked elements. Cheap enough to call per flip. */
74
+ mark(index: number): void;
75
+ /** Give every touched element back the attribute value it had before this instance existed. */
76
+ restore: Cleanup;
77
+ }
78
+ /**
79
+ * Build a marker over the elements a stepped effect drives.
80
+ *
81
+ * `resolve` is called per flip rather than once at setup, so a list rendered or reordered after
82
+ * this element was prepared is still picked up. Flips are rare — the caller is expected to guard on
83
+ * the index actually changing — while frames are not.
84
+ *
85
+ * @param resolve - Produces the current step elements, in document order.
86
+ * @returns A marker; call `restore` from the primitive's teardown.
87
+ * @complexity O(n) per `mark` in the number of step elements; O(n) space in elements ever touched.
88
+ * @overallScore 100
89
+ */
90
+ export declare function createStepMarker(resolve: () => Iterable<Element>): StepMarker;
91
+ /**
92
+ * Where one step sits relative to the live one.
93
+ *
94
+ * Pure, so the three-way split is assertable without a DOM.
95
+ *
96
+ * @complexity O(1) time and space.
97
+ * @overallScore 100
98
+ */
99
+ export declare function stepStateFor(position: number, index: number): StepState;
@@ -1,12 +1,5 @@
1
1
  import type { Preset, Primitive } from '../../core/types.js';
2
2
  import type { Registry } from '../../core/registry.js';
3
- /**
4
- * 3D, perspective, and page-transition effects — all CSS-rendered.
5
- *
6
- * Nothing here needs JavaScript: the browser interpolates `rotate`, `clip-path`, and `opacity`
7
- * natively, so these cost a keyframe block and a registry row each. That ratio is the whole
8
- * architecture, and it is why tripling the catalog does not triple the payload.
9
- */
10
3
  export declare const THREE_D_PRIMITIVES: Primitive[];
11
4
  export declare const THREE_D_PRESETS: Preset[];
12
5
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kuinetic",
3
- "version": "0.1.2",
3
+ "version": "0.1.3",
4
4
  "description": "Declarative web animation from HTML attributes. Native CSS where possible, JS only where necessary.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -39,6 +39,7 @@
39
39
  "lint": "eslint src test scripts",
40
40
  "lint:deps": "depcruise src --config .dependency-cruiser.cjs",
41
41
  "lint:dead": "knip",
42
+ "ci": "npm run build:dist && npm run typecheck && npm run lint && npm run lint:deps && npm run lint:dead && npm run check:css-coverage && npm run test:coverage && npm run test:browser",
42
43
  "size": "npm run build:dist && size-limit",
43
44
  "build": "npm run generate:css && esbuild src/index.ts --bundle --format=iife --global-name=kuinetic --outfile=demo/kuinetic.js && esbuild src/css/index.css --bundle --outfile=demo/kuinetic.css && npx @tailwindcss/cli -i demo/tailwind-entry.css -o demo/tailwind.css && node scripts/build-standalone.mjs demo",
44
45
  "build:dist": "npm run clean:dist && npm run generate:css && esbuild src/index.ts src/core/index.ts src/effects/index.ts --bundle --format=esm --splitting --outdir=dist/esm --out-extension:.js=.mjs && esbuild src/index.ts --bundle --format=iife --global-name=kuinetic --outfile=dist/kuinetic.js && esbuild src/css/index.css --bundle --outfile=dist/kuinetic.css && tsc -p tsconfig.build.json && node scripts/build-standalone.mjs",
@@ -50,9 +51,13 @@
50
51
  "dev": "npm run build && node scripts/dev-server.mjs",
51
52
  "verify:browser": "npm run build && node scripts/verify-browser.mjs",
52
53
  "test:browser": "npm run build && node scripts/run-browser-tests.mjs",
54
+ "test:visual": "npm run build && node scripts/visual-regression.mjs",
55
+ "test:visual:update": "npm run build && node scripts/visual-regression.mjs --update",
53
56
  "generate:css": "node scripts/generate-preset-css.mjs",
57
+ "generate:nav": "node scripts/generate-nav-header.mjs",
54
58
  "record": "npm run build && node scripts/verify-browser.mjs --record",
55
- "check:showcase": "npm run build && node scripts/check-showcase.mjs"
59
+ "check:showcase": "npm run build && node scripts/check-showcase.mjs",
60
+ "check:css-coverage": "npm run build && node scripts/check-css-coverage.mjs"
56
61
  },
57
62
  "devDependencies": {
58
63
  "@eslint/js": "^9.39.5",
@@ -68,7 +73,9 @@
68
73
  "globals": "^14.0.0",
69
74
  "jsdom": "^25.0.1",
70
75
  "knip": "^6.32.2",
76
+ "pixelmatch": "^7.2.0",
71
77
  "playwright-core": "^1.61.1",
78
+ "pngjs": "^7.0.0",
72
79
  "size-limit": "^13.0.3",
73
80
  "tailwindcss": "^4.3.3",
74
81
  "typescript": "^5.9.3",