@zakkster/lite-ui-fx 1.1.0 → 1.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/CHANGELOG.md CHANGED
@@ -5,7 +5,54 @@ All notable changes to `@zakkster/lite-ui-fx` are documented here.
5
5
  The format follows Keep a Changelog; this project adheres to Semantic
6
6
  Versioning.
7
7
 
8
- ## [1.1.0] -- unreleased
8
+ ## [1.2.0] -- unreleased
9
+
10
+ Recipes ship as code (U-13). The three GitHub-only recipe volumes are
11
+ consolidated into one `UIFXRecipes.js` at the package root, exposed as the
12
+ `./recipes` subpath export behind a registry. No recipe body changes -- the
13
+ zero-GC / size-true / theming sweep is U3.
14
+
15
+ ### Added
16
+
17
+ - `./recipes` subpath export: all 50 recipes ship in one `UIFXRecipes.js`
18
+ (with `UIFXRecipes.d.ts`), versioned, typed, and tree-shakeable
19
+ (`sideEffects: false`). `UIFX-RECIPE-GUIDE.md` moves to the package root and
20
+ ships as well.
21
+ - Recipe registry (ported from `@zakkster/lite-scratch-fx`): `RECIPES`
22
+ (null-prototype, id -> factory), `RECIPE_META` (`{ id, name, type, family,
23
+ themeable, motionSafe }`; `themeable` and `motionSafe` are `false` for all
24
+ until U3), `RECIPE_NAMES` (frozen), and `registerRecipe(id, factory, meta)`
25
+ with an in-place meta-merge.
26
+ - `mountRecipe(container, id, options?)`: resolves the id fail closed (an
27
+ unknown id throws with a did-you-mean; a non-string id gets the same clean
28
+ message), asserts any `options.type` matches the recipe's declared type, then
29
+ mounts via `mountUIFX`.
30
+ - Torture: `t0-lifecycle` and `t1-degenerate` iterate `RECIPE_META`, so all 50
31
+ recipes are mounted, exercised, and destroyed by construction. New
32
+ `test/registry.test.mjs` (registry + `mountRecipe` contract + a boundary
33
+ matrix) and `test/treeshake.test.mjs` (an esbuild proof that importing one
34
+ recipe drops the others).
35
+
36
+ ### Changed
37
+
38
+ - Fail closed on the element type: `mountUIFX` now throws on any `type` other
39
+ than `UIType.BUTTON` / `TOGGLE` / `SLIDER` (an unknown type previously became
40
+ a button silently), and `registerRecipe` rejects a recipe with no valid type
41
+ before any mutation.
42
+ - The recipes are no longer a GitHub ZIP / copy-paste. `README.md` and
43
+ `llms.txt` document the `./recipes` import and drop the "not included in the
44
+ npm package" wording.
45
+ - `package.json`: `exports["./recipes"]` added; `files[]` ships
46
+ `UIFXRecipes.js`, `UIFXRecipes.d.ts`, and `UIFX-RECIPE-GUIDE.md`; `esbuild`
47
+ added as a devDependency (the tree-shake proof only -- not shipped).
48
+ - Decision recorded in `decisions/0001-recipes-position.md`.
49
+
50
+ ### Removed
51
+
52
+ - The `recipes/` directory (three volumes plus their `.d.ts`). Their exports
53
+ are unchanged and now come from the root `UIFXRecipes.js`.
54
+
55
+ ## [1.1.0] -- 2026-09-06
9
56
 
10
57
  Controller correctness: the two S1 defects (U-01, U-02) and three
11
58
  controller-level S3s (U-09, U-10, U-11). No visual change at default mounts.
package/README.md CHANGED
@@ -40,26 +40,22 @@ https://cdpn.io/pen/debug/YPGEaYY
40
40
 
41
41
  Every recipe is zero-GC, uses `dt`-based animation, and includes accessibility indicators (focus rings, state labels).
42
42
 
43
- `@zakkster/lite-ui-fx` ships only the core controller on npm (zero bloat).
44
- All visual effects live in the GitHub repo as **recipes**.
43
+ All 50 recipes ship in the package on the `./recipes` subpath -- versioned,
44
+ typed, and tree-shakeable. With `sideEffects: false`, importing one recipe pulls
45
+ in only that recipe, so a controller-only install stays tiny.
45
46
 
