lume-js 2.0.1 → 2.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/README.md +4 -4
- package/dist/addons.min.mjs +1 -1
- package/dist/addons.mjs +50 -1
- package/dist/addons.mjs.map +1 -1
- package/dist/lume.global.js +1 -1
- package/dist/lume.global.js.map +1 -1
- package/package.json +2 -1
- package/src/addons/repeat.js +61 -3
package/README.md
CHANGED
|
@@ -4,13 +4,13 @@
|
|
|
4
4
|
|
|
5
5
|
Minimal reactive state management using only standard JavaScript and HTML. No custom syntax, no build step required, no framework lock-in.
|
|
6
6
|
|
|
7
|
-
> **Current Release:** v2.0
|
|
7
|
+
> **Current Release:** v2.1.0
|
|
8
8
|
> Install: `npm install lume-js`
|
|
9
|
-
> Bundle size: ~2.44KB gzipped |
|
|
9
|
+
> Bundle size: ~2.44KB gzipped | 339 tests passing
|
|
10
10
|
|
|
11
11
|
[](LICENSE)
|
|
12
|
-
[](package.json)
|
|
13
|
+
[](tests/)
|
|
14
14
|
[](scripts/check-size.js)
|
|
15
15
|
|
|
16
16
|
## Why Lume.js?
|
package/dist/addons.min.mjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
function e(e,...t){void 0!==console&&"function"==typeof console.warn&&console.warn(e,...t)}function t(e,...t){void 0!==console&&"function"==typeof console.error&&console.error(e,...t)}const o=new Set;function n(e,t){o.add(e);try{return t()}finally{o.delete(e)}}let r=null;function c(e,o){if("function"!=typeof e)throw Error("effect() requires a function");const c=[];let s=!1;const i=()=>{if(!s){s=!0;try{e()}catch(e){throw t("[Lume.js effect] Error in effect:",e),e}finally{s=!1}}};if(Array.isArray(o)){for(const e of o)if(Array.isArray(e)&&e.length>=2){const[t,...o]=e;if(t&&"function"==typeof t.$subscribe)for(const e of o){let o=!0;const n=t.$subscribe(e,()=>{o?o=!1:i()});c.push(n)}}i()}else{const o=()=>{if(s)return;const i=c.splice(0),l={fn:e,cleanups:c,execute:o,tracking:{}},
|
|
1
|
+
function e(e,...t){void 0!==console&&"function"==typeof console.warn&&console.warn(e,...t)}function t(e,...t){void 0!==console&&"function"==typeof console.error&&console.error(e,...t)}const o=new Set;function n(e,t){o.add(e);try{return t()}finally{o.delete(e)}}let r=null;function c(e,o){if("function"!=typeof e)throw Error("effect() requires a function");const c=[];let s=!1;const i=()=>{if(!s){s=!0;try{e()}catch(e){throw t("[Lume.js effect] Error in effect:",e),e}finally{s=!1}}};if(Array.isArray(o)){for(const e of o)if(Array.isArray(e)&&e.length>=2){const[t,...o]=e;if(t&&"function"==typeof t.$subscribe)for(const e of o){let o=!0;const n=t.$subscribe(e,()=>{o?o=!1:i()});c.push(n)}}i()}else{const o=()=>{if(s)return;const i=c.splice(0),l={fn:e,cleanups:c,execute:o,tracking:{}},f=r;r=l,s=!0;try{n((e,t,o)=>{r===l&&(l.tracking[t]||(l.tracking[t]=!0,l.cleanups.push(o(t,l.execute))))},e)}catch(e){throw c.length=0,c.push(...i),t("[Lume.js effect] Error in effect:",e),e}finally{r=f,s=!1}if(c.length>0)for(const e of i)e();else c.push(...i)};o()}return()=>{for(;c.length;)c.pop()()}}function s(e){if("function"!=typeof e)throw Error("computed() requires a function");let o,n=!1,r=!1,s=!1;const i=[],l=c(()=>{if(!r&&!s){r=!0;try{const t=e();n&&Object.is(t,o)||(o=t,n=!0,i.forEach(e=>e(o)))}catch(e){t("[Lume.js computed] Error in computation:",e),n&&void 0===o||(o=void 0,n=!0,i.forEach(e=>e(o)))}finally{queueMicrotask(()=>{s||(r=!1)})}}});return{get value(){if(!n)throw Error("Computed value accessed before initialization");return o},subscribe(e){if("function"!=typeof e)throw Error("subscribe() requires a function");return i.push(e),n&&e(o),()=>{const t=i.indexOf(e);t>-1&&i.splice(t,1)}},dispose(){s=!0,l(),i.length=0,n=!1,r=!1}}}function i(e,t,o){if(!e.$subscribe)throw Error("store must be created with state()");return e.$subscribe(t,o)}function l(e){const t=document.activeElement;if(!e.contains(t))return null;let o=null,n=null;return"INPUT"!==t.tagName&&"TEXTAREA"!==t.tagName||(o=t.selectionStart,n=t.selectionEnd),()=>{document.body.contains(t)&&(t.focus(),null!==o&&null!==n&&t.setSelectionRange(o,n))}}function f(e,t={}){const{isReorder:o=!1}=t,n=e.scrollTop;if(0===n)return()=>{e.scrollTop=0};let r=null,c=0;if(!o){const t=e.getBoundingClientRect();for(let o=e.firstElementChild;o;o=o.nextElementSibling){const e=o.getBoundingClientRect();if(e.bottom>t.top){r=o,c=e.top-t.top;break}}}return()=>{if(r&&document.body.contains(r)){const t=r.getBoundingClientRect(),o=e.getBoundingClientRect(),n=t.top-o.top-c;e.scrollTop=e.scrollTop+n}else e.scrollTop=n}}function u(o,n,r,c){const{key:s,render:i,create:u,update:a,remove:g,element:p="div",preserveFocus:d=l,preserveScroll:b=f}=c,h="string"==typeof o?document.querySelector(o):o;if(!h)return e(`[Lume.js] repeat(): container "${o}" not found`),()=>{};if("function"!=typeof s)throw Error("[Lume.js] repeat(): options.key must be a function");if("function"!=typeof i&&"function"!=typeof u)throw Error("[Lume.js] repeat(): options.render or options.create must be a function");const y=new Map,m=new Map,$=new Map,w=new Map,E=new Set;function j(){return"function"==typeof p?p():document.createElement(p)}function S(){const o=n[r];if(!Array.isArray(o))return void e(`[Lume.js] repeat(): store.${r} is not an array`);let c=!1;if(b&&y.size===o.length){c=!0;for(let e=0;e<o.length;e++)if(!y.has(s(o[e]))){c=!1;break}}E.clear();const l=[];for(let n=0;n<o.length;n++){const r=o[n],c=s(r);if(E.has(c)){e(`[Lume.js] repeat(): duplicate key "${c}"`);continue}E.add(c);let f=y.get(c);const g=!f;g&&(f=j(),y.set(c,f));try{if(g&&u){const e=u(r,f,n);"function"==typeof e&&w.set(c,e)}const e=m.get(c),t=$.get(c);a?e===r&&t===n||a(r,f,n,{isFirstRender:g}):i&&i(r,f,n),m.set(c,r),$.set(c,n)}catch(e){t(`[Lume.js] repeat(): error rendering key "${c}":`,e)}l.push(f)}!function(e,o,n){const r=document.body.contains(e),c=r&&d?d(e):null,s=r&&b?b(e,{isReorder:n}):null;(()=>{if(function(e,t){let o=e.firstChild;for(let n=0;n<t.length;n++){const r=t[n];o!==r?e.insertBefore(r,o):o=o.nextSibling}for(;o;){const t=o.nextSibling;e.removeChild(o),o=t}}(h,l),y.size!==E.size)for(const e of y.keys())if(!E.has(e)){const o=y.get(e),n=m.get(e),r=w.get(e);if("function"==typeof r)try{r()}catch(o){t(`[Lume.js] repeat(): cleanup error for key "${e}":`,o)}"function"==typeof g&&o&&g(n,o),y.delete(e),m.delete(e),$.delete(e),w.delete(e)}})(),c&&c(),s&&s()}(h,0,c)}let L;if("function"==typeof n.$subscribe)L=n.$subscribe(r,S);else{if("function"!=typeof n.subscribe)return S(),e("[Lume.js] repeat(): store is not reactive (no $subscribe or subscribe method)"),()=>{for(const[e,o]of y){const n=m.get(e),r=w.get(e);if("function"==typeof r)try{r()}catch(o){t(`[Lume.js] repeat(): cleanup error for key "${e}":`,o)}"function"==typeof g&&g(n,o)}h.replaceChildren(),y.clear(),m.clear(),$.clear(),w.clear(),E.clear()};{const e=n.subscribe(()=>S());S(),L="function"==typeof e?e:()=>{e?.unsubscribe?.()}}}return()=>{"function"==typeof L&&L();for(const[e,o]of y){const n=m.get(e),r=w.get(e);if("function"==typeof r)try{r()}catch(o){t(`[Lume.js] repeat(): cleanup error for key "${e}":`,o)}"function"==typeof g&&g(n,o)}h.replaceChildren(),y.clear(),m.clear(),$.clear(),w.clear(),E.clear()}}let a=!0,g=null;const p=new Map;function d(e){return null===g||("string"==typeof g?e.includes(g):!(g instanceof RegExp)||g.test(e))}function b(e){return p.has(e)||p.set(e,{gets:new Map,sets:new Map,notifies:new Map}),p.get(e)}function h(e,t,o){const n=b(e)[t];n.set(o,(n.get(o)||0)+1)}const y=100,m=97;function $(e){try{const t=JSON.stringify(e);return t.length>y?t.slice(0,m)+"...":t}catch{return e+""}}function w(e={}){const t=e.label??"store",o=(t,o)=>{const n=e[t];return void 0!==n?n:o};return{name:"debug:"+t,onInit:()=>{a&&console.log(`%c[${t}]%c initialized`,"color: #888; font-weight: bold","color: inherit")},onGet:(e,n)=>("string"==typeof e&&e.startsWith("$")||(h(t,"gets",e),a&&o("logGet",!1)&&d(e)&&console.log(`%c[${t}]%c GET %c${e}%c = ${$(n)}`,"color: #888; font-weight: bold","color: #4CAF50","color: #2196F3; font-weight: bold","color: inherit")),n),onSet:(e,n,r)=>("string"==typeof e&&e.startsWith("$")||(h(t,"sets",e),a&&o("logSet",!0)&&d(e)&&(console.log(`%c[${t}]%c SET %c${e}%c: ${$(r)} → ${$(n)}`,"color: #888; font-weight: bold","color: #FF9800","color: #2196F3; font-weight: bold","color: inherit"),o("trace",!1)&&console.trace(`%c[${t}] Stack trace for ${e}`,"color: #888"))),n),onSubscribe:e=>{a&&d(e)&&console.log(`%c[${t}]%c SUBSCRIBE %c${e}`,"color: #888; font-weight: bold","color: #9C27B0","color: #2196F3; font-weight: bold")},onNotify:(e,n)=>{"string"==typeof e&&e.startsWith("$")||(h(t,"notifies",e),a&&o("logNotify",!0)&&d(e)&&console.log(`%c[${t}]%c NOTIFY %c${e}%c = ${$(n)}`,"color: #888; font-weight: bold","color: #E91E63","color: #2196F3; font-weight: bold","color: inherit"))}}}const E={enable(){a=!0,console.log("%c[lume-debug]%c Logging enabled","color: #888; font-weight: bold","color: #4CAF50")},disable(){a=!1,console.log("%c[lume-debug]%c Logging disabled","color: #888; font-weight: bold","color: #F44336")},isEnabled:()=>a,filter(e){g=e,console.log(null===e?"%c[lume-debug]%c Filter cleared":"%c[lume-debug]%c Filter set: "+e,"color: #888; font-weight: bold","color: inherit")},getFilter:()=>g,stats(){const e={};for(const[t,o]of p)e[t]={gets:Object.fromEntries(o.gets),sets:Object.fromEntries(o.sets),notifies:Object.fromEntries(o.notifies)};return e},logStats(){const e=this.stats();if(0===Object.keys(e).length)return console.log("%c[lume-debug]%c No stats collected yet","color: #888; font-weight: bold","color: inherit"),e;console.group("%c[lume-debug] Statistics","color: #888; font-weight: bold");for(const[t,o]of Object.entries(e)){console.group("%c"+t,"color: #2196F3; font-weight: bold");const e=[],n=new Set([...Object.keys(o.gets),...Object.keys(o.sets),...Object.keys(o.notifies)]);for(const t of n)e.push({key:t,gets:o.gets[t]||0,sets:o.sets[t]||0,notifies:o.notifies[t]||0});e.length>0&&console.table(e),console.groupEnd()}return console.groupEnd(),e},resetStats(){p.clear(),console.log("%c[lume-debug]%c Stats reset","color: #888; font-weight: bold","color: inherit")}};function j(e,o=[]){if(!o.length)return e;for(const e of o)try{e.onInit?.()}catch(o){t(`[Lume.js] Plugin "${e.name}" error in onInit:`,o)}const n=new Map;let r;return"function"==typeof e.$beforeFlush&&(r=e.$beforeFlush(function(){for(const[e,r]of n)for(const n of o)try{n.onNotify?.(e,r)}catch(e){t(`[Lume.js] Plugin "${n.name}" error in onNotify:`,e)}n.clear()})),new Proxy(e,{get(e,c){if("$dispose"===c)return()=>{r&&r(),n.clear()};if("string"==typeof c&&c.startsWith("$")){const n=e[c];return"$subscribe"===c&&"function"==typeof n?(e,r)=>{for(const n of o)try{n.onSubscribe?.(e)}catch(e){t(`[Lume.js] Plugin "${n.name}" error in onSubscribe:`,e)}return n(e,r)}:n}let s=e[c];for(const e of o)try{const t=e.onGet?.(c,s);void 0!==t&&(s=t)}catch(o){t(`[Lume.js] Plugin "${e.name}" error in onGet:`,o)}return s},set(e,r,c){const s=e[r];let i=c;for(const e of o)try{const t=e.onSet?.(r,i,s);void 0!==t&&(i=t)}catch(o){t(`[Lume.js] Plugin "${e.name}" error in onSet:`,o)}return Object.is(i,s)||n.set(r,i),e[r]=i,!0}})}function S(){const e=[];return{add(t){"function"==typeof t&&e.push(t)},dispose(){for(;e.length;){const t=e.pop();try{t()}catch(e){}}}}}function L(e="#__LUME_DATA__"){const t="undefined"!=typeof document?document.querySelector(e):null;if(!t)return{};try{return JSON.parse(t.textContent)}catch{return{}}}function k(e){return!(!e||"object"!=typeof e||"function"!=typeof e.$subscribe)}export{s as computed,S as createCleanupGroup,w as createDebugPlugin,E as debug,l as defaultFocusPreservation,f as defaultScrollPreservation,L as hydrateState,k as isReactive,u as repeat,i as watch,j as withPlugins};
|
package/dist/addons.mjs
CHANGED
|
@@ -140,6 +140,7 @@ function repeat(container, store, arrayKey, options) {
|
|
|
140
140
|
render,
|
|
141
141
|
create,
|
|
142
142
|
update,
|
|
143
|
+
remove,
|
|
143
144
|
element = "div",
|
|
144
145
|
preserveFocus = defaultFocusPreservation,
|
|
145
146
|
preserveScroll = defaultScrollPreservation
|
|
@@ -159,6 +160,7 @@ function repeat(container, store, arrayKey, options) {
|
|
|
159
160
|
const elementsByKey = /* @__PURE__ */ new Map();
|
|
160
161
|
const prevItemsByKey = /* @__PURE__ */ new Map();
|
|
161
162
|
const prevIndexByKey = /* @__PURE__ */ new Map();
|
|
163
|
+
const cleanupByKey = /* @__PURE__ */ new Map();
|
|
162
164
|
const seenKeys = /* @__PURE__ */ new Set();
|
|
163
165
|
function createElement() {
|
|
164
166
|
return typeof element === "function" ? element() : document.createElement(element);
|
|
@@ -221,7 +223,10 @@ function repeat(container, store, arrayKey, options) {
|
|
|
221
223
|
}
|
|
222
224
|
try {
|
|
223
225
|
if (isFirstRender && create) {
|
|
224
|
-
create(item, el, i);
|
|
226
|
+
const cleanup = create(item, el, i);
|
|
227
|
+
if (typeof cleanup === "function") {
|
|
228
|
+
cleanupByKey.set(k, cleanup);
|
|
229
|
+
}
|
|
225
230
|
}
|
|
226
231
|
const prevItem = prevItemsByKey.get(k);
|
|
227
232
|
const prevIndex = prevIndexByKey.get(k);
|
|
@@ -244,9 +249,23 @@ function repeat(container, store, arrayKey, options) {
|
|
|
244
249
|
if (elementsByKey.size !== seenKeys.size) {
|
|
245
250
|
for (const k of elementsByKey.keys()) {
|
|
246
251
|
if (!seenKeys.has(k)) {
|
|
252
|
+
const el = elementsByKey.get(k);
|
|
253
|
+
const prevItem = prevItemsByKey.get(k);
|
|
254
|
+
const cleanup = cleanupByKey.get(k);
|
|
255
|
+
if (typeof cleanup === "function") {
|
|
256
|
+
try {
|
|
257
|
+
cleanup();
|
|
258
|
+
} catch (err) {
|
|
259
|
+
logError(`[Lume.js] repeat(): cleanup error for key "${k}":`, err);
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
if (typeof remove === "function" && el) {
|
|
263
|
+
remove(prevItem, el);
|
|
264
|
+
}
|
|
247
265
|
elementsByKey.delete(k);
|
|
248
266
|
prevItemsByKey.delete(k);
|
|
249
267
|
prevIndexByKey.delete(k);
|
|
268
|
+
cleanupByKey.delete(k);
|
|
250
269
|
}
|
|
251
270
|
}
|
|
252
271
|
}
|
|
@@ -265,10 +284,25 @@ function repeat(container, store, arrayKey, options) {
|
|
|
265
284
|
updateList();
|
|
266
285
|
logWarn("[Lume.js] repeat(): store is not reactive (no $subscribe or subscribe method)");
|
|
267
286
|
return () => {
|
|
287
|
+
for (const [k, el] of elementsByKey) {
|
|
288
|
+
const prevItem = prevItemsByKey.get(k);
|
|
289
|
+
const cleanup = cleanupByKey.get(k);
|
|
290
|
+
if (typeof cleanup === "function") {
|
|
291
|
+
try {
|
|
292
|
+
cleanup();
|
|
293
|
+
} catch (err) {
|
|
294
|
+
logError(`[Lume.js] repeat(): cleanup error for key "${k}":`, err);
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
if (typeof remove === "function") {
|
|
298
|
+
remove(prevItem, el);
|
|
299
|
+
}
|
|
300
|
+
}
|
|
268
301
|
containerEl.replaceChildren();
|
|
269
302
|
elementsByKey.clear();
|
|
270
303
|
prevItemsByKey.clear();
|
|
271
304
|
prevIndexByKey.clear();
|
|
305
|
+
cleanupByKey.clear();
|
|
272
306
|
seenKeys.clear();
|
|
273
307
|
};
|
|
274
308
|
}
|
|
@@ -276,10 +310,25 @@ function repeat(container, store, arrayKey, options) {
|
|
|
276
310
|
if (typeof unsubscribe === "function") {
|
|
277
311
|
unsubscribe();
|
|
278
312
|
}
|
|
313
|
+
for (const [k, el] of elementsByKey) {
|
|
314
|
+
const prevItem = prevItemsByKey.get(k);
|
|
315
|
+
const cleanup = cleanupByKey.get(k);
|
|
316
|
+
if (typeof cleanup === "function") {
|
|
317
|
+
try {
|
|
318
|
+
cleanup();
|
|
319
|
+
} catch (err) {
|
|
320
|
+
logError(`[Lume.js] repeat(): cleanup error for key "${k}":`, err);
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
if (typeof remove === "function") {
|
|
324
|
+
remove(prevItem, el);
|
|
325
|
+
}
|
|
326
|
+
}
|
|
279
327
|
containerEl.replaceChildren();
|
|
280
328
|
elementsByKey.clear();
|
|
281
329
|
prevItemsByKey.clear();
|
|
282
330
|
prevIndexByKey.clear();
|
|
331
|
+
cleanupByKey.clear();
|
|
283
332
|
seenKeys.clear();
|
|
284
333
|
};
|
|
285
334
|
}
|
package/dist/addons.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"addons.mjs","sources":["../src/addons/computed.js","../src/addons/watch.js","../src/addons/repeat.js","../src/addons/debug.js","../src/addons/withPlugins.js","../src/addons/cleanupGroup.js","../src/addons/hydrateState.js","../src/addons/index.js"],"sourcesContent":["/**\n * Lume-JS Computed Addon\n * \n * Creates computed values that automatically update when dependencies change.\n * Uses core effect() for automatic dependency tracking.\n * \n * Usage:\n * import { computed } from \"lume-js/addons/computed\";\n * \n * const doubled = computed(() => store.count * 2);\n * console.log(doubled.value); // Auto-updates when store.count changes\n * \n * Features:\n * - Automatic dependency tracking (no manual recompute)\n * - Cached values (only recomputes when dependencies change)\n * - Subscribe to changes\n * - Cleanup with dispose()\n * \n * @module addons/computed\n */\n\nimport { effect } from '../core/effect.js';\nimport { logError } from '../utils/log.js';\n\n/**\n * Creates a computed value with automatic dependency tracking\n * \n * The computation function runs immediately and tracks which state\n * properties are accessed. When any dependency changes, the value\n * is automatically recomputed.\n *\n * ⚠️ Circular self-mutations are automatically suppressed. If a computed\n * mutates a state property it depends on, the flush triggered by that\n * mutation is skipped to prevent an infinite microtask loop.\n *\n * @param {function} fn - Function that computes the value\n * @returns {object} Object with .value property and methods\n * \n * @example\n * const store = state({ count: 5 });\n * \n * const doubled = computed(() => store.count * 2);\n * console.log(doubled.value); // 10\n * \n * store.count = 10;\n * // After microtask:\n * console.log(doubled.value); // 20 (auto-updated)\n * \n * @example\n * // Subscribe to changes\n * const unsub = doubled.subscribe(value => {\n * console.log('Doubled changed to:', value);\n * });\n * \n * @example\n * // Cleanup\n * doubled.dispose();\n */\nexport function computed(fn) {\n if (typeof fn !== 'function') {\n throw new Error('computed() requires a function');\n }\n\n let cachedValue;\n let isInitialized = false;\n let isInComputation = false;\n let disposed = false;\n const subscribers = [];\n\n // Use effect to automatically track dependencies\n const cleanupEffect = effect(() => {\n // Skip re-entry from a flush triggered by our own synchronous mutation.\n // The mutation inside fn() queues a microtask flush; we stay flagged\n // until a subsequent microtask clears it, so that flush is dropped.\n if (isInComputation || disposed) return;\n\n isInComputation = true;\n\n try {\n const newValue = fn();\n\n // Check if value actually changed - Object.is() handles NaN and -0\n if (!isInitialized || !Object.is(newValue, cachedValue)) {\n cachedValue = newValue;\n isInitialized = true;\n\n // Notify all subscribers\n subscribers.forEach(callback => callback(cachedValue));\n }\n } catch (error) {\n logError('[Lume.js computed] Error in computation:', error);\n // Set to undefined on error, mark as initialized\n if (!isInitialized || cachedValue !== undefined) {\n cachedValue = undefined;\n isInitialized = true;\n\n // Notify subscribers of error state\n subscribers.forEach(callback => callback(cachedValue));\n }\n } finally {\n // Defer clearing the flag so any flush microtask queued by fn()\n // sees it still set and skips re-entry.\n queueMicrotask(() => {\n if (!disposed) {\n isInComputation = false;\n }\n });\n }\n });\n\n return {\n /**\n * Get the current computed value\n */\n get value() {\n if (!isInitialized) {\n throw new Error('Computed value accessed before initialization');\n }\n return cachedValue;\n },\n\n /**\n * Subscribe to changes in computed value\n * \n * @param {function} callback - Called when value changes\n * @returns {function} Unsubscribe function\n */\n subscribe(callback) {\n if (typeof callback !== 'function') {\n throw new Error('subscribe() requires a function');\n }\n\n subscribers.push(callback);\n\n // Call immediately with current value\n if (isInitialized) {\n callback(cachedValue);\n }\n\n // Return unsubscribe function\n return () => {\n const index = subscribers.indexOf(callback);\n if (index > -1) {\n subscribers.splice(index, 1);\n }\n };\n },\n\n /**\n * Clean up computed value and stop tracking\n */\n dispose() {\n disposed = true;\n cleanupEffect();\n subscribers.length = 0;\n isInitialized = false;\n isInComputation = false;\n }\n };\n}","/**\n * watch - observes changes to a state key and triggers callback\n * @param {Object} store - reactive store created with state()\n * @param {string} key - key in store to watch\n * @param {Function} callback - called with new value\n * @returns {Function} unsubscribe function\n */\nexport function watch(store, key, callback) {\n if (!store.$subscribe) {\n throw new Error(\"store must be created with state()\");\n }\n return store.$subscribe(key, callback);\n}","/**\n * Lume-JS List Rendering (Addon)\n *\n * Renders lists with automatic subscription and element reuse by key.\n * \n * Core guarantees:\n * Element reuse by key (same DOM nodes, not recreated)\n * Minimal DOM operations (only updates what changed)\n * Memory efficiency (cleanup on remove)\n * \n * Default behavior (can be disabled/customized):\n * ✅ Focus preservation (maintains activeElement and selection)\n * ✅ Scroll preservation (intelligent positioning for add/remove/reorder)\n * \n * Philosophy: No artificial limitations\n * - All preservation logic is overridable via options\n * - Set to null/false to disable, or provide custom functions\n * - Export utilities so you can wrap/extend them\n *\n * ⚠️ IMPORTANT: Arrays must be updated immutably!\n * store.items.push(x) // ❌ Won't trigger update\n * store.items = [...items] // ✅ Triggers update\n * \n * ═══════════════════════════════════════════════════════════════════════\n * PATTERN 1: Simple (render only) - for simple cases or backward compat\n * ═══════════════════════════════════════════════════════════════════════\n * \n * repeat('#list', store, 'todos', {\n * key: todo => todo.id,\n * render: (todo, el) => {\n * el.textContent = todo.name; // Called on every update\n * }\n * });\n *\n * ═══════════════════════════════════════════════════════════════════════\n * PATTERN 2: Clean separation (create + update) - recommended\n * ═══════════════════════════════════════════════════════════════════════\n * \n * repeat('#list', store, 'todos', {\n * key: todo => todo.id,\n * create: (todo, el) => {\n * // Called ONCE when element is created - build DOM structure\n * const nameSpan = document.createElement('span');\n * nameSpan.className = 'name';\n * el.appendChild(nameSpan);\n * const btn = document.createElement('button');\n * btn.textContent = 'Delete';\n * btn.onclick = () => deleteTodo(todo.id);\n * el.appendChild(btn);\n * },\n * update: (todo, el, index, { isFirstRender }) => {\n * // Called on every update - bind data\n * // isFirstRender = true on initial render, false on subsequent\n * // Skipped if same object reference (optimization)\n * el.querySelector('.name').textContent = todo.name;\n * }\n * });\n *\n * ═══════════════════════════════════════════════════════════════════════\n * ADVANCED: Custom preservation strategies\n * ═══════════════════════════════════════════════════════════════════════\n * \n * import { defaultFocusPreservation, defaultScrollPreservation } from \"lume-js/addons\";\n * \n * repeat('#list', store, 'items', {\n * key: item => item.id,\n * create: (item, el) => { ... },\n * update: (item, el) => { ... },\n * preserveFocus: null, // disable focus preservation\n * preserveScroll: (container, context) => {\n * const restore = defaultScrollPreservation(container, context);\n * return () => { restore(); console.log('Scroll restored!'); };\n * }\n * });\n */\nimport { logWarn, logError } from '../utils/log.js';\n\n/**\n * Default focus preservation strategy\n * Saves activeElement and selection state before DOM updates\n * \n * @param {HTMLElement} container - The list container\n * @returns {Function|null} Restore function, or null if nothing to restore\n */\nexport function defaultFocusPreservation(container) {\n const activeEl = document.activeElement;\n const shouldRestore = container.contains(activeEl);\n\n if (!shouldRestore) return null;\n\n let selectionStart = null;\n let selectionEnd = null;\n\n if (activeEl.tagName === 'INPUT' || activeEl.tagName === 'TEXTAREA') {\n selectionStart = activeEl.selectionStart;\n selectionEnd = activeEl.selectionEnd;\n }\n\n return () => {\n if (document.body.contains(activeEl)) {\n activeEl.focus();\n if (selectionStart !== null && selectionEnd !== null) {\n activeEl.setSelectionRange(selectionStart, selectionEnd);\n }\n }\n };\n}\n\n/**\n * Default scroll preservation strategy\n * Uses anchor-based preservation for add/remove, pixel position for reorder\n * \n * @param {HTMLElement} container - The list container\n * @param {Object} context - Additional context\n * @param {boolean} context.isReorder - Whether this is a reorder operation\n * @returns {Function} Restore function\n */\nexport function defaultScrollPreservation(container, context = {}) {\n const { isReorder = false } = context;\n const scrollTop = container.scrollTop;\n\n // Early return if no scroll\n if (scrollTop === 0) {\n return () => { container.scrollTop = 0; };\n }\n\n let anchorElement = null;\n let anchorOffset = 0;\n\n // Only use anchor-based preservation for add/remove, not reorder\n if (!isReorder) {\n const containerRect = container.getBoundingClientRect();\n // Avoid Array.from - iterate children directly\n for (let child = container.firstElementChild; child; child = child.nextElementSibling) {\n const rect = child.getBoundingClientRect();\n\n if (rect.bottom > containerRect.top) {\n anchorElement = child;\n anchorOffset = rect.top - containerRect.top;\n break;\n }\n }\n }\n\n return () => {\n if (anchorElement && document.body.contains(anchorElement)) {\n const newRect = anchorElement.getBoundingClientRect();\n const containerRect = container.getBoundingClientRect();\n const currentOffset = newRect.top - containerRect.top;\n const scrollAdjustment = currentOffset - anchorOffset;\n\n container.scrollTop = container.scrollTop + scrollAdjustment;\n } else {\n container.scrollTop = scrollTop;\n }\n };\n}\n\n/**\n * Efficiently render a list with element reuse\n * \n * @param {string|HTMLElement} container - Container element or selector\n * @param {Object} store - Reactive state object\n * @param {string} arrayKey - Key in store containing the array\n * @param {Object} options - Configuration\n * @param {Function} options.key - Function to extract unique key: (item) => key\n * @param {Function} [options.render] - Function to render item (called for all items): (item, element, index) => void\n * @param {Function} [options.create] - Function for new elements only: (item, element, index) => void\n * @param {Function} [options.update] - Function for data binding: (item, element, index, { isFirstRender }) => void. Skipped if same item reference AND same index.\n * @param {string|Function} [options.element='div'] - Element tag name or factory function\n * @param {Function|null} [options.preserveFocus=defaultFocusPreservation] - Focus preservation strategy (null to disable)\n * @param {Function|null} [options.preserveScroll=defaultScrollPreservation] - Scroll preservation strategy (null to disable)\n * @returns {Function} Cleanup function\n */\n\nexport function repeat(container, store, arrayKey, options) {\n const {\n key,\n render,\n create,\n update,\n element = 'div',\n preserveFocus = defaultFocusPreservation,\n preserveScroll = defaultScrollPreservation\n } = options;\n\n // Resolve container\n const containerEl =\n typeof container === 'string'\n ? document.querySelector(container)\n : container;\n\n if (!containerEl) {\n logWarn(`[Lume.js] repeat(): container \"${container}\" not found`);\n return () => { };\n }\n\n if (typeof key !== 'function') {\n throw new Error('[Lume.js] repeat(): options.key must be a function');\n }\n\n if (typeof render !== 'function' && typeof create !== 'function') {\n throw new Error('[Lume.js] repeat(): options.render or options.create must be a function');\n }\n\n // key -> HTMLElement\n const elementsByKey = new Map();\n // key -> previous item (for reference comparison)\n const prevItemsByKey = new Map();\n // key -> previous index (for reorder detection)\n const prevIndexByKey = new Map();\n const seenKeys = new Set();\n\n function createElement() {\n return typeof element === 'function'\n ? element()\n : document.createElement(element);\n }\n\n function reconcileDOM(container, nextEls) {\n let ptr = container.firstChild;\n\n for (let i = 0; i < nextEls.length; i++) {\n const desired = nextEls[i];\n\n if (ptr === desired) {\n ptr = ptr.nextSibling;\n continue;\n }\n\n container.insertBefore(desired, ptr);\n }\n\n // Remove leftover children not in nextEls\n while (ptr) {\n const next = ptr.nextSibling;\n container.removeChild(ptr);\n ptr = next;\n }\n }\n\n function applyPreservation(container, fn, isReorder) {\n const shouldPreserve = document.body.contains(container);\n const restoreFocus = shouldPreserve && preserveFocus ? preserveFocus(container) : null;\n const restoreScroll = shouldPreserve && preserveScroll ? preserveScroll(container, { isReorder }) : null;\n\n fn();\n\n if (restoreFocus) restoreFocus();\n if (restoreScroll) restoreScroll();\n }\n\n function updateList() {\n const items = store[arrayKey];\n\n if (!Array.isArray(items)) {\n logWarn(`[Lume.js] repeat(): store.${arrayKey} is not an array`);\n return;\n }\n\n // Only compute isReorder if scroll preservation needs it.\n // Uses elementsByKey (previous state) and items directly — no Set allocations.\n let isReorder = false;\n if (preserveScroll && elementsByKey.size === items.length) {\n isReorder = true;\n for (let i = 0; i < items.length; i++) {\n if (!elementsByKey.has(key(items[i]))) { isReorder = false; break; }\n }\n }\n\n seenKeys.clear();\n const nextEls = [];\n\n // Build ordered list of DOM nodes (created or reused)\n for (let i = 0; i < items.length; i++) {\n const item = items[i];\n const k = key(item);\n\n if (seenKeys.has(k)) {\n logWarn(`[Lume.js] repeat(): duplicate key \"${k}\"`);\n continue;\n }\n seenKeys.add(k);\n\n let el = elementsByKey.get(k);\n const isFirstRender = !el;\n\n if (isFirstRender) {\n el = createElement();\n elementsByKey.set(k, el);\n }\n\n try {\n // Call create for new elements (DOM structure)\n if (isFirstRender && create) {\n create(item, el, i);\n }\n\n // Call update for data binding (new and existing elements)\n // Skip if same item reference AND same index (optimization)\n const prevItem = prevItemsByKey.get(k);\n const prevIndex = prevIndexByKey.get(k);\n if (update) {\n if (prevItem !== item || prevIndex !== i) {\n update(item, el, i, { isFirstRender });\n }\n } else if (render) {\n // Backward compatibility: render handles both create and update\n render(item, el, i);\n }\n\n // Store reference and index for next comparison\n prevItemsByKey.set(k, item);\n prevIndexByKey.set(k, i);\n\n } catch (err) {\n logError(`[Lume.js] repeat(): error rendering key \"${k}\":`, err);\n }\n\n nextEls.push(el);\n }\n\n applyPreservation(containerEl, () => {\n reconcileDOM(containerEl, nextEls);\n\n // Clean maps: remove keys not in seenKeys (new state)\n if (elementsByKey.size !== seenKeys.size) {\n for (const k of elementsByKey.keys()) {\n if (!seenKeys.has(k)) {\n elementsByKey.delete(k);\n prevItemsByKey.delete(k);\n prevIndexByKey.delete(k);\n }\n }\n }\n }, isReorder);\n }\n\n // Subscription — $subscribe calls updateList immediately (initial render),\n // so no separate updateList() call is needed for reactive stores.\n let unsubscribe;\n if (typeof store.$subscribe === 'function') {\n unsubscribe = store.$subscribe(arrayKey, updateList);\n } else if (typeof store.subscribe === 'function') {\n // Generic subscribe (e.g. computed) — subscribe first, then initial render\n const subResult = store.subscribe(() => updateList());\n updateList();\n // Normalize both function-style and object-style (RxJS Subscription) returns\n unsubscribe = typeof subResult === 'function'\n ? subResult\n : () => { subResult?.unsubscribe?.(); };\n } else {\n // Non-reactive store — render once and return cleanup\n updateList();\n logWarn('[Lume.js] repeat(): store is not reactive (no $subscribe or subscribe method)');\n return () => {\n containerEl.replaceChildren();\n elementsByKey.clear();\n prevItemsByKey.clear();\n prevIndexByKey.clear();\n seenKeys.clear();\n };\n }\n\n return () => {\n if (typeof unsubscribe === 'function') {\n unsubscribe();\n }\n // Clear DOM elements (replaceChildren is faster than loop)\n containerEl.replaceChildren();\n elementsByKey.clear();\n prevItemsByKey.clear();\n prevIndexByKey.clear();\n seenKeys.clear();\n };\n}\n","/**\n * Lume-JS Debug Addon\n * \n * Developer-friendly logging and inspection of reactive state operations.\n * Critical for adoption - hard to debug = hard to adopt.\n * \n * Usage:\n * import { state } from \"lume-js\";\n * import { withPlugins, createDebugPlugin, debug } from \"lume-js/addons\";\n * \n * const store = withPlugins(state({ count: 0 }), [createDebugPlugin({ label: 'myStore' })]);\n * \n * debug.enable(); // Enable logging\n * debug.filter('count'); // Only log 'count' key\n * debug.stats(); // Show statistics\n * \n * @module addons/debug\n */\n\n// Global debug state\nlet globalEnabled = true;\nlet globalFilter = null; // string, RegExp, or null\nconst stats = new Map(); // label -> { gets: Map, sets: Map, notifies: Map }\n\n/**\n * Check if a key matches the current filter\n * @param {string} key\n * @returns {boolean}\n */\nfunction matchesFilter(key) {\n if (globalFilter === null) return true;\n if (typeof globalFilter === 'string') {\n return key.includes(globalFilter);\n }\n if (globalFilter instanceof RegExp) {\n return globalFilter.test(key);\n }\n return true;\n}\n\n/**\n * Get or create stats entry for a label\n * @param {string} label\n * @returns {object}\n */\nfunction getStats(label) {\n if (!stats.has(label)) {\n stats.set(label, {\n gets: new Map(),\n sets: new Map(),\n notifies: new Map()\n });\n }\n return stats.get(label);\n}\n\n/**\n * Increment a stat counter\n * @param {string} label\n * @param {'gets'|'sets'|'notifies'} type\n * @param {string} key\n */\nfunction incrementStat(label, type, key) {\n const s = getStats(label);\n const map = s[type];\n map.set(key, (map.get(key) || 0) + 1);\n}\n\nconst MAX_LOG_LEN = 100;\nconst TRUNCATED_LEN = MAX_LOG_LEN - 3;\n\n/**\n * Format value for logging (truncate long values)\n * @param {any} value\n * @returns {string}\n */\nfunction formatValue(value) {\n try {\n const json = JSON.stringify(value);\n if (json.length > MAX_LOG_LEN) {\n return json.slice(0, TRUNCATED_LEN) + '...';\n }\n return json;\n } catch {\n return String(value);\n }\n}\n\n/**\n * Create a debug plugin instance for a reactive state store.\n * \n * @param {object} [options] - Configuration options\n * @param {string} [options.label='store'] - Label for log messages\n * @param {boolean} [options.logGet=false] - Log property reads (can be noisy)\n * @param {boolean} [options.logSet=true] - Log property writes\n * @param {boolean} [options.logNotify=true] - Log subscriber notifications\n * @param {boolean} [options.trace=false] - Show stack trace for SET operations\n * @returns {object} Plugin object for state()\n * \n * @example\n * const store = withPlugins(state({ count: 0 }), [createDebugPlugin({ label: 'counter' })]);\n * \n * @example\n * // With stack traces for debugging where state changes originate\n * const store = withPlugins(state({ count: 0 }), [createDebugPlugin({ label: 'counter', trace: true })]);\n */\nexport function createDebugPlugin(options = {}) {\n const label = options.label ?? 'store';\n\n // IMPORTANT: Do NOT destructure options here!\n // Options may contain getters for dynamic runtime toggling (e.g., from UI).\n // Destructuring would copy values once at creation time, breaking reactivity.\n // Use getOpt() helper to read options dynamically in each hook.\n const getOpt = (name, defaultVal) => {\n const val = options[name];\n return val !== undefined ? val : defaultVal;\n };\n\n return {\n name: `debug:${label}`,\n\n onInit: () => {\n if (globalEnabled) {\n console.log(`%c[${label}]%c initialized`, 'color: #888; font-weight: bold', 'color: inherit');\n }\n },\n\n onGet: (key, value) => {\n // Skip internal properties\n if (typeof key === 'string' && key.startsWith('$')) {\n return value;\n }\n\n incrementStat(label, 'gets', key);\n\n if (globalEnabled && getOpt('logGet', false) && matchesFilter(key)) {\n console.log(\n `%c[${label}]%c GET %c${key}%c = ${formatValue(value)}`,\n 'color: #888; font-weight: bold',\n 'color: #4CAF50',\n 'color: #2196F3; font-weight: bold',\n 'color: inherit'\n );\n }\n\n return value;\n },\n\n onSet: (key, newValue, oldValue) => {\n // Skip internal properties\n if (typeof key === 'string' && key.startsWith('$')) {\n return newValue;\n }\n\n incrementStat(label, 'sets', key);\n\n if (globalEnabled && getOpt('logSet', true) && matchesFilter(key)) {\n console.log(\n `%c[${label}]%c SET %c${key}%c: ${formatValue(oldValue)} → ${formatValue(newValue)}`,\n 'color: #888; font-weight: bold',\n 'color: #FF9800',\n 'color: #2196F3; font-weight: bold',\n 'color: inherit'\n );\n\n // Show stack trace if enabled (helps find where state changes originate)\n if (getOpt('trace', false)) {\n console.trace(`%c[${label}] Stack trace for ${key}`, 'color: #888');\n }\n }\n\n return newValue;\n },\n\n onSubscribe: (key) => {\n if (globalEnabled && matchesFilter(key)) {\n console.log(\n `%c[${label}]%c SUBSCRIBE %c${key}`,\n 'color: #888; font-weight: bold',\n 'color: #9C27B0',\n 'color: #2196F3; font-weight: bold'\n );\n }\n },\n\n onNotify: (key, value) => {\n // Skip internal properties\n if (typeof key === 'string' && key.startsWith('$')) {\n return;\n }\n\n incrementStat(label, 'notifies', key);\n\n if (globalEnabled && getOpt('logNotify', true) && matchesFilter(key)) {\n console.log(\n `%c[${label}]%c NOTIFY %c${key}%c = ${formatValue(value)}`,\n 'color: #888; font-weight: bold',\n 'color: #E91E63',\n 'color: #2196F3; font-weight: bold',\n 'color: inherit'\n );\n }\n }\n };\n}\n\n/**\n * Global debug controls\n */\nexport const debug = {\n /**\n * Enable debug logging globally\n */\n enable() {\n globalEnabled = true;\n console.log('%c[lume-debug]%c Logging enabled', 'color: #888; font-weight: bold', 'color: #4CAF50');\n },\n\n /**\n * Disable debug logging globally\n */\n disable() {\n globalEnabled = false;\n console.log('%c[lume-debug]%c Logging disabled', 'color: #888; font-weight: bold', 'color: #F44336');\n },\n\n /**\n * Check if debug logging is currently enabled\n * @returns {boolean}\n */\n isEnabled() {\n return globalEnabled;\n },\n\n /**\n * Filter logs by key pattern\n * @param {string|RegExp|null} pattern - Pattern to match, or null to clear filter\n */\n filter(pattern) {\n globalFilter = pattern;\n if (pattern === null) {\n console.log('%c[lume-debug]%c Filter cleared', 'color: #888; font-weight: bold', 'color: inherit');\n } else {\n console.log(`%c[lume-debug]%c Filter set: ${pattern}`, 'color: #888; font-weight: bold', 'color: inherit');\n }\n },\n\n /**\n * Get current filter pattern\n * @returns {string|RegExp|null}\n */\n getFilter() {\n return globalFilter;\n },\n\n /**\n * Get statistics data (silent - no console output)\n * Use logStats() if you want to see stats in console.\n * @returns {object} Stats object for programmatic access\n */\n stats() {\n const result = {};\n\n for (const [label, data] of stats) {\n result[label] = {\n gets: Object.fromEntries(data.gets),\n sets: Object.fromEntries(data.sets),\n notifies: Object.fromEntries(data.notifies)\n };\n }\n\n return result;\n },\n\n /**\n * Log statistics summary to console (with formatting)\n * @returns {object} Stats object for programmatic access\n */\n logStats() {\n const result = this.stats();\n\n if (Object.keys(result).length === 0) {\n console.log('%c[lume-debug]%c No stats collected yet', 'color: #888; font-weight: bold', 'color: inherit');\n return result;\n }\n\n console.group('%c[lume-debug] Statistics', 'color: #888; font-weight: bold');\n\n for (const [label, data] of Object.entries(result)) {\n console.group(`%c${label}`, 'color: #2196F3; font-weight: bold');\n\n // Use console.table for better formatted output\n const tableData = [];\n const allKeys = new Set([\n ...Object.keys(data.gets),\n ...Object.keys(data.sets),\n ...Object.keys(data.notifies)\n ]);\n\n for (const key of allKeys) {\n tableData.push({\n key,\n gets: data.gets[key] || 0,\n sets: data.sets[key] || 0,\n notifies: data.notifies[key] || 0\n });\n }\n\n if (tableData.length > 0) {\n console.table(tableData);\n }\n\n console.groupEnd();\n }\n\n console.groupEnd();\n\n return result;\n },\n\n /**\n * Reset all collected statistics\n */\n resetStats() {\n stats.clear();\n console.log('%c[lume-debug]%c Stats reset', 'color: #888; font-weight: bold', 'color: inherit');\n }\n};\n","/**\n * Lume-JS withPlugins Addon\n *\n * Wraps a reactive state proxy with a plugin layer that intercepts\n * get/set/notify/subscribe operations via plugin hooks.\n *\n * Only stores that opt into debugging or custom behaviors need this.\n * Core state() is not aware of plugins.\n *\n * Usage:\n * import { state } from \"lume-js\";\n * import { withPlugins, createDebugPlugin } from \"lume-js/addons\";\n *\n * const store = withPlugins(state({ count: 0 }), [createDebugPlugin({ label: 'counter' })]);\n */\n\n/**\n * Wrap a reactive state proxy with plugin hooks.\n *\n * Plugin hooks (all optional):\n * onInit() — called once at wrap time\n * onGet(key, value) → value|void — intercept/transform reads\n * onSet(key, newVal, oldVal) → val|void — intercept/transform writes\n * onNotify(key, value) — called before subscribers are notified\n * onSubscribe(key) — called when $subscribe is invoked\n *\n * @param {object} store - A reactive proxy from state()\n * @param {Array<object>} plugins - Array of plugin objects\n * @returns {Proxy} A new proxy wrapping the store with plugin behavior\n */\nimport { logError } from '../utils/log.js';\n\nexport function withPlugins(store, plugins = []) {\n if (!plugins.length) return store;\n\n // Call onInit hooks once at wrap time\n for (const p of plugins) {\n try {\n p.onInit?.();\n } catch (e) {\n logError(`[Lume.js] Plugin \"${p.name}\" error in onInit:`, e);\n }\n }\n\n // Track pending notifications for onNotify hooks.\n // Instead of a separate microtask, we hook into the underlying state's\n // flush via $beforeFlush so onNotify and subscribers share one microtask.\n const pendingNotifications = new Map();\n\n function runNotifyHooks() {\n for (const [key, value] of pendingNotifications) {\n for (const p of plugins) {\n try {\n p.onNotify?.(key, value);\n } catch (e) {\n logError(`[Lume.js] Plugin \"${p.name}\" error in onNotify:`, e);\n }\n }\n }\n pendingNotifications.clear();\n }\n\n // Register once on the underlying state; capture unsubscribe for cleanup.\n let flushUnsub;\n if (typeof store.$beforeFlush === 'function') {\n flushUnsub = store.$beforeFlush(runNotifyHooks);\n }\n\n return new Proxy(store, {\n get(target, key) {\n // $dispose — remove the beforeFlush hook and clear pending state\n if (key === '$dispose') {\n return () => {\n if (flushUnsub) flushUnsub();\n pendingNotifications.clear();\n };\n }\n\n // Pass $-prefixed meta methods through without interception\n if (typeof key === 'string' && key.startsWith('$')) {\n const method = target[key];\n if (key === '$subscribe' && typeof method === 'function') {\n // Wrap $subscribe to call onSubscribe hooks\n return (subKey, fn) => {\n for (const p of plugins) {\n try {\n p.onSubscribe?.(subKey);\n } catch (e) {\n logError(`[Lume.js] Plugin \"${p.name}\" error in onSubscribe:`, e);\n }\n }\n return method(subKey, fn);\n };\n }\n return method;\n }\n\n let value = target[key];\n\n // onGet chain\n for (const p of plugins) {\n try {\n const r = p.onGet?.(key, value);\n if (r !== undefined) value = r;\n } catch (e) {\n logError(`[Lume.js] Plugin \"${p.name}\" error in onGet:`, e);\n }\n }\n\n return value;\n },\n\n set(target, key, value) {\n const oldValue = target[key];\n let newValue = value;\n\n // onSet chain\n for (const p of plugins) {\n try {\n const r = p.onSet?.(key, newValue, oldValue);\n if (r !== undefined) newValue = r;\n } catch (e) {\n logError(`[Lume.js] Plugin \"${p.name}\" error in onSet:`, e);\n }\n }\n\n // Only queue onNotify if the value actually changed after plugin chain\n if (!Object.is(newValue, oldValue)) {\n pendingNotifications.set(key, newValue);\n }\n\n target[key] = newValue;\n return true;\n }\n });\n}\n","/**\n * Creates a cleanup group that can collect and dispose multiple\n * cleanup/unsubscribe functions at once.\n *\n * @returns {CleanupGroup}\n *\n * @example\n * ```js\n * import { createCleanupGroup } from 'lume-js/addons';\n *\n * const group = createCleanupGroup();\n * group.add(bindDom(root, store));\n * group.add(effect(() => { ... }));\n * group.add(store.$subscribe('key', fn));\n *\n * // Dispose everything at once\n * group.dispose();\n * ```\n */\nexport function createCleanupGroup() {\n const cleanups = [];\n\n return {\n /**\n * Add a cleanup function to the group.\n * @param {Function} fn - Cleanup/unsubscribe function\n */\n add(fn) {\n if (typeof fn === 'function') {\n cleanups.push(fn);\n }\n },\n\n /**\n * Run all collected cleanup functions and clear the group.\n */\n dispose() {\n while (cleanups.length) {\n const fn = cleanups.pop();\n try { fn(); } catch (e) { /* ignore cleanup errors */ }\n }\n },\n };\n}\n","/**\n * Reads initial state from a `<script type=\"application/json\">` element\n * embedded in the server-rendered HTML. Useful for SSR / hydration patterns.\n *\n * @param {string} [selector='#__LUME_DATA__'] - CSS selector for the script element\n * @returns {object} Parsed JSON object, or empty object if not found / invalid\n *\n * @example\n * ```html\n * <script id=\"__LUME_DATA__\" type=\"application/json\">\n * {\"title\": \"Welcome\", \"count\": 42}\n * </script>\n * ```\n *\n * ```js\n * import { state } from 'lume-js';\n * import { hydrateState } from 'lume-js/addons';\n *\n * const store = state(hydrateState());\n * ```\n */\nexport function hydrateState(selector = '#__LUME_DATA__') {\n const el = typeof document !== 'undefined' ? document.querySelector(selector) : null;\n if (!el) return {};\n try {\n return JSON.parse(el.textContent);\n } catch {\n return {};\n }\n}\n","export { computed } from \"./computed.js\";\nexport { watch } from \"./watch.js\";\nexport { repeat, defaultFocusPreservation, defaultScrollPreservation } from \"./repeat.js\";\nexport { createDebugPlugin, debug } from \"./debug.js\";\nexport { withPlugins } from \"./withPlugins.js\";\nexport { createCleanupGroup } from \"./cleanupGroup.js\";\nexport { hydrateState } from \"./hydrateState.js\";\n\n/**\n * Returns true if the value is a Lume reactive proxy created by state().\n * Uses duck-typing: checks for the presence of $subscribe.\n * @param {any} obj\n * @returns {boolean}\n */\nexport function isReactive(obj) {\n return !!(obj && typeof obj === 'object' && typeof obj.$subscribe === 'function');\n}\n"],"names":["container"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"addons.mjs","sources":["../src/addons/computed.js","../src/addons/watch.js","../src/addons/repeat.js","../src/addons/debug.js","../src/addons/withPlugins.js","../src/addons/cleanupGroup.js","../src/addons/hydrateState.js","../src/addons/index.js"],"sourcesContent":["/**\n * Lume-JS Computed Addon\n * \n * Creates computed values that automatically update when dependencies change.\n * Uses core effect() for automatic dependency tracking.\n * \n * Usage:\n * import { computed } from \"lume-js/addons/computed\";\n * \n * const doubled = computed(() => store.count * 2);\n * console.log(doubled.value); // Auto-updates when store.count changes\n * \n * Features:\n * - Automatic dependency tracking (no manual recompute)\n * - Cached values (only recomputes when dependencies change)\n * - Subscribe to changes\n * - Cleanup with dispose()\n * \n * @module addons/computed\n */\n\nimport { effect } from '../core/effect.js';\nimport { logError } from '../utils/log.js';\n\n/**\n * Creates a computed value with automatic dependency tracking\n * \n * The computation function runs immediately and tracks which state\n * properties are accessed. When any dependency changes, the value\n * is automatically recomputed.\n *\n * ⚠️ Circular self-mutations are automatically suppressed. If a computed\n * mutates a state property it depends on, the flush triggered by that\n * mutation is skipped to prevent an infinite microtask loop.\n *\n * @param {function} fn - Function that computes the value\n * @returns {object} Object with .value property and methods\n * \n * @example\n * const store = state({ count: 5 });\n * \n * const doubled = computed(() => store.count * 2);\n * console.log(doubled.value); // 10\n * \n * store.count = 10;\n * // After microtask:\n * console.log(doubled.value); // 20 (auto-updated)\n * \n * @example\n * // Subscribe to changes\n * const unsub = doubled.subscribe(value => {\n * console.log('Doubled changed to:', value);\n * });\n * \n * @example\n * // Cleanup\n * doubled.dispose();\n */\nexport function computed(fn) {\n if (typeof fn !== 'function') {\n throw new Error('computed() requires a function');\n }\n\n let cachedValue;\n let isInitialized = false;\n let isInComputation = false;\n let disposed = false;\n const subscribers = [];\n\n // Use effect to automatically track dependencies\n const cleanupEffect = effect(() => {\n // Skip re-entry from a flush triggered by our own synchronous mutation.\n // The mutation inside fn() queues a microtask flush; we stay flagged\n // until a subsequent microtask clears it, so that flush is dropped.\n if (isInComputation || disposed) return;\n\n isInComputation = true;\n\n try {\n const newValue = fn();\n\n // Check if value actually changed - Object.is() handles NaN and -0\n if (!isInitialized || !Object.is(newValue, cachedValue)) {\n cachedValue = newValue;\n isInitialized = true;\n\n // Notify all subscribers\n subscribers.forEach(callback => callback(cachedValue));\n }\n } catch (error) {\n logError('[Lume.js computed] Error in computation:', error);\n // Set to undefined on error, mark as initialized\n if (!isInitialized || cachedValue !== undefined) {\n cachedValue = undefined;\n isInitialized = true;\n\n // Notify subscribers of error state\n subscribers.forEach(callback => callback(cachedValue));\n }\n } finally {\n // Defer clearing the flag so any flush microtask queued by fn()\n // sees it still set and skips re-entry.\n queueMicrotask(() => {\n if (!disposed) {\n isInComputation = false;\n }\n });\n }\n });\n\n return {\n /**\n * Get the current computed value\n */\n get value() {\n if (!isInitialized) {\n throw new Error('Computed value accessed before initialization');\n }\n return cachedValue;\n },\n\n /**\n * Subscribe to changes in computed value\n * \n * @param {function} callback - Called when value changes\n * @returns {function} Unsubscribe function\n */\n subscribe(callback) {\n if (typeof callback !== 'function') {\n throw new Error('subscribe() requires a function');\n }\n\n subscribers.push(callback);\n\n // Call immediately with current value\n if (isInitialized) {\n callback(cachedValue);\n }\n\n // Return unsubscribe function\n return () => {\n const index = subscribers.indexOf(callback);\n if (index > -1) {\n subscribers.splice(index, 1);\n }\n };\n },\n\n /**\n * Clean up computed value and stop tracking\n */\n dispose() {\n disposed = true;\n cleanupEffect();\n subscribers.length = 0;\n isInitialized = false;\n isInComputation = false;\n }\n };\n}","/**\n * watch - observes changes to a state key and triggers callback\n * @param {Object} store - reactive store created with state()\n * @param {string} key - key in store to watch\n * @param {Function} callback - called with new value\n * @returns {Function} unsubscribe function\n */\nexport function watch(store, key, callback) {\n if (!store.$subscribe) {\n throw new Error(\"store must be created with state()\");\n }\n return store.$subscribe(key, callback);\n}","/**\n * Lume-JS List Rendering (Addon)\n *\n * Renders lists with automatic subscription and element reuse by key.\n * \n * Core guarantees:\n * Element reuse by key (same DOM nodes, not recreated)\n * Minimal DOM operations (only updates what changed)\n * Memory efficiency (cleanup on remove)\n * \n * Default behavior (can be disabled/customized):\n * ✅ Focus preservation (maintains activeElement and selection)\n * ✅ Scroll preservation (intelligent positioning for add/remove/reorder)\n * \n * Philosophy: No artificial limitations\n * - All preservation logic is overridable via options\n * - Set to null/false to disable, or provide custom functions\n * - Export utilities so you can wrap/extend them\n *\n * ⚠️ IMPORTANT: Arrays must be updated immutably!\n * store.items.push(x) // ❌ Won't trigger update\n * store.items = [...items] // ✅ Triggers update\n * \n * ═══════════════════════════════════════════════════════════════════════\n * PATTERN 1: Simple (render only) - for simple cases or backward compat\n * ═══════════════════════════════════════════════════════════════════════\n * \n * repeat('#list', store, 'todos', {\n * key: todo => todo.id,\n * render: (todo, el) => {\n * el.textContent = todo.name; // Called on every update\n * }\n * });\n *\n * ═══════════════════════════════════════════════════════════════════════\n * PATTERN 2: Clean separation (create + update) - recommended\n * ═══════════════════════════════════════════════════════════════════════\n *\n * repeat('#list', store, 'todos', {\n * key: todo => todo.id,\n * create: (todo, el) => {\n * // Called ONCE when element is created - build DOM structure\n * const nameSpan = document.createElement('span');\n * nameSpan.className = 'name';\n * el.appendChild(nameSpan);\n * const btn = document.createElement('button');\n * btn.textContent = 'Delete';\n * btn.onclick = () => deleteTodo(todo.id);\n * el.appendChild(btn);\n *\n * // Return a cleanup function — called automatically when element is removed\n * return () => {\n * // Unsubscribe from external listeners, remove timers, etc.\n * };\n * },\n * update: (todo, el, index, { isFirstRender }) => {\n * // Called on every update - bind data\n * // isFirstRender = true on initial render, false on subsequent\n * // Skipped if same object reference (optimization)\n * el.querySelector('.name').textContent = todo.name;\n * }\n * });\n *\n * ═══════════════════════════════════════════════════════════════════════\n * ADVANCED: Custom preservation strategies\n * ═══════════════════════════════════════════════════════════════════════\n * \n * import { defaultFocusPreservation, defaultScrollPreservation } from \"lume-js/addons\";\n * \n * repeat('#list', store, 'items', {\n * key: item => item.id,\n * create: (item, el) => { ... },\n * update: (item, el) => { ... },\n * preserveFocus: null, // disable focus preservation\n * preserveScroll: (container, context) => {\n * const restore = defaultScrollPreservation(container, context);\n * return () => { restore(); console.log('Scroll restored!'); };\n * }\n * });\n */\nimport { logWarn, logError } from '../utils/log.js';\n\n/**\n * Default focus preservation strategy\n * Saves activeElement and selection state before DOM updates\n * \n * @param {HTMLElement} container - The list container\n * @returns {Function|null} Restore function, or null if nothing to restore\n */\nexport function defaultFocusPreservation(container) {\n const activeEl = document.activeElement;\n const shouldRestore = container.contains(activeEl);\n\n if (!shouldRestore) return null;\n\n let selectionStart = null;\n let selectionEnd = null;\n\n if (activeEl.tagName === 'INPUT' || activeEl.tagName === 'TEXTAREA') {\n selectionStart = activeEl.selectionStart;\n selectionEnd = activeEl.selectionEnd;\n }\n\n return () => {\n if (document.body.contains(activeEl)) {\n activeEl.focus();\n if (selectionStart !== null && selectionEnd !== null) {\n activeEl.setSelectionRange(selectionStart, selectionEnd);\n }\n }\n };\n}\n\n/**\n * Default scroll preservation strategy\n * Uses anchor-based preservation for add/remove, pixel position for reorder\n * \n * @param {HTMLElement} container - The list container\n * @param {Object} context - Additional context\n * @param {boolean} context.isReorder - Whether this is a reorder operation\n * @returns {Function} Restore function\n */\nexport function defaultScrollPreservation(container, context = {}) {\n const { isReorder = false } = context;\n const scrollTop = container.scrollTop;\n\n // Early return if no scroll\n if (scrollTop === 0) {\n return () => { container.scrollTop = 0; };\n }\n\n let anchorElement = null;\n let anchorOffset = 0;\n\n // Only use anchor-based preservation for add/remove, not reorder\n if (!isReorder) {\n const containerRect = container.getBoundingClientRect();\n // Avoid Array.from - iterate children directly\n for (let child = container.firstElementChild; child; child = child.nextElementSibling) {\n const rect = child.getBoundingClientRect();\n\n if (rect.bottom > containerRect.top) {\n anchorElement = child;\n anchorOffset = rect.top - containerRect.top;\n break;\n }\n }\n }\n\n return () => {\n if (anchorElement && document.body.contains(anchorElement)) {\n const newRect = anchorElement.getBoundingClientRect();\n const containerRect = container.getBoundingClientRect();\n const currentOffset = newRect.top - containerRect.top;\n const scrollAdjustment = currentOffset - anchorOffset;\n\n container.scrollTop = container.scrollTop + scrollAdjustment;\n } else {\n container.scrollTop = scrollTop;\n }\n };\n}\n\n/**\n * Efficiently render a list with element reuse\n * \n * @param {string|HTMLElement} container - Container element or selector\n * @param {Object} store - Reactive state object\n * @param {string} arrayKey - Key in store containing the array\n * @param {Object} options - Configuration\n * @param {Function} options.key - Function to extract unique key: (item) => key\n * @param {Function} [options.render] - Function to render item (called for all items): (item, element, index) => void\n * @param {Function} [options.create] - Function for new elements only: (item, element, index) => void | Function. If a function is returned, it is registered as the element's cleanup and called automatically when the element is removed (by list update or full cleanup).\n * @param {Function} [options.update] - Function for data binding: (item, element, index, { isFirstRender }) => void. Skipped if same item reference AND same index.\n * @param {Function} [options.remove] - Additional cleanup when element is removed: (item, element) => void. Called after any cleanup function returned by create(). Optional — prefer returning a cleanup from create() for automatic lifecycle management.\n * @param {string|Function} [options.element='div'] - Element tag name or factory function\n * @param {Function|null} [options.preserveFocus=defaultFocusPreservation] - Focus preservation strategy (null to disable)\n * @param {Function|null} [options.preserveScroll=defaultScrollPreservation] - Scroll preservation strategy (null to disable)\n * @returns {Function} Cleanup function\n */\n\nexport function repeat(container, store, arrayKey, options) {\n const {\n key,\n render,\n create,\n update,\n remove,\n element = 'div',\n preserveFocus = defaultFocusPreservation,\n preserveScroll = defaultScrollPreservation\n } = options;\n\n // Resolve container\n const containerEl =\n typeof container === 'string'\n ? document.querySelector(container)\n : container;\n\n if (!containerEl) {\n logWarn(`[Lume.js] repeat(): container \"${container}\" not found`);\n return () => { };\n }\n\n if (typeof key !== 'function') {\n throw new Error('[Lume.js] repeat(): options.key must be a function');\n }\n\n if (typeof render !== 'function' && typeof create !== 'function') {\n throw new Error('[Lume.js] repeat(): options.render or options.create must be a function');\n }\n\n // key -> HTMLElement\n const elementsByKey = new Map();\n // key -> previous item (for reference comparison)\n const prevItemsByKey = new Map();\n // key -> previous index (for reorder detection)\n const prevIndexByKey = new Map();\n // key -> cleanup function returned by create()\n const cleanupByKey = new Map();\n const seenKeys = new Set();\n\n function createElement() {\n return typeof element === 'function'\n ? element()\n : document.createElement(element);\n }\n\n function reconcileDOM(container, nextEls) {\n let ptr = container.firstChild;\n\n for (let i = 0; i < nextEls.length; i++) {\n const desired = nextEls[i];\n\n if (ptr === desired) {\n ptr = ptr.nextSibling;\n continue;\n }\n\n container.insertBefore(desired, ptr);\n }\n\n // Remove leftover children not in nextEls\n while (ptr) {\n const next = ptr.nextSibling;\n container.removeChild(ptr);\n ptr = next;\n }\n }\n\n function applyPreservation(container, fn, isReorder) {\n const shouldPreserve = document.body.contains(container);\n const restoreFocus = shouldPreserve && preserveFocus ? preserveFocus(container) : null;\n const restoreScroll = shouldPreserve && preserveScroll ? preserveScroll(container, { isReorder }) : null;\n\n fn();\n\n if (restoreFocus) restoreFocus();\n if (restoreScroll) restoreScroll();\n }\n\n function updateList() {\n const items = store[arrayKey];\n\n if (!Array.isArray(items)) {\n logWarn(`[Lume.js] repeat(): store.${arrayKey} is not an array`);\n return;\n }\n\n // Only compute isReorder if scroll preservation needs it.\n // Uses elementsByKey (previous state) and items directly — no Set allocations.\n let isReorder = false;\n if (preserveScroll && elementsByKey.size === items.length) {\n isReorder = true;\n for (let i = 0; i < items.length; i++) {\n if (!elementsByKey.has(key(items[i]))) { isReorder = false; break; }\n }\n }\n\n seenKeys.clear();\n const nextEls = [];\n\n // Build ordered list of DOM nodes (created or reused)\n for (let i = 0; i < items.length; i++) {\n const item = items[i];\n const k = key(item);\n\n if (seenKeys.has(k)) {\n logWarn(`[Lume.js] repeat(): duplicate key \"${k}\"`);\n continue;\n }\n seenKeys.add(k);\n\n let el = elementsByKey.get(k);\n const isFirstRender = !el;\n\n if (isFirstRender) {\n el = createElement();\n elementsByKey.set(k, el);\n }\n\n try {\n // Call create for new elements (DOM structure)\n if (isFirstRender && create) {\n const cleanup = create(item, el, i);\n if (typeof cleanup === 'function') {\n cleanupByKey.set(k, cleanup);\n }\n }\n\n // Call update for data binding (new and existing elements)\n // Skip if same item reference AND same index (optimization)\n const prevItem = prevItemsByKey.get(k);\n const prevIndex = prevIndexByKey.get(k);\n if (update) {\n if (prevItem !== item || prevIndex !== i) {\n update(item, el, i, { isFirstRender });\n }\n } else if (render) {\n // Backward compatibility: render handles both create and update\n render(item, el, i);\n }\n\n // Store reference and index for next comparison\n prevItemsByKey.set(k, item);\n prevIndexByKey.set(k, i);\n\n } catch (err) {\n logError(`[Lume.js] repeat(): error rendering key \"${k}\":`, err);\n }\n\n nextEls.push(el);\n }\n\n applyPreservation(containerEl, () => {\n reconcileDOM(containerEl, nextEls);\n\n // Clean maps: remove keys not in seenKeys (new state)\n if (elementsByKey.size !== seenKeys.size) {\n for (const k of elementsByKey.keys()) {\n if (!seenKeys.has(k)) {\n const el = elementsByKey.get(k);\n const prevItem = prevItemsByKey.get(k);\n // Call create-returned cleanup first, then remove callback\n const cleanup = cleanupByKey.get(k);\n if (typeof cleanup === 'function') {\n try {\n cleanup();\n } catch (err) {\n logError(`[Lume.js] repeat(): cleanup error for key \"${k}\":`, err);\n }\n }\n if (typeof remove === 'function' && el) {\n remove(prevItem, el);\n }\n elementsByKey.delete(k);\n prevItemsByKey.delete(k);\n prevIndexByKey.delete(k);\n cleanupByKey.delete(k);\n }\n }\n }\n }, isReorder);\n }\n\n // Subscription — $subscribe calls updateList immediately (initial render),\n // so no separate updateList() call is needed for reactive stores.\n let unsubscribe;\n if (typeof store.$subscribe === 'function') {\n unsubscribe = store.$subscribe(arrayKey, updateList);\n } else if (typeof store.subscribe === 'function') {\n // Generic subscribe (e.g. computed) — subscribe first, then initial render\n const subResult = store.subscribe(() => updateList());\n updateList();\n // Normalize both function-style and object-style (RxJS Subscription) returns\n unsubscribe = typeof subResult === 'function'\n ? subResult\n : () => { subResult?.unsubscribe?.(); };\n } else {\n // Non-reactive store — render once and return cleanup\n updateList();\n logWarn('[Lume.js] repeat(): store is not reactive (no $subscribe or subscribe method)');\n return () => {\n for (const [k, el] of elementsByKey) {\n const prevItem = prevItemsByKey.get(k);\n const cleanup = cleanupByKey.get(k);\n if (typeof cleanup === 'function') {\n try {\n cleanup();\n } catch (err) {\n logError(`[Lume.js] repeat(): cleanup error for key \"${k}\":`, err);\n }\n }\n if (typeof remove === 'function') {\n remove(prevItem, el);\n }\n }\n containerEl.replaceChildren();\n elementsByKey.clear();\n prevItemsByKey.clear();\n prevIndexByKey.clear();\n cleanupByKey.clear();\n seenKeys.clear();\n };\n }\n\n return () => {\n if (typeof unsubscribe === 'function') {\n unsubscribe();\n }\n // Invoke cleanup and remove callback for all remaining elements before clearing\n for (const [k, el] of elementsByKey) {\n const prevItem = prevItemsByKey.get(k);\n const cleanup = cleanupByKey.get(k);\n if (typeof cleanup === 'function') {\n try {\n cleanup();\n } catch (err) {\n logError(`[Lume.js] repeat(): cleanup error for key \"${k}\":`, err);\n }\n }\n if (typeof remove === 'function') {\n remove(prevItem, el);\n }\n }\n // Clear DOM elements (replaceChildren is faster than loop)\n containerEl.replaceChildren();\n elementsByKey.clear();\n prevItemsByKey.clear();\n prevIndexByKey.clear();\n cleanupByKey.clear();\n seenKeys.clear();\n };\n}\n","/**\n * Lume-JS Debug Addon\n * \n * Developer-friendly logging and inspection of reactive state operations.\n * Critical for adoption - hard to debug = hard to adopt.\n * \n * Usage:\n * import { state } from \"lume-js\";\n * import { withPlugins, createDebugPlugin, debug } from \"lume-js/addons\";\n * \n * const store = withPlugins(state({ count: 0 }), [createDebugPlugin({ label: 'myStore' })]);\n * \n * debug.enable(); // Enable logging\n * debug.filter('count'); // Only log 'count' key\n * debug.stats(); // Show statistics\n * \n * @module addons/debug\n */\n\n// Global debug state\nlet globalEnabled = true;\nlet globalFilter = null; // string, RegExp, or null\nconst stats = new Map(); // label -> { gets: Map, sets: Map, notifies: Map }\n\n/**\n * Check if a key matches the current filter\n * @param {string} key\n * @returns {boolean}\n */\nfunction matchesFilter(key) {\n if (globalFilter === null) return true;\n if (typeof globalFilter === 'string') {\n return key.includes(globalFilter);\n }\n if (globalFilter instanceof RegExp) {\n return globalFilter.test(key);\n }\n return true;\n}\n\n/**\n * Get or create stats entry for a label\n * @param {string} label\n * @returns {object}\n */\nfunction getStats(label) {\n if (!stats.has(label)) {\n stats.set(label, {\n gets: new Map(),\n sets: new Map(),\n notifies: new Map()\n });\n }\n return stats.get(label);\n}\n\n/**\n * Increment a stat counter\n * @param {string} label\n * @param {'gets'|'sets'|'notifies'} type\n * @param {string} key\n */\nfunction incrementStat(label, type, key) {\n const s = getStats(label);\n const map = s[type];\n map.set(key, (map.get(key) || 0) + 1);\n}\n\nconst MAX_LOG_LEN = 100;\nconst TRUNCATED_LEN = MAX_LOG_LEN - 3;\n\n/**\n * Format value for logging (truncate long values)\n * @param {any} value\n * @returns {string}\n */\nfunction formatValue(value) {\n try {\n const json = JSON.stringify(value);\n if (json.length > MAX_LOG_LEN) {\n return json.slice(0, TRUNCATED_LEN) + '...';\n }\n return json;\n } catch {\n return String(value);\n }\n}\n\n/**\n * Create a debug plugin instance for a reactive state store.\n * \n * @param {object} [options] - Configuration options\n * @param {string} [options.label='store'] - Label for log messages\n * @param {boolean} [options.logGet=false] - Log property reads (can be noisy)\n * @param {boolean} [options.logSet=true] - Log property writes\n * @param {boolean} [options.logNotify=true] - Log subscriber notifications\n * @param {boolean} [options.trace=false] - Show stack trace for SET operations\n * @returns {object} Plugin object for state()\n * \n * @example\n * const store = withPlugins(state({ count: 0 }), [createDebugPlugin({ label: 'counter' })]);\n * \n * @example\n * // With stack traces for debugging where state changes originate\n * const store = withPlugins(state({ count: 0 }), [createDebugPlugin({ label: 'counter', trace: true })]);\n */\nexport function createDebugPlugin(options = {}) {\n const label = options.label ?? 'store';\n\n // IMPORTANT: Do NOT destructure options here!\n // Options may contain getters for dynamic runtime toggling (e.g., from UI).\n // Destructuring would copy values once at creation time, breaking reactivity.\n // Use getOpt() helper to read options dynamically in each hook.\n const getOpt = (name, defaultVal) => {\n const val = options[name];\n return val !== undefined ? val : defaultVal;\n };\n\n return {\n name: `debug:${label}`,\n\n onInit: () => {\n if (globalEnabled) {\n console.log(`%c[${label}]%c initialized`, 'color: #888; font-weight: bold', 'color: inherit');\n }\n },\n\n onGet: (key, value) => {\n // Skip internal properties\n if (typeof key === 'string' && key.startsWith('$')) {\n return value;\n }\n\n incrementStat(label, 'gets', key);\n\n if (globalEnabled && getOpt('logGet', false) && matchesFilter(key)) {\n console.log(\n `%c[${label}]%c GET %c${key}%c = ${formatValue(value)}`,\n 'color: #888; font-weight: bold',\n 'color: #4CAF50',\n 'color: #2196F3; font-weight: bold',\n 'color: inherit'\n );\n }\n\n return value;\n },\n\n onSet: (key, newValue, oldValue) => {\n // Skip internal properties\n if (typeof key === 'string' && key.startsWith('$')) {\n return newValue;\n }\n\n incrementStat(label, 'sets', key);\n\n if (globalEnabled && getOpt('logSet', true) && matchesFilter(key)) {\n console.log(\n `%c[${label}]%c SET %c${key}%c: ${formatValue(oldValue)} → ${formatValue(newValue)}`,\n 'color: #888; font-weight: bold',\n 'color: #FF9800',\n 'color: #2196F3; font-weight: bold',\n 'color: inherit'\n );\n\n // Show stack trace if enabled (helps find where state changes originate)\n if (getOpt('trace', false)) {\n console.trace(`%c[${label}] Stack trace for ${key}`, 'color: #888');\n }\n }\n\n return newValue;\n },\n\n onSubscribe: (key) => {\n if (globalEnabled && matchesFilter(key)) {\n console.log(\n `%c[${label}]%c SUBSCRIBE %c${key}`,\n 'color: #888; font-weight: bold',\n 'color: #9C27B0',\n 'color: #2196F3; font-weight: bold'\n );\n }\n },\n\n onNotify: (key, value) => {\n // Skip internal properties\n if (typeof key === 'string' && key.startsWith('$')) {\n return;\n }\n\n incrementStat(label, 'notifies', key);\n\n if (globalEnabled && getOpt('logNotify', true) && matchesFilter(key)) {\n console.log(\n `%c[${label}]%c NOTIFY %c${key}%c = ${formatValue(value)}`,\n 'color: #888; font-weight: bold',\n 'color: #E91E63',\n 'color: #2196F3; font-weight: bold',\n 'color: inherit'\n );\n }\n }\n };\n}\n\n/**\n * Global debug controls\n */\nexport const debug = {\n /**\n * Enable debug logging globally\n */\n enable() {\n globalEnabled = true;\n console.log('%c[lume-debug]%c Logging enabled', 'color: #888; font-weight: bold', 'color: #4CAF50');\n },\n\n /**\n * Disable debug logging globally\n */\n disable() {\n globalEnabled = false;\n console.log('%c[lume-debug]%c Logging disabled', 'color: #888; font-weight: bold', 'color: #F44336');\n },\n\n /**\n * Check if debug logging is currently enabled\n * @returns {boolean}\n */\n isEnabled() {\n return globalEnabled;\n },\n\n /**\n * Filter logs by key pattern\n * @param {string|RegExp|null} pattern - Pattern to match, or null to clear filter\n */\n filter(pattern) {\n globalFilter = pattern;\n if (pattern === null) {\n console.log('%c[lume-debug]%c Filter cleared', 'color: #888; font-weight: bold', 'color: inherit');\n } else {\n console.log(`%c[lume-debug]%c Filter set: ${pattern}`, 'color: #888; font-weight: bold', 'color: inherit');\n }\n },\n\n /**\n * Get current filter pattern\n * @returns {string|RegExp|null}\n */\n getFilter() {\n return globalFilter;\n },\n\n /**\n * Get statistics data (silent - no console output)\n * Use logStats() if you want to see stats in console.\n * @returns {object} Stats object for programmatic access\n */\n stats() {\n const result = {};\n\n for (const [label, data] of stats) {\n result[label] = {\n gets: Object.fromEntries(data.gets),\n sets: Object.fromEntries(data.sets),\n notifies: Object.fromEntries(data.notifies)\n };\n }\n\n return result;\n },\n\n /**\n * Log statistics summary to console (with formatting)\n * @returns {object} Stats object for programmatic access\n */\n logStats() {\n const result = this.stats();\n\n if (Object.keys(result).length === 0) {\n console.log('%c[lume-debug]%c No stats collected yet', 'color: #888; font-weight: bold', 'color: inherit');\n return result;\n }\n\n console.group('%c[lume-debug] Statistics', 'color: #888; font-weight: bold');\n\n for (const [label, data] of Object.entries(result)) {\n console.group(`%c${label}`, 'color: #2196F3; font-weight: bold');\n\n // Use console.table for better formatted output\n const tableData = [];\n const allKeys = new Set([\n ...Object.keys(data.gets),\n ...Object.keys(data.sets),\n ...Object.keys(data.notifies)\n ]);\n\n for (const key of allKeys) {\n tableData.push({\n key,\n gets: data.gets[key] || 0,\n sets: data.sets[key] || 0,\n notifies: data.notifies[key] || 0\n });\n }\n\n if (tableData.length > 0) {\n console.table(tableData);\n }\n\n console.groupEnd();\n }\n\n console.groupEnd();\n\n return result;\n },\n\n /**\n * Reset all collected statistics\n */\n resetStats() {\n stats.clear();\n console.log('%c[lume-debug]%c Stats reset', 'color: #888; font-weight: bold', 'color: inherit');\n }\n};\n","/**\n * Lume-JS withPlugins Addon\n *\n * Wraps a reactive state proxy with a plugin layer that intercepts\n * get/set/notify/subscribe operations via plugin hooks.\n *\n * Only stores that opt into debugging or custom behaviors need this.\n * Core state() is not aware of plugins.\n *\n * Usage:\n * import { state } from \"lume-js\";\n * import { withPlugins, createDebugPlugin } from \"lume-js/addons\";\n *\n * const store = withPlugins(state({ count: 0 }), [createDebugPlugin({ label: 'counter' })]);\n */\n\n/**\n * Wrap a reactive state proxy with plugin hooks.\n *\n * Plugin hooks (all optional):\n * onInit() — called once at wrap time\n * onGet(key, value) → value|void — intercept/transform reads\n * onSet(key, newVal, oldVal) → val|void — intercept/transform writes\n * onNotify(key, value) — called before subscribers are notified\n * onSubscribe(key) — called when $subscribe is invoked\n *\n * @param {object} store - A reactive proxy from state()\n * @param {Array<object>} plugins - Array of plugin objects\n * @returns {Proxy} A new proxy wrapping the store with plugin behavior\n */\nimport { logError } from '../utils/log.js';\n\nexport function withPlugins(store, plugins = []) {\n if (!plugins.length) return store;\n\n // Call onInit hooks once at wrap time\n for (const p of plugins) {\n try {\n p.onInit?.();\n } catch (e) {\n logError(`[Lume.js] Plugin \"${p.name}\" error in onInit:`, e);\n }\n }\n\n // Track pending notifications for onNotify hooks.\n // Instead of a separate microtask, we hook into the underlying state's\n // flush via $beforeFlush so onNotify and subscribers share one microtask.\n const pendingNotifications = new Map();\n\n function runNotifyHooks() {\n for (const [key, value] of pendingNotifications) {\n for (const p of plugins) {\n try {\n p.onNotify?.(key, value);\n } catch (e) {\n logError(`[Lume.js] Plugin \"${p.name}\" error in onNotify:`, e);\n }\n }\n }\n pendingNotifications.clear();\n }\n\n // Register once on the underlying state; capture unsubscribe for cleanup.\n let flushUnsub;\n if (typeof store.$beforeFlush === 'function') {\n flushUnsub = store.$beforeFlush(runNotifyHooks);\n }\n\n return new Proxy(store, {\n get(target, key) {\n // $dispose — remove the beforeFlush hook and clear pending state\n if (key === '$dispose') {\n return () => {\n if (flushUnsub) flushUnsub();\n pendingNotifications.clear();\n };\n }\n\n // Pass $-prefixed meta methods through without interception\n if (typeof key === 'string' && key.startsWith('$')) {\n const method = target[key];\n if (key === '$subscribe' && typeof method === 'function') {\n // Wrap $subscribe to call onSubscribe hooks\n return (subKey, fn) => {\n for (const p of plugins) {\n try {\n p.onSubscribe?.(subKey);\n } catch (e) {\n logError(`[Lume.js] Plugin \"${p.name}\" error in onSubscribe:`, e);\n }\n }\n return method(subKey, fn);\n };\n }\n return method;\n }\n\n let value = target[key];\n\n // onGet chain\n for (const p of plugins) {\n try {\n const r = p.onGet?.(key, value);\n if (r !== undefined) value = r;\n } catch (e) {\n logError(`[Lume.js] Plugin \"${p.name}\" error in onGet:`, e);\n }\n }\n\n return value;\n },\n\n set(target, key, value) {\n const oldValue = target[key];\n let newValue = value;\n\n // onSet chain\n for (const p of plugins) {\n try {\n const r = p.onSet?.(key, newValue, oldValue);\n if (r !== undefined) newValue = r;\n } catch (e) {\n logError(`[Lume.js] Plugin \"${p.name}\" error in onSet:`, e);\n }\n }\n\n // Only queue onNotify if the value actually changed after plugin chain\n if (!Object.is(newValue, oldValue)) {\n pendingNotifications.set(key, newValue);\n }\n\n target[key] = newValue;\n return true;\n }\n });\n}\n","/**\n * Creates a cleanup group that can collect and dispose multiple\n * cleanup/unsubscribe functions at once.\n *\n * @returns {CleanupGroup}\n *\n * @example\n * ```js\n * import { createCleanupGroup } from 'lume-js/addons';\n *\n * const group = createCleanupGroup();\n * group.add(bindDom(root, store));\n * group.add(effect(() => { ... }));\n * group.add(store.$subscribe('key', fn));\n *\n * // Dispose everything at once\n * group.dispose();\n * ```\n */\nexport function createCleanupGroup() {\n const cleanups = [];\n\n return {\n /**\n * Add a cleanup function to the group.\n * @param {Function} fn - Cleanup/unsubscribe function\n */\n add(fn) {\n if (typeof fn === 'function') {\n cleanups.push(fn);\n }\n },\n\n /**\n * Run all collected cleanup functions and clear the group.\n */\n dispose() {\n while (cleanups.length) {\n const fn = cleanups.pop();\n try { fn(); } catch (e) { /* ignore cleanup errors */ }\n }\n },\n };\n}\n","/**\n * Reads initial state from a `<script type=\"application/json\">` element\n * embedded in the server-rendered HTML. Useful for SSR / hydration patterns.\n *\n * @param {string} [selector='#__LUME_DATA__'] - CSS selector for the script element\n * @returns {object} Parsed JSON object, or empty object if not found / invalid\n *\n * @example\n * ```html\n * <script id=\"__LUME_DATA__\" type=\"application/json\">\n * {\"title\": \"Welcome\", \"count\": 42}\n * </script>\n * ```\n *\n * ```js\n * import { state } from 'lume-js';\n * import { hydrateState } from 'lume-js/addons';\n *\n * const store = state(hydrateState());\n * ```\n */\nexport function hydrateState(selector = '#__LUME_DATA__') {\n const el = typeof document !== 'undefined' ? document.querySelector(selector) : null;\n if (!el) return {};\n try {\n return JSON.parse(el.textContent);\n } catch {\n return {};\n }\n}\n","export { computed } from \"./computed.js\";\nexport { watch } from \"./watch.js\";\nexport { repeat, defaultFocusPreservation, defaultScrollPreservation } from \"./repeat.js\";\nexport { createDebugPlugin, debug } from \"./debug.js\";\nexport { withPlugins } from \"./withPlugins.js\";\nexport { createCleanupGroup } from \"./cleanupGroup.js\";\nexport { hydrateState } from \"./hydrateState.js\";\n\n/**\n * Returns true if the value is a Lume reactive proxy created by state().\n * Uses duck-typing: checks for the presence of $subscribe.\n * @param {any} obj\n * @returns {boolean}\n */\nexport function isReactive(obj) {\n return !!(obj && typeof obj === 'object' && typeof obj.$subscribe === 'function');\n}\n"],"names":["container"],"mappings":"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"}
|
package/dist/lume.global.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
var Lume=function(e){"use strict";function t(e,...t){void 0!==console&&"function"==typeof console.warn&&console.warn(e,...t)}function o(e,...t){void 0!==console&&"function"==typeof console.error&&console.error(e,...t)}const n=new Set;function r(e,t){n.add(e);try{return t()}finally{n.delete(e)}}const c=e=>({attr:"data-"+e,apply(t,o){t[e]=!!o}}),s=e=>({attr:"data-"+e,apply(t,o){t.setAttribute(e,o?"true":"false")}}),i=[c("hidden"),c("disabled"),c("checked"),c("required"),s("aria-expanded"),s("aria-hidden")];function l(e,t,o,n){const r=u(t,o);if(!r)return null;const{target:c,key:s}=r;return c.$subscribe(s,t=>n.apply(e,t))}function a(e,t,o,n){const r=u(t,o);if(!r)return null;const{target:c,key:s}=r,i=c.$subscribe(s,t=>function(e,t){"INPUT"===e.tagName?"checkbox"===e.type?e.checked=!!t:"radio"===e.type?e.checked=e.value===t+"":e.value=t??"":"TEXTAREA"===e.tagName||"SELECT"===e.tagName?e.value=t??"":e.textContent=t??""}(e,t));return function(e){return"INPUT"===e.tagName||"TEXTAREA"===e.tagName||"SELECT"===e.tagName}(e)&&n.set(e,{target:c,key:s}),i}function u(e,o){if(!o)return null;const n=o.split("."),r=n.pop(),c=function(e,t){if(!t||0===t.length)return e;let o=e;for(let n=0;n<t.length;n++){const e=t[n];if(null==o)return null;if(!(e in o))return null;o=o[e]}return o}(e,n);return null==c?(t(`[Lume.js] Invalid path "${o}"`),null):c?.$subscribe?{target:c,key:r}:(t(`[Lume.js] Target for "${o}" is not reactive`),null)}let f=null;function d(e,t){if("function"!=typeof e)throw Error("effect() requires a function");const n=[];let c=!1;const s=()=>{if(!c){c=!0;try{e()}catch(t){throw o("[Lume.js effect] Error in effect:",t),t}finally{c=!1}}};if(Array.isArray(t)){for(const e of t)if(Array.isArray(e)&&e.length>=2){const[t,...o]=e;if(t&&"function"==typeof t.$subscribe)for(const e of o){let o=!0;const r=t.$subscribe(e,()=>{o?o=!1:s()});n.push(r)}}s()}else{const t=()=>{if(c)return;const s=n.splice(0),i={fn:e,cleanups:n,execute:t,tracking:{}},l=f;f=i,c=!0;try{r((e,t,o)=>{f===i&&(i.tracking[t]||(i.tracking[t]=!0,i.cleanups.push(o(t,i.execute))))},e)}catch(a){throw n.length=0,n.push(...s),o("[Lume.js effect] Error in effect:",a),a}finally{f=l,c=!1}if(n.length>0)for(const e of s)e();else n.push(...s)};t()}return()=>{for(;n.length;)n.pop()()}}function p(e){const t=document.activeElement;if(!e.contains(t))return null;let o=null,n=null;return"INPUT"!==t.tagName&&"TEXTAREA"!==t.tagName||(o=t.selectionStart,n=t.selectionEnd),()=>{document.body.contains(t)&&(t.focus(),null!==o&&null!==n&&t.setSelectionRange(o,n))}}function b(e,t={}){const{isReorder:o=!1}=t,n=e.scrollTop;if(0===n)return()=>{e.scrollTop=0};let r=null,c=0;if(!o){const t=e.getBoundingClientRect();for(let o=e.firstElementChild;o;o=o.nextElementSibling){const e=o.getBoundingClientRect();if(e.bottom>t.top){r=o,c=e.top-t.top;break}}}return()=>{if(r&&document.body.contains(r)){const t=r.getBoundingClientRect(),o=e.getBoundingClientRect(),n=t.top-o.top-c;e.scrollTop=e.scrollTop+n}else e.scrollTop=n}}let g=!0,h=null;const y=new Map;function m(e){return null===h||("string"==typeof h?e.includes(h):!(h instanceof RegExp)||h.test(e))}function w(e,t,o){const n=function(e){return y.has(e)||y.set(e,{gets:new Map,sets:new Map,notifies:new Map}),y.get(e)}(e),r=n[t];r.set(o,(r.get(o)||0)+1)}function E(e){try{const t=JSON.stringify(e);return t.length>100?t.slice(0,97)+"...":t}catch{return e+""}}const v={enable(){g=!0,console.log("%c[lume-debug]%c Logging enabled","color: #888; font-weight: bold","color: #4CAF50")},disable(){g=!1,console.log("%c[lume-debug]%c Logging disabled","color: #888; font-weight: bold","color: #F44336")},isEnabled:()=>g,filter(e){h=e,console.log(null===e?"%c[lume-debug]%c Filter cleared":"%c[lume-debug]%c Filter set: "+e,"color: #888; font-weight: bold","color: inherit")},getFilter:()=>h,stats(){const e={};for(const[t,o]of y)e[t]={gets:Object.fromEntries(o.gets),sets:Object.fromEntries(o.sets),notifies:Object.fromEntries(o.notifies)};return e},logStats(){const e=this.stats();if(0===Object.keys(e).length)return console.log("%c[lume-debug]%c No stats collected yet","color: #888; font-weight: bold","color: inherit"),e;console.group("%c[lume-debug] Statistics","color: #888; font-weight: bold");for(const[t,o]of Object.entries(e)){console.group("%c"+t,"color: #2196F3; font-weight: bold");const e=[],n=new Set([...Object.keys(o.gets),...Object.keys(o.sets),...Object.keys(o.notifies)]);for(const t of n)e.push({key:t,gets:o.gets[t]||0,sets:o.sets[t]||0,notifies:o.notifies[t]||0});e.length>0&&console.table(e),console.groupEnd()}return console.groupEnd(),e},resetStats(){y.clear(),console.log("%c[lume-debug]%c Stats reset","color: #888; font-weight: bold","color: inherit")}},$={attr:"data-show",apply(e,t){e.hidden=!t}};function j(e){return{attr:"data-"+e,apply(t,o){t.toggleAttribute(e,!!o)}}}function S(e){const t=e.startsWith("aria-")?e:"aria-"+e;return{attr:"data-"+t,apply(e,o){e.setAttribute(t,o?"true":"false")}}}function k(e){return{attr:"data-"+e,apply(t,o){null==o?t.removeAttribute(e):t.setAttribute(e,o+"")}}}const L=[j("readonly")],A=[S("pressed"),S("selected"),S("disabled")],x=["readonly","open","novalidate","formnovalidate","multiple","autofocus","autoplay","controls","loop","muted","defer","async","reversed","selected","inert","allowfullscreen"],O=["href","src","alt","title","placeholder","action","method","target","rel","type","name","role","lang","tabindex","pattern","min","max","step","minlength","maxlength","width","height","for","form","accept","autocomplete","loading","decoding","inputmode","enterkeyhint","draggable","contenteditable","spellcheck","translate","dir","id","poster","preload","download","media","sizes","srcset","colspan","rowspan","scope","headers","wrap","sandbox"],T=["pressed","selected","disabled","checked","invalid","required","busy","modal","multiselectable","multiline","readonly","atomic"],F=["current","live","relevant","haspopup","sort","autocomplete","orientation","label","describedby","labelledby","controls","owns","activedescendant","errormessage","details","flowto","valuenow","valuemin","valuemax","valuetext","colcount","colindex","colspan","rowcount","rowindex","rowspan","level","setsize","posinset","placeholder","roledescription","keyshortcuts","braillelabel","brailleroledescription"];return e.a11yHandlers=A,e.ariaAttr=S,e.bindDom=function(e,t,o={}){if(!(e instanceof HTMLElement))throw Error("bindDom() requires a valid HTMLElement as root");if(!t||"object"!=typeof t)throw Error("bindDom() requires a reactive state object");const{immediate:n=!1,handlers:r=[]}=o,c=function(e,t){if(!t.length)return e;const o=new Map;for(const n of e)o.set(n.attr,n);for(const n of t.flat())o.set(n.attr,n);return[...o.values()]}(i,r),s=()=>{const o=[],n=new WeakMap,r=["[data-bind]",...c.map(e=>`[${e.attr}]`)].join(","),s=e.querySelectorAll(r);for(const e of s){if(e.hasAttribute("data-bind")){const r=a(e,t,e.getAttribute("data-bind"),n);r&&o.push(r)}for(const n of c)if(e.hasAttribute(n.attr)){const r=l(e,t,e.getAttribute(n.attr),n);r&&o.push(r)}}const i=e=>{const t=n.get(e.target);var o;t&&(t.target[t.key]="checkbox"===(o=e.target).type?o.checked:"number"===o.type||"range"===o.type?o.valueAsNumber:o.value)};return e.addEventListener("input",i),o.push(()=>e.removeEventListener("input",i)),()=>o.forEach(e=>e())};if(!n&&"loading"===document.readyState){let e=null;const t=()=>{e=s()};return document.addEventListener("DOMContentLoaded",t,{once:!0}),()=>e?e():document.removeEventListener("DOMContentLoaded",t)}return s()},e.boolAttr=j,e.classToggle=function(...e){return e.map(e=>({attr:"data-class-"+e,apply(t,o){t.classList.toggle(e,!!o)}}))},e.computed=function(e){if("function"!=typeof e)throw Error("computed() requires a function");let t,n=!1,r=!1,c=!1;const s=[],i=d(()=>{if(!r&&!c){r=!0;try{const o=e();n&&Object.is(o,t)||(t=o,n=!0,s.forEach(e=>e(t)))}catch(i){o("[Lume.js computed] Error in computation:",i),n&&void 0===t||(t=void 0,n=!0,s.forEach(e=>e(t)))}finally{queueMicrotask(()=>{c||(r=!1)})}}});return{get value(){if(!n)throw Error("Computed value accessed before initialization");return t},subscribe(e){if("function"!=typeof e)throw Error("subscribe() requires a function");return s.push(e),n&&e(t),()=>{const t=s.indexOf(e);t>-1&&s.splice(t,1)}},dispose(){c=!0,i(),s.length=0,n=!1,r=!1}}},e.createCleanupGroup=function(){const e=[];return{add(t){"function"==typeof t&&e.push(t)},dispose(){for(;e.length;){const o=e.pop();try{o()}catch(t){}}}}},e.createDebugPlugin=function(e={}){const t=e.label??"store",o=(t,o)=>{const n=e[t];return void 0!==n?n:o};return{name:"debug:"+t,onInit:()=>{g&&console.log(`%c[${t}]%c initialized`,"color: #888; font-weight: bold","color: inherit")},onGet:(e,n)=>("string"==typeof e&&e.startsWith("$")||(w(t,"gets",e),g&&o("logGet",!1)&&m(e)&&console.log(`%c[${t}]%c GET %c${e}%c = ${E(n)}`,"color: #888; font-weight: bold","color: #4CAF50","color: #2196F3; font-weight: bold","color: inherit")),n),onSet:(e,n,r)=>("string"==typeof e&&e.startsWith("$")||(w(t,"sets",e),g&&o("logSet",!0)&&m(e)&&(console.log(`%c[${t}]%c SET %c${e}%c: ${E(r)} → ${E(n)}`,"color: #888; font-weight: bold","color: #FF9800","color: #2196F3; font-weight: bold","color: inherit"),o("trace",!1)&&console.trace(`%c[${t}] Stack trace for ${e}`,"color: #888"))),n),onSubscribe:e=>{g&&m(e)&&console.log(`%c[${t}]%c SUBSCRIBE %c${e}`,"color: #888; font-weight: bold","color: #9C27B0","color: #2196F3; font-weight: bold")},onNotify:(e,n)=>{"string"==typeof e&&e.startsWith("$")||(w(t,"notifies",e),g&&o("logNotify",!0)&&m(e)&&console.log(`%c[${t}]%c NOTIFY %c${e}%c = ${E(n)}`,"color: #888; font-weight: bold","color: #E91E63","color: #2196F3; font-weight: bold","color: inherit"))}}},e.debug=v,e.defaultFocusPreservation=p,e.defaultScrollPreservation=b,e.effect=d,e.formHandlers=L,e.htmlAttrs=function(){return[$,...x.map(e=>j(e)),...O.map(e=>k(e)),...T.map(e=>S(e)),...F.map(e=>k("aria-"+e))]},e.hydrateState=function(e="#__LUME_DATA__"){const t="undefined"!=typeof document?document.querySelector(e):null;if(!t)return{};try{return JSON.parse(t.textContent)}catch{return{}}},e.isReactive=function(e){return!(!e||"object"!=typeof e||"function"!=typeof e.$subscribe)},e.repeat=function(e,n,r,c){const{key:s,render:i,create:l,update:a,element:u="div",preserveFocus:f=p,preserveScroll:d=b}=c,g="string"==typeof e?document.querySelector(e):e;if(!g)return t(`[Lume.js] repeat(): container "${e}" not found`),()=>{};if("function"!=typeof s)throw Error("[Lume.js] repeat(): options.key must be a function");if("function"!=typeof i&&"function"!=typeof l)throw Error("[Lume.js] repeat(): options.render or options.create must be a function");const h=new Map,y=new Map,m=new Map,w=new Set;function E(){return"function"==typeof u?u():document.createElement(u)}function v(){const e=n[r];if(!Array.isArray(e))return void t(`[Lume.js] repeat(): store.${r} is not an array`);let c=!1;if(d&&h.size===e.length){c=!0;for(let t=0;t<e.length;t++)if(!h.has(s(e[t]))){c=!1;break}}w.clear();const u=[];for(let n=0;n<e.length;n++){const r=e[n],c=s(r);if(w.has(c)){t(`[Lume.js] repeat(): duplicate key "${c}"`);continue}w.add(c);let f=h.get(c);const d=!f;d&&(f=E(),h.set(c,f));try{d&&l&&l(r,f,n);const e=y.get(c),t=m.get(c);a?e===r&&t===n||a(r,f,n,{isFirstRender:d}):i&&i(r,f,n),y.set(c,r),m.set(c,n)}catch(p){o(`[Lume.js] repeat(): error rendering key "${c}":`,p)}u.push(f)}!function(e,t,o){const n=document.body.contains(e),r=n&&f?f(e):null,c=n&&d?d(e,{isReorder:o}):null;(()=>{if(function(e,t){let o=e.firstChild;for(let n=0;n<t.length;n++){const r=t[n];o!==r?e.insertBefore(r,o):o=o.nextSibling}for(;o;){const t=o.nextSibling;e.removeChild(o),o=t}}(g,u),h.size!==w.size)for(const e of h.keys())w.has(e)||(h.delete(e),y.delete(e),m.delete(e))})(),r&&r(),c&&c()}(g,0,c)}let $;if("function"==typeof n.$subscribe)$=n.$subscribe(r,v);else{if("function"!=typeof n.subscribe)return v(),t("[Lume.js] repeat(): store is not reactive (no $subscribe or subscribe method)"),()=>{g.replaceChildren(),h.clear(),y.clear(),m.clear(),w.clear()};{const e=n.subscribe(()=>v());v(),$="function"==typeof e?e:()=>{e?.unsubscribe?.()}}}return()=>{"function"==typeof $&&$(),g.replaceChildren(),h.clear(),y.clear(),m.clear(),w.clear()}},e.show=$,e.state=function(e){if(!e||"object"!=typeof e||Array.isArray(e))throw Error("state() requires a plain object");if(Object.isFrozen(e)||Object.isSealed(e))throw Error("state() requires a mutable plain object");const t=Object.create(null),r=new Map,c=new Set,s=[];let i=!1;e[Symbol("lume.reactive")]=!0;const l=(e,o)=>{t[e]||(t[e]=[]);const n=()=>{c.add(o)};return t[e].push(n),()=>{if(t[e]){const o=t[e].indexOf(n);-1!==o&&(t[e].splice(o,1),0===t[e].length&&delete t[e])}}},a=new Proxy(e,{get(e,t){if("string"==typeof t&&t.startsWith("$"))return e[t];const o=e[t];if(n.size>0)for(const r of n)r(a,t,l);return o},set(e,n,l){const a=e[n];return Object.is(a,l)||(e[n]=l,r.set(n,l),i||(i=!0,queueMicrotask(()=>{let e=0;try{for(;(r.size>0||c.size>0)&&100>e;){e++;for(let e=0;e<s.length;e++)try{s[e]()}catch(n){o("[Lume.js state] Error in beforeFlush hook:",n)}for(const[e,c]of r)if(t[e]){const r=t[e];let s=0;for(;s<r.length;){const t=r[s];try{t(c)}catch(n){o(`[Lume.js state] Error notifying subscriber for key "${e+""}":`,n)}r[s]===t&&s++}}r.clear();const i=Array(c.size);let l=0;for(const e of c)i[l++]=e;c.clear();for(let e=0;e<i.length;e++)try{i[e]()}catch(n){o("[Lume.js state] Error in effect:",n)}}}finally{i=!1}100>e||o("[Lume.js state] Maximum flush iterations reached (100). This usually indicates an infinite loop caused by an effect or computed mutating state it depends on.")}))),!0}});return e.$beforeFlush=e=>{if("function"!=typeof e)throw Error("$beforeFlush requires a function");return-1===s.indexOf(e)&&s.push(e),()=>{const t=s.indexOf(e);-1!==t&&s.splice(t,1)}},e.$subscribe=(e,o)=>{if("function"!=typeof o)throw Error("Subscriber must be a function");return t[e]||(t[e]=[]),t[e].push(o),o(a[e]),()=>{if(t[e]){const n=t[e].indexOf(o);-1!==n&&(t[e].splice(n,1),0===t[e].length&&delete t[e])}}},a},e.stringAttr=k,e.watch=function(e,t,o){if(!e.$subscribe)throw Error("store must be created with state()");return e.$subscribe(t,o)},e.withPlugins=function(e,t=[]){if(!t.length)return e;for(const s of t)try{s.onInit?.()}catch(c){o(`[Lume.js] Plugin "${s.name}" error in onInit:`,c)}const n=new Map;let r;return"function"==typeof e.$beforeFlush&&(r=e.$beforeFlush(function(){for(const[e,r]of n)for(const n of t)try{n.onNotify?.(e,r)}catch(c){o(`[Lume.js] Plugin "${n.name}" error in onNotify:`,c)}n.clear()})),new Proxy(e,{get(e,s){if("$dispose"===s)return()=>{r&&r(),n.clear()};if("string"==typeof s&&s.startsWith("$")){const n=e[s];return"$subscribe"===s&&"function"==typeof n?(e,r)=>{for(const n of t)try{n.onSubscribe?.(e)}catch(c){o(`[Lume.js] Plugin "${n.name}" error in onSubscribe:`,c)}return n(e,r)}:n}let i=e[s];for(const n of t)try{const e=n.onGet?.(s,i);void 0!==e&&(i=e)}catch(c){o(`[Lume.js] Plugin "${n.name}" error in onGet:`,c)}return i},set(e,r,s){const i=e[r];let l=s;for(const n of t)try{const e=n.onSet?.(r,l,i);void 0!==e&&(l=e)}catch(c){o(`[Lume.js] Plugin "${n.name}" error in onSet:`,c)}return Object.is(l,i)||n.set(r,l),e[r]=l,!0}})},e.withReadObserver=r,Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),e}({});
|
|
1
|
+
var Lume=function(e){"use strict";function t(e,...t){void 0!==console&&"function"==typeof console.warn&&console.warn(e,...t)}function o(e,...t){void 0!==console&&"function"==typeof console.error&&console.error(e,...t)}const n=new Set;function r(e,t){n.add(e);try{return t()}finally{n.delete(e)}}const c=e=>({attr:"data-"+e,apply(t,o){t[e]=!!o}}),s=e=>({attr:"data-"+e,apply(t,o){t.setAttribute(e,o?"true":"false")}}),i=[c("hidden"),c("disabled"),c("checked"),c("required"),s("aria-expanded"),s("aria-hidden")];function l(e,t,o,n){const r=u(t,o);if(!r)return null;const{target:c,key:s}=r;return c.$subscribe(s,t=>n.apply(e,t))}function a(e,t,o,n){const r=u(t,o);if(!r)return null;const{target:c,key:s}=r,i=c.$subscribe(s,t=>function(e,t){"INPUT"===e.tagName?"checkbox"===e.type?e.checked=!!t:"radio"===e.type?e.checked=e.value===t+"":e.value=t??"":"TEXTAREA"===e.tagName||"SELECT"===e.tagName?e.value=t??"":e.textContent=t??""}(e,t));return function(e){return"INPUT"===e.tagName||"TEXTAREA"===e.tagName||"SELECT"===e.tagName}(e)&&n.set(e,{target:c,key:s}),i}function u(e,o){if(!o)return null;const n=o.split("."),r=n.pop(),c=function(e,t){if(!t||0===t.length)return e;let o=e;for(let n=0;n<t.length;n++){const e=t[n];if(null==o)return null;if(!(e in o))return null;o=o[e]}return o}(e,n);return null==c?(t(`[Lume.js] Invalid path "${o}"`),null):c?.$subscribe?{target:c,key:r}:(t(`[Lume.js] Target for "${o}" is not reactive`),null)}let f=null;function d(e,t){if("function"!=typeof e)throw Error("effect() requires a function");const n=[];let c=!1;const s=()=>{if(!c){c=!0;try{e()}catch(t){throw o("[Lume.js effect] Error in effect:",t),t}finally{c=!1}}};if(Array.isArray(t)){for(const e of t)if(Array.isArray(e)&&e.length>=2){const[t,...o]=e;if(t&&"function"==typeof t.$subscribe)for(const e of o){let o=!0;const r=t.$subscribe(e,()=>{o?o=!1:s()});n.push(r)}}s()}else{const t=()=>{if(c)return;const s=n.splice(0),i={fn:e,cleanups:n,execute:t,tracking:{}},l=f;f=i,c=!0;try{r((e,t,o)=>{f===i&&(i.tracking[t]||(i.tracking[t]=!0,i.cleanups.push(o(t,i.execute))))},e)}catch(a){throw n.length=0,n.push(...s),o("[Lume.js effect] Error in effect:",a),a}finally{f=l,c=!1}if(n.length>0)for(const e of s)e();else n.push(...s)};t()}return()=>{for(;n.length;)n.pop()()}}function p(e){const t=document.activeElement;if(!e.contains(t))return null;let o=null,n=null;return"INPUT"!==t.tagName&&"TEXTAREA"!==t.tagName||(o=t.selectionStart,n=t.selectionEnd),()=>{document.body.contains(t)&&(t.focus(),null!==o&&null!==n&&t.setSelectionRange(o,n))}}function g(e,t={}){const{isReorder:o=!1}=t,n=e.scrollTop;if(0===n)return()=>{e.scrollTop=0};let r=null,c=0;if(!o){const t=e.getBoundingClientRect();for(let o=e.firstElementChild;o;o=o.nextElementSibling){const e=o.getBoundingClientRect();if(e.bottom>t.top){r=o,c=e.top-t.top;break}}}return()=>{if(r&&document.body.contains(r)){const t=r.getBoundingClientRect(),o=e.getBoundingClientRect(),n=t.top-o.top-c;e.scrollTop=e.scrollTop+n}else e.scrollTop=n}}let b=!0,h=null;const y=new Map;function m(e){return null===h||("string"==typeof h?e.includes(h):!(h instanceof RegExp)||h.test(e))}function w(e,t,o){const n=function(e){return y.has(e)||y.set(e,{gets:new Map,sets:new Map,notifies:new Map}),y.get(e)}(e),r=n[t];r.set(o,(r.get(o)||0)+1)}function E(e){try{const t=JSON.stringify(e);return t.length>100?t.slice(0,97)+"...":t}catch{return e+""}}const v={enable(){b=!0,console.log("%c[lume-debug]%c Logging enabled","color: #888; font-weight: bold","color: #4CAF50")},disable(){b=!1,console.log("%c[lume-debug]%c Logging disabled","color: #888; font-weight: bold","color: #F44336")},isEnabled:()=>b,filter(e){h=e,console.log(null===e?"%c[lume-debug]%c Filter cleared":"%c[lume-debug]%c Filter set: "+e,"color: #888; font-weight: bold","color: inherit")},getFilter:()=>h,stats(){const e={};for(const[t,o]of y)e[t]={gets:Object.fromEntries(o.gets),sets:Object.fromEntries(o.sets),notifies:Object.fromEntries(o.notifies)};return e},logStats(){const e=this.stats();if(0===Object.keys(e).length)return console.log("%c[lume-debug]%c No stats collected yet","color: #888; font-weight: bold","color: inherit"),e;console.group("%c[lume-debug] Statistics","color: #888; font-weight: bold");for(const[t,o]of Object.entries(e)){console.group("%c"+t,"color: #2196F3; font-weight: bold");const e=[],n=new Set([...Object.keys(o.gets),...Object.keys(o.sets),...Object.keys(o.notifies)]);for(const t of n)e.push({key:t,gets:o.gets[t]||0,sets:o.sets[t]||0,notifies:o.notifies[t]||0});e.length>0&&console.table(e),console.groupEnd()}return console.groupEnd(),e},resetStats(){y.clear(),console.log("%c[lume-debug]%c Stats reset","color: #888; font-weight: bold","color: inherit")}},$={attr:"data-show",apply(e,t){e.hidden=!t}};function j(e){return{attr:"data-"+e,apply(t,o){t.toggleAttribute(e,!!o)}}}function S(e){const t=e.startsWith("aria-")?e:"aria-"+e;return{attr:"data-"+t,apply(e,o){e.setAttribute(t,o?"true":"false")}}}function k(e){return{attr:"data-"+e,apply(t,o){null==o?t.removeAttribute(e):t.setAttribute(e,o+"")}}}const L=[j("readonly")],A=[S("pressed"),S("selected"),S("disabled")],x=["readonly","open","novalidate","formnovalidate","multiple","autofocus","autoplay","controls","loop","muted","defer","async","reversed","selected","inert","allowfullscreen"],O=["href","src","alt","title","placeholder","action","method","target","rel","type","name","role","lang","tabindex","pattern","min","max","step","minlength","maxlength","width","height","for","form","accept","autocomplete","loading","decoding","inputmode","enterkeyhint","draggable","contenteditable","spellcheck","translate","dir","id","poster","preload","download","media","sizes","srcset","colspan","rowspan","scope","headers","wrap","sandbox"],T=["pressed","selected","disabled","checked","invalid","required","busy","modal","multiselectable","multiline","readonly","atomic"],F=["current","live","relevant","haspopup","sort","autocomplete","orientation","label","describedby","labelledby","controls","owns","activedescendant","errormessage","details","flowto","valuenow","valuemin","valuemax","valuetext","colcount","colindex","colspan","rowcount","rowindex","rowspan","level","setsize","posinset","placeholder","roledescription","keyshortcuts","braillelabel","brailleroledescription"];return e.a11yHandlers=A,e.ariaAttr=S,e.bindDom=function(e,t,o={}){if(!(e instanceof HTMLElement))throw Error("bindDom() requires a valid HTMLElement as root");if(!t||"object"!=typeof t)throw Error("bindDom() requires a reactive state object");const{immediate:n=!1,handlers:r=[]}=o,c=function(e,t){if(!t.length)return e;const o=new Map;for(const n of e)o.set(n.attr,n);for(const n of t.flat())o.set(n.attr,n);return[...o.values()]}(i,r),s=()=>{const o=[],n=new WeakMap,r=["[data-bind]",...c.map(e=>`[${e.attr}]`)].join(","),s=e.querySelectorAll(r);for(const e of s){if(e.hasAttribute("data-bind")){const r=a(e,t,e.getAttribute("data-bind"),n);r&&o.push(r)}for(const n of c)if(e.hasAttribute(n.attr)){const r=l(e,t,e.getAttribute(n.attr),n);r&&o.push(r)}}const i=e=>{const t=n.get(e.target);var o;t&&(t.target[t.key]="checkbox"===(o=e.target).type?o.checked:"number"===o.type||"range"===o.type?o.valueAsNumber:o.value)};return e.addEventListener("input",i),o.push(()=>e.removeEventListener("input",i)),()=>o.forEach(e=>e())};if(!n&&"loading"===document.readyState){let e=null;const t=()=>{e=s()};return document.addEventListener("DOMContentLoaded",t,{once:!0}),()=>e?e():document.removeEventListener("DOMContentLoaded",t)}return s()},e.boolAttr=j,e.classToggle=function(...e){return e.map(e=>({attr:"data-class-"+e,apply(t,o){t.classList.toggle(e,!!o)}}))},e.computed=function(e){if("function"!=typeof e)throw Error("computed() requires a function");let t,n=!1,r=!1,c=!1;const s=[],i=d(()=>{if(!r&&!c){r=!0;try{const o=e();n&&Object.is(o,t)||(t=o,n=!0,s.forEach(e=>e(t)))}catch(i){o("[Lume.js computed] Error in computation:",i),n&&void 0===t||(t=void 0,n=!0,s.forEach(e=>e(t)))}finally{queueMicrotask(()=>{c||(r=!1)})}}});return{get value(){if(!n)throw Error("Computed value accessed before initialization");return t},subscribe(e){if("function"!=typeof e)throw Error("subscribe() requires a function");return s.push(e),n&&e(t),()=>{const t=s.indexOf(e);t>-1&&s.splice(t,1)}},dispose(){c=!0,i(),s.length=0,n=!1,r=!1}}},e.createCleanupGroup=function(){const e=[];return{add(t){"function"==typeof t&&e.push(t)},dispose(){for(;e.length;){const o=e.pop();try{o()}catch(t){}}}}},e.createDebugPlugin=function(e={}){const t=e.label??"store",o=(t,o)=>{const n=e[t];return void 0!==n?n:o};return{name:"debug:"+t,onInit:()=>{b&&console.log(`%c[${t}]%c initialized`,"color: #888; font-weight: bold","color: inherit")},onGet:(e,n)=>("string"==typeof e&&e.startsWith("$")||(w(t,"gets",e),b&&o("logGet",!1)&&m(e)&&console.log(`%c[${t}]%c GET %c${e}%c = ${E(n)}`,"color: #888; font-weight: bold","color: #4CAF50","color: #2196F3; font-weight: bold","color: inherit")),n),onSet:(e,n,r)=>("string"==typeof e&&e.startsWith("$")||(w(t,"sets",e),b&&o("logSet",!0)&&m(e)&&(console.log(`%c[${t}]%c SET %c${e}%c: ${E(r)} → ${E(n)}`,"color: #888; font-weight: bold","color: #FF9800","color: #2196F3; font-weight: bold","color: inherit"),o("trace",!1)&&console.trace(`%c[${t}] Stack trace for ${e}`,"color: #888"))),n),onSubscribe:e=>{b&&m(e)&&console.log(`%c[${t}]%c SUBSCRIBE %c${e}`,"color: #888; font-weight: bold","color: #9C27B0","color: #2196F3; font-weight: bold")},onNotify:(e,n)=>{"string"==typeof e&&e.startsWith("$")||(w(t,"notifies",e),b&&o("logNotify",!0)&&m(e)&&console.log(`%c[${t}]%c NOTIFY %c${e}%c = ${E(n)}`,"color: #888; font-weight: bold","color: #E91E63","color: #2196F3; font-weight: bold","color: inherit"))}}},e.debug=v,e.defaultFocusPreservation=p,e.defaultScrollPreservation=g,e.effect=d,e.formHandlers=L,e.htmlAttrs=function(){return[$,...x.map(e=>j(e)),...O.map(e=>k(e)),...T.map(e=>S(e)),...F.map(e=>k("aria-"+e))]},e.hydrateState=function(e="#__LUME_DATA__"){const t="undefined"!=typeof document?document.querySelector(e):null;if(!t)return{};try{return JSON.parse(t.textContent)}catch{return{}}},e.isReactive=function(e){return!(!e||"object"!=typeof e||"function"!=typeof e.$subscribe)},e.repeat=function(e,n,r,c){const{key:s,render:i,create:l,update:a,remove:u,element:f="div",preserveFocus:d=p,preserveScroll:b=g}=c,h="string"==typeof e?document.querySelector(e):e;if(!h)return t(`[Lume.js] repeat(): container "${e}" not found`),()=>{};if("function"!=typeof s)throw Error("[Lume.js] repeat(): options.key must be a function");if("function"!=typeof i&&"function"!=typeof l)throw Error("[Lume.js] repeat(): options.render or options.create must be a function");const y=new Map,m=new Map,w=new Map,E=new Map,v=new Set;function $(){return"function"==typeof f?f():document.createElement(f)}function j(){const e=n[r];if(!Array.isArray(e))return void t(`[Lume.js] repeat(): store.${r} is not an array`);let c=!1;if(b&&y.size===e.length){c=!0;for(let t=0;t<e.length;t++)if(!y.has(s(e[t]))){c=!1;break}}v.clear();const f=[];for(let n=0;n<e.length;n++){const r=e[n],c=s(r);if(v.has(c)){t(`[Lume.js] repeat(): duplicate key "${c}"`);continue}v.add(c);let u=y.get(c);const d=!u;d&&(u=$(),y.set(c,u));try{if(d&&l){const e=l(r,u,n);"function"==typeof e&&E.set(c,e)}const e=m.get(c),t=w.get(c);a?e===r&&t===n||a(r,u,n,{isFirstRender:d}):i&&i(r,u,n),m.set(c,r),w.set(c,n)}catch(p){o(`[Lume.js] repeat(): error rendering key "${c}":`,p)}f.push(u)}!function(e,t,n){const r=document.body.contains(e),c=r&&d?d(e):null,s=r&&b?b(e,{isReorder:n}):null;(()=>{if(function(e,t){let o=e.firstChild;for(let n=0;n<t.length;n++){const r=t[n];o!==r?e.insertBefore(r,o):o=o.nextSibling}for(;o;){const t=o.nextSibling;e.removeChild(o),o=t}}(h,f),y.size!==v.size)for(const e of y.keys())if(!v.has(e)){const t=y.get(e),n=m.get(e),r=E.get(e);if("function"==typeof r)try{r()}catch(p){o(`[Lume.js] repeat(): cleanup error for key "${e}":`,p)}"function"==typeof u&&t&&u(n,t),y.delete(e),m.delete(e),w.delete(e),E.delete(e)}})(),c&&c(),s&&s()}(h,0,c)}let S;if("function"==typeof n.$subscribe)S=n.$subscribe(r,j);else{if("function"!=typeof n.subscribe)return j(),t("[Lume.js] repeat(): store is not reactive (no $subscribe or subscribe method)"),()=>{for(const[t,n]of y){const r=m.get(t),c=E.get(t);if("function"==typeof c)try{c()}catch(e){o(`[Lume.js] repeat(): cleanup error for key "${t}":`,e)}"function"==typeof u&&u(r,n)}h.replaceChildren(),y.clear(),m.clear(),w.clear(),E.clear(),v.clear()};{const e=n.subscribe(()=>j());j(),S="function"==typeof e?e:()=>{e?.unsubscribe?.()}}}return()=>{"function"==typeof S&&S();for(const[t,n]of y){const r=m.get(t),c=E.get(t);if("function"==typeof c)try{c()}catch(e){o(`[Lume.js] repeat(): cleanup error for key "${t}":`,e)}"function"==typeof u&&u(r,n)}h.replaceChildren(),y.clear(),m.clear(),w.clear(),E.clear(),v.clear()}},e.show=$,e.state=function(e){if(!e||"object"!=typeof e||Array.isArray(e))throw Error("state() requires a plain object");if(Object.isFrozen(e)||Object.isSealed(e))throw Error("state() requires a mutable plain object");const t=Object.create(null),r=new Map,c=new Set,s=[];let i=!1;e[Symbol("lume.reactive")]=!0;const l=(e,o)=>{t[e]||(t[e]=[]);const n=()=>{c.add(o)};return t[e].push(n),()=>{if(t[e]){const o=t[e].indexOf(n);-1!==o&&(t[e].splice(o,1),0===t[e].length&&delete t[e])}}},a=new Proxy(e,{get(e,t){if("string"==typeof t&&t.startsWith("$"))return e[t];const o=e[t];if(n.size>0)for(const r of n)r(a,t,l);return o},set(e,n,l){const a=e[n];return Object.is(a,l)||(e[n]=l,r.set(n,l),i||(i=!0,queueMicrotask(()=>{let e=0;try{for(;(r.size>0||c.size>0)&&100>e;){e++;for(let e=0;e<s.length;e++)try{s[e]()}catch(n){o("[Lume.js state] Error in beforeFlush hook:",n)}for(const[e,c]of r)if(t[e]){const r=t[e];let s=0;for(;s<r.length;){const t=r[s];try{t(c)}catch(n){o(`[Lume.js state] Error notifying subscriber for key "${e+""}":`,n)}r[s]===t&&s++}}r.clear();const i=Array(c.size);let l=0;for(const e of c)i[l++]=e;c.clear();for(let e=0;e<i.length;e++)try{i[e]()}catch(n){o("[Lume.js state] Error in effect:",n)}}}finally{i=!1}100>e||o("[Lume.js state] Maximum flush iterations reached (100). This usually indicates an infinite loop caused by an effect or computed mutating state it depends on.")}))),!0}});return e.$beforeFlush=e=>{if("function"!=typeof e)throw Error("$beforeFlush requires a function");return-1===s.indexOf(e)&&s.push(e),()=>{const t=s.indexOf(e);-1!==t&&s.splice(t,1)}},e.$subscribe=(e,o)=>{if("function"!=typeof o)throw Error("Subscriber must be a function");return t[e]||(t[e]=[]),t[e].push(o),o(a[e]),()=>{if(t[e]){const n=t[e].indexOf(o);-1!==n&&(t[e].splice(n,1),0===t[e].length&&delete t[e])}}},a},e.stringAttr=k,e.watch=function(e,t,o){if(!e.$subscribe)throw Error("store must be created with state()");return e.$subscribe(t,o)},e.withPlugins=function(e,t=[]){if(!t.length)return e;for(const s of t)try{s.onInit?.()}catch(c){o(`[Lume.js] Plugin "${s.name}" error in onInit:`,c)}const n=new Map;let r;return"function"==typeof e.$beforeFlush&&(r=e.$beforeFlush(function(){for(const[e,r]of n)for(const n of t)try{n.onNotify?.(e,r)}catch(c){o(`[Lume.js] Plugin "${n.name}" error in onNotify:`,c)}n.clear()})),new Proxy(e,{get(e,s){if("$dispose"===s)return()=>{r&&r(),n.clear()};if("string"==typeof s&&s.startsWith("$")){const n=e[s];return"$subscribe"===s&&"function"==typeof n?(e,r)=>{for(const n of t)try{n.onSubscribe?.(e)}catch(c){o(`[Lume.js] Plugin "${n.name}" error in onSubscribe:`,c)}return n(e,r)}:n}let i=e[s];for(const n of t)try{const e=n.onGet?.(s,i);void 0!==e&&(i=e)}catch(c){o(`[Lume.js] Plugin "${n.name}" error in onGet:`,c)}return i},set(e,r,s){const i=e[r];let l=s;for(const n of t)try{const e=n.onSet?.(r,l,i);void 0!==e&&(l=e)}catch(c){o(`[Lume.js] Plugin "${n.name}" error in onSet:`,c)}return Object.is(l,i)||n.set(r,l),e[r]=l,!0}})},e.withReadObserver=r,Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),e}({});
|
|
2
2
|
//# sourceMappingURL=lume.global.js.map
|
package/dist/lume.global.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"lume.global.js","sources":["../src/utils/log.js","../src/core/state.js","../src/core/bindDom.js","../src/core/effect.js","../src/addons/repeat.js","../src/addons/debug.js","../src/handlers/index.js","../src/addons/computed.js","../src/addons/cleanupGroup.js","../src/addons/hydrateState.js","../src/addons/index.js","../src/addons/watch.js","../src/addons/withPlugins.js"],"sourcesContent":["/**\n * Environment-safe logging utilities for constrained runtimes\n * (e.g. service workers, embedded engines, SSR environments).\n *\n * All core and addon files should import these instead of\n * calling console.* directly to avoid ReferenceError when\n * console is not defined.\n */\n\nexport function logWarn(msg, ...rest) {\n if (typeof console !== 'undefined' && typeof console.warn === 'function') {\n console.warn(msg, ...rest);\n }\n}\n\nexport function logError(msg, ...rest) {\n if (typeof console !== 'undefined' && typeof console.error === 'function') {\n console.error(msg, ...rest);\n }\n}\n","/**\n * Lume-JS Reactive State Core\n *\n * Provides minimal reactive state with standard JavaScript.\n * Features automatic microtask batching for performance.\n * Read tracking is opt-in via withReadObserver — state.js has zero permanent\n * dependency on effect.js or any other module.\n *\n * Features:\n * - Lightweight and Go-style\n * - Explicit nested states\n * - $subscribe for listening to key changes\n * - Cleanup with unsubscribe\n * - Per-state microtask batching for writes\n * - Scope-based read tracking via withReadObserver (multi-observer safe)\n *\n * Usage:\n * import { state } from \"lume-js\";\n *\n * const store = state({ count: 0 });\n * const unsub = store.$subscribe(\"count\", val => console.log(val));\n * unsub(); // cleanup\n */\n\nimport { logError } from '../utils/log.js';\n\n// Per-state batching – each state object maintains its own microtask flush.\n// This keeps effects simple and aligned with Lume's minimal philosophy.\n\n/**\n * Creates a reactive state object.\n *\n * @param {Object} obj - Initial state object (must be plain object)\n * @returns {Proxy} Reactive proxy with $subscribe method\n *\n * @example\n * const store = state({ count: 0 });\n */\n\n// Active read observers — only populated during withReadObserver scopes.\n// This keeps state.js pure: tracking only happens when someone explicitly\n// asks to observe reads within a synchronous function call.\n//\n// Note: This Set is module-level, so all reactive state instances and effects\n// within the SAME module instance share it. This is standard behavior for\n// auto-tracking reactive libraries (Vue, MobX, Solid, etc.). Multiple copies\n// of the lume-js module (e.g. from different bundled chunks) each get their\n// own independent Set via ES module / CommonJS isolation.\nconst readers = new Set();\n\n/**\n * Run a function with a read observer active.\n * The observer receives (proxy, key, registerEffect) for every property read.\n * Multiple observers can be active simultaneously (nested effects, devtools, etc.)\n *\n * Internal API — used by effect.js for auto-tracking. May be stabilized\n * for third-party addons in a future release.\n * @param {function} onRead - Called on each property access inside fn\n * @param {function} fn - The function to run under observation\n */\nexport function withReadObserver(onRead, fn) {\n readers.add(onRead);\n try {\n return fn();\n } finally {\n readers.delete(onRead);\n }\n}\n\nexport function state(obj) {\n // Validate input\n if (!obj || typeof obj !== 'object' || Array.isArray(obj)) {\n throw new Error('state() requires a plain object');\n }\n if (Object.isFrozen(obj) || Object.isSealed(obj)) {\n throw new Error('state() requires a mutable plain object');\n }\n\n // Object.create(null) - no prototype chain lookups\n const listeners = Object.create(null);\n const pendingNotifications = new Map(); // Per-state pending changes\n const pendingEffects = new Set(); // Dedupe effects per state\n const beforeFlushHooks = [];\n let flushScheduled = false;\n\n /**\n * Schedule a single microtask flush for this state object.\n *\n * Flush order per state:\n * 1) Notify subscribers for changed keys (key → subscribers)\n * 2) Run each queued effect exactly once (Set-based dedupe)\n * 3) Repeat up to 100 iterations to handle cascading updates,\n * then log an error to prevent infinite loops.\n *\n * Notes:\n * - Batching is per state; effects that depend on multiple states\n * may run once per state that changed (by design).\n */\n function scheduleFlush() {\n if (flushScheduled) return;\n\n flushScheduled = true;\n queueMicrotask(() => {\n let iterations = 0;\n const MAX_ITERATIONS = 100;\n\n try {\n while ((pendingNotifications.size > 0 || pendingEffects.size > 0) && iterations < MAX_ITERATIONS) {\n iterations++;\n\n // Run registered before-flush hooks (e.g. plugin onNotify)\n for (let i = 0; i < beforeFlushHooks.length; i++) {\n try {\n beforeFlushHooks[i]();\n } catch (err) {\n logError('[Lume.js state] Error in beforeFlush hook:', err);\n }\n }\n\n // Notify all subscribers of changed keys\n for (const [key, value] of pendingNotifications) {\n if (listeners[key]) {\n const subs = listeners[key];\n let i = 0;\n while (i < subs.length) {\n const fn = subs[i];\n try {\n fn(value);\n } catch (err) {\n logError(`[Lume.js state] Error notifying subscriber for key \"${String(key)}\":`, err);\n }\n // Only advance if fn wasn't removed (something shifted into its place)\n if (subs[i] === fn) i++;\n }\n }\n }\n\n pendingNotifications.clear();\n\n // Run each effect exactly once (Set deduplicates)\n const effects = new Array(pendingEffects.size);\n let idx = 0;\n for (const effect of pendingEffects) {\n effects[idx++] = effect;\n }\n pendingEffects.clear();\n for (let i = 0; i < effects.length; i++) {\n try {\n effects[i]();\n } catch (err) {\n logError('[Lume.js state] Error in effect:', err);\n }\n }\n }\n } finally {\n flushScheduled = false;\n }\n\n if (iterations >= MAX_ITERATIONS) {\n logError(\n '[Lume.js state] Maximum flush iterations reached (100). ' +\n 'This usually indicates an infinite loop caused by an effect or computed mutating state it depends on.'\n );\n }\n });\n }\n\n // Brand symbol for type-level reactive identification\n const REACTIVE_BRAND = Symbol('lume.reactive');\n obj[REACTIVE_BRAND] = true;\n\n // Defined once per state instance — not per property read — to avoid per-read closure allocation.\n const registerEffect = (key, executeFn) => {\n if (!listeners[key]) listeners[key] = [];\n\n const callback = () => {\n pendingEffects.add(executeFn);\n };\n\n listeners[key].push(callback);\n\n return () => {\n if (listeners[key]) {\n const idx = listeners[key].indexOf(callback);\n if (idx !== -1) {\n listeners[key].splice(idx, 1);\n if (listeners[key].length === 0) delete listeners[key];\n }\n }\n };\n };\n\n const proxy = new Proxy(obj, {\n get(target, key) {\n // Skip effect tracking for internal meta methods (e.g. $subscribe)\n if (typeof key === 'string' && key.startsWith('$')) {\n return target[key];\n }\n\n const value = target[key];\n\n // Notify active read observers (effects, devtools, etc.)\n if (readers.size > 0) {\n for (const reader of readers) {\n reader(proxy, key, registerEffect);\n }\n }\n\n return value;\n },\n\n set(target, key, value) {\n const oldValue = target[key];\n\n // Skip update if value unchanged - Object.is() handles NaN and -0 correctly\n if (Object.is(oldValue, value)) return true;\n\n target[key] = value;\n\n // Batch notifications at the state level (per-state, not global)\n pendingNotifications.set(key, value);\n scheduleFlush();\n\n return true;\n }\n });\n\n /**\n * Subscribe to changes for a specific key.\n * Calls the callback immediately with the current value.\n * Returns an unsubscribe function for cleanup.\n *\n * @param {string} key - Property key to watch\n * @param {function} fn - Callback function\n * @returns {function} Unsubscribe function\n */\n // Set on obj (not proxy) to avoid triggering the set trap.\n // The get trap already returns target[key] directly for $-prefixed keys.\n /**\n * Register a callback to run before each flush.\n * Returns an unsubscribe function.\n */\n obj.$beforeFlush = (fn) => {\n if (typeof fn !== 'function') {\n throw new Error('$beforeFlush requires a function');\n }\n if (beforeFlushHooks.indexOf(fn) === -1) {\n beforeFlushHooks.push(fn);\n }\n return () => {\n const idx = beforeFlushHooks.indexOf(fn);\n if (idx !== -1) {\n beforeFlushHooks.splice(idx, 1);\n }\n };\n };\n\n obj.$subscribe = (key, fn) => {\n if (typeof fn !== 'function') {\n throw new Error('Subscriber must be a function');\n }\n\n if (!listeners[key]) listeners[key] = [];\n listeners[key].push(fn);\n\n // Call immediately with current value (NOT batched)\n fn(proxy[key]);\n\n // Return unsubscribe function\n return () => {\n if (listeners[key]) {\n const idx = listeners[key].indexOf(fn);\n if (idx !== -1) {\n listeners[key].splice(idx, 1);\n if (listeners[key].length === 0) delete listeners[key];\n }\n }\n };\n };\n\n return proxy;\n}\n","// src/core/bindDom.js\n/**\n * Lume-JS DOM Binding\n *\n * Binds reactive state to DOM elements using data-* attributes.\n *\n * Built-in attributes (always available):\n * data-bind=\"key\" → Two-way binding for inputs, textContent for others\n * data-hidden=\"key\" → Toggles hidden (truthy = hidden)\n * data-disabled=\"key\" → Toggles disabled (truthy = disabled)\n * data-checked=\"key\" → Toggles checked (for checkboxes/radios)\n * data-required=\"key\" → Toggles required (truthy = required)\n * data-aria-expanded=\"key\" → Sets aria-expanded to \"true\"/\"false\"\n * data-aria-hidden=\"key\" → Sets aria-hidden to \"true\"/\"false\"\n *\n * Extensible via handlers option:\n * import { show, classToggle } from 'lume-js/handlers';\n * bindDom(root, store, { handlers: [show, classToggle('active')] });\n *\n * Custom handlers:\n * const tooltip = { attr: 'data-tooltip', apply(el, val) { el.title = val ?? ''; } };\n * bindDom(root, store, { handlers: [tooltip] });\n *\n * Usage:\n * import { bindDom } from \"lume-js\";\n * const cleanup = bindDom(document.body, store);\n */\n\nimport { logWarn } from '../utils/log.js';\n\n// --- Default Handlers (always active, backwards compatible) ---\n\nconst boolHandler = (name) => ({\n attr: `data-${name}`,\n apply(el, val) { el[name] = Boolean(val); }\n});\n\nconst ariaHandler = (name) => ({\n attr: `data-${name}`,\n apply(el, val) { el.setAttribute(name, val ? 'true' : 'false'); }\n});\n\nconst DEFAULT_HANDLERS = [\n boolHandler('hidden'),\n boolHandler('disabled'),\n boolHandler('checked'),\n boolHandler('required'),\n ariaHandler('aria-expanded'),\n ariaHandler('aria-hidden'),\n];\n\n/**\n * Merge default and user handlers.\n * User handlers override defaults with same attr (Map deduplicates).\n * User handler arrays are flattened one level (supports classToggle()).\n */\nfunction mergeHandlers(defaults, userHandlers) {\n if (!userHandlers.length) return defaults;\n const merged = new Map();\n for (const h of defaults) merged.set(h.attr, h);\n for (const h of userHandlers.flat()) merged.set(h.attr, h);\n return [...merged.values()];\n}\n\n/**\n * DOM binding for reactive state\n */\nexport function bindDom(root, store, options = {}) {\n if (!(root instanceof HTMLElement)) {\n throw new Error('bindDom() requires a valid HTMLElement as root');\n }\n if (!store || typeof store !== 'object') {\n throw new Error('bindDom() requires a reactive state object');\n }\n\n const { immediate = false, handlers: userHandlers = [] } = options;\n const handlers = mergeHandlers(DEFAULT_HANDLERS, userHandlers);\n\n const performBinding = () => {\n const cleanups = [];\n const bindingMap = new WeakMap();\n\n // Build compiled selector: data-bind (always) + all handler attrs\n const selector = ['[data-bind]', ...handlers.map(h => `[${h.attr}]`)].join(',');\n const elements = root.querySelectorAll(selector);\n\n for (const el of elements) {\n // data-bind (two-way) — always in core, special handling\n if (el.hasAttribute('data-bind')) {\n const c = handleDataBind(el, store, el.getAttribute('data-bind'), bindingMap);\n if (c) cleanups.push(c);\n }\n\n // All registered handlers (default + user)\n for (const handler of handlers) {\n if (el.hasAttribute(handler.attr)) {\n const c = applyHandler(el, store, el.getAttribute(handler.attr), handler);\n if (c) cleanups.push(c);\n }\n }\n }\n\n // Event delegation for two-way bindings\n const inputHandler = e => {\n const binding = bindingMap.get(e.target);\n if (binding) binding.target[binding.key] = getInputValue(e.target);\n };\n root.addEventListener(\"input\", inputHandler);\n cleanups.push(() => root.removeEventListener(\"input\", inputHandler));\n\n return () => cleanups.forEach(c => c());\n };\n\n // Auto-wait for DOM if needed\n if (!immediate && document.readyState === 'loading') {\n let cleanup = null;\n const onReady = () => { cleanup = performBinding(); };\n document.addEventListener('DOMContentLoaded', onReady, { once: true });\n return () => cleanup ? cleanup() : document.removeEventListener('DOMContentLoaded', onReady);\n }\n\n return performBinding();\n}\n\n/**\n * Apply a handler to an element via subscription.\n * Resolves the state path and subscribes to changes.\n */\nfunction applyHandler(el, store, path, handler) {\n const result = resolveProp(store, path);\n if (!result) return null;\n const { target, key } = result;\n return target.$subscribe(key, val => handler.apply(el, val));\n}\n\n/**\n * Handle data-bind (two-way for inputs, textContent for others)\n */\nfunction handleDataBind(el, store, path, bindingMap) {\n const result = resolveProp(store, path);\n if (!result) return null;\n\n const { target, key } = result;\n const unsub = target.$subscribe(key, val => updateElement(el, val));\n\n if (isFormInput(el)) {\n bindingMap.set(el, { target, key });\n }\n\n return unsub;\n}\n\n/**\n * Resolve a nested path in an object.\n * Example: resolvePath(obj, ['user', 'address']) returns obj.user.address\n */\nfunction resolvePath(obj, pathArr) {\n if (!pathArr || pathArr.length === 0) {\n return obj;\n }\n let current = obj;\n for (let i = 0; i < pathArr.length; i++) {\n const key = pathArr[i];\n if (current === null || current === undefined) {\n return null;\n }\n if (!(key in current)) {\n return null;\n }\n current = current[key];\n }\n return current;\n}\n\n/**\n * Resolve path to target and key.\n *\n * ⚠️ Path bindings are resolved once at bind time. If an intermediate\n * object in the path is null/undefined at bindDom call time, the binding\n * is permanently dead and will not self-heal when the path later becomes valid.\n */\nfunction resolveProp(store, path) {\n if (!path) return null;\n\n const pathArr = path.split(\".\");\n const key = pathArr.pop();\n const target = resolvePath(store, pathArr);\n\n if (target === null || target === undefined) {\n logWarn(`[Lume.js] Invalid path \"${path}\"`);\n return null;\n }\n\n if (!target?.$subscribe) {\n logWarn(`[Lume.js] Target for \"${path}\" is not reactive`);\n return null;\n }\n\n return { target, key };\n}\n\n/**\n * Update element with value (for data-bind)\n */\nfunction updateElement(el, val) {\n if (el.tagName === \"INPUT\") {\n if (el.type === \"checkbox\") el.checked = Boolean(val);\n else if (el.type === \"radio\") el.checked = el.value === String(val);\n else el.value = val ?? '';\n } else if (el.tagName === \"TEXTAREA\" || el.tagName === \"SELECT\") {\n el.value = val ?? '';\n } else {\n el.textContent = val ?? '';\n }\n}\n\n/**\n * Get value from input\n */\nfunction getInputValue(el) {\n if (el.type === \"checkbox\") return el.checked;\n if (el.type === \"number\" || el.type === \"range\") return el.valueAsNumber;\n return el.value;\n}\n\n/**\n * Check if element is form input\n */\nfunction isFormInput(el) {\n return el.tagName === \"INPUT\" || el.tagName === \"TEXTAREA\" || el.tagName === \"SELECT\";\n}","import { withReadObserver } from './state.js';\nimport { logError } from '../utils/log.js';\n\n/**\n * Lume-JS Effect\n *\n * Reactive effects with two modes:\n * 1. Auto-tracking (default): Tracks dependencies automatically via withReadObserver\n * 2. Explicit deps: You specify exactly what triggers re-runs\n *\n * Auto-tracking uses scope-based read observation — state.js has zero permanent\n * dependency on this module. Read tracking is only active during the synchronous\n * execution of an effect's body.\n *\n * Usage:\n * import { effect } from \"lume-js\";\n *\n * // Auto-tracking mode (existing behavior)\n * effect(() => {\n * console.log('Count is:', store.count);\n * // Automatically re-runs when store.count changes\n * });\n *\n * // Explicit deps mode (new - no magic)\n * effect(() => {\n * console.log('Count is:', store.count);\n * }, [[store, 'count']]); // Only re-runs when store.count changes\n *\n * Features:\n * - Automatic dependency collection via withReadObserver scope (default)\n * - Explicit dependencies for side-effects\n * - Returns cleanup function\n * - Compatible with per-state batching\n */\n\n// Module-scoped effect context (prevents third-party spoofing via globalThis)\nlet currentEffect = null;\n\n// withReadObserver is used below to scope read tracking to synchronous effect execution.\n\n/**\n * Creates an effect that runs reactively\n *\n * @param {function} fn - Function to run reactively\n * @param {Array<[object, string]>} [deps] - Optional explicit dependencies as [store, key] tuples\n * @returns {function} Cleanup function to stop the effect\n *\n * @example\n * // Auto-tracking (default)\n * const store = state({ count: 0 });\n * effect(() => {\n * document.title = `Count: ${store.count}`;\n * });\n * \n * @example\n * // Explicit deps (no magic)\n * effect(() => {\n * analytics.log(store.count); // Won't track store.count automatically\n * }, [[store, 'count']]); // Explicit: only re-run on store.count\n */\nexport function effect(fn, deps) {\n if (typeof fn !== 'function') {\n throw new Error('effect() requires a function');\n }\n\n const cleanups = [];\n let isRunning = false;\n\n /**\n * Execute the effect function\n */\n const execute = () => {\n /* v8 ignore next -- re-entry guard: unreachable because $subscribe fires via microtask after isRunning resets in finally */\n if (isRunning) return;\n isRunning = true;\n\n try {\n fn();\n } catch (error) {\n logError('[Lume.js effect] Error in effect:', error);\n throw error;\n } finally {\n isRunning = false;\n }\n };\n\n // EXPLICIT DEPS MODE: deps array provided\n if (Array.isArray(deps)) {\n // Subscribe to each [store, key1, key2, ...] tuple explicitly\n for (const dep of deps) {\n if (Array.isArray(dep) && dep.length >= 2) {\n const [store, ...keys] = dep;\n if (store && typeof store.$subscribe === 'function') {\n // Subscribe to each key in this tuple\n for (const key of keys) {\n // $subscribe calls immediately, then on changes\n // We want: call execute immediately once, then on changes\n let isFirst = true;\n const unsub = store.$subscribe(key, () => {\n if (isFirst) {\n isFirst = false;\n return; // Skip first call, we'll run execute() below\n }\n execute();\n });\n cleanups.push(unsub);\n }\n }\n }\n }\n // Run immediately\n execute();\n }\n // AUTO-TRACKING MODE: no deps (existing behavior)\n else {\n const executeWithTracking = () => {\n /* v8 ignore next -- defensive guard: synchronous re-entry is unreachable through the public API */\n if (isRunning) return;\n\n // Save previous subscriptions instead of cleaning immediately.\n // If fn() doesn't read any state (early return / error), we restore\n // them so the effect stays reactive.\n const oldCleanups = cleanups.splice(0);\n\n // Create effect context for tracking\n const myContext = {\n fn,\n cleanups,\n execute: executeWithTracking,\n tracking: {}\n };\n\n // Set as current effect (for state.js to detect)\n // Save previous context to support nested effects/computed\n const previousEffect = currentEffect;\n currentEffect = myContext;\n isRunning = true;\n\n try {\n const onRead = (proxy, key, registerEffect) => {\n // Only the currently active effect (not a nested one) creates subscriptions\n if (currentEffect !== myContext) return;\n if (myContext.tracking[key]) return;\n myContext.tracking[key] = true;\n myContext.cleanups.push(registerEffect(key, myContext.execute));\n };\n withReadObserver(onRead, fn);\n } catch (error) {\n // On error, restore old subscriptions so the effect stays reactive\n cleanups.length = 0;\n cleanups.push(...oldCleanups);\n logError('[Lume.js effect] Error in effect:', error);\n throw error;\n } finally {\n // Restore previous context (not undefined) to support nesting\n currentEffect = previousEffect;\n isRunning = false;\n }\n\n // If fn() created new subscriptions, clean old ones.\n // If it didn't (e.g., early return), keep old subscriptions intact.\n if (cleanups.length > 0) {\n for (const cleanup of oldCleanups) cleanup();\n } else {\n cleanups.push(...oldCleanups);\n }\n };\n\n // Run immediately to collect initial dependencies\n executeWithTracking();\n }\n\n // Return cleanup function\n return () => {\n // while/pop is faster than forEach\n while (cleanups.length) cleanups.pop()();\n };\n}","/**\n * Lume-JS List Rendering (Addon)\n *\n * Renders lists with automatic subscription and element reuse by key.\n * \n * Core guarantees:\n * Element reuse by key (same DOM nodes, not recreated)\n * Minimal DOM operations (only updates what changed)\n * Memory efficiency (cleanup on remove)\n * \n * Default behavior (can be disabled/customized):\n * ✅ Focus preservation (maintains activeElement and selection)\n * ✅ Scroll preservation (intelligent positioning for add/remove/reorder)\n * \n * Philosophy: No artificial limitations\n * - All preservation logic is overridable via options\n * - Set to null/false to disable, or provide custom functions\n * - Export utilities so you can wrap/extend them\n *\n * ⚠️ IMPORTANT: Arrays must be updated immutably!\n * store.items.push(x) // ❌ Won't trigger update\n * store.items = [...items] // ✅ Triggers update\n * \n * ═══════════════════════════════════════════════════════════════════════\n * PATTERN 1: Simple (render only) - for simple cases or backward compat\n * ═══════════════════════════════════════════════════════════════════════\n * \n * repeat('#list', store, 'todos', {\n * key: todo => todo.id,\n * render: (todo, el) => {\n * el.textContent = todo.name; // Called on every update\n * }\n * });\n *\n * ═══════════════════════════════════════════════════════════════════════\n * PATTERN 2: Clean separation (create + update) - recommended\n * ═══════════════════════════════════════════════════════════════════════\n * \n * repeat('#list', store, 'todos', {\n * key: todo => todo.id,\n * create: (todo, el) => {\n * // Called ONCE when element is created - build DOM structure\n * const nameSpan = document.createElement('span');\n * nameSpan.className = 'name';\n * el.appendChild(nameSpan);\n * const btn = document.createElement('button');\n * btn.textContent = 'Delete';\n * btn.onclick = () => deleteTodo(todo.id);\n * el.appendChild(btn);\n * },\n * update: (todo, el, index, { isFirstRender }) => {\n * // Called on every update - bind data\n * // isFirstRender = true on initial render, false on subsequent\n * // Skipped if same object reference (optimization)\n * el.querySelector('.name').textContent = todo.name;\n * }\n * });\n *\n * ═══════════════════════════════════════════════════════════════════════\n * ADVANCED: Custom preservation strategies\n * ═══════════════════════════════════════════════════════════════════════\n * \n * import { defaultFocusPreservation, defaultScrollPreservation } from \"lume-js/addons\";\n * \n * repeat('#list', store, 'items', {\n * key: item => item.id,\n * create: (item, el) => { ... },\n * update: (item, el) => { ... },\n * preserveFocus: null, // disable focus preservation\n * preserveScroll: (container, context) => {\n * const restore = defaultScrollPreservation(container, context);\n * return () => { restore(); console.log('Scroll restored!'); };\n * }\n * });\n */\nimport { logWarn, logError } from '../utils/log.js';\n\n/**\n * Default focus preservation strategy\n * Saves activeElement and selection state before DOM updates\n * \n * @param {HTMLElement} container - The list container\n * @returns {Function|null} Restore function, or null if nothing to restore\n */\nexport function defaultFocusPreservation(container) {\n const activeEl = document.activeElement;\n const shouldRestore = container.contains(activeEl);\n\n if (!shouldRestore) return null;\n\n let selectionStart = null;\n let selectionEnd = null;\n\n if (activeEl.tagName === 'INPUT' || activeEl.tagName === 'TEXTAREA') {\n selectionStart = activeEl.selectionStart;\n selectionEnd = activeEl.selectionEnd;\n }\n\n return () => {\n if (document.body.contains(activeEl)) {\n activeEl.focus();\n if (selectionStart !== null && selectionEnd !== null) {\n activeEl.setSelectionRange(selectionStart, selectionEnd);\n }\n }\n };\n}\n\n/**\n * Default scroll preservation strategy\n * Uses anchor-based preservation for add/remove, pixel position for reorder\n * \n * @param {HTMLElement} container - The list container\n * @param {Object} context - Additional context\n * @param {boolean} context.isReorder - Whether this is a reorder operation\n * @returns {Function} Restore function\n */\nexport function defaultScrollPreservation(container, context = {}) {\n const { isReorder = false } = context;\n const scrollTop = container.scrollTop;\n\n // Early return if no scroll\n if (scrollTop === 0) {\n return () => { container.scrollTop = 0; };\n }\n\n let anchorElement = null;\n let anchorOffset = 0;\n\n // Only use anchor-based preservation for add/remove, not reorder\n if (!isReorder) {\n const containerRect = container.getBoundingClientRect();\n // Avoid Array.from - iterate children directly\n for (let child = container.firstElementChild; child; child = child.nextElementSibling) {\n const rect = child.getBoundingClientRect();\n\n if (rect.bottom > containerRect.top) {\n anchorElement = child;\n anchorOffset = rect.top - containerRect.top;\n break;\n }\n }\n }\n\n return () => {\n if (anchorElement && document.body.contains(anchorElement)) {\n const newRect = anchorElement.getBoundingClientRect();\n const containerRect = container.getBoundingClientRect();\n const currentOffset = newRect.top - containerRect.top;\n const scrollAdjustment = currentOffset - anchorOffset;\n\n container.scrollTop = container.scrollTop + scrollAdjustment;\n } else {\n container.scrollTop = scrollTop;\n }\n };\n}\n\n/**\n * Efficiently render a list with element reuse\n * \n * @param {string|HTMLElement} container - Container element or selector\n * @param {Object} store - Reactive state object\n * @param {string} arrayKey - Key in store containing the array\n * @param {Object} options - Configuration\n * @param {Function} options.key - Function to extract unique key: (item) => key\n * @param {Function} [options.render] - Function to render item (called for all items): (item, element, index) => void\n * @param {Function} [options.create] - Function for new elements only: (item, element, index) => void\n * @param {Function} [options.update] - Function for data binding: (item, element, index, { isFirstRender }) => void. Skipped if same item reference AND same index.\n * @param {string|Function} [options.element='div'] - Element tag name or factory function\n * @param {Function|null} [options.preserveFocus=defaultFocusPreservation] - Focus preservation strategy (null to disable)\n * @param {Function|null} [options.preserveScroll=defaultScrollPreservation] - Scroll preservation strategy (null to disable)\n * @returns {Function} Cleanup function\n */\n\nexport function repeat(container, store, arrayKey, options) {\n const {\n key,\n render,\n create,\n update,\n element = 'div',\n preserveFocus = defaultFocusPreservation,\n preserveScroll = defaultScrollPreservation\n } = options;\n\n // Resolve container\n const containerEl =\n typeof container === 'string'\n ? document.querySelector(container)\n : container;\n\n if (!containerEl) {\n logWarn(`[Lume.js] repeat(): container \"${container}\" not found`);\n return () => { };\n }\n\n if (typeof key !== 'function') {\n throw new Error('[Lume.js] repeat(): options.key must be a function');\n }\n\n if (typeof render !== 'function' && typeof create !== 'function') {\n throw new Error('[Lume.js] repeat(): options.render or options.create must be a function');\n }\n\n // key -> HTMLElement\n const elementsByKey = new Map();\n // key -> previous item (for reference comparison)\n const prevItemsByKey = new Map();\n // key -> previous index (for reorder detection)\n const prevIndexByKey = new Map();\n const seenKeys = new Set();\n\n function createElement() {\n return typeof element === 'function'\n ? element()\n : document.createElement(element);\n }\n\n function reconcileDOM(container, nextEls) {\n let ptr = container.firstChild;\n\n for (let i = 0; i < nextEls.length; i++) {\n const desired = nextEls[i];\n\n if (ptr === desired) {\n ptr = ptr.nextSibling;\n continue;\n }\n\n container.insertBefore(desired, ptr);\n }\n\n // Remove leftover children not in nextEls\n while (ptr) {\n const next = ptr.nextSibling;\n container.removeChild(ptr);\n ptr = next;\n }\n }\n\n function applyPreservation(container, fn, isReorder) {\n const shouldPreserve = document.body.contains(container);\n const restoreFocus = shouldPreserve && preserveFocus ? preserveFocus(container) : null;\n const restoreScroll = shouldPreserve && preserveScroll ? preserveScroll(container, { isReorder }) : null;\n\n fn();\n\n if (restoreFocus) restoreFocus();\n if (restoreScroll) restoreScroll();\n }\n\n function updateList() {\n const items = store[arrayKey];\n\n if (!Array.isArray(items)) {\n logWarn(`[Lume.js] repeat(): store.${arrayKey} is not an array`);\n return;\n }\n\n // Only compute isReorder if scroll preservation needs it.\n // Uses elementsByKey (previous state) and items directly — no Set allocations.\n let isReorder = false;\n if (preserveScroll && elementsByKey.size === items.length) {\n isReorder = true;\n for (let i = 0; i < items.length; i++) {\n if (!elementsByKey.has(key(items[i]))) { isReorder = false; break; }\n }\n }\n\n seenKeys.clear();\n const nextEls = [];\n\n // Build ordered list of DOM nodes (created or reused)\n for (let i = 0; i < items.length; i++) {\n const item = items[i];\n const k = key(item);\n\n if (seenKeys.has(k)) {\n logWarn(`[Lume.js] repeat(): duplicate key \"${k}\"`);\n continue;\n }\n seenKeys.add(k);\n\n let el = elementsByKey.get(k);\n const isFirstRender = !el;\n\n if (isFirstRender) {\n el = createElement();\n elementsByKey.set(k, el);\n }\n\n try {\n // Call create for new elements (DOM structure)\n if (isFirstRender && create) {\n create(item, el, i);\n }\n\n // Call update for data binding (new and existing elements)\n // Skip if same item reference AND same index (optimization)\n const prevItem = prevItemsByKey.get(k);\n const prevIndex = prevIndexByKey.get(k);\n if (update) {\n if (prevItem !== item || prevIndex !== i) {\n update(item, el, i, { isFirstRender });\n }\n } else if (render) {\n // Backward compatibility: render handles both create and update\n render(item, el, i);\n }\n\n // Store reference and index for next comparison\n prevItemsByKey.set(k, item);\n prevIndexByKey.set(k, i);\n\n } catch (err) {\n logError(`[Lume.js] repeat(): error rendering key \"${k}\":`, err);\n }\n\n nextEls.push(el);\n }\n\n applyPreservation(containerEl, () => {\n reconcileDOM(containerEl, nextEls);\n\n // Clean maps: remove keys not in seenKeys (new state)\n if (elementsByKey.size !== seenKeys.size) {\n for (const k of elementsByKey.keys()) {\n if (!seenKeys.has(k)) {\n elementsByKey.delete(k);\n prevItemsByKey.delete(k);\n prevIndexByKey.delete(k);\n }\n }\n }\n }, isReorder);\n }\n\n // Subscription — $subscribe calls updateList immediately (initial render),\n // so no separate updateList() call is needed for reactive stores.\n let unsubscribe;\n if (typeof store.$subscribe === 'function') {\n unsubscribe = store.$subscribe(arrayKey, updateList);\n } else if (typeof store.subscribe === 'function') {\n // Generic subscribe (e.g. computed) — subscribe first, then initial render\n const subResult = store.subscribe(() => updateList());\n updateList();\n // Normalize both function-style and object-style (RxJS Subscription) returns\n unsubscribe = typeof subResult === 'function'\n ? subResult\n : () => { subResult?.unsubscribe?.(); };\n } else {\n // Non-reactive store — render once and return cleanup\n updateList();\n logWarn('[Lume.js] repeat(): store is not reactive (no $subscribe or subscribe method)');\n return () => {\n containerEl.replaceChildren();\n elementsByKey.clear();\n prevItemsByKey.clear();\n prevIndexByKey.clear();\n seenKeys.clear();\n };\n }\n\n return () => {\n if (typeof unsubscribe === 'function') {\n unsubscribe();\n }\n // Clear DOM elements (replaceChildren is faster than loop)\n containerEl.replaceChildren();\n elementsByKey.clear();\n prevItemsByKey.clear();\n prevIndexByKey.clear();\n seenKeys.clear();\n };\n}\n","/**\n * Lume-JS Debug Addon\n * \n * Developer-friendly logging and inspection of reactive state operations.\n * Critical for adoption - hard to debug = hard to adopt.\n * \n * Usage:\n * import { state } from \"lume-js\";\n * import { withPlugins, createDebugPlugin, debug } from \"lume-js/addons\";\n * \n * const store = withPlugins(state({ count: 0 }), [createDebugPlugin({ label: 'myStore' })]);\n * \n * debug.enable(); // Enable logging\n * debug.filter('count'); // Only log 'count' key\n * debug.stats(); // Show statistics\n * \n * @module addons/debug\n */\n\n// Global debug state\nlet globalEnabled = true;\nlet globalFilter = null; // string, RegExp, or null\nconst stats = new Map(); // label -> { gets: Map, sets: Map, notifies: Map }\n\n/**\n * Check if a key matches the current filter\n * @param {string} key\n * @returns {boolean}\n */\nfunction matchesFilter(key) {\n if (globalFilter === null) return true;\n if (typeof globalFilter === 'string') {\n return key.includes(globalFilter);\n }\n if (globalFilter instanceof RegExp) {\n return globalFilter.test(key);\n }\n return true;\n}\n\n/**\n * Get or create stats entry for a label\n * @param {string} label\n * @returns {object}\n */\nfunction getStats(label) {\n if (!stats.has(label)) {\n stats.set(label, {\n gets: new Map(),\n sets: new Map(),\n notifies: new Map()\n });\n }\n return stats.get(label);\n}\n\n/**\n * Increment a stat counter\n * @param {string} label\n * @param {'gets'|'sets'|'notifies'} type\n * @param {string} key\n */\nfunction incrementStat(label, type, key) {\n const s = getStats(label);\n const map = s[type];\n map.set(key, (map.get(key) || 0) + 1);\n}\n\nconst MAX_LOG_LEN = 100;\nconst TRUNCATED_LEN = MAX_LOG_LEN - 3;\n\n/**\n * Format value for logging (truncate long values)\n * @param {any} value\n * @returns {string}\n */\nfunction formatValue(value) {\n try {\n const json = JSON.stringify(value);\n if (json.length > MAX_LOG_LEN) {\n return json.slice(0, TRUNCATED_LEN) + '...';\n }\n return json;\n } catch {\n return String(value);\n }\n}\n\n/**\n * Create a debug plugin instance for a reactive state store.\n * \n * @param {object} [options] - Configuration options\n * @param {string} [options.label='store'] - Label for log messages\n * @param {boolean} [options.logGet=false] - Log property reads (can be noisy)\n * @param {boolean} [options.logSet=true] - Log property writes\n * @param {boolean} [options.logNotify=true] - Log subscriber notifications\n * @param {boolean} [options.trace=false] - Show stack trace for SET operations\n * @returns {object} Plugin object for state()\n * \n * @example\n * const store = withPlugins(state({ count: 0 }), [createDebugPlugin({ label: 'counter' })]);\n * \n * @example\n * // With stack traces for debugging where state changes originate\n * const store = withPlugins(state({ count: 0 }), [createDebugPlugin({ label: 'counter', trace: true })]);\n */\nexport function createDebugPlugin(options = {}) {\n const label = options.label ?? 'store';\n\n // IMPORTANT: Do NOT destructure options here!\n // Options may contain getters for dynamic runtime toggling (e.g., from UI).\n // Destructuring would copy values once at creation time, breaking reactivity.\n // Use getOpt() helper to read options dynamically in each hook.\n const getOpt = (name, defaultVal) => {\n const val = options[name];\n return val !== undefined ? val : defaultVal;\n };\n\n return {\n name: `debug:${label}`,\n\n onInit: () => {\n if (globalEnabled) {\n console.log(`%c[${label}]%c initialized`, 'color: #888; font-weight: bold', 'color: inherit');\n }\n },\n\n onGet: (key, value) => {\n // Skip internal properties\n if (typeof key === 'string' && key.startsWith('$')) {\n return value;\n }\n\n incrementStat(label, 'gets', key);\n\n if (globalEnabled && getOpt('logGet', false) && matchesFilter(key)) {\n console.log(\n `%c[${label}]%c GET %c${key}%c = ${formatValue(value)}`,\n 'color: #888; font-weight: bold',\n 'color: #4CAF50',\n 'color: #2196F3; font-weight: bold',\n 'color: inherit'\n );\n }\n\n return value;\n },\n\n onSet: (key, newValue, oldValue) => {\n // Skip internal properties\n if (typeof key === 'string' && key.startsWith('$')) {\n return newValue;\n }\n\n incrementStat(label, 'sets', key);\n\n if (globalEnabled && getOpt('logSet', true) && matchesFilter(key)) {\n console.log(\n `%c[${label}]%c SET %c${key}%c: ${formatValue(oldValue)} → ${formatValue(newValue)}`,\n 'color: #888; font-weight: bold',\n 'color: #FF9800',\n 'color: #2196F3; font-weight: bold',\n 'color: inherit'\n );\n\n // Show stack trace if enabled (helps find where state changes originate)\n if (getOpt('trace', false)) {\n console.trace(`%c[${label}] Stack trace for ${key}`, 'color: #888');\n }\n }\n\n return newValue;\n },\n\n onSubscribe: (key) => {\n if (globalEnabled && matchesFilter(key)) {\n console.log(\n `%c[${label}]%c SUBSCRIBE %c${key}`,\n 'color: #888; font-weight: bold',\n 'color: #9C27B0',\n 'color: #2196F3; font-weight: bold'\n );\n }\n },\n\n onNotify: (key, value) => {\n // Skip internal properties\n if (typeof key === 'string' && key.startsWith('$')) {\n return;\n }\n\n incrementStat(label, 'notifies', key);\n\n if (globalEnabled && getOpt('logNotify', true) && matchesFilter(key)) {\n console.log(\n `%c[${label}]%c NOTIFY %c${key}%c = ${formatValue(value)}`,\n 'color: #888; font-weight: bold',\n 'color: #E91E63',\n 'color: #2196F3; font-weight: bold',\n 'color: inherit'\n );\n }\n }\n };\n}\n\n/**\n * Global debug controls\n */\nexport const debug = {\n /**\n * Enable debug logging globally\n */\n enable() {\n globalEnabled = true;\n console.log('%c[lume-debug]%c Logging enabled', 'color: #888; font-weight: bold', 'color: #4CAF50');\n },\n\n /**\n * Disable debug logging globally\n */\n disable() {\n globalEnabled = false;\n console.log('%c[lume-debug]%c Logging disabled', 'color: #888; font-weight: bold', 'color: #F44336');\n },\n\n /**\n * Check if debug logging is currently enabled\n * @returns {boolean}\n */\n isEnabled() {\n return globalEnabled;\n },\n\n /**\n * Filter logs by key pattern\n * @param {string|RegExp|null} pattern - Pattern to match, or null to clear filter\n */\n filter(pattern) {\n globalFilter = pattern;\n if (pattern === null) {\n console.log('%c[lume-debug]%c Filter cleared', 'color: #888; font-weight: bold', 'color: inherit');\n } else {\n console.log(`%c[lume-debug]%c Filter set: ${pattern}`, 'color: #888; font-weight: bold', 'color: inherit');\n }\n },\n\n /**\n * Get current filter pattern\n * @returns {string|RegExp|null}\n */\n getFilter() {\n return globalFilter;\n },\n\n /**\n * Get statistics data (silent - no console output)\n * Use logStats() if you want to see stats in console.\n * @returns {object} Stats object for programmatic access\n */\n stats() {\n const result = {};\n\n for (const [label, data] of stats) {\n result[label] = {\n gets: Object.fromEntries(data.gets),\n sets: Object.fromEntries(data.sets),\n notifies: Object.fromEntries(data.notifies)\n };\n }\n\n return result;\n },\n\n /**\n * Log statistics summary to console (with formatting)\n * @returns {object} Stats object for programmatic access\n */\n logStats() {\n const result = this.stats();\n\n if (Object.keys(result).length === 0) {\n console.log('%c[lume-debug]%c No stats collected yet', 'color: #888; font-weight: bold', 'color: inherit');\n return result;\n }\n\n console.group('%c[lume-debug] Statistics', 'color: #888; font-weight: bold');\n\n for (const [label, data] of Object.entries(result)) {\n console.group(`%c${label}`, 'color: #2196F3; font-weight: bold');\n\n // Use console.table for better formatted output\n const tableData = [];\n const allKeys = new Set([\n ...Object.keys(data.gets),\n ...Object.keys(data.sets),\n ...Object.keys(data.notifies)\n ]);\n\n for (const key of allKeys) {\n tableData.push({\n key,\n gets: data.gets[key] || 0,\n sets: data.sets[key] || 0,\n notifies: data.notifies[key] || 0\n });\n }\n\n if (tableData.length > 0) {\n console.table(tableData);\n }\n\n console.groupEnd();\n }\n\n console.groupEnd();\n\n return result;\n },\n\n /**\n * Reset all collected statistics\n */\n resetStats() {\n stats.clear();\n console.log('%c[lume-debug]%c Stats reset', 'color: #888; font-weight: bold', 'color: inherit');\n }\n};\n","/**\n * Lume-JS DOM Binding Handlers\n *\n * Extend bindDom() with additional reactive data-* attribute capabilities.\n * Each handler is a plain object — no framework API, no registration.\n *\n * Usage:\n * import { state, bindDom } from 'lume-js';\n * import { show, classToggle } from 'lume-js/handlers';\n *\n * const store = state({ isVisible: true, isActive: false });\n * bindDom(document.body, store, { handlers: [show, classToggle('active')] });\n *\n * Custom handlers:\n * const tooltip = { attr: 'data-tooltip', apply(el, val) { el.title = val ?? ''; } };\n * bindDom(root, store, { handlers: [tooltip] });\n *\n * Handler contract:\n * { attr: string, apply(el: HTMLElement, val: any): void }\n */\n\n// --- Ready-to-use Handlers ---\n\n/**\n * data-show=\"key\" → el.hidden = !Boolean(val)\n * Shows element when state value is truthy (inverse of built-in data-hidden).\n */\nexport const show = {\n attr: 'data-show',\n apply(el, val) { el.hidden = !Boolean(val); }\n};\n\n// --- Factory Functions ---\n\n/**\n * Create a handler for any HTML boolean attribute.\n * Uses toggleAttribute() — works correctly with any attribute name\n * (readonly, contenteditable, etc.) without worrying about camelCase property names.\n *\n * Note: built-in boolean handlers (hidden, disabled, checked, required) use\n * property assignment directly. This factory uses toggleAttribute for broader\n * attribute name compatibility.\n *\n * @param {string} name - Attribute name (e.g., 'readonly', 'open', 'contenteditable')\n * @returns {{ attr: string, apply: function }}\n *\n * @example\n * bindDom(root, store, { handlers: [boolAttr('readonly')] });\n * // <input data-readonly=\"isReadonly\" />\n */\nexport function boolAttr(name) {\n return {\n attr: `data-${name}`,\n apply(el, val) { el.toggleAttribute(name, Boolean(val)); }\n };\n}\n\n/**\n * Create a handler for an ARIA attribute.\n * Use for ARIA attrs beyond the built-in aria-expanded/aria-hidden.\n *\n * @param {string} name - ARIA name, with or without \"aria-\" prefix\n * @returns {{ attr: string, apply: function }}\n *\n * @example\n * bindDom(root, store, { handlers: [ariaAttr('pressed'), ariaAttr('selected')] });\n * // <button data-aria-pressed=\"isPressed\">Toggle</button>\n */\nexport function ariaAttr(name) {\n const fullName = name.startsWith('aria-') ? name : `aria-${name}`;\n return {\n attr: `data-${fullName}`,\n apply(el, val) { el.setAttribute(fullName, val ? 'true' : 'false'); }\n };\n}\n\n/**\n * Create handlers for CSS class toggling.\n * Each name creates a handler: data-class-{name}=\"key\" → el.classList.toggle(name, Boolean(val))\n *\n * Returns an array — pass directly to handlers (auto-flattened by bindDom).\n *\n * @param {...string} names - CSS class names to create handlers for\n * @returns {Array<{ attr: string, apply: function }>}\n *\n * @example\n * bindDom(root, store, { handlers: [classToggle('active', 'loading', 'error')] });\n * // <div data-class-active=\"isActive\" data-class-loading=\"isLoading\">\n */\nexport function classToggle(...names) {\n return names.map(name => ({\n attr: `data-class-${name}`,\n apply(el, val) { el.classList.toggle(name, Boolean(val)); }\n }));\n}\n\n/**\n * Create a handler for any string attribute (href, src, title, alt, action, etc.)\n * Sets the attribute value as a string. Removes attribute when value is null/undefined.\n *\n * @param {string} name - HTML attribute name (e.g., 'href', 'src', 'title')\n * @returns {{ attr: string, apply: function }}\n *\n * @example\n * bindDom(root, store, { handlers: [stringAttr('href'), stringAttr('src')] });\n * // <a data-href=\"profileUrl\">Profile</a>\n * // <img data-src=\"imageUrl\" />\n */\nexport function stringAttr(name) {\n return {\n attr: `data-${name}`,\n apply(el, val) {\n if (val == null) el.removeAttribute(name);\n else el.setAttribute(name, String(val));\n }\n };\n}\n\n// --- Presets ---\n\n/** Form-related handlers (beyond built-in disabled/checked/required) */\nexport const formHandlers = [\n boolAttr('readonly'),\n];\n\n/** Additional ARIA handlers (beyond built-in aria-expanded/aria-hidden) */\nexport const a11yHandlers = [\n ariaAttr('pressed'),\n ariaAttr('selected'),\n ariaAttr('disabled'),\n];\n\n// --- htmlAttrs() — All Standard HTML Attributes ---\n\n/**\n * Standard HTML boolean attributes (beyond built-in hidden/disabled/checked/required).\n * @see https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes#boolean_attributes\n */\nconst BOOL_ATTRS = [\n 'readonly', 'open', 'novalidate', 'formnovalidate', 'multiple',\n 'autofocus', 'autoplay', 'controls', 'loop', 'muted', 'defer',\n 'async', 'reversed', 'selected', 'inert', 'allowfullscreen',\n];\n\n/**\n * Standard HTML string attributes.\n * @see https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes\n */\nconst STRING_ATTRS = [\n 'href', 'src', 'alt', 'title', 'placeholder', 'action', 'method',\n 'target', 'rel', 'type', 'name', 'role', 'lang', 'tabindex',\n 'pattern', 'min', 'max', 'step', 'minlength', 'maxlength',\n 'width', 'height', 'for', 'form', 'accept', 'autocomplete',\n 'loading', 'decoding', 'inputmode', 'enterkeyhint', 'draggable',\n 'contenteditable', 'spellcheck', 'translate', 'dir', 'id',\n 'poster', 'preload', 'download', 'media', 'sizes', 'srcset',\n 'colspan', 'rowspan', 'scope', 'headers', 'wrap', 'sandbox',\n];\n\n/**\n * ARIA boolean state attributes — toggled between \"true\" and \"false\".\n * Use ariaAttr() for these (coerces to \"true\"/\"false\" string).\n * @see https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes\n */\nconst ARIA_BOOL_ATTRS = [\n 'pressed', 'selected', 'disabled', 'checked', 'invalid', 'required',\n 'busy', 'modal', 'multiselectable', 'multiline', 'readonly', 'atomic',\n];\n\n/**\n * ARIA string/token/numeric attributes — value passed through as-is.\n * Use stringAttr() with \"aria-\" prefix for these.\n * @see https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes\n */\nconst ARIA_STRING_ATTRS = [\n 'current', 'live', 'relevant', 'haspopup',\n 'sort', 'autocomplete', 'orientation',\n 'label', 'describedby', 'labelledby', 'controls', 'owns',\n 'activedescendant', 'errormessage', 'details', 'flowto',\n 'valuenow', 'valuemin', 'valuemax', 'valuetext',\n 'colcount', 'colindex', 'colspan', 'rowcount', 'rowindex', 'rowspan',\n 'level', 'setsize', 'posinset', 'placeholder', 'roledescription',\n 'keyshortcuts', 'braillelabel', 'brailleroledescription',\n];\n\n/**\n * One-import preset that enables all standard HTML attributes as reactive handlers.\n *\n * Includes:\n * - Boolean attributes: readonly, open, autofocus, controls, muted, inert, etc.\n * - String attributes: href, src, alt, title, placeholder, role, tabindex, etc.\n * - ARIA attributes: aria-pressed, aria-label, aria-describedby, aria-valuenow, etc.\n * - Show handler: data-show (inverse of data-hidden)\n *\n * Returns a flat array — pass directly to handlers option.\n *\n * @returns {Array<{ attr: string, apply: function }>}\n *\n * @example\n * import { htmlAttrs } from 'lume-js/handlers';\n *\n * bindDom(document.body, store, { handlers: [htmlAttrs()] });\n * // Now use any data-* attribute:\n * // <a data-href=\"url\">Link</a>\n * // <input data-readonly=\"isLocked\" />\n * // <div data-aria-label=\"labelText\">...</div>\n * // <div data-show=\"isVisible\">...</div>\n */\nexport function htmlAttrs() {\n return [\n show,\n ...BOOL_ATTRS.map(name => boolAttr(name)),\n ...STRING_ATTRS.map(name => stringAttr(name)),\n ...ARIA_BOOL_ATTRS.map(name => ariaAttr(name)),\n ...ARIA_STRING_ATTRS.map(name => stringAttr(`aria-${name}`)),\n ];\n}\n","/**\n * Lume-JS Computed Addon\n * \n * Creates computed values that automatically update when dependencies change.\n * Uses core effect() for automatic dependency tracking.\n * \n * Usage:\n * import { computed } from \"lume-js/addons/computed\";\n * \n * const doubled = computed(() => store.count * 2);\n * console.log(doubled.value); // Auto-updates when store.count changes\n * \n * Features:\n * - Automatic dependency tracking (no manual recompute)\n * - Cached values (only recomputes when dependencies change)\n * - Subscribe to changes\n * - Cleanup with dispose()\n * \n * @module addons/computed\n */\n\nimport { effect } from '../core/effect.js';\nimport { logError } from '../utils/log.js';\n\n/**\n * Creates a computed value with automatic dependency tracking\n * \n * The computation function runs immediately and tracks which state\n * properties are accessed. When any dependency changes, the value\n * is automatically recomputed.\n *\n * ⚠️ Circular self-mutations are automatically suppressed. If a computed\n * mutates a state property it depends on, the flush triggered by that\n * mutation is skipped to prevent an infinite microtask loop.\n *\n * @param {function} fn - Function that computes the value\n * @returns {object} Object with .value property and methods\n * \n * @example\n * const store = state({ count: 5 });\n * \n * const doubled = computed(() => store.count * 2);\n * console.log(doubled.value); // 10\n * \n * store.count = 10;\n * // After microtask:\n * console.log(doubled.value); // 20 (auto-updated)\n * \n * @example\n * // Subscribe to changes\n * const unsub = doubled.subscribe(value => {\n * console.log('Doubled changed to:', value);\n * });\n * \n * @example\n * // Cleanup\n * doubled.dispose();\n */\nexport function computed(fn) {\n if (typeof fn !== 'function') {\n throw new Error('computed() requires a function');\n }\n\n let cachedValue;\n let isInitialized = false;\n let isInComputation = false;\n let disposed = false;\n const subscribers = [];\n\n // Use effect to automatically track dependencies\n const cleanupEffect = effect(() => {\n // Skip re-entry from a flush triggered by our own synchronous mutation.\n // The mutation inside fn() queues a microtask flush; we stay flagged\n // until a subsequent microtask clears it, so that flush is dropped.\n if (isInComputation || disposed) return;\n\n isInComputation = true;\n\n try {\n const newValue = fn();\n\n // Check if value actually changed - Object.is() handles NaN and -0\n if (!isInitialized || !Object.is(newValue, cachedValue)) {\n cachedValue = newValue;\n isInitialized = true;\n\n // Notify all subscribers\n subscribers.forEach(callback => callback(cachedValue));\n }\n } catch (error) {\n logError('[Lume.js computed] Error in computation:', error);\n // Set to undefined on error, mark as initialized\n if (!isInitialized || cachedValue !== undefined) {\n cachedValue = undefined;\n isInitialized = true;\n\n // Notify subscribers of error state\n subscribers.forEach(callback => callback(cachedValue));\n }\n } finally {\n // Defer clearing the flag so any flush microtask queued by fn()\n // sees it still set and skips re-entry.\n queueMicrotask(() => {\n if (!disposed) {\n isInComputation = false;\n }\n });\n }\n });\n\n return {\n /**\n * Get the current computed value\n */\n get value() {\n if (!isInitialized) {\n throw new Error('Computed value accessed before initialization');\n }\n return cachedValue;\n },\n\n /**\n * Subscribe to changes in computed value\n * \n * @param {function} callback - Called when value changes\n * @returns {function} Unsubscribe function\n */\n subscribe(callback) {\n if (typeof callback !== 'function') {\n throw new Error('subscribe() requires a function');\n }\n\n subscribers.push(callback);\n\n // Call immediately with current value\n if (isInitialized) {\n callback(cachedValue);\n }\n\n // Return unsubscribe function\n return () => {\n const index = subscribers.indexOf(callback);\n if (index > -1) {\n subscribers.splice(index, 1);\n }\n };\n },\n\n /**\n * Clean up computed value and stop tracking\n */\n dispose() {\n disposed = true;\n cleanupEffect();\n subscribers.length = 0;\n isInitialized = false;\n isInComputation = false;\n }\n };\n}","/**\n * Creates a cleanup group that can collect and dispose multiple\n * cleanup/unsubscribe functions at once.\n *\n * @returns {CleanupGroup}\n *\n * @example\n * ```js\n * import { createCleanupGroup } from 'lume-js/addons';\n *\n * const group = createCleanupGroup();\n * group.add(bindDom(root, store));\n * group.add(effect(() => { ... }));\n * group.add(store.$subscribe('key', fn));\n *\n * // Dispose everything at once\n * group.dispose();\n * ```\n */\nexport function createCleanupGroup() {\n const cleanups = [];\n\n return {\n /**\n * Add a cleanup function to the group.\n * @param {Function} fn - Cleanup/unsubscribe function\n */\n add(fn) {\n if (typeof fn === 'function') {\n cleanups.push(fn);\n }\n },\n\n /**\n * Run all collected cleanup functions and clear the group.\n */\n dispose() {\n while (cleanups.length) {\n const fn = cleanups.pop();\n try { fn(); } catch (e) { /* ignore cleanup errors */ }\n }\n },\n };\n}\n","/**\n * Reads initial state from a `<script type=\"application/json\">` element\n * embedded in the server-rendered HTML. Useful for SSR / hydration patterns.\n *\n * @param {string} [selector='#__LUME_DATA__'] - CSS selector for the script element\n * @returns {object} Parsed JSON object, or empty object if not found / invalid\n *\n * @example\n * ```html\n * <script id=\"__LUME_DATA__\" type=\"application/json\">\n * {\"title\": \"Welcome\", \"count\": 42}\n * </script>\n * ```\n *\n * ```js\n * import { state } from 'lume-js';\n * import { hydrateState } from 'lume-js/addons';\n *\n * const store = state(hydrateState());\n * ```\n */\nexport function hydrateState(selector = '#__LUME_DATA__') {\n const el = typeof document !== 'undefined' ? document.querySelector(selector) : null;\n if (!el) return {};\n try {\n return JSON.parse(el.textContent);\n } catch {\n return {};\n }\n}\n","export { computed } from \"./computed.js\";\nexport { watch } from \"./watch.js\";\nexport { repeat, defaultFocusPreservation, defaultScrollPreservation } from \"./repeat.js\";\nexport { createDebugPlugin, debug } from \"./debug.js\";\nexport { withPlugins } from \"./withPlugins.js\";\nexport { createCleanupGroup } from \"./cleanupGroup.js\";\nexport { hydrateState } from \"./hydrateState.js\";\n\n/**\n * Returns true if the value is a Lume reactive proxy created by state().\n * Uses duck-typing: checks for the presence of $subscribe.\n * @param {any} obj\n * @returns {boolean}\n */\nexport function isReactive(obj) {\n return !!(obj && typeof obj === 'object' && typeof obj.$subscribe === 'function');\n}\n","/**\n * watch - observes changes to a state key and triggers callback\n * @param {Object} store - reactive store created with state()\n * @param {string} key - key in store to watch\n * @param {Function} callback - called with new value\n * @returns {Function} unsubscribe function\n */\nexport function watch(store, key, callback) {\n if (!store.$subscribe) {\n throw new Error(\"store must be created with state()\");\n }\n return store.$subscribe(key, callback);\n}","/**\n * Lume-JS withPlugins Addon\n *\n * Wraps a reactive state proxy with a plugin layer that intercepts\n * get/set/notify/subscribe operations via plugin hooks.\n *\n * Only stores that opt into debugging or custom behaviors need this.\n * Core state() is not aware of plugins.\n *\n * Usage:\n * import { state } from \"lume-js\";\n * import { withPlugins, createDebugPlugin } from \"lume-js/addons\";\n *\n * const store = withPlugins(state({ count: 0 }), [createDebugPlugin({ label: 'counter' })]);\n */\n\n/**\n * Wrap a reactive state proxy with plugin hooks.\n *\n * Plugin hooks (all optional):\n * onInit() — called once at wrap time\n * onGet(key, value) → value|void — intercept/transform reads\n * onSet(key, newVal, oldVal) → val|void — intercept/transform writes\n * onNotify(key, value) — called before subscribers are notified\n * onSubscribe(key) — called when $subscribe is invoked\n *\n * @param {object} store - A reactive proxy from state()\n * @param {Array<object>} plugins - Array of plugin objects\n * @returns {Proxy} A new proxy wrapping the store with plugin behavior\n */\nimport { logError } from '../utils/log.js';\n\nexport function withPlugins(store, plugins = []) {\n if (!plugins.length) return store;\n\n // Call onInit hooks once at wrap time\n for (const p of plugins) {\n try {\n p.onInit?.();\n } catch (e) {\n logError(`[Lume.js] Plugin \"${p.name}\" error in onInit:`, e);\n }\n }\n\n // Track pending notifications for onNotify hooks.\n // Instead of a separate microtask, we hook into the underlying state's\n // flush via $beforeFlush so onNotify and subscribers share one microtask.\n const pendingNotifications = new Map();\n\n function runNotifyHooks() {\n for (const [key, value] of pendingNotifications) {\n for (const p of plugins) {\n try {\n p.onNotify?.(key, value);\n } catch (e) {\n logError(`[Lume.js] Plugin \"${p.name}\" error in onNotify:`, e);\n }\n }\n }\n pendingNotifications.clear();\n }\n\n // Register once on the underlying state; capture unsubscribe for cleanup.\n let flushUnsub;\n if (typeof store.$beforeFlush === 'function') {\n flushUnsub = store.$beforeFlush(runNotifyHooks);\n }\n\n return new Proxy(store, {\n get(target, key) {\n // $dispose — remove the beforeFlush hook and clear pending state\n if (key === '$dispose') {\n return () => {\n if (flushUnsub) flushUnsub();\n pendingNotifications.clear();\n };\n }\n\n // Pass $-prefixed meta methods through without interception\n if (typeof key === 'string' && key.startsWith('$')) {\n const method = target[key];\n if (key === '$subscribe' && typeof method === 'function') {\n // Wrap $subscribe to call onSubscribe hooks\n return (subKey, fn) => {\n for (const p of plugins) {\n try {\n p.onSubscribe?.(subKey);\n } catch (e) {\n logError(`[Lume.js] Plugin \"${p.name}\" error in onSubscribe:`, e);\n }\n }\n return method(subKey, fn);\n };\n }\n return method;\n }\n\n let value = target[key];\n\n // onGet chain\n for (const p of plugins) {\n try {\n const r = p.onGet?.(key, value);\n if (r !== undefined) value = r;\n } catch (e) {\n logError(`[Lume.js] Plugin \"${p.name}\" error in onGet:`, e);\n }\n }\n\n return value;\n },\n\n set(target, key, value) {\n const oldValue = target[key];\n let newValue = value;\n\n // onSet chain\n for (const p of plugins) {\n try {\n const r = p.onSet?.(key, newValue, oldValue);\n if (r !== undefined) newValue = r;\n } catch (e) {\n logError(`[Lume.js] Plugin \"${p.name}\" error in onSet:`, e);\n }\n }\n\n // Only queue onNotify if the value actually changed after plugin chain\n if (!Object.is(newValue, oldValue)) {\n pendingNotifications.set(key, newValue);\n }\n\n target[key] = newValue;\n return true;\n }\n });\n}\n"],"names":["logWarn","msg","rest","console","warn","logError","error","readers","Set","withReadObserver","onRead","fn","add","delete","boolHandler","name","attr","apply","el","val","ariaHandler","setAttribute","DEFAULT_HANDLERS","applyHandler","store","path","handler","result","resolveProp","target","key","$subscribe","handleDataBind","bindingMap","unsub","tagName","type","checked","value","String","textContent","updateElement","isFormInput","set","pathArr","split","pop","obj","length","current","i","resolvePath","currentEffect","effect","deps","Error","cleanups","isRunning","execute","Array","isArray","dep","keys","isFirst","push","executeWithTracking","oldCleanups","splice","myContext","tracking","previousEffect","proxy","registerEffect","cleanup","defaultFocusPreservation","container","activeEl","document","activeElement","contains","selectionStart","selectionEnd","body","focus","setSelectionRange","defaultScrollPreservation","context","isReorder","scrollTop","anchorElement","anchorOffset","containerRect","getBoundingClientRect","child","firstElementChild","nextElementSibling","rect","bottom","top","newRect","scrollAdjustment","globalEnabled","globalFilter","stats","Map","matchesFilter","includes","RegExp","test","incrementStat","label","s","has","gets","sets","notifies","get","getStats","map","formatValue","json","JSON","stringify","slice","MAX_LOG_LEN","debug","enable","log","disable","isEnabled","filter","pattern","getFilter","data","Object","fromEntries","logStats","this","group","entries","tableData","allKeys","table","groupEnd","resetStats","clear","show","hidden","boolAttr","toggleAttribute","ariaAttr","fullName","startsWith","stringAttr","removeAttribute","formHandlers","a11yHandlers","BOOL_ATTRS","STRING_ATTRS","ARIA_BOOL_ATTRS","ARIA_STRING_ATTRS","root","options","HTMLElement","immediate","handlers","userHandlers","defaults","merged","h","flat","values","mergeHandlers","performBinding","WeakMap","selector","join","elements","querySelectorAll","hasAttribute","c","getAttribute","inputHandler","e","binding","valueAsNumber","addEventListener","removeEventListener","forEach","readyState","onReady","once","names","classList","toggle","cachedValue","isInitialized","isInComputation","disposed","subscribers","cleanupEffect","newValue","is","callback","queueMicrotask","subscribe","index","indexOf","dispose","getOpt","defaultVal","onInit","onGet","onSet","oldValue","trace","onSubscribe","onNotify","querySelector","parse","arrayKey","render","create","update","element","preserveFocus","preserveScroll","containerEl","elementsByKey","prevItemsByKey","prevIndexByKey","seenKeys","createElement","updateList","items","size","nextEls","item","k","isFirstRender","prevItem","prevIndex","err","shouldPreserve","restoreFocus","restoreScroll","ptr","firstChild","desired","insertBefore","nextSibling","next","removeChild","reconcileDOM","applyPreservation","unsubscribe","replaceChildren","subResult","isFrozen","isSealed","listeners","pendingNotifications","pendingEffects","beforeFlushHooks","flushScheduled","Symbol","executeFn","idx","Proxy","reader","iterations","subs","effects","$beforeFlush","plugins","p","flushUnsub","method","subKey","r"],"mappings":"kCASO,SAASA,EAAQC,KAAQC,QACP,IAAZC,SAAmD,mBAAjBA,QAAQC,MACnDD,QAAQC,KAAKH,KAAQC,EAEzB,CAEO,SAASG,EAASJ,KAAQC,QACR,IAAZC,SAAoD,mBAAlBA,QAAQG,OACnDH,QAAQG,MAAML,KAAQC,EAE1B,CC6BA,MAAMK,MAAcC,IAYb,SAASC,EAAiBC,EAAQC,GACvCJ,EAAQK,IAAIF,GACZ,IACE,OAAOC,GACT,CAAA,QACEJ,EAAQM,OAAOH,EACjB,CACF,CCnCA,MAAMI,EAAeC,IAAA,CACnBC,KAAM,QAAQD,EACd,KAAAE,CAAMC,EAAIC,GAAOD,EAAGH,KAAgBI,CAAM,IAGtCC,EAAeL,IAAA,CACnBC,KAAM,QAAQD,EACd,KAAAE,CAAMC,EAAIC,GAAOD,EAAGG,aAAaN,EAAMI,EAAM,OAAS,QAAU,IAG5DG,EAAmB,CACvBR,EAAY,UACZA,EAAY,YACZA,EAAY,WACZA,EAAY,YACZM,EAAY,iBACZA,EAAY,gBAgFd,SAASG,EAAaL,EAAIM,EAAOC,EAAMC,GACrC,MAAMC,EAASC,EAAYJ,EAAOC,GAClC,IAAKE,EAAQ,OAAO,KACpB,MAAME,OAAEA,EAAAC,IAAQA,GAAQH,EACxB,OAAOE,EAAOE,WAAWD,EAAKX,GAAOO,EAAQT,MAAMC,EAAIC,GACzD,CAKA,SAASa,EAAed,EAAIM,EAAOC,EAAMQ,GACvC,MAAMN,EAASC,EAAYJ,EAAOC,GAClC,IAAKE,EAAQ,OAAO,KAEpB,MAAME,OAAEA,EAAAC,IAAQA,GAAQH,EAClBO,EAAQL,EAAOE,WAAWD,KA6DlC,SAAuBZ,EAAIC,GACN,UAAfD,EAAGiB,QACW,aAAZjB,EAAGkB,KAAqBlB,EAAGmB,UAAkBlB,EAC5B,UAAZD,EAAGkB,KAAkBlB,EAAGmB,QAAUnB,EAAGoB,QAAiBnB,EAAPoB,GACnDrB,EAAGoB,MAAQnB,GAAO,GACC,aAAfD,EAAGiB,SAAyC,WAAfjB,EAAGiB,QACzCjB,EAAGoB,MAAQnB,GAAO,GAElBD,EAAGsB,YAAcrB,GAAO,EAE5B,CAvE8CsB,CAAcvB,EAAIC,IAM9D,OA+EF,SAAqBD,GACnB,MAAsB,UAAfA,EAAGiB,SAAsC,aAAfjB,EAAGiB,SAAyC,WAAfjB,EAAGiB,OACnE,CArFMO,CAAYxB,IACde,EAAWU,IAAIzB,EAAI,CAAEW,SAAQC,QAGxBI,CACT,CA+BA,SAASN,EAAYJ,EAAOC,GAC1B,IAAKA,EAAM,OAAO,KAElB,MAAMmB,EAAUnB,EAAKoB,MAAM,KACrBf,EAAMc,EAAQE,MACdjB,EA9BR,SAAqBkB,EAAKH,GACxB,IAAKA,GAA8B,IAAnBA,EAAQI,OACtB,OAAOD,EAET,IAAIE,EAAUF,EACd,IAAA,IAASG,EAAI,EAAGA,EAAIN,EAAQI,OAAQE,IAAK,CACvC,MAAMpB,EAAMc,EAAQM,GACpB,GAAID,QACF,OAAO,KAET,KAAMnB,KAAOmB,GACX,OAAO,KAETA,EAAUA,EAAQnB,EACpB,CACA,OAAOmB,CACT,CAciBE,CAAY3B,EAAOoB,GAElC,OAAIf,SACF7B,EAAQ,2BAA2ByB,MAC5B,MAGJI,GAAQE,WAKN,CAAEF,SAAQC,QAJf9B,EAAQ,yBAAyByB,sBAC1B,KAIX,CCnKA,IAAI2B,EAAgB,KAwBb,SAASC,EAAO1C,EAAI2C,GACzB,GAAkB,mBAAP3C,EACT,MAAU4C,MAAM,gCAGlB,MAAMC,EAAW,GACjB,IAAIC,GAAY,EAKhB,MAAMC,EAAU,KAEd,IAAID,EAAJ,CACAA,GAAY,EAEZ,IACE9C,GACF,OAASL,GAEP,MADAD,EAAS,oCAAqCC,GACxCA,CACR,CAAA,QACEmD,GAAY,CACd,CAVe,GAcjB,GAAIE,MAAMC,QAAQN,GAAO,CAEvB,IAAA,MAAWO,KAAOP,EAChB,GAAIK,MAAMC,QAAQC,IAAQA,EAAIb,QAAU,EAAG,CACzC,MAAOxB,KAAUsC,GAAQD,EACzB,GAAIrC,GAAqC,mBAArBA,EAAMO,WAExB,IAAA,MAAWD,KAAOgC,EAAM,CAGtB,IAAIC,GAAU,EACd,MAAM7B,EAAQV,EAAMO,WAAWD,EAAK,KAC9BiC,EACFA,GAAU,EAGZL,MAEFF,EAASQ,KAAK9B,EAChB,CAEJ,CAGFwB,GACF,KAEK,CACH,MAAMO,EAAsB,KAE1B,GAAIR,EAAW,OAKf,MAAMS,EAAcV,EAASW,OAAO,GAG9BC,EAAY,CAChBzD,KACA6C,WACAE,QAASO,EACTI,SAAU,CAAA,GAKNC,EAAiBlB,EACvBA,EAAgBgB,EAChBX,GAAY,EAEZ,IAQEhD,EAPe,CAAC8D,EAAOzC,EAAK0C,KAEtBpB,IAAkBgB,IAClBA,EAAUC,SAASvC,KACvBsC,EAAUC,SAASvC,IAAO,EAC1BsC,EAAUZ,SAASQ,KAAKQ,EAAe1C,EAAKsC,EAAUV,aAE/B/C,EAC3B,OAASL,GAKP,MAHAkD,EAASR,OAAS,EAClBQ,EAASQ,QAAQE,GACjB7D,EAAS,oCAAqCC,GACxCA,CACR,CAAA,QAEE8C,EAAgBkB,EAChBb,GAAY,CACd,CAIA,GAAID,EAASR,OAAS,EACpB,IAAA,MAAWyB,KAAWP,EAAaO,SAEnCjB,EAASQ,QAAQE,IAKrBD,GACF,CAGA,MAAO,KAEL,KAAOT,EAASR,QAAQQ,EAASV,KAATU,GAE5B,CC7FO,SAASkB,EAAyBC,GACvC,MAAMC,EAAWC,SAASC,cAG1B,IAFsBH,EAAUI,SAASH,GAErB,OAAO,KAE3B,IAAII,EAAiB,KACjBC,EAAe,KAOnB,MALyB,UAArBL,EAASzC,SAA4C,aAArByC,EAASzC,UAC3C6C,EAAiBJ,EAASI,eAC1BC,EAAeL,EAASK,cAGnB,KACDJ,SAASK,KAAKH,SAASH,KACzBA,EAASO,QACc,OAAnBH,GAA4C,OAAjBC,GAC7BL,EAASQ,kBAAkBJ,EAAgBC,IAInD,CAWO,SAASI,EAA0BV,EAAWW,EAAU,IAC7D,MAAMC,UAAEA,GAAY,GAAUD,EACxBE,EAAYb,EAAUa,UAG5B,GAAkB,IAAdA,EACF,MAAO,KAAQb,EAAUa,UAAY,GAGvC,IAAIC,EAAgB,KAChBC,EAAe,EAGnB,IAAKH,EAAW,CACd,MAAMI,EAAgBhB,EAAUiB,wBAEhC,IAAA,IAASC,EAAQlB,EAAUmB,kBAAmBD,EAAOA,EAAQA,EAAME,mBAAoB,CACrF,MAAMC,EAAOH,EAAMD,wBAEnB,GAAII,EAAKC,OAASN,EAAcO,IAAK,CACnCT,EAAgBI,EAChBH,EAAeM,EAAKE,IAAMP,EAAcO,IACxC,KACF,CACF,CACF,CAEA,MAAO,KACL,GAAIT,GAAiBZ,SAASK,KAAKH,SAASU,GAAgB,CAC1D,MAAMU,EAAUV,EAAcG,wBACxBD,EAAgBhB,EAAUiB,wBAE1BQ,EADgBD,EAAQD,IAAMP,EAAcO,IACTR,EAEzCf,EAAUa,UAAYb,EAAUa,UAAYY,CAC9C,MACEzB,EAAUa,UAAYA,EAG5B,CCxIA,IAAIa,GAAgB,EAChBC,EAAe,KACnB,MAAMC,MAAYC,IAOlB,SAASC,EAAc3E,GACrB,OAAqB,OAAjBwE,IACwB,iBAAjBA,EACFxE,EAAI4E,SAASJ,KAElBA,aAAwBK,SACnBL,EAAaM,KAAK9E,GAG7B,CAwBA,SAAS+E,EAAcC,EAAO1E,EAAMN,GAClC,MAAMiF,EAlBR,SAAkBD,GAQhB,OAPKP,EAAMS,IAAIF,IACbP,EAAM5D,IAAImE,EAAO,CACfG,SAAUT,IACVU,SAAUV,IACVW,aAAcX,MAGXD,EAAMa,IAAIN,EACnB,CASYO,CAASP,GACbQ,EAAMP,EAAE3E,GACdkF,EAAI3E,IAAIb,GAAMwF,EAAIF,IAAItF,IAAQ,GAAK,EACrC,CAUA,SAASyF,EAAYjF,GACnB,IACE,MAAMkF,EAAOC,KAAKC,UAAUpF,GAC5B,OAAIkF,EAAKxE,OAXO,IAYPwE,EAAKG,MAAM,EAXFC,IAWsB,MAEjCJ,CACT,CAAA,MACE,OAAclF,EAAPC,EACT,CACF,CA2HY,MAACsF,EAAQ,CAInB,MAAAC,GACEzB,GAAgB,EAChBlG,QAAQ4H,IAAI,mCAAoC,iCAAkC,iBACpF,EAKA,OAAAC,GACE3B,GAAgB,EAChBlG,QAAQ4H,IAAI,oCAAqC,iCAAkC,iBACrF,EAMAE,UAAA,IACS5B,EAOT,MAAA6B,CAAOC,GACL7B,EAAe6B,EAEbhI,QAAQ4H,IADM,OAAZI,EACU,kCAEA,gCAAgCA,EAFG,iCAAkC,iBAIrF,EAMAC,UAAA,IACS9B,EAQT,KAAAC,GACE,MAAM5E,EAAS,CAAA,EAEf,IAAA,MAAYmF,EAAOuB,KAAS9B,EAC1B5E,EAAOmF,GAAS,CACdG,KAAMqB,OAAOC,YAAYF,EAAKpB,MAC9BC,KAAMoB,OAAOC,YAAYF,EAAKnB,MAC9BC,SAAUmB,OAAOC,YAAYF,EAAKlB,WAItC,OAAOxF,CACT,EAMA,QAAA6G,GACE,MAAM7G,EAAS8G,KAAKlC,QAEpB,GAAmC,IAA/B+B,OAAOxE,KAAKnC,GAAQqB,OAEtB,OADA7C,QAAQ4H,IAAI,0CAA2C,iCAAkC,kBAClFpG,EAGTxB,QAAQuI,MAAM,4BAA6B,kCAE3C,IAAA,MAAY5B,EAAOuB,KAASC,OAAOK,QAAQhH,GAAS,CAClDxB,QAAQuI,MAAM,KAAK5B,EAAS,qCAG5B,MAAM8B,EAAY,GACZC,MAAcrI,IAAI,IACnB8H,OAAOxE,KAAKuE,EAAKpB,SACjBqB,OAAOxE,KAAKuE,EAAKnB,SACjBoB,OAAOxE,KAAKuE,EAAKlB,YAGtB,IAAA,MAAWrF,KAAO+G,EAChBD,EAAU5E,KAAK,CACblC,MACAmF,KAAMoB,EAAKpB,KAAKnF,IAAQ,EACxBoF,KAAMmB,EAAKnB,KAAKpF,IAAQ,EACxBqF,SAAUkB,EAAKlB,SAASrF,IAAQ,IAIhC8G,EAAU5F,OAAS,GACrB7C,QAAQ2I,MAAMF,GAGhBzI,QAAQ4I,UACV,CAIA,OAFA5I,QAAQ4I,WAEDpH,CACT,EAKA,UAAAqH,GACEzC,EAAM0C,QACN9I,QAAQ4H,IAAI,+BAAgC,iCAAkC,iBAChF,GC3SWmB,EAAO,CAClBlI,KAAM,YACN,KAAAC,CAAMC,EAAIC,GAAOD,EAAGiI,QAAkBhI,CAAM,GAqBvC,SAASiI,EAASrI,GACvB,MAAO,CACLC,KAAM,QAAQD,EACd,KAAAE,CAAMC,EAAIC,GAAOD,EAAGmI,gBAAgBtI,IAAcI,EAAO,EAE7D,CAaO,SAASmI,EAASvI,GACvB,MAAMwI,EAAWxI,EAAKyI,WAAW,SAAWzI,EAAO,QAAQA,EAC3D,MAAO,CACLC,KAAM,QAAQuI,EACd,KAAAtI,CAAMC,EAAIC,GAAOD,EAAGG,aAAakI,EAAUpI,EAAM,OAAS,QAAU,EAExE,CAkCO,SAASsI,EAAW1I,GACzB,MAAO,CACLC,KAAM,QAAQD,EACd,KAAAE,CAAMC,EAAIC,GACG,MAAPA,EAAaD,EAAGwI,gBAAgB3I,GAC/BG,EAAGG,aAAaN,EAAaI,EAAPoB,GAC7B,EAEJ,CAKY,MAACoH,EAAe,CAC1BP,EAAS,aAIEQ,EAAe,CAC1BN,EAAS,WACTA,EAAS,YACTA,EAAS,aASLO,EAAa,CACjB,WAAY,OAAQ,aAAc,iBAAkB,WACpD,YAAa,WAAY,WAAY,OAAQ,QAAS,QACtD,QAAS,WAAY,WAAY,QAAS,mBAOtCC,EAAe,CACnB,OAAQ,MAAO,MAAO,QAAS,cAAe,SAAU,SACxD,SAAU,MAAO,OAAQ,OAAQ,OAAQ,OAAQ,WACjD,UAAW,MAAO,MAAO,OAAQ,YAAa,YAC9C,QAAS,SAAU,MAAO,OAAQ,SAAU,eAC5C,UAAW,WAAY,YAAa,eAAgB,YACpD,kBAAmB,aAAc,YAAa,MAAO,KACrD,SAAU,UAAW,WAAY,QAAS,QAAS,SACnD,UAAW,UAAW,QAAS,UAAW,OAAQ,WAQ9CC,EAAkB,CACtB,UAAW,WAAY,WAAY,UAAW,UAAW,WACzD,OAAQ,QAAS,kBAAmB,YAAa,WAAY,UAQzDC,EAAoB,CACxB,UAAW,OAAQ,WAAY,WAC/B,OAAQ,eAAgB,cACxB,QAAS,cAAe,aAAc,WAAY,OAClD,mBAAoB,eAAgB,UAAW,SAC/C,WAAY,WAAY,WAAY,YACpC,WAAY,WAAY,UAAW,WAAY,WAAY,UAC3D,QAAS,UAAW,WAAY,cAAe,kBAC/C,eAAgB,eAAgB,yEJnH3B,SAAiBC,EAAMzI,EAAO0I,EAAU,CAAA,GAC7C,KAAMD,aAAgBE,aACpB,MAAU5G,MAAM,kDAElB,IAAK/B,GAA0B,iBAAVA,EACnB,MAAU+B,MAAM,8CAGlB,MAAM6G,UAAEA,GAAY,EAAOC,SAAUC,EAAe,IAAOJ,EACrDG,EApBR,SAAuBE,EAAUD,GAC/B,IAAKA,EAAatH,OAAQ,OAAOuH,EACjC,MAAMC,MAAahE,IACnB,IAAA,MAAWiE,KAAKF,EAAUC,EAAO7H,IAAI8H,EAAEzJ,KAAMyJ,GAC7C,IAAA,MAAWA,KAAKH,EAAaI,SAAe/H,IAAI8H,EAAEzJ,KAAMyJ,GACxD,MAAO,IAAID,EAAOG,SACpB,CAcmBC,CAActJ,EAAkBgJ,GAE3CO,EAAiB,KACrB,MAAMrH,EAAW,GACXvB,MAAiB6I,QAGjBC,EAAW,CAAC,iBAAkBV,EAAS/C,IAAImD,GAAK,IAAIA,EAAEzJ,UAAUgK,KAAK,KACrEC,EAAWhB,EAAKiB,iBAAiBH,GAEvC,IAAA,MAAW7J,KAAM+J,EAAU,CAEzB,GAAI/J,EAAGiK,aAAa,aAAc,CAChC,MAAMC,EAAIpJ,EAAed,EAAIM,EAAON,EAAGmK,aAAa,aAAcpJ,GAC9DmJ,GAAG5H,EAASQ,KAAKoH,EACvB,CAGA,IAAA,MAAW1J,KAAW2I,EACpB,GAAInJ,EAAGiK,aAAazJ,EAAQV,MAAO,CACjC,MAAMoK,EAAI7J,EAAaL,EAAIM,EAAON,EAAGmK,aAAa3J,EAAQV,MAAOU,GAC7D0J,GAAG5H,EAASQ,KAAKoH,EACvB,CAEJ,CAGA,MAAME,EAAeC,IACnB,MAAMC,EAAUvJ,EAAWmF,IAAImE,EAAE1J,QAmHvC,IAAuBX,EAlHbsK,MAAiB3J,OAAO2J,EAAQ1J,KAmHxB,cADKZ,EAlHwCqK,EAAE1J,QAmHxDO,KAA4BlB,EAAGmB,QACtB,WAAZnB,EAAGkB,MAAiC,UAAZlB,EAAGkB,KAAyBlB,EAAGuK,cACpDvK,EAAGoB,QAhHR,OAHA2H,EAAKyB,iBAAiB,QAASJ,GAC/B9H,EAASQ,KAAK,IAAMiG,EAAK0B,oBAAoB,QAASL,IAE/C,IAAM9H,EAASoI,QAAQR,GAAKA,MAIrC,IAAKhB,GAAqC,YAAxBvF,SAASgH,WAA0B,CACnD,IAAIpH,EAAU,KACd,MAAMqH,EAAU,KAAQrH,EAAUoG,KAElC,OADAhG,SAAS6G,iBAAiB,mBAAoBI,EAAS,CAAEC,MAAM,IACxD,IAAMtH,EAAUA,IAAYI,SAAS8G,oBAAoB,mBAAoBG,EACtF,CAEA,OAAOjB,GACT,6BIjCO,YAAwBmB,GAC7B,OAAOA,EAAM1E,IAAIvG,IAAA,CACfC,KAAM,cAAcD,EACpB,KAAAE,CAAMC,EAAIC,GAAOD,EAAG+K,UAAUC,OAAOnL,IAAcI,EAAO,IAE9D,aCpCO,SAAkBR,GACvB,GAAkB,mBAAPA,EACT,MAAU4C,MAAM,kCAGlB,IAAI4I,EACAC,GAAgB,EAChBC,GAAkB,EAClBC,GAAW,EACf,MAAMC,EAAc,GAGdC,EAAgBnJ,EAAO,KAI3B,IAAIgJ,IAAmBC,EAAvB,CAEAD,GAAkB,EAElB,IACE,MAAMI,EAAW9L,IAGZyL,GAAkB9D,OAAOoE,GAAGD,EAAUN,KACzCA,EAAcM,EACdL,GAAgB,EAGhBG,EAAYX,QAAQe,GAAYA,EAASR,IAE7C,OAAS7L,GACPD,EAAS,2CAA4CC,GAEhD8L,QAAiC,IAAhBD,IACpBA,OAAc,EACdC,GAAgB,EAGhBG,EAAYX,QAAQe,GAAYA,EAASR,IAE7C,CAAA,QAGES,eAAe,KACRN,IACHD,GAAkB,IAGxB,CAjCiC,IAoCnC,MAAO,CAIL,SAAI/J,GACF,IAAK8J,EACH,MAAU7I,MAAM,iDAElB,OAAO4I,CACT,EAQA,SAAAU,CAAUF,GACR,GAAwB,mBAAbA,EACT,MAAUpJ,MAAM,mCAWlB,OARAgJ,EAAYvI,KAAK2I,GAGbP,GACFO,EAASR,GAIJ,KACL,MAAMW,EAAQP,EAAYQ,QAAQJ,GAC9BG,GAAQ,GACVP,EAAYpI,OAAO2I,EAAO,GAGhC,EAKA,OAAAE,GACEV,GAAW,EACXE,IACAD,EAAYvJ,OAAS,EACrBoJ,GAAgB,EAChBC,GAAkB,CACpB,EAEJ,uBC5IO,WACL,MAAM7I,EAAW,GAEjB,MAAO,CAKL,GAAA5C,CAAID,GACgB,mBAAPA,GACT6C,EAASQ,KAAKrD,EAElB,EAKA,OAAAqM,GACE,KAAOxJ,EAASR,QAAQ,CACtB,MAAMrC,EAAK6C,EAASV,MACpB,IAAMnC,GAAM,OAAS4K,GAAiC,CACxD,CACF,EAEJ,sBH+DO,SAA2BrB,EAAU,IAC1C,MAAMpD,EAAQoD,EAAQpD,OAAS,QAMzBmG,EAAS,CAAClM,EAAMmM,KACpB,MAAM/L,EAAM+I,EAAQnJ,GACpB,YAAe,IAARI,EAAoBA,EAAM+L,GAGnC,MAAO,CACLnM,KAAM,SAAS+F,EAEfqG,OAAQ,KACF9G,GACFlG,QAAQ4H,IAAI,MAAMjB,mBAAwB,iCAAkC,mBAIhFsG,MAAO,CAACtL,EAAKQ,KAEQ,iBAARR,GAAoBA,EAAI0H,WAAW,OAI9C3C,EAAcC,EAAO,OAAQhF,GAEzBuE,GAAiB4G,EAAO,UAAU,IAAUxG,EAAc3E,IAC5D3B,QAAQ4H,IACN,MAAMjB,cAAkBhF,SAAWyF,EAAYjF,KAC/C,iCACA,iBACA,oCACA,mBAXKA,GAkBX+K,MAAO,CAACvL,EAAK2K,EAAUa,KAEF,iBAARxL,GAAoBA,EAAI0H,WAAW,OAI9C3C,EAAcC,EAAO,OAAQhF,GAEzBuE,GAAiB4G,EAAO,UAAU,IAASxG,EAAc3E,KAC3D3B,QAAQ4H,IACN,MAAMjB,cAAkBhF,QAAUyF,EAAY+F,QAAe/F,EAAYkF,KACzE,iCACA,iBACA,oCACA,kBAIEQ,EAAO,SAAS,IAClB9M,QAAQoN,MAAM,MAAMzG,sBAA0BhF,IAAO,iBAhBhD2K,GAuBXe,YAAc1L,IACRuE,GAAiBI,EAAc3E,IACjC3B,QAAQ4H,IACN,MAAMjB,oBAAwBhF,IAC9B,iCACA,iBACA,sCAKN2L,SAAU,CAAC3L,EAAKQ,KAEK,iBAARR,GAAoBA,EAAI0H,WAAW,OAI9C3C,EAAcC,EAAO,WAAYhF,GAE7BuE,GAAiB4G,EAAO,aAAa,IAASxG,EAAc3E,IAC9D3B,QAAQ4H,IACN,MAAMjB,iBAAqBhF,SAAWyF,EAAYjF,KAClD,iCACA,iBACA,oCACA,oBAKV,+GCIO,WACL,MAAO,CACL4G,KACGW,EAAWvC,IAAIvG,GAAQqI,EAASrI,OAChC+I,EAAaxC,IAAIvG,GAAQ0I,EAAW1I,OACpCgJ,EAAgBzC,IAAIvG,GAAQuI,EAASvI,OACrCiJ,EAAkB1C,IAAIvG,GAAQ0I,EAAW,QAAQ1I,IAExD,iBGnMO,SAAsBgK,EAAW,kBACtC,MAAM7J,EAAyB,oBAAb2D,SAA2BA,SAAS6I,cAAc3C,GAAY,KAChF,IAAK7J,EAAI,MAAO,CAAA,EAChB,IACE,OAAOuG,KAAKkG,MAAMzM,EAAGsB,YACvB,CAAA,MACE,MAAO,CAAA,CACT,CACF,eCfO,SAAoBO,GACzB,SAAUA,GAAsB,iBAARA,GAA8C,mBAAnBA,EAAIhB,WACzD,WN+JO,SAAgB4C,EAAWnD,EAAOoM,EAAU1D,GACjD,MAAMpI,IACJA,EAAA+L,OACAA,EAAAC,OACAA,EAAAC,OACAA,EAAAC,QACAA,EAAU,MAAAC,cACVA,EAAgBvJ,EAAAwJ,eAChBA,EAAiB7I,GACf6E,EAGEiE,EACiB,iBAAdxJ,EACHE,SAAS6I,cAAc/I,GACvBA,EAEN,IAAKwJ,EAEH,OADAnO,EAAQ,kCAAkC2E,gBACnC,OAGT,GAAmB,mBAAR7C,EACT,MAAUyB,MAAM,sDAGlB,GAAsB,mBAAXsK,GAA2C,mBAAXC,EACzC,MAAUvK,MAAM,2EAIlB,MAAM6K,MAAoB5H,IAEpB6H,MAAqB7H,IAErB8H,MAAqB9H,IACrB+H,MAAe/N,IAErB,SAASgO,IACP,MAA0B,mBAAZR,EACVA,IACAnJ,SAAS2J,cAAcR,EAC7B,CAmCA,SAASS,IACP,MAAMC,EAAQlN,EAAMoM,GAEpB,IAAKjK,MAAMC,QAAQ8K,GAEjB,YADA1O,EAAQ,6BAA6B4N,qBAMvC,IAAIrI,GAAY,EAChB,GAAI2I,GAAkBE,EAAcO,OAASD,EAAM1L,OAAQ,CACzDuC,GAAY,EACZ,IAAA,IAASrC,EAAI,EAAGA,EAAIwL,EAAM1L,OAAQE,IAChC,IAAKkL,EAAcpH,IAAIlF,EAAI4M,EAAMxL,KAAM,CAAEqC,GAAY,EAAO,KAAO,CAEvE,CAEAgJ,EAAStF,QACT,MAAM2F,EAAU,GAGhB,IAAA,IAAS1L,EAAI,EAAGA,EAAIwL,EAAM1L,OAAQE,IAAK,CACrC,MAAM2L,EAAOH,EAAMxL,GACb4L,EAAIhN,EAAI+M,GAEd,GAAIN,EAASvH,IAAI8H,GAAI,CACnB9O,EAAQ,sCAAsC8O,MAC9C,QACF,CACAP,EAAS3N,IAAIkO,GAEb,IAAI5N,EAAKkN,EAAchH,IAAI0H,GAC3B,MAAMC,GAAiB7N,EAEnB6N,IACF7N,EAAKsN,IACLJ,EAAczL,IAAImM,EAAG5N,IAGvB,IAEM6N,GAAiBjB,GACnBA,EAAOe,EAAM3N,EAAIgC,GAKnB,MAAM8L,EAAWX,EAAejH,IAAI0H,GAC9BG,EAAYX,EAAelH,IAAI0H,GACjCf,EACEiB,IAAaH,GAAQI,IAAc/L,GACrC6K,EAAOc,EAAM3N,EAAIgC,EAAG,CAAE6L,kBAEflB,GAETA,EAAOgB,EAAM3N,EAAIgC,GAInBmL,EAAe1L,IAAImM,EAAGD,GACtBP,EAAe3L,IAAImM,EAAG5L,EAExB,OAASgM,GACP7O,EAAS,4CAA4CyO,MAAOI,EAC9D,CAEAN,EAAQ5K,KAAK9C,EACf,EA/EF,SAA2ByD,EAAWhE,EAAI4E,GACxC,MAAM4J,EAAiBtK,SAASK,KAAKH,SAASJ,GACxCyK,EAAeD,GAAkBlB,EAAgBA,EAActJ,GAAa,KAC5E0K,EAAgBF,GAAkBjB,EAAiBA,EAAevJ,EAAW,CAAEY,cAAe,KA8ErE,MAI7B,GA3GJ,SAAsBZ,EAAWiK,GAC/B,IAAIU,EAAM3K,EAAU4K,WAEpB,IAAA,IAASrM,EAAI,EAAGA,EAAI0L,EAAQ5L,OAAQE,IAAK,CACvC,MAAMsM,EAAUZ,EAAQ1L,GAEpBoM,IAAQE,EAKZ7K,EAAU8K,aAAaD,EAASF,GAJ9BA,EAAMA,EAAII,WAKd,CAGA,KAAOJ,GAAK,CACV,MAAMK,EAAOL,EAAII,YACjB/K,EAAUiL,YAAYN,GACtBA,EAAMK,CACR,CACF,CAoFIE,CAAa1B,EAAaS,GAGtBR,EAAcO,OAASJ,EAASI,KAClC,IAAA,MAAWG,KAAKV,EAActK,OACvByK,EAASvH,IAAI8H,KAChBV,EAAcvN,OAAOiO,GACrBT,EAAexN,OAAOiO,GACtBR,EAAezN,OAAOiO,KArF9BnO,GAEIyO,GAAcA,IACdC,GAAeA,GACrB,CAwEES,CAAkB3B,EAAa,EAa5B5I,EACL,CAIA,IAAIwK,EACJ,GAAgC,mBAArBvO,EAAMO,WACfgO,EAAcvO,EAAMO,WAAW6L,EAAUa,OAC3C,IAAsC,mBAApBjN,EAAMqL,UAYtB,OAFA4B,IACAzO,EAAQ,iFACD,KACLmO,EAAY6B,kBACZ5B,EAAcnF,QACdoF,EAAepF,QACfqF,EAAerF,QACfsF,EAAStF,SAjBqC,CAEhD,MAAMgH,EAAYzO,EAAMqL,UAAU,IAAM4B,KACxCA,IAEAsB,EAAmC,mBAAdE,EACjBA,EACA,KAAQA,GAAWF,gBACzB,CAWA,CAEA,MAAO,KACsB,mBAAhBA,GACTA,IAGF5B,EAAY6B,kBACZ5B,EAAcnF,QACdoF,EAAepF,QACfqF,EAAerF,QACfsF,EAAStF,QAEb,mBHlTO,SAAelG,GAEpB,IAAKA,GAAsB,iBAARA,GAAoBY,MAAMC,QAAQb,GACnD,MAAUQ,MAAM,mCAElB,GAAI+E,OAAO4H,SAASnN,IAAQuF,OAAO6H,SAASpN,GAC1C,MAAUQ,MAAM,2CAIlB,MAAM6M,EAAY9H,OAAOwF,OAAO,MAC1BuC,MAA2B7J,IAC3B8J,MAAqB9P,IACrB+P,EAAmB,GACzB,IAAIC,GAAiB,EAsFrBzN,EADuB0N,OAAO,mBACR,EAGtB,MAAMjM,EAAiB,CAAC1C,EAAK4O,KACtBN,EAAUtO,KAAMsO,EAAUtO,GAAO,IAEtC,MAAM6K,EAAW,KACf2D,EAAe1P,IAAI8P,IAKrB,OAFAN,EAAUtO,GAAKkC,KAAK2I,GAEb,KACL,GAAIyD,EAAUtO,GAAM,CAClB,MAAM6O,EAAMP,EAAUtO,GAAKiL,QAAQJ,IACvB,IAARgE,IACFP,EAAUtO,GAAKqC,OAAOwM,EAAK,GACG,IAA1BP,EAAUtO,GAAKkB,eAAqBoN,EAAUtO,GAEtD,IAIEyC,EAAQ,IAAIqM,MAAM7N,EAAK,CAC3B,GAAAqE,CAAIvF,EAAQC,GAEV,GAAmB,iBAARA,GAAoBA,EAAI0H,WAAW,KAC5C,OAAO3H,EAAOC,GAGhB,MAAMQ,EAAQT,EAAOC,GAGrB,GAAIvB,EAAQoO,KAAO,EACjB,IAAA,MAAWkC,KAAUtQ,EACnBsQ,EAAOtM,EAAOzC,EAAK0C,GAIvB,OAAOlC,CACT,EAEA,GAAAK,CAAId,EAAQC,EAAKQ,GACf,MAAMgL,EAAWzL,EAAOC,GAGxB,OAAIwG,OAAOoE,GAAGY,EAAUhL,KAExBT,EAAOC,GAAOQ,EAGd+N,EAAqB1N,IAAIb,EAAKQ,GAzH5BkO,IAEJA,GAAiB,EACjB5D,eAAe,KACb,IAAIkE,EAAa,EAGjB,IACE,MAAQT,EAAqB1B,KAAO,GAAK2B,EAAe3B,KAAO,IAH1C,IAGgDmC,GAA6B,CAChGA,IAGA,IAAA,IAAS5N,EAAI,EAAGA,EAAIqN,EAAiBvN,OAAQE,IAC3C,IACEqN,EAAiBrN,IACnB,OAASgM,GACP7O,EAAS,6CAA8C6O,EACzD,CAIF,IAAA,MAAYpN,EAAKQ,KAAU+N,EACzB,GAAID,EAAUtO,GAAM,CAClB,MAAMiP,EAAOX,EAAUtO,GACvB,IAAIoB,EAAI,EACR,KAAOA,EAAI6N,EAAK/N,QAAQ,CACtB,MAAMrC,EAAKoQ,EAAK7N,GAChB,IACEvC,EAAG2B,EACL,OAAS4M,GACP7O,EAAS,uDAA8DyB,EAAPS,OAAiB2M,EACnF,CAEI6B,EAAK7N,KAAOvC,GAAIuC,GACtB,CACF,CAGFmN,EAAqBpH,QAGrB,MAAM+H,EAAcrN,MAAM2M,EAAe3B,MACzC,IAAIgC,EAAM,EACV,IAAA,MAAWtN,KAAUiN,EACnBU,EAAQL,KAAStN,EAEnBiN,EAAerH,QACf,IAAA,IAAS/F,EAAI,EAAGA,EAAI8N,EAAQhO,OAAQE,IAClC,IACE8N,EAAQ9N,IACV,OAASgM,GACP7O,EAAS,mCAAoC6O,EAC/C,CAEJ,CACF,CAAA,QACEsB,GAAiB,CACnB,CApDuB,IAsDnBM,GACFzQ,EACE,sKAuDmC,CASzC,IAwDF,OAtCA0C,EAAIkO,aAAgBtQ,IAClB,GAAkB,mBAAPA,EACT,MAAU4C,MAAM,oCAKlB,OAHqC,IAAjCgN,EAAiBxD,QAAQpM,IAC3B4P,EAAiBvM,KAAKrD,GAEjB,KACL,MAAMgQ,EAAMJ,EAAiBxD,QAAQpM,IACzB,IAARgQ,GACFJ,EAAiBpM,OAAOwM,EAAK,KAKnC5N,EAAIhB,WAAa,CAACD,EAAKnB,KACrB,GAAkB,mBAAPA,EACT,MAAU4C,MAAM,iCAUlB,OAPK6M,EAAUtO,KAAMsO,EAAUtO,GAAO,IACtCsO,EAAUtO,GAAKkC,KAAKrD,GAGpBA,EAAG4D,EAAMzC,IAGF,KACL,GAAIsO,EAAUtO,GAAM,CAClB,MAAM6O,EAAMP,EAAUtO,GAAKiL,QAAQpM,IACvB,IAARgQ,IACFP,EAAUtO,GAAKqC,OAAOwM,EAAK,GACG,IAA1BP,EAAUtO,GAAKkB,eAAqBoN,EAAUtO,GAEtD,IAIGyC,CACT,yBUlRO,SAAe/C,EAAOM,EAAK6K,GAChC,IAAKnL,EAAMO,WACT,MAAUwB,MAAM,sCAElB,OAAO/B,EAAMO,WAAWD,EAAK6K,EAC/B,gBCoBO,SAAqBnL,EAAO0P,EAAU,IAC3C,IAAKA,EAAQlO,OAAQ,OAAOxB,EAG5B,IAAA,MAAW2P,KAAKD,EACd,IACEC,EAAEhE,UACJ,OAAS5B,GACPlL,EAAS,qBAAqB8Q,EAAEpQ,yBAA0BwK,EAC5D,CAMF,MAAM8E,MAA2B7J,IAgBjC,IAAI4K,EAKJ,MAJkC,mBAAvB5P,EAAMyP,eACfG,EAAa5P,EAAMyP,aAhBrB,WACE,IAAA,MAAYnP,EAAKQ,KAAU+N,EACzB,IAAA,MAAWc,KAAKD,EACd,IACEC,EAAE1D,WAAW3L,EAAKQ,EACpB,OAASiJ,GACPlL,EAAS,qBAAqB8Q,EAAEpQ,2BAA4BwK,EAC9D,CAGJ8E,EAAqBpH,OACvB,IAQO,IAAI2H,MAAMpP,EAAO,CACtB,GAAA4F,CAAIvF,EAAQC,GAEV,GAAY,aAARA,EACF,MAAO,KACDsP,GAAYA,IAChBf,EAAqBpH,SAKzB,GAAmB,iBAARnH,GAAoBA,EAAI0H,WAAW,KAAM,CAClD,MAAM6H,EAASxP,EAAOC,GACtB,MAAY,eAARA,GAA0C,mBAAXuP,EAE1B,CAACC,EAAQ3Q,KACd,IAAA,MAAWwQ,KAAKD,EACd,IACEC,EAAE3D,cAAc8D,EAClB,OAAS/F,GACPlL,EAAS,qBAAqB8Q,EAAEpQ,8BAA+BwK,EACjE,CAEF,OAAO8F,EAAOC,EAAQ3Q,IAGnB0Q,CACT,CAEA,IAAI/O,EAAQT,EAAOC,GAGnB,IAAA,MAAWqP,KAAKD,EACd,IACE,MAAMK,EAAIJ,EAAE/D,QAAQtL,EAAKQ,QACf,IAANiP,IAAiBjP,EAAQiP,EAC/B,OAAShG,GACPlL,EAAS,qBAAqB8Q,EAAEpQ,wBAAyBwK,EAC3D,CAGF,OAAOjJ,CACT,EAEA,GAAAK,CAAId,EAAQC,EAAKQ,GACf,MAAMgL,EAAWzL,EAAOC,GACxB,IAAI2K,EAAWnK,EAGf,IAAA,MAAW6O,KAAKD,EACd,IACE,MAAMK,EAAIJ,EAAE9D,QAAQvL,EAAK2K,EAAUa,QACzB,IAANiE,IAAiB9E,EAAW8E,EAClC,OAAShG,GACPlL,EAAS,qBAAqB8Q,EAAEpQ,wBAAyBwK,EAC3D,CASF,OALKjD,OAAOoE,GAAGD,EAAUa,IACvB+C,EAAqB1N,IAAIb,EAAK2K,GAGhC5K,EAAOC,GAAO2K,GACP,CACT,GAEJ"}
|
|
1
|
+
{"version":3,"file":"lume.global.js","sources":["../src/utils/log.js","../src/core/state.js","../src/core/bindDom.js","../src/core/effect.js","../src/addons/repeat.js","../src/addons/debug.js","../src/handlers/index.js","../src/addons/computed.js","../src/addons/cleanupGroup.js","../src/addons/hydrateState.js","../src/addons/index.js","../src/addons/watch.js","../src/addons/withPlugins.js"],"sourcesContent":["/**\n * Environment-safe logging utilities for constrained runtimes\n * (e.g. service workers, embedded engines, SSR environments).\n *\n * All core and addon files should import these instead of\n * calling console.* directly to avoid ReferenceError when\n * console is not defined.\n */\n\nexport function logWarn(msg, ...rest) {\n if (typeof console !== 'undefined' && typeof console.warn === 'function') {\n console.warn(msg, ...rest);\n }\n}\n\nexport function logError(msg, ...rest) {\n if (typeof console !== 'undefined' && typeof console.error === 'function') {\n console.error(msg, ...rest);\n }\n}\n","/**\n * Lume-JS Reactive State Core\n *\n * Provides minimal reactive state with standard JavaScript.\n * Features automatic microtask batching for performance.\n * Read tracking is opt-in via withReadObserver — state.js has zero permanent\n * dependency on effect.js or any other module.\n *\n * Features:\n * - Lightweight and Go-style\n * - Explicit nested states\n * - $subscribe for listening to key changes\n * - Cleanup with unsubscribe\n * - Per-state microtask batching for writes\n * - Scope-based read tracking via withReadObserver (multi-observer safe)\n *\n * Usage:\n * import { state } from \"lume-js\";\n *\n * const store = state({ count: 0 });\n * const unsub = store.$subscribe(\"count\", val => console.log(val));\n * unsub(); // cleanup\n */\n\nimport { logError } from '../utils/log.js';\n\n// Per-state batching – each state object maintains its own microtask flush.\n// This keeps effects simple and aligned with Lume's minimal philosophy.\n\n/**\n * Creates a reactive state object.\n *\n * @param {Object} obj - Initial state object (must be plain object)\n * @returns {Proxy} Reactive proxy with $subscribe method\n *\n * @example\n * const store = state({ count: 0 });\n */\n\n// Active read observers — only populated during withReadObserver scopes.\n// This keeps state.js pure: tracking only happens when someone explicitly\n// asks to observe reads within a synchronous function call.\n//\n// Note: This Set is module-level, so all reactive state instances and effects\n// within the SAME module instance share it. This is standard behavior for\n// auto-tracking reactive libraries (Vue, MobX, Solid, etc.). Multiple copies\n// of the lume-js module (e.g. from different bundled chunks) each get their\n// own independent Set via ES module / CommonJS isolation.\nconst readers = new Set();\n\n/**\n * Run a function with a read observer active.\n * The observer receives (proxy, key, registerEffect) for every property read.\n * Multiple observers can be active simultaneously (nested effects, devtools, etc.)\n *\n * Internal API — used by effect.js for auto-tracking. May be stabilized\n * for third-party addons in a future release.\n * @param {function} onRead - Called on each property access inside fn\n * @param {function} fn - The function to run under observation\n */\nexport function withReadObserver(onRead, fn) {\n readers.add(onRead);\n try {\n return fn();\n } finally {\n readers.delete(onRead);\n }\n}\n\nexport function state(obj) {\n // Validate input\n if (!obj || typeof obj !== 'object' || Array.isArray(obj)) {\n throw new Error('state() requires a plain object');\n }\n if (Object.isFrozen(obj) || Object.isSealed(obj)) {\n throw new Error('state() requires a mutable plain object');\n }\n\n // Object.create(null) - no prototype chain lookups\n const listeners = Object.create(null);\n const pendingNotifications = new Map(); // Per-state pending changes\n const pendingEffects = new Set(); // Dedupe effects per state\n const beforeFlushHooks = [];\n let flushScheduled = false;\n\n /**\n * Schedule a single microtask flush for this state object.\n *\n * Flush order per state:\n * 1) Notify subscribers for changed keys (key → subscribers)\n * 2) Run each queued effect exactly once (Set-based dedupe)\n * 3) Repeat up to 100 iterations to handle cascading updates,\n * then log an error to prevent infinite loops.\n *\n * Notes:\n * - Batching is per state; effects that depend on multiple states\n * may run once per state that changed (by design).\n */\n function scheduleFlush() {\n if (flushScheduled) return;\n\n flushScheduled = true;\n queueMicrotask(() => {\n let iterations = 0;\n const MAX_ITERATIONS = 100;\n\n try {\n while ((pendingNotifications.size > 0 || pendingEffects.size > 0) && iterations < MAX_ITERATIONS) {\n iterations++;\n\n // Run registered before-flush hooks (e.g. plugin onNotify)\n for (let i = 0; i < beforeFlushHooks.length; i++) {\n try {\n beforeFlushHooks[i]();\n } catch (err) {\n logError('[Lume.js state] Error in beforeFlush hook:', err);\n }\n }\n\n // Notify all subscribers of changed keys\n for (const [key, value] of pendingNotifications) {\n if (listeners[key]) {\n const subs = listeners[key];\n let i = 0;\n while (i < subs.length) {\n const fn = subs[i];\n try {\n fn(value);\n } catch (err) {\n logError(`[Lume.js state] Error notifying subscriber for key \"${String(key)}\":`, err);\n }\n // Only advance if fn wasn't removed (something shifted into its place)\n if (subs[i] === fn) i++;\n }\n }\n }\n\n pendingNotifications.clear();\n\n // Run each effect exactly once (Set deduplicates)\n const effects = new Array(pendingEffects.size);\n let idx = 0;\n for (const effect of pendingEffects) {\n effects[idx++] = effect;\n }\n pendingEffects.clear();\n for (let i = 0; i < effects.length; i++) {\n try {\n effects[i]();\n } catch (err) {\n logError('[Lume.js state] Error in effect:', err);\n }\n }\n }\n } finally {\n flushScheduled = false;\n }\n\n if (iterations >= MAX_ITERATIONS) {\n logError(\n '[Lume.js state] Maximum flush iterations reached (100). ' +\n 'This usually indicates an infinite loop caused by an effect or computed mutating state it depends on.'\n );\n }\n });\n }\n\n // Brand symbol for type-level reactive identification\n const REACTIVE_BRAND = Symbol('lume.reactive');\n obj[REACTIVE_BRAND] = true;\n\n // Defined once per state instance — not per property read — to avoid per-read closure allocation.\n const registerEffect = (key, executeFn) => {\n if (!listeners[key]) listeners[key] = [];\n\n const callback = () => {\n pendingEffects.add(executeFn);\n };\n\n listeners[key].push(callback);\n\n return () => {\n if (listeners[key]) {\n const idx = listeners[key].indexOf(callback);\n if (idx !== -1) {\n listeners[key].splice(idx, 1);\n if (listeners[key].length === 0) delete listeners[key];\n }\n }\n };\n };\n\n const proxy = new Proxy(obj, {\n get(target, key) {\n // Skip effect tracking for internal meta methods (e.g. $subscribe)\n if (typeof key === 'string' && key.startsWith('$')) {\n return target[key];\n }\n\n const value = target[key];\n\n // Notify active read observers (effects, devtools, etc.)\n if (readers.size > 0) {\n for (const reader of readers) {\n reader(proxy, key, registerEffect);\n }\n }\n\n return value;\n },\n\n set(target, key, value) {\n const oldValue = target[key];\n\n // Skip update if value unchanged - Object.is() handles NaN and -0 correctly\n if (Object.is(oldValue, value)) return true;\n\n target[key] = value;\n\n // Batch notifications at the state level (per-state, not global)\n pendingNotifications.set(key, value);\n scheduleFlush();\n\n return true;\n }\n });\n\n /**\n * Subscribe to changes for a specific key.\n * Calls the callback immediately with the current value.\n * Returns an unsubscribe function for cleanup.\n *\n * @param {string} key - Property key to watch\n * @param {function} fn - Callback function\n * @returns {function} Unsubscribe function\n */\n // Set on obj (not proxy) to avoid triggering the set trap.\n // The get trap already returns target[key] directly for $-prefixed keys.\n /**\n * Register a callback to run before each flush.\n * Returns an unsubscribe function.\n */\n obj.$beforeFlush = (fn) => {\n if (typeof fn !== 'function') {\n throw new Error('$beforeFlush requires a function');\n }\n if (beforeFlushHooks.indexOf(fn) === -1) {\n beforeFlushHooks.push(fn);\n }\n return () => {\n const idx = beforeFlushHooks.indexOf(fn);\n if (idx !== -1) {\n beforeFlushHooks.splice(idx, 1);\n }\n };\n };\n\n obj.$subscribe = (key, fn) => {\n if (typeof fn !== 'function') {\n throw new Error('Subscriber must be a function');\n }\n\n if (!listeners[key]) listeners[key] = [];\n listeners[key].push(fn);\n\n // Call immediately with current value (NOT batched)\n fn(proxy[key]);\n\n // Return unsubscribe function\n return () => {\n if (listeners[key]) {\n const idx = listeners[key].indexOf(fn);\n if (idx !== -1) {\n listeners[key].splice(idx, 1);\n if (listeners[key].length === 0) delete listeners[key];\n }\n }\n };\n };\n\n return proxy;\n}\n","// src/core/bindDom.js\n/**\n * Lume-JS DOM Binding\n *\n * Binds reactive state to DOM elements using data-* attributes.\n *\n * Built-in attributes (always available):\n * data-bind=\"key\" → Two-way binding for inputs, textContent for others\n * data-hidden=\"key\" → Toggles hidden (truthy = hidden)\n * data-disabled=\"key\" → Toggles disabled (truthy = disabled)\n * data-checked=\"key\" → Toggles checked (for checkboxes/radios)\n * data-required=\"key\" → Toggles required (truthy = required)\n * data-aria-expanded=\"key\" → Sets aria-expanded to \"true\"/\"false\"\n * data-aria-hidden=\"key\" → Sets aria-hidden to \"true\"/\"false\"\n *\n * Extensible via handlers option:\n * import { show, classToggle } from 'lume-js/handlers';\n * bindDom(root, store, { handlers: [show, classToggle('active')] });\n *\n * Custom handlers:\n * const tooltip = { attr: 'data-tooltip', apply(el, val) { el.title = val ?? ''; } };\n * bindDom(root, store, { handlers: [tooltip] });\n *\n * Usage:\n * import { bindDom } from \"lume-js\";\n * const cleanup = bindDom(document.body, store);\n */\n\nimport { logWarn } from '../utils/log.js';\n\n// --- Default Handlers (always active, backwards compatible) ---\n\nconst boolHandler = (name) => ({\n attr: `data-${name}`,\n apply(el, val) { el[name] = Boolean(val); }\n});\n\nconst ariaHandler = (name) => ({\n attr: `data-${name}`,\n apply(el, val) { el.setAttribute(name, val ? 'true' : 'false'); }\n});\n\nconst DEFAULT_HANDLERS = [\n boolHandler('hidden'),\n boolHandler('disabled'),\n boolHandler('checked'),\n boolHandler('required'),\n ariaHandler('aria-expanded'),\n ariaHandler('aria-hidden'),\n];\n\n/**\n * Merge default and user handlers.\n * User handlers override defaults with same attr (Map deduplicates).\n * User handler arrays are flattened one level (supports classToggle()).\n */\nfunction mergeHandlers(defaults, userHandlers) {\n if (!userHandlers.length) return defaults;\n const merged = new Map();\n for (const h of defaults) merged.set(h.attr, h);\n for (const h of userHandlers.flat()) merged.set(h.attr, h);\n return [...merged.values()];\n}\n\n/**\n * DOM binding for reactive state\n */\nexport function bindDom(root, store, options = {}) {\n if (!(root instanceof HTMLElement)) {\n throw new Error('bindDom() requires a valid HTMLElement as root');\n }\n if (!store || typeof store !== 'object') {\n throw new Error('bindDom() requires a reactive state object');\n }\n\n const { immediate = false, handlers: userHandlers = [] } = options;\n const handlers = mergeHandlers(DEFAULT_HANDLERS, userHandlers);\n\n const performBinding = () => {\n const cleanups = [];\n const bindingMap = new WeakMap();\n\n // Build compiled selector: data-bind (always) + all handler attrs\n const selector = ['[data-bind]', ...handlers.map(h => `[${h.attr}]`)].join(',');\n const elements = root.querySelectorAll(selector);\n\n for (const el of elements) {\n // data-bind (two-way) — always in core, special handling\n if (el.hasAttribute('data-bind')) {\n const c = handleDataBind(el, store, el.getAttribute('data-bind'), bindingMap);\n if (c) cleanups.push(c);\n }\n\n // All registered handlers (default + user)\n for (const handler of handlers) {\n if (el.hasAttribute(handler.attr)) {\n const c = applyHandler(el, store, el.getAttribute(handler.attr), handler);\n if (c) cleanups.push(c);\n }\n }\n }\n\n // Event delegation for two-way bindings\n const inputHandler = e => {\n const binding = bindingMap.get(e.target);\n if (binding) binding.target[binding.key] = getInputValue(e.target);\n };\n root.addEventListener(\"input\", inputHandler);\n cleanups.push(() => root.removeEventListener(\"input\", inputHandler));\n\n return () => cleanups.forEach(c => c());\n };\n\n // Auto-wait for DOM if needed\n if (!immediate && document.readyState === 'loading') {\n let cleanup = null;\n const onReady = () => { cleanup = performBinding(); };\n document.addEventListener('DOMContentLoaded', onReady, { once: true });\n return () => cleanup ? cleanup() : document.removeEventListener('DOMContentLoaded', onReady);\n }\n\n return performBinding();\n}\n\n/**\n * Apply a handler to an element via subscription.\n * Resolves the state path and subscribes to changes.\n */\nfunction applyHandler(el, store, path, handler) {\n const result = resolveProp(store, path);\n if (!result) return null;\n const { target, key } = result;\n return target.$subscribe(key, val => handler.apply(el, val));\n}\n\n/**\n * Handle data-bind (two-way for inputs, textContent for others)\n */\nfunction handleDataBind(el, store, path, bindingMap) {\n const result = resolveProp(store, path);\n if (!result) return null;\n\n const { target, key } = result;\n const unsub = target.$subscribe(key, val => updateElement(el, val));\n\n if (isFormInput(el)) {\n bindingMap.set(el, { target, key });\n }\n\n return unsub;\n}\n\n/**\n * Resolve a nested path in an object.\n * Example: resolvePath(obj, ['user', 'address']) returns obj.user.address\n */\nfunction resolvePath(obj, pathArr) {\n if (!pathArr || pathArr.length === 0) {\n return obj;\n }\n let current = obj;\n for (let i = 0; i < pathArr.length; i++) {\n const key = pathArr[i];\n if (current === null || current === undefined) {\n return null;\n }\n if (!(key in current)) {\n return null;\n }\n current = current[key];\n }\n return current;\n}\n\n/**\n * Resolve path to target and key.\n *\n * ⚠️ Path bindings are resolved once at bind time. If an intermediate\n * object in the path is null/undefined at bindDom call time, the binding\n * is permanently dead and will not self-heal when the path later becomes valid.\n */\nfunction resolveProp(store, path) {\n if (!path) return null;\n\n const pathArr = path.split(\".\");\n const key = pathArr.pop();\n const target = resolvePath(store, pathArr);\n\n if (target === null || target === undefined) {\n logWarn(`[Lume.js] Invalid path \"${path}\"`);\n return null;\n }\n\n if (!target?.$subscribe) {\n logWarn(`[Lume.js] Target for \"${path}\" is not reactive`);\n return null;\n }\n\n return { target, key };\n}\n\n/**\n * Update element with value (for data-bind)\n */\nfunction updateElement(el, val) {\n if (el.tagName === \"INPUT\") {\n if (el.type === \"checkbox\") el.checked = Boolean(val);\n else if (el.type === \"radio\") el.checked = el.value === String(val);\n else el.value = val ?? '';\n } else if (el.tagName === \"TEXTAREA\" || el.tagName === \"SELECT\") {\n el.value = val ?? '';\n } else {\n el.textContent = val ?? '';\n }\n}\n\n/**\n * Get value from input\n */\nfunction getInputValue(el) {\n if (el.type === \"checkbox\") return el.checked;\n if (el.type === \"number\" || el.type === \"range\") return el.valueAsNumber;\n return el.value;\n}\n\n/**\n * Check if element is form input\n */\nfunction isFormInput(el) {\n return el.tagName === \"INPUT\" || el.tagName === \"TEXTAREA\" || el.tagName === \"SELECT\";\n}","import { withReadObserver } from './state.js';\nimport { logError } from '../utils/log.js';\n\n/**\n * Lume-JS Effect\n *\n * Reactive effects with two modes:\n * 1. Auto-tracking (default): Tracks dependencies automatically via withReadObserver\n * 2. Explicit deps: You specify exactly what triggers re-runs\n *\n * Auto-tracking uses scope-based read observation — state.js has zero permanent\n * dependency on this module. Read tracking is only active during the synchronous\n * execution of an effect's body.\n *\n * Usage:\n * import { effect } from \"lume-js\";\n *\n * // Auto-tracking mode (existing behavior)\n * effect(() => {\n * console.log('Count is:', store.count);\n * // Automatically re-runs when store.count changes\n * });\n *\n * // Explicit deps mode (new - no magic)\n * effect(() => {\n * console.log('Count is:', store.count);\n * }, [[store, 'count']]); // Only re-runs when store.count changes\n *\n * Features:\n * - Automatic dependency collection via withReadObserver scope (default)\n * - Explicit dependencies for side-effects\n * - Returns cleanup function\n * - Compatible with per-state batching\n */\n\n// Module-scoped effect context (prevents third-party spoofing via globalThis)\nlet currentEffect = null;\n\n// withReadObserver is used below to scope read tracking to synchronous effect execution.\n\n/**\n * Creates an effect that runs reactively\n *\n * @param {function} fn - Function to run reactively\n * @param {Array<[object, string]>} [deps] - Optional explicit dependencies as [store, key] tuples\n * @returns {function} Cleanup function to stop the effect\n *\n * @example\n * // Auto-tracking (default)\n * const store = state({ count: 0 });\n * effect(() => {\n * document.title = `Count: ${store.count}`;\n * });\n * \n * @example\n * // Explicit deps (no magic)\n * effect(() => {\n * analytics.log(store.count); // Won't track store.count automatically\n * }, [[store, 'count']]); // Explicit: only re-run on store.count\n */\nexport function effect(fn, deps) {\n if (typeof fn !== 'function') {\n throw new Error('effect() requires a function');\n }\n\n const cleanups = [];\n let isRunning = false;\n\n /**\n * Execute the effect function\n */\n const execute = () => {\n /* v8 ignore next -- re-entry guard: unreachable because $subscribe fires via microtask after isRunning resets in finally */\n if (isRunning) return;\n isRunning = true;\n\n try {\n fn();\n } catch (error) {\n logError('[Lume.js effect] Error in effect:', error);\n throw error;\n } finally {\n isRunning = false;\n }\n };\n\n // EXPLICIT DEPS MODE: deps array provided\n if (Array.isArray(deps)) {\n // Subscribe to each [store, key1, key2, ...] tuple explicitly\n for (const dep of deps) {\n if (Array.isArray(dep) && dep.length >= 2) {\n const [store, ...keys] = dep;\n if (store && typeof store.$subscribe === 'function') {\n // Subscribe to each key in this tuple\n for (const key of keys) {\n // $subscribe calls immediately, then on changes\n // We want: call execute immediately once, then on changes\n let isFirst = true;\n const unsub = store.$subscribe(key, () => {\n if (isFirst) {\n isFirst = false;\n return; // Skip first call, we'll run execute() below\n }\n execute();\n });\n cleanups.push(unsub);\n }\n }\n }\n }\n // Run immediately\n execute();\n }\n // AUTO-TRACKING MODE: no deps (existing behavior)\n else {\n const executeWithTracking = () => {\n /* v8 ignore next -- defensive guard: synchronous re-entry is unreachable through the public API */\n if (isRunning) return;\n\n // Save previous subscriptions instead of cleaning immediately.\n // If fn() doesn't read any state (early return / error), we restore\n // them so the effect stays reactive.\n const oldCleanups = cleanups.splice(0);\n\n // Create effect context for tracking\n const myContext = {\n fn,\n cleanups,\n execute: executeWithTracking,\n tracking: {}\n };\n\n // Set as current effect (for state.js to detect)\n // Save previous context to support nested effects/computed\n const previousEffect = currentEffect;\n currentEffect = myContext;\n isRunning = true;\n\n try {\n const onRead = (proxy, key, registerEffect) => {\n // Only the currently active effect (not a nested one) creates subscriptions\n if (currentEffect !== myContext) return;\n if (myContext.tracking[key]) return;\n myContext.tracking[key] = true;\n myContext.cleanups.push(registerEffect(key, myContext.execute));\n };\n withReadObserver(onRead, fn);\n } catch (error) {\n // On error, restore old subscriptions so the effect stays reactive\n cleanups.length = 0;\n cleanups.push(...oldCleanups);\n logError('[Lume.js effect] Error in effect:', error);\n throw error;\n } finally {\n // Restore previous context (not undefined) to support nesting\n currentEffect = previousEffect;\n isRunning = false;\n }\n\n // If fn() created new subscriptions, clean old ones.\n // If it didn't (e.g., early return), keep old subscriptions intact.\n if (cleanups.length > 0) {\n for (const cleanup of oldCleanups) cleanup();\n } else {\n cleanups.push(...oldCleanups);\n }\n };\n\n // Run immediately to collect initial dependencies\n executeWithTracking();\n }\n\n // Return cleanup function\n return () => {\n // while/pop is faster than forEach\n while (cleanups.length) cleanups.pop()();\n };\n}","/**\n * Lume-JS List Rendering (Addon)\n *\n * Renders lists with automatic subscription and element reuse by key.\n * \n * Core guarantees:\n * Element reuse by key (same DOM nodes, not recreated)\n * Minimal DOM operations (only updates what changed)\n * Memory efficiency (cleanup on remove)\n * \n * Default behavior (can be disabled/customized):\n * ✅ Focus preservation (maintains activeElement and selection)\n * ✅ Scroll preservation (intelligent positioning for add/remove/reorder)\n * \n * Philosophy: No artificial limitations\n * - All preservation logic is overridable via options\n * - Set to null/false to disable, or provide custom functions\n * - Export utilities so you can wrap/extend them\n *\n * ⚠️ IMPORTANT: Arrays must be updated immutably!\n * store.items.push(x) // ❌ Won't trigger update\n * store.items = [...items] // ✅ Triggers update\n * \n * ═══════════════════════════════════════════════════════════════════════\n * PATTERN 1: Simple (render only) - for simple cases or backward compat\n * ═══════════════════════════════════════════════════════════════════════\n * \n * repeat('#list', store, 'todos', {\n * key: todo => todo.id,\n * render: (todo, el) => {\n * el.textContent = todo.name; // Called on every update\n * }\n * });\n *\n * ═══════════════════════════════════════════════════════════════════════\n * PATTERN 2: Clean separation (create + update) - recommended\n * ═══════════════════════════════════════════════════════════════════════\n *\n * repeat('#list', store, 'todos', {\n * key: todo => todo.id,\n * create: (todo, el) => {\n * // Called ONCE when element is created - build DOM structure\n * const nameSpan = document.createElement('span');\n * nameSpan.className = 'name';\n * el.appendChild(nameSpan);\n * const btn = document.createElement('button');\n * btn.textContent = 'Delete';\n * btn.onclick = () => deleteTodo(todo.id);\n * el.appendChild(btn);\n *\n * // Return a cleanup function — called automatically when element is removed\n * return () => {\n * // Unsubscribe from external listeners, remove timers, etc.\n * };\n * },\n * update: (todo, el, index, { isFirstRender }) => {\n * // Called on every update - bind data\n * // isFirstRender = true on initial render, false on subsequent\n * // Skipped if same object reference (optimization)\n * el.querySelector('.name').textContent = todo.name;\n * }\n * });\n *\n * ═══════════════════════════════════════════════════════════════════════\n * ADVANCED: Custom preservation strategies\n * ═══════════════════════════════════════════════════════════════════════\n * \n * import { defaultFocusPreservation, defaultScrollPreservation } from \"lume-js/addons\";\n * \n * repeat('#list', store, 'items', {\n * key: item => item.id,\n * create: (item, el) => { ... },\n * update: (item, el) => { ... },\n * preserveFocus: null, // disable focus preservation\n * preserveScroll: (container, context) => {\n * const restore = defaultScrollPreservation(container, context);\n * return () => { restore(); console.log('Scroll restored!'); };\n * }\n * });\n */\nimport { logWarn, logError } from '../utils/log.js';\n\n/**\n * Default focus preservation strategy\n * Saves activeElement and selection state before DOM updates\n * \n * @param {HTMLElement} container - The list container\n * @returns {Function|null} Restore function, or null if nothing to restore\n */\nexport function defaultFocusPreservation(container) {\n const activeEl = document.activeElement;\n const shouldRestore = container.contains(activeEl);\n\n if (!shouldRestore) return null;\n\n let selectionStart = null;\n let selectionEnd = null;\n\n if (activeEl.tagName === 'INPUT' || activeEl.tagName === 'TEXTAREA') {\n selectionStart = activeEl.selectionStart;\n selectionEnd = activeEl.selectionEnd;\n }\n\n return () => {\n if (document.body.contains(activeEl)) {\n activeEl.focus();\n if (selectionStart !== null && selectionEnd !== null) {\n activeEl.setSelectionRange(selectionStart, selectionEnd);\n }\n }\n };\n}\n\n/**\n * Default scroll preservation strategy\n * Uses anchor-based preservation for add/remove, pixel position for reorder\n * \n * @param {HTMLElement} container - The list container\n * @param {Object} context - Additional context\n * @param {boolean} context.isReorder - Whether this is a reorder operation\n * @returns {Function} Restore function\n */\nexport function defaultScrollPreservation(container, context = {}) {\n const { isReorder = false } = context;\n const scrollTop = container.scrollTop;\n\n // Early return if no scroll\n if (scrollTop === 0) {\n return () => { container.scrollTop = 0; };\n }\n\n let anchorElement = null;\n let anchorOffset = 0;\n\n // Only use anchor-based preservation for add/remove, not reorder\n if (!isReorder) {\n const containerRect = container.getBoundingClientRect();\n // Avoid Array.from - iterate children directly\n for (let child = container.firstElementChild; child; child = child.nextElementSibling) {\n const rect = child.getBoundingClientRect();\n\n if (rect.bottom > containerRect.top) {\n anchorElement = child;\n anchorOffset = rect.top - containerRect.top;\n break;\n }\n }\n }\n\n return () => {\n if (anchorElement && document.body.contains(anchorElement)) {\n const newRect = anchorElement.getBoundingClientRect();\n const containerRect = container.getBoundingClientRect();\n const currentOffset = newRect.top - containerRect.top;\n const scrollAdjustment = currentOffset - anchorOffset;\n\n container.scrollTop = container.scrollTop + scrollAdjustment;\n } else {\n container.scrollTop = scrollTop;\n }\n };\n}\n\n/**\n * Efficiently render a list with element reuse\n * \n * @param {string|HTMLElement} container - Container element or selector\n * @param {Object} store - Reactive state object\n * @param {string} arrayKey - Key in store containing the array\n * @param {Object} options - Configuration\n * @param {Function} options.key - Function to extract unique key: (item) => key\n * @param {Function} [options.render] - Function to render item (called for all items): (item, element, index) => void\n * @param {Function} [options.create] - Function for new elements only: (item, element, index) => void | Function. If a function is returned, it is registered as the element's cleanup and called automatically when the element is removed (by list update or full cleanup).\n * @param {Function} [options.update] - Function for data binding: (item, element, index, { isFirstRender }) => void. Skipped if same item reference AND same index.\n * @param {Function} [options.remove] - Additional cleanup when element is removed: (item, element) => void. Called after any cleanup function returned by create(). Optional — prefer returning a cleanup from create() for automatic lifecycle management.\n * @param {string|Function} [options.element='div'] - Element tag name or factory function\n * @param {Function|null} [options.preserveFocus=defaultFocusPreservation] - Focus preservation strategy (null to disable)\n * @param {Function|null} [options.preserveScroll=defaultScrollPreservation] - Scroll preservation strategy (null to disable)\n * @returns {Function} Cleanup function\n */\n\nexport function repeat(container, store, arrayKey, options) {\n const {\n key,\n render,\n create,\n update,\n remove,\n element = 'div',\n preserveFocus = defaultFocusPreservation,\n preserveScroll = defaultScrollPreservation\n } = options;\n\n // Resolve container\n const containerEl =\n typeof container === 'string'\n ? document.querySelector(container)\n : container;\n\n if (!containerEl) {\n logWarn(`[Lume.js] repeat(): container \"${container}\" not found`);\n return () => { };\n }\n\n if (typeof key !== 'function') {\n throw new Error('[Lume.js] repeat(): options.key must be a function');\n }\n\n if (typeof render !== 'function' && typeof create !== 'function') {\n throw new Error('[Lume.js] repeat(): options.render or options.create must be a function');\n }\n\n // key -> HTMLElement\n const elementsByKey = new Map();\n // key -> previous item (for reference comparison)\n const prevItemsByKey = new Map();\n // key -> previous index (for reorder detection)\n const prevIndexByKey = new Map();\n // key -> cleanup function returned by create()\n const cleanupByKey = new Map();\n const seenKeys = new Set();\n\n function createElement() {\n return typeof element === 'function'\n ? element()\n : document.createElement(element);\n }\n\n function reconcileDOM(container, nextEls) {\n let ptr = container.firstChild;\n\n for (let i = 0; i < nextEls.length; i++) {\n const desired = nextEls[i];\n\n if (ptr === desired) {\n ptr = ptr.nextSibling;\n continue;\n }\n\n container.insertBefore(desired, ptr);\n }\n\n // Remove leftover children not in nextEls\n while (ptr) {\n const next = ptr.nextSibling;\n container.removeChild(ptr);\n ptr = next;\n }\n }\n\n function applyPreservation(container, fn, isReorder) {\n const shouldPreserve = document.body.contains(container);\n const restoreFocus = shouldPreserve && preserveFocus ? preserveFocus(container) : null;\n const restoreScroll = shouldPreserve && preserveScroll ? preserveScroll(container, { isReorder }) : null;\n\n fn();\n\n if (restoreFocus) restoreFocus();\n if (restoreScroll) restoreScroll();\n }\n\n function updateList() {\n const items = store[arrayKey];\n\n if (!Array.isArray(items)) {\n logWarn(`[Lume.js] repeat(): store.${arrayKey} is not an array`);\n return;\n }\n\n // Only compute isReorder if scroll preservation needs it.\n // Uses elementsByKey (previous state) and items directly — no Set allocations.\n let isReorder = false;\n if (preserveScroll && elementsByKey.size === items.length) {\n isReorder = true;\n for (let i = 0; i < items.length; i++) {\n if (!elementsByKey.has(key(items[i]))) { isReorder = false; break; }\n }\n }\n\n seenKeys.clear();\n const nextEls = [];\n\n // Build ordered list of DOM nodes (created or reused)\n for (let i = 0; i < items.length; i++) {\n const item = items[i];\n const k = key(item);\n\n if (seenKeys.has(k)) {\n logWarn(`[Lume.js] repeat(): duplicate key \"${k}\"`);\n continue;\n }\n seenKeys.add(k);\n\n let el = elementsByKey.get(k);\n const isFirstRender = !el;\n\n if (isFirstRender) {\n el = createElement();\n elementsByKey.set(k, el);\n }\n\n try {\n // Call create for new elements (DOM structure)\n if (isFirstRender && create) {\n const cleanup = create(item, el, i);\n if (typeof cleanup === 'function') {\n cleanupByKey.set(k, cleanup);\n }\n }\n\n // Call update for data binding (new and existing elements)\n // Skip if same item reference AND same index (optimization)\n const prevItem = prevItemsByKey.get(k);\n const prevIndex = prevIndexByKey.get(k);\n if (update) {\n if (prevItem !== item || prevIndex !== i) {\n update(item, el, i, { isFirstRender });\n }\n } else if (render) {\n // Backward compatibility: render handles both create and update\n render(item, el, i);\n }\n\n // Store reference and index for next comparison\n prevItemsByKey.set(k, item);\n prevIndexByKey.set(k, i);\n\n } catch (err) {\n logError(`[Lume.js] repeat(): error rendering key \"${k}\":`, err);\n }\n\n nextEls.push(el);\n }\n\n applyPreservation(containerEl, () => {\n reconcileDOM(containerEl, nextEls);\n\n // Clean maps: remove keys not in seenKeys (new state)\n if (elementsByKey.size !== seenKeys.size) {\n for (const k of elementsByKey.keys()) {\n if (!seenKeys.has(k)) {\n const el = elementsByKey.get(k);\n const prevItem = prevItemsByKey.get(k);\n // Call create-returned cleanup first, then remove callback\n const cleanup = cleanupByKey.get(k);\n if (typeof cleanup === 'function') {\n try {\n cleanup();\n } catch (err) {\n logError(`[Lume.js] repeat(): cleanup error for key \"${k}\":`, err);\n }\n }\n if (typeof remove === 'function' && el) {\n remove(prevItem, el);\n }\n elementsByKey.delete(k);\n prevItemsByKey.delete(k);\n prevIndexByKey.delete(k);\n cleanupByKey.delete(k);\n }\n }\n }\n }, isReorder);\n }\n\n // Subscription — $subscribe calls updateList immediately (initial render),\n // so no separate updateList() call is needed for reactive stores.\n let unsubscribe;\n if (typeof store.$subscribe === 'function') {\n unsubscribe = store.$subscribe(arrayKey, updateList);\n } else if (typeof store.subscribe === 'function') {\n // Generic subscribe (e.g. computed) — subscribe first, then initial render\n const subResult = store.subscribe(() => updateList());\n updateList();\n // Normalize both function-style and object-style (RxJS Subscription) returns\n unsubscribe = typeof subResult === 'function'\n ? subResult\n : () => { subResult?.unsubscribe?.(); };\n } else {\n // Non-reactive store — render once and return cleanup\n updateList();\n logWarn('[Lume.js] repeat(): store is not reactive (no $subscribe or subscribe method)');\n return () => {\n for (const [k, el] of elementsByKey) {\n const prevItem = prevItemsByKey.get(k);\n const cleanup = cleanupByKey.get(k);\n if (typeof cleanup === 'function') {\n try {\n cleanup();\n } catch (err) {\n logError(`[Lume.js] repeat(): cleanup error for key \"${k}\":`, err);\n }\n }\n if (typeof remove === 'function') {\n remove(prevItem, el);\n }\n }\n containerEl.replaceChildren();\n elementsByKey.clear();\n prevItemsByKey.clear();\n prevIndexByKey.clear();\n cleanupByKey.clear();\n seenKeys.clear();\n };\n }\n\n return () => {\n if (typeof unsubscribe === 'function') {\n unsubscribe();\n }\n // Invoke cleanup and remove callback for all remaining elements before clearing\n for (const [k, el] of elementsByKey) {\n const prevItem = prevItemsByKey.get(k);\n const cleanup = cleanupByKey.get(k);\n if (typeof cleanup === 'function') {\n try {\n cleanup();\n } catch (err) {\n logError(`[Lume.js] repeat(): cleanup error for key \"${k}\":`, err);\n }\n }\n if (typeof remove === 'function') {\n remove(prevItem, el);\n }\n }\n // Clear DOM elements (replaceChildren is faster than loop)\n containerEl.replaceChildren();\n elementsByKey.clear();\n prevItemsByKey.clear();\n prevIndexByKey.clear();\n cleanupByKey.clear();\n seenKeys.clear();\n };\n}\n","/**\n * Lume-JS Debug Addon\n * \n * Developer-friendly logging and inspection of reactive state operations.\n * Critical for adoption - hard to debug = hard to adopt.\n * \n * Usage:\n * import { state } from \"lume-js\";\n * import { withPlugins, createDebugPlugin, debug } from \"lume-js/addons\";\n * \n * const store = withPlugins(state({ count: 0 }), [createDebugPlugin({ label: 'myStore' })]);\n * \n * debug.enable(); // Enable logging\n * debug.filter('count'); // Only log 'count' key\n * debug.stats(); // Show statistics\n * \n * @module addons/debug\n */\n\n// Global debug state\nlet globalEnabled = true;\nlet globalFilter = null; // string, RegExp, or null\nconst stats = new Map(); // label -> { gets: Map, sets: Map, notifies: Map }\n\n/**\n * Check if a key matches the current filter\n * @param {string} key\n * @returns {boolean}\n */\nfunction matchesFilter(key) {\n if (globalFilter === null) return true;\n if (typeof globalFilter === 'string') {\n return key.includes(globalFilter);\n }\n if (globalFilter instanceof RegExp) {\n return globalFilter.test(key);\n }\n return true;\n}\n\n/**\n * Get or create stats entry for a label\n * @param {string} label\n * @returns {object}\n */\nfunction getStats(label) {\n if (!stats.has(label)) {\n stats.set(label, {\n gets: new Map(),\n sets: new Map(),\n notifies: new Map()\n });\n }\n return stats.get(label);\n}\n\n/**\n * Increment a stat counter\n * @param {string} label\n * @param {'gets'|'sets'|'notifies'} type\n * @param {string} key\n */\nfunction incrementStat(label, type, key) {\n const s = getStats(label);\n const map = s[type];\n map.set(key, (map.get(key) || 0) + 1);\n}\n\nconst MAX_LOG_LEN = 100;\nconst TRUNCATED_LEN = MAX_LOG_LEN - 3;\n\n/**\n * Format value for logging (truncate long values)\n * @param {any} value\n * @returns {string}\n */\nfunction formatValue(value) {\n try {\n const json = JSON.stringify(value);\n if (json.length > MAX_LOG_LEN) {\n return json.slice(0, TRUNCATED_LEN) + '...';\n }\n return json;\n } catch {\n return String(value);\n }\n}\n\n/**\n * Create a debug plugin instance for a reactive state store.\n * \n * @param {object} [options] - Configuration options\n * @param {string} [options.label='store'] - Label for log messages\n * @param {boolean} [options.logGet=false] - Log property reads (can be noisy)\n * @param {boolean} [options.logSet=true] - Log property writes\n * @param {boolean} [options.logNotify=true] - Log subscriber notifications\n * @param {boolean} [options.trace=false] - Show stack trace for SET operations\n * @returns {object} Plugin object for state()\n * \n * @example\n * const store = withPlugins(state({ count: 0 }), [createDebugPlugin({ label: 'counter' })]);\n * \n * @example\n * // With stack traces for debugging where state changes originate\n * const store = withPlugins(state({ count: 0 }), [createDebugPlugin({ label: 'counter', trace: true })]);\n */\nexport function createDebugPlugin(options = {}) {\n const label = options.label ?? 'store';\n\n // IMPORTANT: Do NOT destructure options here!\n // Options may contain getters for dynamic runtime toggling (e.g., from UI).\n // Destructuring would copy values once at creation time, breaking reactivity.\n // Use getOpt() helper to read options dynamically in each hook.\n const getOpt = (name, defaultVal) => {\n const val = options[name];\n return val !== undefined ? val : defaultVal;\n };\n\n return {\n name: `debug:${label}`,\n\n onInit: () => {\n if (globalEnabled) {\n console.log(`%c[${label}]%c initialized`, 'color: #888; font-weight: bold', 'color: inherit');\n }\n },\n\n onGet: (key, value) => {\n // Skip internal properties\n if (typeof key === 'string' && key.startsWith('$')) {\n return value;\n }\n\n incrementStat(label, 'gets', key);\n\n if (globalEnabled && getOpt('logGet', false) && matchesFilter(key)) {\n console.log(\n `%c[${label}]%c GET %c${key}%c = ${formatValue(value)}`,\n 'color: #888; font-weight: bold',\n 'color: #4CAF50',\n 'color: #2196F3; font-weight: bold',\n 'color: inherit'\n );\n }\n\n return value;\n },\n\n onSet: (key, newValue, oldValue) => {\n // Skip internal properties\n if (typeof key === 'string' && key.startsWith('$')) {\n return newValue;\n }\n\n incrementStat(label, 'sets', key);\n\n if (globalEnabled && getOpt('logSet', true) && matchesFilter(key)) {\n console.log(\n `%c[${label}]%c SET %c${key}%c: ${formatValue(oldValue)} → ${formatValue(newValue)}`,\n 'color: #888; font-weight: bold',\n 'color: #FF9800',\n 'color: #2196F3; font-weight: bold',\n 'color: inherit'\n );\n\n // Show stack trace if enabled (helps find where state changes originate)\n if (getOpt('trace', false)) {\n console.trace(`%c[${label}] Stack trace for ${key}`, 'color: #888');\n }\n }\n\n return newValue;\n },\n\n onSubscribe: (key) => {\n if (globalEnabled && matchesFilter(key)) {\n console.log(\n `%c[${label}]%c SUBSCRIBE %c${key}`,\n 'color: #888; font-weight: bold',\n 'color: #9C27B0',\n 'color: #2196F3; font-weight: bold'\n );\n }\n },\n\n onNotify: (key, value) => {\n // Skip internal properties\n if (typeof key === 'string' && key.startsWith('$')) {\n return;\n }\n\n incrementStat(label, 'notifies', key);\n\n if (globalEnabled && getOpt('logNotify', true) && matchesFilter(key)) {\n console.log(\n `%c[${label}]%c NOTIFY %c${key}%c = ${formatValue(value)}`,\n 'color: #888; font-weight: bold',\n 'color: #E91E63',\n 'color: #2196F3; font-weight: bold',\n 'color: inherit'\n );\n }\n }\n };\n}\n\n/**\n * Global debug controls\n */\nexport const debug = {\n /**\n * Enable debug logging globally\n */\n enable() {\n globalEnabled = true;\n console.log('%c[lume-debug]%c Logging enabled', 'color: #888; font-weight: bold', 'color: #4CAF50');\n },\n\n /**\n * Disable debug logging globally\n */\n disable() {\n globalEnabled = false;\n console.log('%c[lume-debug]%c Logging disabled', 'color: #888; font-weight: bold', 'color: #F44336');\n },\n\n /**\n * Check if debug logging is currently enabled\n * @returns {boolean}\n */\n isEnabled() {\n return globalEnabled;\n },\n\n /**\n * Filter logs by key pattern\n * @param {string|RegExp|null} pattern - Pattern to match, or null to clear filter\n */\n filter(pattern) {\n globalFilter = pattern;\n if (pattern === null) {\n console.log('%c[lume-debug]%c Filter cleared', 'color: #888; font-weight: bold', 'color: inherit');\n } else {\n console.log(`%c[lume-debug]%c Filter set: ${pattern}`, 'color: #888; font-weight: bold', 'color: inherit');\n }\n },\n\n /**\n * Get current filter pattern\n * @returns {string|RegExp|null}\n */\n getFilter() {\n return globalFilter;\n },\n\n /**\n * Get statistics data (silent - no console output)\n * Use logStats() if you want to see stats in console.\n * @returns {object} Stats object for programmatic access\n */\n stats() {\n const result = {};\n\n for (const [label, data] of stats) {\n result[label] = {\n gets: Object.fromEntries(data.gets),\n sets: Object.fromEntries(data.sets),\n notifies: Object.fromEntries(data.notifies)\n };\n }\n\n return result;\n },\n\n /**\n * Log statistics summary to console (with formatting)\n * @returns {object} Stats object for programmatic access\n */\n logStats() {\n const result = this.stats();\n\n if (Object.keys(result).length === 0) {\n console.log('%c[lume-debug]%c No stats collected yet', 'color: #888; font-weight: bold', 'color: inherit');\n return result;\n }\n\n console.group('%c[lume-debug] Statistics', 'color: #888; font-weight: bold');\n\n for (const [label, data] of Object.entries(result)) {\n console.group(`%c${label}`, 'color: #2196F3; font-weight: bold');\n\n // Use console.table for better formatted output\n const tableData = [];\n const allKeys = new Set([\n ...Object.keys(data.gets),\n ...Object.keys(data.sets),\n ...Object.keys(data.notifies)\n ]);\n\n for (const key of allKeys) {\n tableData.push({\n key,\n gets: data.gets[key] || 0,\n sets: data.sets[key] || 0,\n notifies: data.notifies[key] || 0\n });\n }\n\n if (tableData.length > 0) {\n console.table(tableData);\n }\n\n console.groupEnd();\n }\n\n console.groupEnd();\n\n return result;\n },\n\n /**\n * Reset all collected statistics\n */\n resetStats() {\n stats.clear();\n console.log('%c[lume-debug]%c Stats reset', 'color: #888; font-weight: bold', 'color: inherit');\n }\n};\n","/**\n * Lume-JS DOM Binding Handlers\n *\n * Extend bindDom() with additional reactive data-* attribute capabilities.\n * Each handler is a plain object — no framework API, no registration.\n *\n * Usage:\n * import { state, bindDom } from 'lume-js';\n * import { show, classToggle } from 'lume-js/handlers';\n *\n * const store = state({ isVisible: true, isActive: false });\n * bindDom(document.body, store, { handlers: [show, classToggle('active')] });\n *\n * Custom handlers:\n * const tooltip = { attr: 'data-tooltip', apply(el, val) { el.title = val ?? ''; } };\n * bindDom(root, store, { handlers: [tooltip] });\n *\n * Handler contract:\n * { attr: string, apply(el: HTMLElement, val: any): void }\n */\n\n// --- Ready-to-use Handlers ---\n\n/**\n * data-show=\"key\" → el.hidden = !Boolean(val)\n * Shows element when state value is truthy (inverse of built-in data-hidden).\n */\nexport const show = {\n attr: 'data-show',\n apply(el, val) { el.hidden = !Boolean(val); }\n};\n\n// --- Factory Functions ---\n\n/**\n * Create a handler for any HTML boolean attribute.\n * Uses toggleAttribute() — works correctly with any attribute name\n * (readonly, contenteditable, etc.) without worrying about camelCase property names.\n *\n * Note: built-in boolean handlers (hidden, disabled, checked, required) use\n * property assignment directly. This factory uses toggleAttribute for broader\n * attribute name compatibility.\n *\n * @param {string} name - Attribute name (e.g., 'readonly', 'open', 'contenteditable')\n * @returns {{ attr: string, apply: function }}\n *\n * @example\n * bindDom(root, store, { handlers: [boolAttr('readonly')] });\n * // <input data-readonly=\"isReadonly\" />\n */\nexport function boolAttr(name) {\n return {\n attr: `data-${name}`,\n apply(el, val) { el.toggleAttribute(name, Boolean(val)); }\n };\n}\n\n/**\n * Create a handler for an ARIA attribute.\n * Use for ARIA attrs beyond the built-in aria-expanded/aria-hidden.\n *\n * @param {string} name - ARIA name, with or without \"aria-\" prefix\n * @returns {{ attr: string, apply: function }}\n *\n * @example\n * bindDom(root, store, { handlers: [ariaAttr('pressed'), ariaAttr('selected')] });\n * // <button data-aria-pressed=\"isPressed\">Toggle</button>\n */\nexport function ariaAttr(name) {\n const fullName = name.startsWith('aria-') ? name : `aria-${name}`;\n return {\n attr: `data-${fullName}`,\n apply(el, val) { el.setAttribute(fullName, val ? 'true' : 'false'); }\n };\n}\n\n/**\n * Create handlers for CSS class toggling.\n * Each name creates a handler: data-class-{name}=\"key\" → el.classList.toggle(name, Boolean(val))\n *\n * Returns an array — pass directly to handlers (auto-flattened by bindDom).\n *\n * @param {...string} names - CSS class names to create handlers for\n * @returns {Array<{ attr: string, apply: function }>}\n *\n * @example\n * bindDom(root, store, { handlers: [classToggle('active', 'loading', 'error')] });\n * // <div data-class-active=\"isActive\" data-class-loading=\"isLoading\">\n */\nexport function classToggle(...names) {\n return names.map(name => ({\n attr: `data-class-${name}`,\n apply(el, val) { el.classList.toggle(name, Boolean(val)); }\n }));\n}\n\n/**\n * Create a handler for any string attribute (href, src, title, alt, action, etc.)\n * Sets the attribute value as a string. Removes attribute when value is null/undefined.\n *\n * @param {string} name - HTML attribute name (e.g., 'href', 'src', 'title')\n * @returns {{ attr: string, apply: function }}\n *\n * @example\n * bindDom(root, store, { handlers: [stringAttr('href'), stringAttr('src')] });\n * // <a data-href=\"profileUrl\">Profile</a>\n * // <img data-src=\"imageUrl\" />\n */\nexport function stringAttr(name) {\n return {\n attr: `data-${name}`,\n apply(el, val) {\n if (val == null) el.removeAttribute(name);\n else el.setAttribute(name, String(val));\n }\n };\n}\n\n// --- Presets ---\n\n/** Form-related handlers (beyond built-in disabled/checked/required) */\nexport const formHandlers = [\n boolAttr('readonly'),\n];\n\n/** Additional ARIA handlers (beyond built-in aria-expanded/aria-hidden) */\nexport const a11yHandlers = [\n ariaAttr('pressed'),\n ariaAttr('selected'),\n ariaAttr('disabled'),\n];\n\n// --- htmlAttrs() — All Standard HTML Attributes ---\n\n/**\n * Standard HTML boolean attributes (beyond built-in hidden/disabled/checked/required).\n * @see https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes#boolean_attributes\n */\nconst BOOL_ATTRS = [\n 'readonly', 'open', 'novalidate', 'formnovalidate', 'multiple',\n 'autofocus', 'autoplay', 'controls', 'loop', 'muted', 'defer',\n 'async', 'reversed', 'selected', 'inert', 'allowfullscreen',\n];\n\n/**\n * Standard HTML string attributes.\n * @see https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes\n */\nconst STRING_ATTRS = [\n 'href', 'src', 'alt', 'title', 'placeholder', 'action', 'method',\n 'target', 'rel', 'type', 'name', 'role', 'lang', 'tabindex',\n 'pattern', 'min', 'max', 'step', 'minlength', 'maxlength',\n 'width', 'height', 'for', 'form', 'accept', 'autocomplete',\n 'loading', 'decoding', 'inputmode', 'enterkeyhint', 'draggable',\n 'contenteditable', 'spellcheck', 'translate', 'dir', 'id',\n 'poster', 'preload', 'download', 'media', 'sizes', 'srcset',\n 'colspan', 'rowspan', 'scope', 'headers', 'wrap', 'sandbox',\n];\n\n/**\n * ARIA boolean state attributes — toggled between \"true\" and \"false\".\n * Use ariaAttr() for these (coerces to \"true\"/\"false\" string).\n * @see https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes\n */\nconst ARIA_BOOL_ATTRS = [\n 'pressed', 'selected', 'disabled', 'checked', 'invalid', 'required',\n 'busy', 'modal', 'multiselectable', 'multiline', 'readonly', 'atomic',\n];\n\n/**\n * ARIA string/token/numeric attributes — value passed through as-is.\n * Use stringAttr() with \"aria-\" prefix for these.\n * @see https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes\n */\nconst ARIA_STRING_ATTRS = [\n 'current', 'live', 'relevant', 'haspopup',\n 'sort', 'autocomplete', 'orientation',\n 'label', 'describedby', 'labelledby', 'controls', 'owns',\n 'activedescendant', 'errormessage', 'details', 'flowto',\n 'valuenow', 'valuemin', 'valuemax', 'valuetext',\n 'colcount', 'colindex', 'colspan', 'rowcount', 'rowindex', 'rowspan',\n 'level', 'setsize', 'posinset', 'placeholder', 'roledescription',\n 'keyshortcuts', 'braillelabel', 'brailleroledescription',\n];\n\n/**\n * One-import preset that enables all standard HTML attributes as reactive handlers.\n *\n * Includes:\n * - Boolean attributes: readonly, open, autofocus, controls, muted, inert, etc.\n * - String attributes: href, src, alt, title, placeholder, role, tabindex, etc.\n * - ARIA attributes: aria-pressed, aria-label, aria-describedby, aria-valuenow, etc.\n * - Show handler: data-show (inverse of data-hidden)\n *\n * Returns a flat array — pass directly to handlers option.\n *\n * @returns {Array<{ attr: string, apply: function }>}\n *\n * @example\n * import { htmlAttrs } from 'lume-js/handlers';\n *\n * bindDom(document.body, store, { handlers: [htmlAttrs()] });\n * // Now use any data-* attribute:\n * // <a data-href=\"url\">Link</a>\n * // <input data-readonly=\"isLocked\" />\n * // <div data-aria-label=\"labelText\">...</div>\n * // <div data-show=\"isVisible\">...</div>\n */\nexport function htmlAttrs() {\n return [\n show,\n ...BOOL_ATTRS.map(name => boolAttr(name)),\n ...STRING_ATTRS.map(name => stringAttr(name)),\n ...ARIA_BOOL_ATTRS.map(name => ariaAttr(name)),\n ...ARIA_STRING_ATTRS.map(name => stringAttr(`aria-${name}`)),\n ];\n}\n","/**\n * Lume-JS Computed Addon\n * \n * Creates computed values that automatically update when dependencies change.\n * Uses core effect() for automatic dependency tracking.\n * \n * Usage:\n * import { computed } from \"lume-js/addons/computed\";\n * \n * const doubled = computed(() => store.count * 2);\n * console.log(doubled.value); // Auto-updates when store.count changes\n * \n * Features:\n * - Automatic dependency tracking (no manual recompute)\n * - Cached values (only recomputes when dependencies change)\n * - Subscribe to changes\n * - Cleanup with dispose()\n * \n * @module addons/computed\n */\n\nimport { effect } from '../core/effect.js';\nimport { logError } from '../utils/log.js';\n\n/**\n * Creates a computed value with automatic dependency tracking\n * \n * The computation function runs immediately and tracks which state\n * properties are accessed. When any dependency changes, the value\n * is automatically recomputed.\n *\n * ⚠️ Circular self-mutations are automatically suppressed. If a computed\n * mutates a state property it depends on, the flush triggered by that\n * mutation is skipped to prevent an infinite microtask loop.\n *\n * @param {function} fn - Function that computes the value\n * @returns {object} Object with .value property and methods\n * \n * @example\n * const store = state({ count: 5 });\n * \n * const doubled = computed(() => store.count * 2);\n * console.log(doubled.value); // 10\n * \n * store.count = 10;\n * // After microtask:\n * console.log(doubled.value); // 20 (auto-updated)\n * \n * @example\n * // Subscribe to changes\n * const unsub = doubled.subscribe(value => {\n * console.log('Doubled changed to:', value);\n * });\n * \n * @example\n * // Cleanup\n * doubled.dispose();\n */\nexport function computed(fn) {\n if (typeof fn !== 'function') {\n throw new Error('computed() requires a function');\n }\n\n let cachedValue;\n let isInitialized = false;\n let isInComputation = false;\n let disposed = false;\n const subscribers = [];\n\n // Use effect to automatically track dependencies\n const cleanupEffect = effect(() => {\n // Skip re-entry from a flush triggered by our own synchronous mutation.\n // The mutation inside fn() queues a microtask flush; we stay flagged\n // until a subsequent microtask clears it, so that flush is dropped.\n if (isInComputation || disposed) return;\n\n isInComputation = true;\n\n try {\n const newValue = fn();\n\n // Check if value actually changed - Object.is() handles NaN and -0\n if (!isInitialized || !Object.is(newValue, cachedValue)) {\n cachedValue = newValue;\n isInitialized = true;\n\n // Notify all subscribers\n subscribers.forEach(callback => callback(cachedValue));\n }\n } catch (error) {\n logError('[Lume.js computed] Error in computation:', error);\n // Set to undefined on error, mark as initialized\n if (!isInitialized || cachedValue !== undefined) {\n cachedValue = undefined;\n isInitialized = true;\n\n // Notify subscribers of error state\n subscribers.forEach(callback => callback(cachedValue));\n }\n } finally {\n // Defer clearing the flag so any flush microtask queued by fn()\n // sees it still set and skips re-entry.\n queueMicrotask(() => {\n if (!disposed) {\n isInComputation = false;\n }\n });\n }\n });\n\n return {\n /**\n * Get the current computed value\n */\n get value() {\n if (!isInitialized) {\n throw new Error('Computed value accessed before initialization');\n }\n return cachedValue;\n },\n\n /**\n * Subscribe to changes in computed value\n * \n * @param {function} callback - Called when value changes\n * @returns {function} Unsubscribe function\n */\n subscribe(callback) {\n if (typeof callback !== 'function') {\n throw new Error('subscribe() requires a function');\n }\n\n subscribers.push(callback);\n\n // Call immediately with current value\n if (isInitialized) {\n callback(cachedValue);\n }\n\n // Return unsubscribe function\n return () => {\n const index = subscribers.indexOf(callback);\n if (index > -1) {\n subscribers.splice(index, 1);\n }\n };\n },\n\n /**\n * Clean up computed value and stop tracking\n */\n dispose() {\n disposed = true;\n cleanupEffect();\n subscribers.length = 0;\n isInitialized = false;\n isInComputation = false;\n }\n };\n}","/**\n * Creates a cleanup group that can collect and dispose multiple\n * cleanup/unsubscribe functions at once.\n *\n * @returns {CleanupGroup}\n *\n * @example\n * ```js\n * import { createCleanupGroup } from 'lume-js/addons';\n *\n * const group = createCleanupGroup();\n * group.add(bindDom(root, store));\n * group.add(effect(() => { ... }));\n * group.add(store.$subscribe('key', fn));\n *\n * // Dispose everything at once\n * group.dispose();\n * ```\n */\nexport function createCleanupGroup() {\n const cleanups = [];\n\n return {\n /**\n * Add a cleanup function to the group.\n * @param {Function} fn - Cleanup/unsubscribe function\n */\n add(fn) {\n if (typeof fn === 'function') {\n cleanups.push(fn);\n }\n },\n\n /**\n * Run all collected cleanup functions and clear the group.\n */\n dispose() {\n while (cleanups.length) {\n const fn = cleanups.pop();\n try { fn(); } catch (e) { /* ignore cleanup errors */ }\n }\n },\n };\n}\n","/**\n * Reads initial state from a `<script type=\"application/json\">` element\n * embedded in the server-rendered HTML. Useful for SSR / hydration patterns.\n *\n * @param {string} [selector='#__LUME_DATA__'] - CSS selector for the script element\n * @returns {object} Parsed JSON object, or empty object if not found / invalid\n *\n * @example\n * ```html\n * <script id=\"__LUME_DATA__\" type=\"application/json\">\n * {\"title\": \"Welcome\", \"count\": 42}\n * </script>\n * ```\n *\n * ```js\n * import { state } from 'lume-js';\n * import { hydrateState } from 'lume-js/addons';\n *\n * const store = state(hydrateState());\n * ```\n */\nexport function hydrateState(selector = '#__LUME_DATA__') {\n const el = typeof document !== 'undefined' ? document.querySelector(selector) : null;\n if (!el) return {};\n try {\n return JSON.parse(el.textContent);\n } catch {\n return {};\n }\n}\n","export { computed } from \"./computed.js\";\nexport { watch } from \"./watch.js\";\nexport { repeat, defaultFocusPreservation, defaultScrollPreservation } from \"./repeat.js\";\nexport { createDebugPlugin, debug } from \"./debug.js\";\nexport { withPlugins } from \"./withPlugins.js\";\nexport { createCleanupGroup } from \"./cleanupGroup.js\";\nexport { hydrateState } from \"./hydrateState.js\";\n\n/**\n * Returns true if the value is a Lume reactive proxy created by state().\n * Uses duck-typing: checks for the presence of $subscribe.\n * @param {any} obj\n * @returns {boolean}\n */\nexport function isReactive(obj) {\n return !!(obj && typeof obj === 'object' && typeof obj.$subscribe === 'function');\n}\n","/**\n * watch - observes changes to a state key and triggers callback\n * @param {Object} store - reactive store created with state()\n * @param {string} key - key in store to watch\n * @param {Function} callback - called with new value\n * @returns {Function} unsubscribe function\n */\nexport function watch(store, key, callback) {\n if (!store.$subscribe) {\n throw new Error(\"store must be created with state()\");\n }\n return store.$subscribe(key, callback);\n}","/**\n * Lume-JS withPlugins Addon\n *\n * Wraps a reactive state proxy with a plugin layer that intercepts\n * get/set/notify/subscribe operations via plugin hooks.\n *\n * Only stores that opt into debugging or custom behaviors need this.\n * Core state() is not aware of plugins.\n *\n * Usage:\n * import { state } from \"lume-js\";\n * import { withPlugins, createDebugPlugin } from \"lume-js/addons\";\n *\n * const store = withPlugins(state({ count: 0 }), [createDebugPlugin({ label: 'counter' })]);\n */\n\n/**\n * Wrap a reactive state proxy with plugin hooks.\n *\n * Plugin hooks (all optional):\n * onInit() — called once at wrap time\n * onGet(key, value) → value|void — intercept/transform reads\n * onSet(key, newVal, oldVal) → val|void — intercept/transform writes\n * onNotify(key, value) — called before subscribers are notified\n * onSubscribe(key) — called when $subscribe is invoked\n *\n * @param {object} store - A reactive proxy from state()\n * @param {Array<object>} plugins - Array of plugin objects\n * @returns {Proxy} A new proxy wrapping the store with plugin behavior\n */\nimport { logError } from '../utils/log.js';\n\nexport function withPlugins(store, plugins = []) {\n if (!plugins.length) return store;\n\n // Call onInit hooks once at wrap time\n for (const p of plugins) {\n try {\n p.onInit?.();\n } catch (e) {\n logError(`[Lume.js] Plugin \"${p.name}\" error in onInit:`, e);\n }\n }\n\n // Track pending notifications for onNotify hooks.\n // Instead of a separate microtask, we hook into the underlying state's\n // flush via $beforeFlush so onNotify and subscribers share one microtask.\n const pendingNotifications = new Map();\n\n function runNotifyHooks() {\n for (const [key, value] of pendingNotifications) {\n for (const p of plugins) {\n try {\n p.onNotify?.(key, value);\n } catch (e) {\n logError(`[Lume.js] Plugin \"${p.name}\" error in onNotify:`, e);\n }\n }\n }\n pendingNotifications.clear();\n }\n\n // Register once on the underlying state; capture unsubscribe for cleanup.\n let flushUnsub;\n if (typeof store.$beforeFlush === 'function') {\n flushUnsub = store.$beforeFlush(runNotifyHooks);\n }\n\n return new Proxy(store, {\n get(target, key) {\n // $dispose — remove the beforeFlush hook and clear pending state\n if (key === '$dispose') {\n return () => {\n if (flushUnsub) flushUnsub();\n pendingNotifications.clear();\n };\n }\n\n // Pass $-prefixed meta methods through without interception\n if (typeof key === 'string' && key.startsWith('$')) {\n const method = target[key];\n if (key === '$subscribe' && typeof method === 'function') {\n // Wrap $subscribe to call onSubscribe hooks\n return (subKey, fn) => {\n for (const p of plugins) {\n try {\n p.onSubscribe?.(subKey);\n } catch (e) {\n logError(`[Lume.js] Plugin \"${p.name}\" error in onSubscribe:`, e);\n }\n }\n return method(subKey, fn);\n };\n }\n return method;\n }\n\n let value = target[key];\n\n // onGet chain\n for (const p of plugins) {\n try {\n const r = p.onGet?.(key, value);\n if (r !== undefined) value = r;\n } catch (e) {\n logError(`[Lume.js] Plugin \"${p.name}\" error in onGet:`, e);\n }\n }\n\n return value;\n },\n\n set(target, key, value) {\n const oldValue = target[key];\n let newValue = value;\n\n // onSet chain\n for (const p of plugins) {\n try {\n const r = p.onSet?.(key, newValue, oldValue);\n if (r !== undefined) newValue = r;\n } catch (e) {\n logError(`[Lume.js] Plugin \"${p.name}\" error in onSet:`, e);\n }\n }\n\n // Only queue onNotify if the value actually changed after plugin chain\n if (!Object.is(newValue, oldValue)) {\n pendingNotifications.set(key, newValue);\n }\n\n target[key] = newValue;\n return true;\n }\n });\n}\n"],"names":["logWarn","msg","rest","console","warn","logError","error","readers","Set","withReadObserver","onRead","fn","add","delete","boolHandler","name","attr","apply","el","val","ariaHandler","setAttribute","DEFAULT_HANDLERS","applyHandler","store","path","handler","result","resolveProp","target","key","$subscribe","handleDataBind","bindingMap","unsub","tagName","type","checked","value","String","textContent","updateElement","isFormInput","set","pathArr","split","pop","obj","length","current","i","resolvePath","currentEffect","effect","deps","Error","cleanups","isRunning","execute","Array","isArray","dep","keys","isFirst","push","executeWithTracking","oldCleanups","splice","myContext","tracking","previousEffect","proxy","registerEffect","cleanup","defaultFocusPreservation","container","activeEl","document","activeElement","contains","selectionStart","selectionEnd","body","focus","setSelectionRange","defaultScrollPreservation","context","isReorder","scrollTop","anchorElement","anchorOffset","containerRect","getBoundingClientRect","child","firstElementChild","nextElementSibling","rect","bottom","top","newRect","scrollAdjustment","globalEnabled","globalFilter","stats","Map","matchesFilter","includes","RegExp","test","incrementStat","label","s","has","gets","sets","notifies","get","getStats","map","formatValue","json","JSON","stringify","slice","MAX_LOG_LEN","debug","enable","log","disable","isEnabled","filter","pattern","getFilter","data","Object","fromEntries","logStats","this","group","entries","tableData","allKeys","table","groupEnd","resetStats","clear","show","hidden","boolAttr","toggleAttribute","ariaAttr","fullName","startsWith","stringAttr","removeAttribute","formHandlers","a11yHandlers","BOOL_ATTRS","STRING_ATTRS","ARIA_BOOL_ATTRS","ARIA_STRING_ATTRS","root","options","HTMLElement","immediate","handlers","userHandlers","defaults","merged","h","flat","values","mergeHandlers","performBinding","WeakMap","selector","join","elements","querySelectorAll","hasAttribute","c","getAttribute","inputHandler","e","binding","valueAsNumber","addEventListener","removeEventListener","forEach","readyState","onReady","once","names","classList","toggle","cachedValue","isInitialized","isInComputation","disposed","subscribers","cleanupEffect","newValue","is","callback","queueMicrotask","subscribe","index","indexOf","dispose","getOpt","defaultVal","onInit","onGet","onSet","oldValue","trace","onSubscribe","onNotify","querySelector","parse","arrayKey","render","create","update","remove","element","preserveFocus","preserveScroll","containerEl","elementsByKey","prevItemsByKey","prevIndexByKey","cleanupByKey","seenKeys","createElement","updateList","items","size","nextEls","item","k","isFirstRender","prevItem","prevIndex","err","shouldPreserve","restoreFocus","restoreScroll","ptr","firstChild","desired","insertBefore","nextSibling","next","removeChild","reconcileDOM","applyPreservation","unsubscribe","replaceChildren","subResult","isFrozen","isSealed","listeners","pendingNotifications","pendingEffects","beforeFlushHooks","flushScheduled","Symbol","executeFn","idx","Proxy","reader","iterations","subs","effects","$beforeFlush","plugins","p","flushUnsub","method","subKey","r"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "lume-js",
|
|
3
|
-
"version": "2.0
|
|
3
|
+
"version": "2.1.0",
|
|
4
4
|
"description": "Minimal reactive state management using only standard JavaScript and HTML - no custom syntax, no build step required",
|
|
5
5
|
"main": "dist/index.mjs",
|
|
6
6
|
"module": "dist/index.mjs",
|
|
@@ -28,6 +28,7 @@
|
|
|
28
28
|
"dev": "vite",
|
|
29
29
|
"dev:site": "node scripts/build-site-assets.js && vite -c vite.site.config.js",
|
|
30
30
|
"build:site": "node scripts/build-site-assets.js && vite build -c vite.site.config.js",
|
|
31
|
+
"preview:site": "node scripts/build-site-assets.js && vite build -c vite.site.config.js --base / && vite preview -c vite.site.config.js",
|
|
31
32
|
"build": "node scripts/build.js",
|
|
32
33
|
"size": "node scripts/check-size.js",
|
|
33
34
|
"test": "vitest run",
|
package/src/addons/repeat.js
CHANGED
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
* ═══════════════════════════════════════════════════════════════════════
|
|
36
36
|
* PATTERN 2: Clean separation (create + update) - recommended
|
|
37
37
|
* ═══════════════════════════════════════════════════════════════════════
|
|
38
|
-
*
|
|
38
|
+
*
|
|
39
39
|
* repeat('#list', store, 'todos', {
|
|
40
40
|
* key: todo => todo.id,
|
|
41
41
|
* create: (todo, el) => {
|
|
@@ -47,6 +47,11 @@
|
|
|
47
47
|
* btn.textContent = 'Delete';
|
|
48
48
|
* btn.onclick = () => deleteTodo(todo.id);
|
|
49
49
|
* el.appendChild(btn);
|
|
50
|
+
*
|
|
51
|
+
* // Return a cleanup function — called automatically when element is removed
|
|
52
|
+
* return () => {
|
|
53
|
+
* // Unsubscribe from external listeners, remove timers, etc.
|
|
54
|
+
* };
|
|
50
55
|
* },
|
|
51
56
|
* update: (todo, el, index, { isFirstRender }) => {
|
|
52
57
|
* // Called on every update - bind data
|
|
@@ -165,8 +170,9 @@ export function defaultScrollPreservation(container, context = {}) {
|
|
|
165
170
|
* @param {Object} options - Configuration
|
|
166
171
|
* @param {Function} options.key - Function to extract unique key: (item) => key
|
|
167
172
|
* @param {Function} [options.render] - Function to render item (called for all items): (item, element, index) => void
|
|
168
|
-
* @param {Function} [options.create] - Function for new elements only: (item, element, index) => void
|
|
173
|
+
* @param {Function} [options.create] - Function for new elements only: (item, element, index) => void | Function. If a function is returned, it is registered as the element's cleanup and called automatically when the element is removed (by list update or full cleanup).
|
|
169
174
|
* @param {Function} [options.update] - Function for data binding: (item, element, index, { isFirstRender }) => void. Skipped if same item reference AND same index.
|
|
175
|
+
* @param {Function} [options.remove] - Additional cleanup when element is removed: (item, element) => void. Called after any cleanup function returned by create(). Optional — prefer returning a cleanup from create() for automatic lifecycle management.
|
|
170
176
|
* @param {string|Function} [options.element='div'] - Element tag name or factory function
|
|
171
177
|
* @param {Function|null} [options.preserveFocus=defaultFocusPreservation] - Focus preservation strategy (null to disable)
|
|
172
178
|
* @param {Function|null} [options.preserveScroll=defaultScrollPreservation] - Scroll preservation strategy (null to disable)
|
|
@@ -179,6 +185,7 @@ export function repeat(container, store, arrayKey, options) {
|
|
|
179
185
|
render,
|
|
180
186
|
create,
|
|
181
187
|
update,
|
|
188
|
+
remove,
|
|
182
189
|
element = 'div',
|
|
183
190
|
preserveFocus = defaultFocusPreservation,
|
|
184
191
|
preserveScroll = defaultScrollPreservation
|
|
@@ -209,6 +216,8 @@ export function repeat(container, store, arrayKey, options) {
|
|
|
209
216
|
const prevItemsByKey = new Map();
|
|
210
217
|
// key -> previous index (for reorder detection)
|
|
211
218
|
const prevIndexByKey = new Map();
|
|
219
|
+
// key -> cleanup function returned by create()
|
|
220
|
+
const cleanupByKey = new Map();
|
|
212
221
|
const seenKeys = new Set();
|
|
213
222
|
|
|
214
223
|
function createElement() {
|
|
@@ -293,7 +302,10 @@ export function repeat(container, store, arrayKey, options) {
|
|
|
293
302
|
try {
|
|
294
303
|
// Call create for new elements (DOM structure)
|
|
295
304
|
if (isFirstRender && create) {
|
|
296
|
-
create(item, el, i);
|
|
305
|
+
const cleanup = create(item, el, i);
|
|
306
|
+
if (typeof cleanup === 'function') {
|
|
307
|
+
cleanupByKey.set(k, cleanup);
|
|
308
|
+
}
|
|
297
309
|
}
|
|
298
310
|
|
|
299
311
|
// Call update for data binding (new and existing elements)
|
|
@@ -327,9 +339,24 @@ export function repeat(container, store, arrayKey, options) {
|
|
|
327
339
|
if (elementsByKey.size !== seenKeys.size) {
|
|
328
340
|
for (const k of elementsByKey.keys()) {
|
|
329
341
|
if (!seenKeys.has(k)) {
|
|
342
|
+
const el = elementsByKey.get(k);
|
|
343
|
+
const prevItem = prevItemsByKey.get(k);
|
|
344
|
+
// Call create-returned cleanup first, then remove callback
|
|
345
|
+
const cleanup = cleanupByKey.get(k);
|
|
346
|
+
if (typeof cleanup === 'function') {
|
|
347
|
+
try {
|
|
348
|
+
cleanup();
|
|
349
|
+
} catch (err) {
|
|
350
|
+
logError(`[Lume.js] repeat(): cleanup error for key "${k}":`, err);
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
if (typeof remove === 'function' && el) {
|
|
354
|
+
remove(prevItem, el);
|
|
355
|
+
}
|
|
330
356
|
elementsByKey.delete(k);
|
|
331
357
|
prevItemsByKey.delete(k);
|
|
332
358
|
prevIndexByKey.delete(k);
|
|
359
|
+
cleanupByKey.delete(k);
|
|
333
360
|
}
|
|
334
361
|
}
|
|
335
362
|
}
|
|
@@ -354,10 +381,25 @@ export function repeat(container, store, arrayKey, options) {
|
|
|
354
381
|
updateList();
|
|
355
382
|
logWarn('[Lume.js] repeat(): store is not reactive (no $subscribe or subscribe method)');
|
|
356
383
|
return () => {
|
|
384
|
+
for (const [k, el] of elementsByKey) {
|
|
385
|
+
const prevItem = prevItemsByKey.get(k);
|
|
386
|
+
const cleanup = cleanupByKey.get(k);
|
|
387
|
+
if (typeof cleanup === 'function') {
|
|
388
|
+
try {
|
|
389
|
+
cleanup();
|
|
390
|
+
} catch (err) {
|
|
391
|
+
logError(`[Lume.js] repeat(): cleanup error for key "${k}":`, err);
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
if (typeof remove === 'function') {
|
|
395
|
+
remove(prevItem, el);
|
|
396
|
+
}
|
|
397
|
+
}
|
|
357
398
|
containerEl.replaceChildren();
|
|
358
399
|
elementsByKey.clear();
|
|
359
400
|
prevItemsByKey.clear();
|
|
360
401
|
prevIndexByKey.clear();
|
|
402
|
+
cleanupByKey.clear();
|
|
361
403
|
seenKeys.clear();
|
|
362
404
|
};
|
|
363
405
|
}
|
|
@@ -366,11 +408,27 @@ export function repeat(container, store, arrayKey, options) {
|
|
|
366
408
|
if (typeof unsubscribe === 'function') {
|
|
367
409
|
unsubscribe();
|
|
368
410
|
}
|
|
411
|
+
// Invoke cleanup and remove callback for all remaining elements before clearing
|
|
412
|
+
for (const [k, el] of elementsByKey) {
|
|
413
|
+
const prevItem = prevItemsByKey.get(k);
|
|
414
|
+
const cleanup = cleanupByKey.get(k);
|
|
415
|
+
if (typeof cleanup === 'function') {
|
|
416
|
+
try {
|
|
417
|
+
cleanup();
|
|
418
|
+
} catch (err) {
|
|
419
|
+
logError(`[Lume.js] repeat(): cleanup error for key "${k}":`, err);
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
if (typeof remove === 'function') {
|
|
423
|
+
remove(prevItem, el);
|
|
424
|
+
}
|
|
425
|
+
}
|
|
369
426
|
// Clear DOM elements (replaceChildren is faster than loop)
|
|
370
427
|
containerEl.replaceChildren();
|
|
371
428
|
elementsByKey.clear();
|
|
372
429
|
prevItemsByKey.clear();
|
|
373
430
|
prevIndexByKey.clear();
|
|
431
|
+
cleanupByKey.clear();
|
|
374
432
|
seenKeys.clear();
|
|
375
433
|
};
|
|
376
434
|
}
|