lume-js 2.0.0-beta.2 → 2.0.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 +7 -7
- package/dist/addons.min.mjs +1 -1
- package/dist/addons.mjs +46 -2
- package/dist/addons.mjs.map +1 -1
- package/dist/index.min.mjs +1 -1
- package/dist/index.mjs +4 -3
- package/dist/lume.global.js +1 -1
- package/dist/lume.global.js.map +1 -1
- package/dist/{shared-DMnPGlYI.mjs → shared-nXhT2Lh7.mjs} +4 -4
- package/dist/{shared-DMnPGlYI.mjs.map → shared-nXhT2Lh7.mjs.map} +1 -1
- package/package.json +1 -1
- package/src/addons/cleanupGroup.js +44 -0
- package/src/addons/hydrateState.js +30 -0
- package/src/addons/index.d.ts +53 -5
- package/src/addons/index.js +2 -0
- package/src/addons/repeat.js +0 -1
- package/src/addons/withPlugins.js +11 -2
- package/src/core/effect.js +1 -2
- package/src/index.d.ts +29 -1
- package/src/index.js +2 -1
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Creates a cleanup group that can collect and dispose multiple
|
|
3
|
+
* cleanup/unsubscribe functions at once.
|
|
4
|
+
*
|
|
5
|
+
* @returns {CleanupGroup}
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```js
|
|
9
|
+
* import { createCleanupGroup } from 'lume-js/addons';
|
|
10
|
+
*
|
|
11
|
+
* const group = createCleanupGroup();
|
|
12
|
+
* group.add(bindDom(root, store));
|
|
13
|
+
* group.add(effect(() => { ... }));
|
|
14
|
+
* group.add(store.$subscribe('key', fn));
|
|
15
|
+
*
|
|
16
|
+
* // Dispose everything at once
|
|
17
|
+
* group.dispose();
|
|
18
|
+
* ```
|
|
19
|
+
*/
|
|
20
|
+
export function createCleanupGroup() {
|
|
21
|
+
const cleanups = [];
|
|
22
|
+
|
|
23
|
+
return {
|
|
24
|
+
/**
|
|
25
|
+
* Add a cleanup function to the group.
|
|
26
|
+
* @param {Function} fn - Cleanup/unsubscribe function
|
|
27
|
+
*/
|
|
28
|
+
add(fn) {
|
|
29
|
+
if (typeof fn === 'function') {
|
|
30
|
+
cleanups.push(fn);
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Run all collected cleanup functions and clear the group.
|
|
36
|
+
*/
|
|
37
|
+
dispose() {
|
|
38
|
+
while (cleanups.length) {
|
|
39
|
+
const fn = cleanups.pop();
|
|
40
|
+
try { fn(); } catch (e) { /* ignore cleanup errors */ }
|
|
41
|
+
}
|
|
42
|
+
},
|
|
43
|
+
};
|
|
44
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reads initial state from a `<script type="application/json">` element
|
|
3
|
+
* embedded in the server-rendered HTML. Useful for SSR / hydration patterns.
|
|
4
|
+
*
|
|
5
|
+
* @param {string} [selector='#__LUME_DATA__'] - CSS selector for the script element
|
|
6
|
+
* @returns {object} Parsed JSON object, or empty object if not found / invalid
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* ```html
|
|
10
|
+
* <script id="__LUME_DATA__" type="application/json">
|
|
11
|
+
* {"title": "Welcome", "count": 42}
|
|
12
|
+
* </script>
|
|
13
|
+
* ```
|
|
14
|
+
*
|
|
15
|
+
* ```js
|
|
16
|
+
* import { state } from 'lume-js';
|
|
17
|
+
* import { hydrateState } from 'lume-js/addons';
|
|
18
|
+
*
|
|
19
|
+
* const store = state(hydrateState());
|
|
20
|
+
* ```
|
|
21
|
+
*/
|
|
22
|
+
export function hydrateState(selector = '#__LUME_DATA__') {
|
|
23
|
+
const el = typeof document !== 'undefined' ? document.querySelector(selector) : null;
|
|
24
|
+
if (!el) return {};
|
|
25
|
+
try {
|
|
26
|
+
return JSON.parse(el.textContent);
|
|
27
|
+
} catch {
|
|
28
|
+
return {};
|
|
29
|
+
}
|
|
30
|
+
}
|
package/src/addons/index.d.ts
CHANGED
|
@@ -320,11 +320,11 @@ export interface DebugPlugin {
|
|
|
320
320
|
* @example
|
|
321
321
|
* ```typescript
|
|
322
322
|
* import { state } from 'lume-js';
|
|
323
|
-
* import { createDebugPlugin } from 'lume-js/addons';
|
|
324
|
-
*
|
|
325
|
-
* const store = state({ count: 0 },
|
|
326
|
-
*
|
|
327
|
-
*
|
|
323
|
+
* import { createDebugPlugin, withPlugins } from 'lume-js/addons';
|
|
324
|
+
*
|
|
325
|
+
* const store = withPlugins(state({ count: 0 }), [
|
|
326
|
+
* createDebugPlugin({ label: 'counter' })
|
|
327
|
+
* ]);
|
|
328
328
|
* ```
|
|
329
329
|
*/
|
|
330
330
|
export function createDebugPlugin(options?: DebugPluginOptions): DebugPlugin;
|
|
@@ -434,3 +434,51 @@ export interface Plugin {
|
|
|
434
434
|
*/
|
|
435
435
|
export function withPlugins<T extends object>(store: ReactiveState<T>, plugins: Plugin[]): ReactiveState<T>;
|
|
436
436
|
|
|
437
|
+
/**
|
|
438
|
+
* A group that collects cleanup/unsubscribe functions and can dispose them all at once.
|
|
439
|
+
*
|
|
440
|
+
* @example
|
|
441
|
+
* ```typescript
|
|
442
|
+
* import { createCleanupGroup } from 'lume-js/addons';
|
|
443
|
+
*
|
|
444
|
+
* const group = createCleanupGroup();
|
|
445
|
+
* group.add(bindDom(root, store));
|
|
446
|
+
* group.add(effect(() => { ... }));
|
|
447
|
+
* group.add(store.$subscribe('key', fn));
|
|
448
|
+
*
|
|
449
|
+
* // Dispose everything at once
|
|
450
|
+
* group.dispose();
|
|
451
|
+
* ```
|
|
452
|
+
*/
|
|
453
|
+
export interface CleanupGroup {
|
|
454
|
+
/** Add a cleanup/unsubscribe function to the group */
|
|
455
|
+
add(fn: () => void): void;
|
|
456
|
+
/** Run all collected cleanup functions and clear the group */
|
|
457
|
+
dispose(): void;
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
/**
|
|
461
|
+
* Creates a cleanup group that can collect and dispose multiple
|
|
462
|
+
* cleanup/unsubscribe functions at once.
|
|
463
|
+
*
|
|
464
|
+
* @returns A CleanupGroup instance
|
|
465
|
+
*/
|
|
466
|
+
export function createCleanupGroup(): CleanupGroup;
|
|
467
|
+
|
|
468
|
+
/**
|
|
469
|
+
* Reads initial state from a `<script type="application/json">` element
|
|
470
|
+
* embedded in the server-rendered HTML.
|
|
471
|
+
*
|
|
472
|
+
* @param selector - CSS selector for the script element (default: '#__LUME_DATA__')
|
|
473
|
+
* @returns Parsed JSON object, or empty object if not found or invalid
|
|
474
|
+
*
|
|
475
|
+
* @example
|
|
476
|
+
* ```typescript
|
|
477
|
+
* import { state } from 'lume-js';
|
|
478
|
+
* import { hydrateState } from 'lume-js/addons';
|
|
479
|
+
*
|
|
480
|
+
* const store = state(hydrateState());
|
|
481
|
+
* ```
|
|
482
|
+
*/
|
|
483
|
+
export function hydrateState(selector?: string): object;
|
|
484
|
+
|
package/src/addons/index.js
CHANGED
|
@@ -3,6 +3,8 @@ export { watch } from "./watch.js";
|
|
|
3
3
|
export { repeat, defaultFocusPreservation, defaultScrollPreservation } from "./repeat.js";
|
|
4
4
|
export { createDebugPlugin, debug } from "./debug.js";
|
|
5
5
|
export { withPlugins } from "./withPlugins.js";
|
|
6
|
+
export { createCleanupGroup } from "./cleanupGroup.js";
|
|
7
|
+
export { hydrateState } from "./hydrateState.js";
|
|
6
8
|
|
|
7
9
|
/**
|
|
8
10
|
* Returns true if the value is a Lume reactive proxy created by state().
|
package/src/addons/repeat.js
CHANGED
|
@@ -60,13 +60,22 @@ export function withPlugins(store, plugins = []) {
|
|
|
60
60
|
pendingNotifications.clear();
|
|
61
61
|
}
|
|
62
62
|
|
|
63
|
-
// Register once on the underlying state;
|
|
63
|
+
// Register once on the underlying state; capture unsubscribe for cleanup.
|
|
64
|
+
let flushUnsub;
|
|
64
65
|
if (typeof store.$beforeFlush === 'function') {
|
|
65
|
-
store.$beforeFlush(runNotifyHooks);
|
|
66
|
+
flushUnsub = store.$beforeFlush(runNotifyHooks);
|
|
66
67
|
}
|
|
67
68
|
|
|
68
69
|
return new Proxy(store, {
|
|
69
70
|
get(target, key) {
|
|
71
|
+
// $dispose — remove the beforeFlush hook and clear pending state
|
|
72
|
+
if (key === '$dispose') {
|
|
73
|
+
return () => {
|
|
74
|
+
if (flushUnsub) flushUnsub();
|
|
75
|
+
pendingNotifications.clear();
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
|
|
70
79
|
// Pass $-prefixed meta methods through without interception
|
|
71
80
|
if (typeof key === 'string' && key.startsWith('$')) {
|
|
72
81
|
const method = target[key];
|
package/src/core/effect.js
CHANGED
|
@@ -120,8 +120,7 @@ export function effect(fn, deps) {
|
|
|
120
120
|
// Save previous subscriptions instead of cleaning immediately.
|
|
121
121
|
// If fn() doesn't read any state (early return / error), we restore
|
|
122
122
|
// them so the effect stays reactive.
|
|
123
|
-
const oldCleanups =
|
|
124
|
-
cleanups.length = 0;
|
|
123
|
+
const oldCleanups = cleanups.splice(0);
|
|
125
124
|
|
|
126
125
|
// Create effect context for tracking
|
|
127
126
|
const myContext = {
|
package/src/index.d.ts
CHANGED
|
@@ -14,6 +14,12 @@ export type Unsubscribe = () => void;
|
|
|
14
14
|
*/
|
|
15
15
|
export type Subscriber<T> = (value: T) => void;
|
|
16
16
|
|
|
17
|
+
/**
|
|
18
|
+
* Internal unique symbol for reactive state branding
|
|
19
|
+
* @internal
|
|
20
|
+
*/
|
|
21
|
+
declare const lumeReactiveSymbol: unique symbol;
|
|
22
|
+
|
|
17
23
|
/**
|
|
18
24
|
* Plugin interface for extending state behavior
|
|
19
25
|
*
|
|
@@ -147,7 +153,7 @@ export type ReactiveState<T extends object> = T & {
|
|
|
147
153
|
* Brand to identify reactive state objects at the type level
|
|
148
154
|
* @internal
|
|
149
155
|
*/
|
|
150
|
-
readonly [
|
|
156
|
+
readonly [lumeReactiveSymbol]?: true;
|
|
151
157
|
};
|
|
152
158
|
|
|
153
159
|
/**
|
|
@@ -347,6 +353,28 @@ export function effect(fn: () => void): Unsubscribe;
|
|
|
347
353
|
*/
|
|
348
354
|
export function effect(fn: () => void, deps: EffectDependency[]): Unsubscribe;
|
|
349
355
|
|
|
356
|
+
/**
|
|
357
|
+
* Run a function with a read observer active.
|
|
358
|
+
*
|
|
359
|
+
* The observer receives `(proxy, key, registerEffect)` for every property read
|
|
360
|
+
* during the synchronous execution of `fn`. Used internally by `effect()` for
|
|
361
|
+
* auto-tracking, and exposed for building custom reactive primitives.
|
|
362
|
+
*
|
|
363
|
+
* @param onRead - Called on each property access inside fn
|
|
364
|
+
* @param fn - The function to run under observation
|
|
365
|
+
* @returns The return value of fn
|
|
366
|
+
*
|
|
367
|
+
* @internal
|
|
368
|
+
*/
|
|
369
|
+
export function withReadObserver<T>(
|
|
370
|
+
onRead: (
|
|
371
|
+
proxy: ReactiveState<any>,
|
|
372
|
+
key: string,
|
|
373
|
+
registerEffect: (key: string, executeFn: () => void) => () => void
|
|
374
|
+
) => void,
|
|
375
|
+
fn: () => T
|
|
376
|
+
): T;
|
|
377
|
+
|
|
350
378
|
|
|
351
379
|
// ============================================================================
|
|
352
380
|
// Utility Types
|
package/src/index.js
CHANGED
|
@@ -5,11 +5,12 @@
|
|
|
5
5
|
* - state(): create reactive state
|
|
6
6
|
* - bindDom(): zero-runtime DOM binding
|
|
7
7
|
* - effect(): reactive effect with automatic dependency tracking
|
|
8
|
+
* - withReadObserver(): advanced API for custom reactive primitives
|
|
8
9
|
*
|
|
9
10
|
* Usage:
|
|
10
11
|
* import { state, bindDom, effect } from "lume-js";
|
|
11
12
|
*/
|
|
12
13
|
|
|
13
|
-
export { state } from "./core/state.js";
|
|
14
|
+
export { state, withReadObserver } from "./core/state.js";
|
|
14
15
|
export { bindDom } from "./core/bindDom.js";
|
|
15
16
|
export { effect } from "./core/effect.js";
|