46
- **Recipe Collections:**
47
- - Vol. 1 (10 recipes):
48
- https://github.com/PeshoVurtoleta/lite-ui-fx/blob/main/recipes/UIFXRecipes.js
49
- - Vol. 2 (20 recipes):
50
- https://github.com/PeshoVurtoleta/lite-ui-fx/blob/main/recipes/UIFXRecipes2.js
51
- - Vol. 3 (20 recipes):
52
- https://github.com/PeshoVurtoleta/lite-ui-fx/blob/main/recipes/UIFXRecipes3.js
53
-
54
- **How to write your own:**
55
- https://github.com/PeshoVurtoleta/lite-ui-fx/blob/main/recipes/UIFX-RECIPE-GUIDE.md
47
+ ```javascript
48
+ import { mountUIFX, UIType } from '@zakkster/lite-ui-fx';
49
+ import { SwarmToggle } from '@zakkster/lite-ui-fx/recipes';
50
+ ```
56
51
 
57
- Recipes are **optional**, **open-source**, and **not included in the npm package**
58
- to keep the install size tiny (<2 KB).
59
- You can copy/paste any recipe into your project or use them as inspiration.
52
+ The `./recipes` entry also exports a registry for data-driven pickers:
53
+ `RECIPES` (id -> factory), `RECIPE_META` (`{ id, name, type, family }`),
54
+ `RECIPE_NAMES`, `registerRecipe(id, factory, meta)`, and
55
+ `mountRecipe(container, id, options?)` -- which resolves the id fail-closed
56
+ (did-you-mean on a typo) and mounts it as its declared type.
60
57
 
61
- Download all recipes as a ZIP
62
- https://github.com/PeshoVurtoleta/lite-ui-fx/archive/refs/heads/main.zip
58
+ **How to write your own:** see [UIFX-RECIPE-GUIDE.md](UIFX-RECIPE-GUIDE.md), shipped in the package.
63
59
 
64
60
  Part of the [@zakkster/lite-*](https://www.npmjs.com/org/zakkster) ecosystem.
65
61
 
@@ -69,15 +65,15 @@ Part of the [@zakkster/lite-*](https://www.npmjs.com/org/zakkster) ecosystem.
69
65
  npm i @zakkster/lite-ui-fx
70
66
  ```
71
67
 
72
- > Looking for the visual effects?
73
- > Recipes live in the GitHub repo -- not in the npm package -- to keep the library tiny.
68
+ > The 50 recipes ship in the same package on the `./recipes` subpath and
69
+ > tree-shake, so importing one adds only that one.
74
70
 
75
71
 
76
72
  ## Quick Start
77
73
 
78
74
  ```javascript
79
75
  import { mountUIFX, UIType } from '@zakkster/lite-ui-fx';
80
- import { SwarmToggle } from './recipes/UIFXRecipes.js';
76
+ import { SwarmToggle } from '@zakkster/lite-ui-fx/recipes';
81
77
 
82
78
  // Mount a canvas-rendered toggle onto a container
83
79
  const instance = mountUIFX(
@@ -101,17 +97,13 @@ instance.destroy();
101
97
  // Controller (always needed)
102
98
  import { mountUIFX, UIType } from '@zakkster/lite-ui-fx';
103
99
 
104
- // Recipes are NOT included in the npm package.
105
- // Copy them from the GitHub repo into your own ./recipes folder:
106
-
107
- // Vol. 1 -- 10 recipes (toggles, buttons, sliders)
108
- import { SwarmToggle, MagneticButton, SparkSlider } from './recipes/UIFXRecipes.js';
109
-
110
- // Vol. 2 -- 20 recipes (+ loaders, checkboxes, counters, rating)
111
- import { PendulumToggle, HeartbeatButton, RippleCheck } from './recipes/UIFXRecipes2.js';
100
+ // All 50 recipes ship on the ./recipes subpath (tree-shakeable) -- import by name:
101
+ import { SwarmToggle, MagneticButton, SparkSlider } from '@zakkster/lite-ui-fx/recipes';
102
+ import { PendulumToggle, HeartbeatButton, RippleCheck } from '@zakkster/lite-ui-fx/recipes';
103
+ import { VolumeKnob, WaterLevel, TimerCountdown } from '@zakkster/lite-ui-fx/recipes';
112
104
 
113
- // Vol. 3 -- 20 recipes (knobs, progress, controls, indicators, mood, feedback, fun)
114
- import { VolumeKnob, WaterLevel, TimerCountdown } from './recipes/UIFXRecipes3.js';
105
+ // Registry surface for data-driven pickers:
106
+ import { RECIPES, RECIPE_META, RECIPE_NAMES, registerRecipe, mountRecipe } from '@zakkster/lite-ui-fx/recipes';
115
107
  ```
116
108
 
117
109
  ## How It Works
@@ -192,7 +184,7 @@ Returns `{ el, canvas, wrapper, state, destroy() }`.
192
184
 
193
185
  ## Writing Custom Recipes
194
186
 
195
- See the full [UIFX-RECIPE-GUIDE.md](recipes/UIFX-RECIPE-GUIDE.md) (included in the package).
187
+ See the full [UIFX-RECIPE-GUIDE.md](UIFX-RECIPE-GUIDE.md) (included in the package).
196
188
 
197
189
  Minimal recipe:
198
190
 
@@ -226,7 +218,7 @@ Full TypeScript declarations are included for:
226
218
  - `UIFXPointer`
227
219
  - `UIFXRecipe`
228
220
 
229
- (Recipes are not part of the npm package, so their types are not included.)
221
+ Recipe types ship too, on the `./recipes` subpath (`UIFXRecipes.d.ts`).
230
222
 
231
223
 
232
224
  ## LLM-Friendly Documentation
package/UIFXController.js CHANGED
@@ -22,7 +22,7 @@ import { Ticker } from '@zakkster/lite-ticker';
22
22
 
23
23
  // Three-place version sync: this constant, package.json "version", and the
24
24
  // VERSION line in llms.txt must always match. /release keeps them locked.
25
- export const VERSION = '1.1.0';
25
+ export const VERSION = '1.2.0';
26
26
 
27
27
  // ---------------------------------------------------------
28
28
  // SHARED TICKER (ref-counted, one RAF for all UI components)
@@ -177,6 +177,13 @@ export function mountUIFX(container, type, recipeFactory, options = {}) {
177
177
  throw new Error('mountUIFX: container must be a DOM element');
178
178
  }
179
179
 
180
+ // 1b. type: exactly one of the three known element types. An unknown or
181
+ // undefined type is an Error here, never a silent default to a button
182
+ // (fail closed -- the type selects the native element).
183
+ if (type !== UIType.BUTTON && type !== UIType.TOGGLE && type !== UIType.SLIDER) {
184
+ throw new Error('mountUIFX: type must be UIType.BUTTON, UIType.TOGGLE, or UIType.SLIDER');
185
+ }
186
+
180
187
  // 2. options: unknown keys -> did-you-mean; value/checked/disabled
181
188
  // validated and coerced HERE, before any element exists.
182
189
  for (const k in options) {
@@ -0,0 +1,253 @@
1
+ import type { UIFXRecipe, UIFXInstance, MountOptions } from './UIFXController';
2
+
3
+ // ===========================================================
4
+ // RECIPE FACTORIES (all 50)
5
+ // ===========================================================
6
+
7
+ // -- Vol.1: Toggles --
8
+ export declare function SwarmToggle(options?: { seed?: number; count?: number }): UIFXRecipe;
9
+ export declare function LiquidToggle(): UIFXRecipe;
10
+ export declare function NeonPulseToggle(): UIFXRecipe;
11
+
12
+ // -- Vol.1: Buttons --
13
+ export declare function MagneticButton(options?: { maxPull?: number }): UIFXRecipe;
14
+ export declare function ShatterButton(options?: { seed?: number }): UIFXRecipe;
15
+ export declare function ConfettiButton(options?: { seed?: number; colors?: string[] }): UIFXRecipe;
16
+ export declare function GlitchButton(options?: { seed?: number }): UIFXRecipe;
17
+
18
+ // -- Vol.1: Sliders --
19
+ export declare function SparkSlider(options?: { seed?: number; color?: string }): UIFXRecipe;
20
+ export declare function CosmicSlider(options?: { seed?: number; dustCount?: number }): UIFXRecipe;
21
+ export declare function LaserSlider(): UIFXRecipe;
22
+
23
+ // -- Vol.2: Toggles --
24
+ export declare function PendulumToggle(): UIFXRecipe;
25
+ export declare function CircuitToggle(options?: { seed?: number }): UIFXRecipe;
26
+ export declare function LightningToggle(options?: { seed?: number }): UIFXRecipe;
27
+ export declare function DNAToggle(): UIFXRecipe;
28
+
29
+ // -- Vol.2: Buttons --
30
+ export declare function HeartbeatButton(options?: { seed?: number }): UIFXRecipe;
31
+ export declare function BreathingButton(): UIFXRecipe;
32
+ export declare function InkSplashButton(options?: { seed?: number }): UIFXRecipe;
33
+ export declare function PixelDissolveButton(options?: { seed?: number; cols?: number; rows?: number }): UIFXRecipe;
34
+ export declare function FireworkButton(options?: { seed?: number }): UIFXRecipe;
35
+
36
+ // -- Vol.2: Sliders --
37
+ export declare function AuroraSlider(): UIFXRecipe;
38
+ export declare function WaveSlider(options?: { seed?: number }): UIFXRecipe;
39
+ export declare function ElasticBandSlider(): UIFXRecipe;
40
+ export declare function GravitySlider(): UIFXRecipe;
41
+
42
+ // -- Vol.2: Loaders --
43
+ export declare function OrbitLoader(): UIFXRecipe;
44
+ export declare function HelixLoader(): UIFXRecipe;
45
+
46
+ // -- Vol.2: Checkboxes --
47
+ export declare function RippleCheck(): UIFXRecipe;
48
+ export declare function MorphCheck(): UIFXRecipe;
49
+
50
+ // -- Vol.2: Counters --
51
+ export declare function FlameCounter(options?: { seed?: number }): UIFXRecipe;
52
+ export declare function GlitchCounter(options?: { seed?: number }): UIFXRecipe;
53
+
54
+ // -- Vol.2: Rating --
55
+ export declare function BubbleRating(options?: { seed?: number }): UIFXRecipe;
56
+
57
+ // -- Vol.3: Knobs --
58
+ export declare function VolumeKnob(): UIFXRecipe;
59
+ export declare function CompassKnob(): UIFXRecipe;
60
+
61
+ // -- Vol.3: Progress --
62
+ export declare function RingProgress(options?: { seed?: number }): UIFXRecipe;
63
+ export declare function BatteryGauge(): UIFXRecipe;
64
+ export declare function SignalMeter(): UIFXRecipe;
65
+
66
+ // -- Vol.3: Controls --
67
+ export declare function PillTabs(): UIFXRecipe;
68
+ export declare function Stepper(): UIFXRecipe;
69
+ export declare function RadioOrbit(): UIFXRecipe;
70
+
71
+ // -- Vol.3: Indicators --
72
+ export declare function PasswordStrength(): UIFXRecipe;
73
+ export declare function WaterLevel(): UIFXRecipe;
74
+ export declare function HeatMap(options?: { seed?: number }): UIFXRecipe;
75
+
76
+ // -- Vol.3: Mood --
77
+ export declare function DayNightToggle(options?: { seed?: number }): UIFXRecipe;
78
+ export declare function ReactionPicker(): UIFXRecipe;
79
+ export declare function NotificationBell(): UIFXRecipe;
80
+
81
+ // -- Vol.3: Feedback --
82
+ export declare function TypewriterField(): UIFXRecipe;
83
+ export declare function SoundWaveBtn(): UIFXRecipe;
84
+ export declare function UploadProgress(): UIFXRecipe;
85
+
86
+ // -- Vol.3: Fun --
87
+ export declare function ScratchReveal(options?: { seed?: number }): UIFXRecipe;
88
+ export declare function TimerCountdown(): UIFXRecipe;
89
+ export declare function PullRefresh(): UIFXRecipe;
90
+
91
+ // ===========================================================
92
+ // BARREL OBJECTS (back-compat)
93
+ // ===========================================================
94
+
95
+ export declare const UIFXRecipes: {
96
+ SwarmToggle: typeof SwarmToggle;
97
+ LiquidToggle: typeof LiquidToggle;
98
+ NeonPulseToggle: typeof NeonPulseToggle;
99
+ MagneticButton: typeof MagneticButton;
100
+ ShatterButton: typeof ShatterButton;
101
+ ConfettiButton: typeof ConfettiButton;
102
+ GlitchButton: typeof GlitchButton;
103
+ SparkSlider: typeof SparkSlider;
104
+ CosmicSlider: typeof CosmicSlider;
105
+ LaserSlider: typeof LaserSlider;
106
+ };
107
+
108
+ export declare const UIFXRecipes2: {
109
+ PendulumToggle: typeof PendulumToggle;
110
+ CircuitToggle: typeof CircuitToggle;
111
+ LightningToggle: typeof LightningToggle;
112
+ DNAToggle: typeof DNAToggle;
113
+ HeartbeatButton: typeof HeartbeatButton;
114
+ BreathingButton: typeof BreathingButton;
115
+ InkSplashButton: typeof InkSplashButton;
116
+ PixelDissolveButton: typeof PixelDissolveButton;
117
+ FireworkButton: typeof FireworkButton;
118
+ AuroraSlider: typeof AuroraSlider;
119
+ WaveSlider: typeof WaveSlider;
120
+ ElasticBandSlider: typeof ElasticBandSlider;
121
+ GravitySlider: typeof GravitySlider;
122
+ OrbitLoader: typeof OrbitLoader;
123
+ HelixLoader: typeof HelixLoader;
124
+ RippleCheck: typeof RippleCheck;
125
+ MorphCheck: typeof MorphCheck;
126
+ FlameCounter: typeof FlameCounter;
127
+ GlitchCounter: typeof GlitchCounter;
128
+ BubbleRating: typeof BubbleRating;
129
+ };
130
+
131
+ export declare const UIFXRecipes3: {
132
+ VolumeKnob: typeof VolumeKnob;
133
+ CompassKnob: typeof CompassKnob;
134
+ RingProgress: typeof RingProgress;
135
+ BatteryGauge: typeof BatteryGauge;
136
+ SignalMeter: typeof SignalMeter;
137
+ PillTabs: typeof PillTabs;
138
+ Stepper: typeof Stepper;
139
+ RadioOrbit: typeof RadioOrbit;
140
+ PasswordStrength: typeof PasswordStrength;
141
+ WaterLevel: typeof WaterLevel;
142
+ HeatMap: typeof HeatMap;
143
+ DayNightToggle: typeof DayNightToggle;
144
+ ReactionPicker: typeof ReactionPicker;
145
+ NotificationBell: typeof NotificationBell;
146
+ TypewriterField: typeof TypewriterField;
147
+ SoundWaveBtn: typeof SoundWaveBtn;
148
+ UploadProgress: typeof UploadProgress;
149
+ ScratchReveal: typeof ScratchReveal;
150
+ TimerCountdown: typeof TimerCountdown;
151
+ PullRefresh: typeof PullRefresh;
152
+ };
153
+
154
+ // ===========================================================
155
+ // RECIPE REGISTRY
156
+ // ===========================================================
157
+
158
+ export type RecipeType = 'toggle' | 'button' | 'slider';
159
+
160
+ export type RecipeFactory = (options?: Record<string, unknown>) => UIFXRecipe;
161
+
162
+ export interface RecipeMeta {
163
+ id: string;
164
+ name: string;
165
+ type: RecipeType;
166
+ family: string;
167
+ themeable: boolean;
168
+ motionSafe: boolean;
169
+ }
170
+
171
+ /** Every recipe factory keyed by id. Null-prototype map. */
172
+ export declare const RECIPES: { [id: string]: RecipeFactory };
173
+
174
+ /** Live metadata array, one row per registered recipe. */
175
+ export declare const RECIPE_META: RecipeMeta[];
176
+
177
+ /** Frozen list of the built-in recipe ids at load time. */
178
+ export declare const RECIPE_NAMES: readonly string[];
179
+
180
+ /** Register a custom recipe, or override a built-in. Returns the factory. */
181
+ export declare function registerRecipe(
182
+ id: string,
183
+ factory: RecipeFactory,
184
+ meta?: Partial<Omit<RecipeMeta, 'id'>>,
185
+ ): RecipeFactory;
186
+
187
+ /**
188
+ * Resolve a recipe id to its factory + declared type and mount it via mountUIFX.
189
+ * Fail closed: unknown id or a conflicting options.type throws.
190
+ */
191
+ export declare function mountRecipe(
192
+ container: HTMLElement,
193
+ id: string,
194
+ options?: MountOptions & { type?: RecipeType },
195
+ ): UIFXInstance;
196
+
197
+ // ===========================================================
198
+ // DEFAULT EXPORT -- combined all-50 namespace
199
+ // ===========================================================
200
+
201
+ declare const UIFXAllRecipes: {
202
+ SwarmToggle: typeof SwarmToggle;
203
+ LiquidToggle: typeof LiquidToggle;
204
+ NeonPulseToggle: typeof NeonPulseToggle;
205
+ MagneticButton: typeof MagneticButton;
206
+ ShatterButton: typeof ShatterButton;
207
+ ConfettiButton: typeof ConfettiButton;
208
+ GlitchButton: typeof GlitchButton;
209
+ SparkSlider: typeof SparkSlider;
210
+ CosmicSlider: typeof CosmicSlider;
211
+ LaserSlider: typeof LaserSlider;
212
+ PendulumToggle: typeof PendulumToggle;
213
+ CircuitToggle: typeof CircuitToggle;
214
+ LightningToggle: typeof LightningToggle;
215
+ DNAToggle: typeof DNAToggle;
216
+ HeartbeatButton: typeof HeartbeatButton;
217
+ BreathingButton: typeof BreathingButton;
218
+ InkSplashButton: typeof InkSplashButton;
219
+ PixelDissolveButton: typeof PixelDissolveButton;
220
+ FireworkButton: typeof FireworkButton;
221
+ AuroraSlider: typeof AuroraSlider;
222
+ WaveSlider: typeof WaveSlider;
223
+ ElasticBandSlider: typeof ElasticBandSlider;
224
+ GravitySlider: typeof GravitySlider;
225
+ OrbitLoader: typeof OrbitLoader;
226
+ HelixLoader: typeof HelixLoader;
227
+ RippleCheck: typeof RippleCheck;
228
+ MorphCheck: typeof MorphCheck;
229
+ FlameCounter: typeof FlameCounter;
230
+ GlitchCounter: typeof GlitchCounter;
231
+ BubbleRating: typeof BubbleRating;
232
+ VolumeKnob: typeof VolumeKnob;
233
+ CompassKnob: typeof CompassKnob;
234
+ RingProgress: typeof RingProgress;
235
+ BatteryGauge: typeof BatteryGauge;
236
+ SignalMeter: typeof SignalMeter;
237
+ PillTabs: typeof PillTabs;
238
+ Stepper: typeof Stepper;
239
+ RadioOrbit: typeof RadioOrbit;
240
+ PasswordStrength: typeof PasswordStrength;
241
+ WaterLevel: typeof WaterLevel;
242
+ HeatMap: typeof HeatMap;
243
+ DayNightToggle: typeof DayNightToggle;
244
+ ReactionPicker: typeof ReactionPicker;
245
+ NotificationBell: typeof NotificationBell;
246
+ TypewriterField: typeof TypewriterField;
247
+ SoundWaveBtn: typeof SoundWaveBtn;
248
+ UploadProgress: typeof UploadProgress;
249
+ ScratchReveal: typeof ScratchReveal;
250
+ TimerCountdown: typeof TimerCountdown;
251
+ PullRefresh: typeof PullRefresh;
252
+ };
253
+ export default UIFXAllRecipes;