lume-js 2.2.1 → 2.3.1
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/AGENT_GUIDE.md +224 -0
- package/README.md +97 -16
- package/dist/addons.min.mjs +1 -1
- package/dist/addons.mjs +190 -7
- package/dist/addons.mjs.map +1 -1
- package/dist/handlers.min.mjs +1 -1
- package/dist/handlers.mjs +29 -2
- package/dist/handlers.mjs.map +1 -1
- package/dist/index.min.mjs +1 -1
- package/dist/index.mjs +4 -141
- package/dist/index.mjs.map +1 -1
- package/dist/lume.global.js +1 -1
- package/dist/lume.global.js.map +1 -1
- package/dist/shared-BGg9PbiG.mjs +249 -0
- package/dist/shared-BGg9PbiG.mjs.map +1 -0
- package/dist/shared-DmpHYKx7.mjs +15 -0
- package/dist/shared-DmpHYKx7.mjs.map +1 -0
- package/dist/shared-SUXdsYBx.mjs +233 -0
- package/dist/shared-SUXdsYBx.mjs.map +1 -0
- package/dist/state.min.mjs +1 -0
- package/dist/state.mjs +7 -0
- package/dist/state.mjs.map +1 -0
- package/llms-full.txt +6999 -0
- package/llms.txt +89 -0
- package/package.json +11 -2
- package/src/addons/index.d.ts +99 -7
- package/src/addons/index.js +13 -2
- package/src/addons/persist.js +190 -0
- package/src/addons/repeat.js +159 -10
- package/src/core/batch.js +139 -0
- package/src/core/bindDom.js +7 -3
- package/src/core/effect.js +34 -5
- package/src/core/state.js +118 -73
- package/src/handlers/index.d.ts +24 -0
- package/src/handlers/index.js +1 -0
- package/src/handlers/on.js +60 -0
- package/src/handlers/stringAttr.js +9 -2
- package/src/index.d.ts +14 -200
- package/src/index.js +3 -1
- package/src/state.d.ts +252 -0
- package/src/state.js +25 -0
- package/dist/shared-x2HJmEyO.mjs +0 -260
- package/dist/shared-x2HJmEyO.mjs.map +0 -1
package/dist/addons.mjs
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { R as REACTIVE_BRAND } from "./shared-SUXdsYBx.mjs";
|
|
2
|
+
import { e as effect, a as applyBindValue } from "./shared-BGg9PbiG.mjs";
|
|
3
|
+
import { l as logError, a as logWarn } from "./shared-DmpHYKx7.mjs";
|
|
2
4
|
function computed(fn) {
|
|
3
5
|
if (typeof fn !== "function") {
|
|
4
6
|
throw new Error("computed() requires a function");
|
|
@@ -92,6 +94,54 @@ function watch(store, key, callback, { immediate = true } = {}) {
|
|
|
92
94
|
}
|
|
93
95
|
return store.$subscribe(key, callback);
|
|
94
96
|
}
|
|
97
|
+
function resolveTemplateEl(template, containerEl) {
|
|
98
|
+
let templateEl = template;
|
|
99
|
+
if (template === true) {
|
|
100
|
+
templateEl = containerEl.querySelector("template");
|
|
101
|
+
} else if (typeof template === "string") {
|
|
102
|
+
templateEl = document.querySelector(template);
|
|
103
|
+
}
|
|
104
|
+
if (!templateEl || templateEl.tagName !== "TEMPLATE") {
|
|
105
|
+
throw new Error("[Lume.js] repeat(): template not found or not a <template> element");
|
|
106
|
+
}
|
|
107
|
+
if (templateEl.content.children.length !== 1) {
|
|
108
|
+
throw new Error("[Lume.js] repeat(): template must contain exactly one root element");
|
|
109
|
+
}
|
|
110
|
+
return templateEl;
|
|
111
|
+
}
|
|
112
|
+
function resolveTemplate(template, containerEl) {
|
|
113
|
+
if (!template) return { templateRoot: null, keepEl: null };
|
|
114
|
+
const templateEl = resolveTemplateEl(template, containerEl);
|
|
115
|
+
let keepEl = templateEl;
|
|
116
|
+
while (keepEl && keepEl.parentNode !== containerEl) keepEl = keepEl.parentNode;
|
|
117
|
+
return { templateRoot: templateEl.content.firstElementChild, keepEl };
|
|
118
|
+
}
|
|
119
|
+
function collectItemBindings(el) {
|
|
120
|
+
const bindings = [];
|
|
121
|
+
const add = (node) => {
|
|
122
|
+
const path = node.getAttribute("data-bind");
|
|
123
|
+
bindings.push({ node, path, keys: path === "$item" || path === "$index" ? null : path.split(".") });
|
|
124
|
+
};
|
|
125
|
+
if (el.hasAttribute("data-bind")) add(el);
|
|
126
|
+
for (const node of el.querySelectorAll("[data-bind]")) add(node);
|
|
127
|
+
return bindings;
|
|
128
|
+
}
|
|
129
|
+
function applyItemBindings(bindings, item, index) {
|
|
130
|
+
for (const b of bindings) {
|
|
131
|
+
let val;
|
|
132
|
+
if (b.path === "$index") {
|
|
133
|
+
val = index;
|
|
134
|
+
} else if (b.path === "$item") {
|
|
135
|
+
val = item;
|
|
136
|
+
} else {
|
|
137
|
+
val = item;
|
|
138
|
+
for (let i = 0; i < b.keys.length && val != null; i++) {
|
|
139
|
+
val = val[b.keys[i]];
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
applyBindValue(b.node, val);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
95
145
|
function defaultFocusPreservation(container) {
|
|
96
146
|
const activeEl = document.activeElement;
|
|
97
147
|
const shouldRestore = container.contains(activeEl);
|
|
@@ -151,6 +201,7 @@ function repeat(container, store, arrayKey, options) {
|
|
|
151
201
|
create,
|
|
152
202
|
update,
|
|
153
203
|
remove,
|
|
204
|
+
template = null,
|
|
154
205
|
element = "div",
|
|
155
206
|
preserveFocus = defaultFocusPreservation,
|
|
156
207
|
preserveScroll = defaultScrollPreservation
|
|
@@ -164,20 +215,35 @@ function repeat(container, store, arrayKey, options) {
|
|
|
164
215
|
if (typeof key !== "function") {
|
|
165
216
|
throw new Error("[Lume.js] repeat(): options.key must be a function");
|
|
166
217
|
}
|
|
167
|
-
|
|
218
|
+
const { templateRoot, keepEl } = resolveTemplate(template, containerEl);
|
|
219
|
+
if (templateRoot && typeof render === "function") {
|
|
220
|
+
logWarn("[Lume.js] repeat(): options.render is ignored when options.template is set — use create/update instead");
|
|
221
|
+
}
|
|
222
|
+
if (!templateRoot && typeof render !== "function" && typeof create !== "function") {
|
|
168
223
|
throw new Error("[Lume.js] repeat(): options.render or options.create must be a function");
|
|
169
224
|
}
|
|
170
225
|
const elementsByKey = /* @__PURE__ */ new Map();
|
|
171
226
|
const prevItemsByKey = /* @__PURE__ */ new Map();
|
|
172
227
|
const prevIndexByKey = /* @__PURE__ */ new Map();
|
|
173
228
|
const cleanupByKey = /* @__PURE__ */ new Map();
|
|
229
|
+
const bindingsByKey = /* @__PURE__ */ new Map();
|
|
174
230
|
const seenKeys = /* @__PURE__ */ new Set();
|
|
175
231
|
function createElement() {
|
|
232
|
+
if (templateRoot) return templateRoot.cloneNode(true);
|
|
176
233
|
return typeof element === "function" ? element() : document.createElement(element);
|
|
177
234
|
}
|
|
235
|
+
function clearContainer() {
|
|
236
|
+
let node = containerEl.firstChild;
|
|
237
|
+
while (node) {
|
|
238
|
+
const next = node.nextSibling;
|
|
239
|
+
if (node !== keepEl) containerEl.removeChild(node);
|
|
240
|
+
node = next;
|
|
241
|
+
}
|
|
242
|
+
}
|
|
178
243
|
function reconcileDOM(container2, nextEls) {
|
|
179
244
|
let ptr = container2.firstChild;
|
|
180
245
|
for (let i = 0; i < nextEls.length; i++) {
|
|
246
|
+
if (keepEl && ptr === keepEl) ptr = ptr.nextSibling;
|
|
181
247
|
const desired = nextEls[i];
|
|
182
248
|
if (ptr === desired) {
|
|
183
249
|
ptr = ptr.nextSibling;
|
|
@@ -187,7 +253,7 @@ function repeat(container, store, arrayKey, options) {
|
|
|
187
253
|
}
|
|
188
254
|
while (ptr) {
|
|
189
255
|
const next = ptr.nextSibling;
|
|
190
|
-
container2.removeChild(ptr);
|
|
256
|
+
if (ptr !== keepEl) container2.removeChild(ptr);
|
|
191
257
|
ptr = next;
|
|
192
258
|
}
|
|
193
259
|
}
|
|
@@ -230,6 +296,9 @@ function repeat(container, store, arrayKey, options) {
|
|
|
230
296
|
if (isFirstRender) {
|
|
231
297
|
el = createElement();
|
|
232
298
|
elementsByKey.set(k, el);
|
|
299
|
+
if (templateRoot) {
|
|
300
|
+
bindingsByKey.set(k, collectItemBindings(el));
|
|
301
|
+
}
|
|
233
302
|
}
|
|
234
303
|
try {
|
|
235
304
|
if (isFirstRender && create) {
|
|
@@ -240,7 +309,12 @@ function repeat(container, store, arrayKey, options) {
|
|
|
240
309
|
}
|
|
241
310
|
const prevItem = prevItemsByKey.get(k);
|
|
242
311
|
const prevIndex = prevIndexByKey.get(k);
|
|
243
|
-
if (
|
|
312
|
+
if (templateRoot) {
|
|
313
|
+
if (prevItem !== item || prevIndex !== i) {
|
|
314
|
+
applyItemBindings(bindingsByKey.get(k), item, i);
|
|
315
|
+
if (update) update(item, el, i, { isFirstRender });
|
|
316
|
+
}
|
|
317
|
+
} else if (update) {
|
|
244
318
|
if (prevItem !== item || prevIndex !== i) {
|
|
245
319
|
update(item, el, i, { isFirstRender });
|
|
246
320
|
}
|
|
@@ -276,6 +350,7 @@ function repeat(container, store, arrayKey, options) {
|
|
|
276
350
|
prevItemsByKey.delete(k);
|
|
277
351
|
prevIndexByKey.delete(k);
|
|
278
352
|
cleanupByKey.delete(k);
|
|
353
|
+
bindingsByKey.delete(k);
|
|
279
354
|
}
|
|
280
355
|
}
|
|
281
356
|
}
|
|
@@ -308,11 +383,12 @@ function repeat(container, store, arrayKey, options) {
|
|
|
308
383
|
remove(prevItem, el);
|
|
309
384
|
}
|
|
310
385
|
}
|
|
311
|
-
|
|
386
|
+
clearContainer();
|
|
312
387
|
elementsByKey.clear();
|
|
313
388
|
prevItemsByKey.clear();
|
|
314
389
|
prevIndexByKey.clear();
|
|
315
390
|
cleanupByKey.clear();
|
|
391
|
+
bindingsByKey.clear();
|
|
316
392
|
seenKeys.clear();
|
|
317
393
|
};
|
|
318
394
|
}
|
|
@@ -334,11 +410,12 @@ function repeat(container, store, arrayKey, options) {
|
|
|
334
410
|
remove(prevItem, el);
|
|
335
411
|
}
|
|
336
412
|
}
|
|
337
|
-
|
|
413
|
+
clearContainer();
|
|
338
414
|
elementsByKey.clear();
|
|
339
415
|
prevItemsByKey.clear();
|
|
340
416
|
prevIndexByKey.clear();
|
|
341
417
|
cleanupByKey.clear();
|
|
418
|
+
bindingsByKey.clear();
|
|
342
419
|
seenKeys.clear();
|
|
343
420
|
};
|
|
344
421
|
}
|
|
@@ -682,8 +759,113 @@ function hydrateState(selector = "#__LUME_DATA__", validate) {
|
|
|
682
759
|
}
|
|
683
760
|
return data;
|
|
684
761
|
}
|
|
762
|
+
function readStored(storage, storageKey) {
|
|
763
|
+
try {
|
|
764
|
+
const raw = storage.getItem(storageKey);
|
|
765
|
+
if (!raw) return null;
|
|
766
|
+
const data = JSON.parse(raw);
|
|
767
|
+
return data && typeof data === "object" && !Array.isArray(data) ? data : null;
|
|
768
|
+
} catch {
|
|
769
|
+
logWarn(`[Lume.js] persist(): could not read "${storageKey}" — starting fresh`);
|
|
770
|
+
return null;
|
|
771
|
+
}
|
|
772
|
+
}
|
|
773
|
+
function serializeKeys(store, watched) {
|
|
774
|
+
const out = {};
|
|
775
|
+
for (const k of watched) out[k] = store[k];
|
|
776
|
+
return JSON.stringify(out);
|
|
777
|
+
}
|
|
778
|
+
const activeEntries = /* @__PURE__ */ new WeakMap();
|
|
779
|
+
function defaultStorage() {
|
|
780
|
+
try {
|
|
781
|
+
return typeof localStorage !== "undefined" ? localStorage : void 0;
|
|
782
|
+
} catch {
|
|
783
|
+
return void 0;
|
|
784
|
+
}
|
|
785
|
+
}
|
|
786
|
+
function persist(store, storageKey, options = {}) {
|
|
787
|
+
if (!store || typeof store.$subscribe !== "function") {
|
|
788
|
+
throw new Error("[Lume.js] persist() requires a reactive store from state()");
|
|
789
|
+
}
|
|
790
|
+
if (typeof storageKey !== "string" || storageKey.length === 0) {
|
|
791
|
+
throw new Error("[Lume.js] persist() requires a non-empty storage key");
|
|
792
|
+
}
|
|
793
|
+
const storage = options.storage !== void 0 ? options.storage : defaultStorage();
|
|
794
|
+
if (!storage || typeof storage.getItem !== "function") {
|
|
795
|
+
logWarn("[Lume.js] persist(): no storage available — persistence disabled");
|
|
796
|
+
return () => {
|
|
797
|
+
};
|
|
798
|
+
}
|
|
799
|
+
const watched = Array.isArray(options.keys) ? options.keys.slice() : Object.keys(store).filter((k) => !k.startsWith("$"));
|
|
800
|
+
let entrySet = activeEntries.get(storage);
|
|
801
|
+
if (!entrySet) {
|
|
802
|
+
entrySet = /* @__PURE__ */ new Set();
|
|
803
|
+
activeEntries.set(storage, entrySet);
|
|
804
|
+
}
|
|
805
|
+
const ownsEntry = !entrySet.has(storageKey);
|
|
806
|
+
if (ownsEntry) {
|
|
807
|
+
entrySet.add(storageKey);
|
|
808
|
+
} else {
|
|
809
|
+
logWarn(
|
|
810
|
+
`[Lume.js] persist(): "${storageKey}" is already managed by another persist() on this storage — instances will overwrite each other's data. Use a distinct key per store.`
|
|
811
|
+
);
|
|
812
|
+
}
|
|
813
|
+
const stored = readStored(storage, storageKey);
|
|
814
|
+
if (stored) {
|
|
815
|
+
for (const k of watched) {
|
|
816
|
+
if (Object.prototype.hasOwnProperty.call(stored, k)) {
|
|
817
|
+
store[k] = stored[k];
|
|
818
|
+
}
|
|
819
|
+
}
|
|
820
|
+
}
|
|
821
|
+
let lastWritten = null;
|
|
822
|
+
try {
|
|
823
|
+
lastWritten = serializeKeys(store, watched);
|
|
824
|
+
} catch {
|
|
825
|
+
}
|
|
826
|
+
let scheduled = false;
|
|
827
|
+
let disposed = false;
|
|
828
|
+
const flushSave = () => {
|
|
829
|
+
scheduled = false;
|
|
830
|
+
if (disposed) return;
|
|
831
|
+
let json;
|
|
832
|
+
try {
|
|
833
|
+
json = serializeKeys(store, watched);
|
|
834
|
+
} catch (err) {
|
|
835
|
+
logWarn("[Lume.js] persist(): state not serializable — skipping save", err);
|
|
836
|
+
return;
|
|
837
|
+
}
|
|
838
|
+
if (json === lastWritten) return;
|
|
839
|
+
try {
|
|
840
|
+
storage.setItem(storageKey, json);
|
|
841
|
+
lastWritten = json;
|
|
842
|
+
} catch (err) {
|
|
843
|
+
logWarn("[Lume.js] persist(): could not write — storage full or unavailable?", err);
|
|
844
|
+
}
|
|
845
|
+
};
|
|
846
|
+
const save = () => {
|
|
847
|
+
if (scheduled) return;
|
|
848
|
+
scheduled = true;
|
|
849
|
+
queueMicrotask(flushSave);
|
|
850
|
+
};
|
|
851
|
+
const unsubs = watched.map((k) => {
|
|
852
|
+
let first = true;
|
|
853
|
+
return store.$subscribe(k, () => {
|
|
854
|
+
if (first) {
|
|
855
|
+
first = false;
|
|
856
|
+
return;
|
|
857
|
+
}
|
|
858
|
+
save();
|
|
859
|
+
});
|
|
860
|
+
});
|
|
861
|
+
return () => {
|
|
862
|
+
disposed = true;
|
|
863
|
+
if (ownsEntry) entrySet.delete(storageKey);
|
|
864
|
+
while (unsubs.length) unsubs.pop()();
|
|
865
|
+
};
|
|
866
|
+
}
|
|
685
867
|
function isReactive(obj) {
|
|
686
|
-
return !!(obj && typeof obj === "object" && typeof obj.$subscribe === "function");
|
|
868
|
+
return !!(obj && typeof obj === "object" && (REACTIVE_BRAND in obj || typeof obj.$subscribe === "function"));
|
|
687
869
|
}
|
|
688
870
|
export {
|
|
689
871
|
computed,
|
|
@@ -694,6 +876,7 @@ export {
|
|
|
694
876
|
defaultScrollPreservation,
|
|
695
877
|
hydrateState,
|
|
696
878
|
isReactive,
|
|
879
|
+
persist,
|
|
697
880
|
repeat,
|
|
698
881
|
watch,
|
|
699
882
|
withPlugins
|
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 * @security After each computation, a re-entry guard stays active until the\n * next microtask. If a dependency changes synchronously during this window,\n * the computed will not recompute until a subsequent microtask. This is\n * intentional — it prevents infinite loops from self-mutating computeds.\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 * @param {Object} [options]\n * @param {boolean} [options.immediate=true] - call callback immediately with current value\n * @returns {Function} unsubscribe function\n */\nexport function watch(store, key, callback, { immediate = true } = {}) {\n if (!store.$subscribe) {\n throw new Error(\"store must be created with state()\");\n }\n if (!immediate) {\n let skipped = false;\n return store.$subscribe(key, (val) => {\n if (!skipped) { skipped = true; return; }\n callback(val);\n });\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 // eslint-disable-next-line sonarjs/cognitive-complexity -- keyed DOM reconciliation: create/reuse/remove nodes, key dedup, scroll/focus preservation\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 // eslint-disable-next-line sonarjs/cognitive-complexity -- DOM cleanup pass: remove stale nodes, call per-item cleanup callbacks, update maps\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 * @security Plugins run with full application privilege. A plugin can read\n * all state, alter any write, or suppress mutations. Only pass trusted objects.\n * Plugin objects are frozen after registration to prevent post-init mutation.\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, then freeze each plugin to prevent\n // post-registration mutation of its hooks (defense-in-depth).\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 Object.freeze(p);\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 * @security Hydration trusts the DOM. An attacker who can inject HTML before\n * the legitimate script (DOM clobbering) can control the parsed data. The\n * element must be a real `<script type=\"application/json\">` tag; non-script\n * elements are rejected. Use the optional `validate` parameter to enforce a\n * schema (e.g., whitelist allowed keys) before passing to `state()`.\n *\n * @param {string} [selector='#__LUME_DATA__'] - CSS selector for the script element\n * @param {function} [validate] - Optional validator: (data) => boolean. If it\n * returns false, hydrateState returns {} instead of the parsed data.\n * @returns {object} Parsed JSON object, or empty object if not found / invalid / rejected\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 * // With optional schema validation\n * const data = hydrateState('#__LUME_DATA__', d =>\n * typeof d.title === 'string' && typeof d.count === 'number'\n * );\n * const store = state(data);\n * ```\n */\nexport function hydrateState(selector = '#__LUME_DATA__', validate) {\n const el = typeof document !== 'undefined' ? document.querySelector(selector) : null;\n if (!el) return {};\n\n // Reject non-script elements or scripts without the correct type.\n // This mitigates DOM clobbering where an attacker injects a matching\n // element with a different tag name (e.g., a div or a script with\n // a different type that would still match querySelector by id).\n if (el.tagName !== 'SCRIPT' || el.type !== 'application/json') {\n return {};\n }\n\n let data;\n try {\n data = JSON.parse(el.textContent);\n } catch {\n return {};\n }\n\n if (typeof validate === 'function' && !validate(data)) {\n return {};\n }\n\n return data;\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/persist.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 * @security After each computation, a re-entry guard stays active until the\n * next microtask. If a dependency changes synchronously during this window,\n * the computed will not recompute until a subsequent microtask. This is\n * intentional — it prevents infinite loops from self-mutating computeds.\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 * @param {Object} [options]\n * @param {boolean} [options.immediate=true] - call callback immediately with current value\n * @returns {Function} unsubscribe function\n */\nexport function watch(store, key, callback, { immediate = true } = {}) {\n if (!store.$subscribe) {\n throw new Error(\"store must be created with state()\");\n }\n if (!immediate) {\n let skipped = false;\n return store.$subscribe(key, (val) => {\n if (!skipped) { skipped = true; return; }\n callback(val);\n });\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 0: Template-based (recommended) — declarative, zero DOM code\n * ═══════════════════════════════════════════════════════════════════════\n *\n * HTML — a standard <template> element, valid HTML, no custom syntax:\n *\n * <ul id=\"list\">\n * <template>\n * <li>\n * <strong data-bind=\"name\"></strong>\n * <span data-bind=\"role\"></span>\n * <em data-bind=\"$index\"></em>\n * </li>\n * </template>\n * </ul>\n *\n * JS:\n *\n * repeat('#list', store, 'people', {\n * key: p => p.id,\n * template: true // use the <template> inside the container\n * });\n *\n * data-bind paths resolve against EACH ITEM: \"name\" → item.name,\n * \"user.city\" → item.user.city, \"$item\" → the item itself (primitive\n * arrays), \"$index\" → current index. Inputs get .value/.checked, other\n * elements get textContent — identical semantics to bindDom's data-bind.\n * These are one-way snapshot bindings re-applied per list update (items\n * are plain objects, not stores). Combine with create/update for event\n * listeners or extra binding. template also accepts a CSS selector or an\n * HTMLTemplateElement.\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';\nimport { applyBindValue } from '../core/bindDom.js';\n\n/**\n * Resolve the template option to the <template> element.\n * Accepts true (first <template> inside the container), a CSS selector,\n * or an HTMLTemplateElement directly.\n */\nfunction resolveTemplateEl(template, containerEl) {\n let templateEl = template;\n if (template === true) {\n templateEl = containerEl.querySelector('template');\n } else if (typeof template === 'string') {\n templateEl = document.querySelector(template);\n }\n if (!templateEl || templateEl.tagName !== 'TEMPLATE') {\n throw new Error('[Lume.js] repeat(): template not found or not a <template> element');\n }\n if (templateEl.content.children.length !== 1) {\n throw new Error('[Lume.js] repeat(): template must contain exactly one root element');\n }\n return templateEl;\n}\n\n/**\n * Resolve the template option to its clone root, plus the container child\n * that is (or contains) the source <template>. Reconciliation and cleanup\n * must leave that child alone — it's the user's markup, and removing it\n * would break re-binding with `template: true` after cleanup. keepEl is\n * null when there is no template or it lives outside the container.\n */\nfunction resolveTemplate(template, containerEl) {\n if (!template) return { templateRoot: null, keepEl: null };\n const templateEl = resolveTemplateEl(template, containerEl);\n let keepEl = templateEl;\n while (keepEl && keepEl.parentNode !== containerEl) keepEl = keepEl.parentNode;\n return { templateRoot: templateEl.content.firstElementChild, keepEl };\n}\n\n/**\n * Collect [data-bind] nodes of a cloned item element into a compiled\n * binding list. Paths are resolved against the ITEM (not the store):\n * data-bind=\"name\" → item.name\n * data-bind=\"user.city\" → item.user.city\n * data-bind=\"$item\" → the item itself (for primitive arrays)\n * data-bind=\"$index\" → the item's current index\n */\nfunction collectItemBindings(el) {\n const bindings = [];\n const add = (node) => {\n const path = node.getAttribute('data-bind');\n bindings.push({ node, path, keys: path === '$item' || path === '$index' ? null : path.split('.') });\n };\n if (el.hasAttribute('data-bind')) add(el);\n for (const node of el.querySelectorAll('[data-bind]')) add(node);\n return bindings;\n}\n\nfunction applyItemBindings(bindings, item, index) {\n for (const b of bindings) {\n let val;\n if (b.path === '$index') {\n val = index;\n } else if (b.path === '$item') {\n val = item;\n } else {\n val = item;\n for (let i = 0; i < b.keys.length && val != null; i++) {\n val = val[b.keys[i]];\n }\n }\n applyBindValue(b.node, val);\n }\n}\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 {true|string|HTMLTemplateElement} [options.template] - Declarative item structure from a <template> element: true = first <template> inside the container, string = CSS selector, or the element itself. The template must have exactly one root element; it is cloned per item and its [data-bind] paths are bound to the item on every update (\"name\", \"user.city\", \"$item\", \"$index\"). When set, options.element is ignored and options.render is ignored (with a warning); create/update remain available on top.\n * @param {string|Function} [options.element='div'] - Element tag name or factory function (ignored when options.template is set)\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 template = null,\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 // Template mode: structure and data binding come from the <template>;\n // render/create/update are all optional on top of it.\n const { templateRoot, keepEl } = resolveTemplate(template, containerEl);\n\n if (templateRoot && typeof render === 'function') {\n logWarn('[Lume.js] repeat(): options.render is ignored when options.template is set — use create/update instead');\n }\n\n if (!templateRoot && 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 // key -> compiled [data-bind] nodes of the clone (template mode only)\n const bindingsByKey = new Map();\n const seenKeys = new Set();\n\n function createElement() {\n if (templateRoot) return templateRoot.cloneNode(true);\n return typeof element === 'function'\n ? element()\n : document.createElement(element);\n }\n\n /**\n * Empty the container, preserving the in-container source template.\n * Manual removal (not replaceChildren) for two reasons: replaceChildren\n * is Chrome 86+/Safari 14+ — above the documented ES2020-era browser\n * floor — and re-inserting keepEl would re-parent a template the user\n * has since moved (or resurrect one they deleted).\n */\n function clearContainer() {\n let node = containerEl.firstChild;\n while (node) {\n const next = node.nextSibling;\n if (node !== keepEl) containerEl.removeChild(node);\n node = next;\n }\n }\n\n function reconcileDOM(container, nextEls) {\n let ptr = container.firstChild;\n\n for (let i = 0; i < nextEls.length; i++) {\n // Never treat the source template (or its wrapper) as a list row\n if (keepEl && ptr === keepEl) ptr = ptr.nextSibling;\n\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 (preserving the template)\n while (ptr) {\n const next = ptr.nextSibling;\n if (ptr !== keepEl) 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 // eslint-disable-next-line sonarjs/cognitive-complexity -- keyed DOM reconciliation: create/reuse/remove nodes, key dedup, scroll/focus preservation\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 if (templateRoot) {\n bindingsByKey.set(k, collectItemBindings(el));\n }\n }\n\n try {\n // Call create for new elements (DOM structure / event listeners)\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 // 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 (templateRoot) {\n if (prevItem !== item || prevIndex !== i) {\n applyItemBindings(bindingsByKey.get(k), item, i);\n // update is optional extra binding on top of the template\n if (update) update(item, el, i, { isFirstRender });\n }\n } else 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 // eslint-disable-next-line sonarjs/cognitive-complexity -- DOM cleanup pass: remove stale nodes, call per-item cleanup callbacks, update maps\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 bindingsByKey.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 clearContainer();\n elementsByKey.clear();\n prevItemsByKey.clear();\n prevIndexByKey.clear();\n cleanupByKey.clear();\n bindingsByKey.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 (preserving the source template, if any)\n clearContainer();\n elementsByKey.clear();\n prevItemsByKey.clear();\n prevIndexByKey.clear();\n cleanupByKey.clear();\n bindingsByKey.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 * @security Plugins run with full application privilege. A plugin can read\n * all state, alter any write, or suppress mutations. Only pass trusted objects.\n * Plugin objects are frozen after registration to prevent post-init mutation.\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, then freeze each plugin to prevent\n // post-registration mutation of its hooks (defense-in-depth).\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 Object.freeze(p);\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 * @security Hydration trusts the DOM. An attacker who can inject HTML before\n * the legitimate script (DOM clobbering) can control the parsed data. The\n * element must be a real `<script type=\"application/json\">` tag; non-script\n * elements are rejected. Use the optional `validate` parameter to enforce a\n * schema (e.g., whitelist allowed keys) before passing to `state()`.\n *\n * @param {string} [selector='#__LUME_DATA__'] - CSS selector for the script element\n * @param {function} [validate] - Optional validator: (data) => boolean. If it\n * returns false, hydrateState returns {} instead of the parsed data.\n * @returns {object} Parsed JSON object, or empty object if not found / invalid / rejected\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 * // With optional schema validation\n * const data = hydrateState('#__LUME_DATA__', d =>\n * typeof d.title === 'string' && typeof d.count === 'number'\n * );\n * const store = state(data);\n * ```\n */\nexport function hydrateState(selector = '#__LUME_DATA__', validate) {\n const el = typeof document !== 'undefined' ? document.querySelector(selector) : null;\n if (!el) return {};\n\n // Reject non-script elements or scripts without the correct type.\n // This mitigates DOM clobbering where an attacker injects a matching\n // element with a different tag name (e.g., a div or a script with\n // a different type that would still match querySelector by id).\n if (el.tagName !== 'SCRIPT' || el.type !== 'application/json') {\n return {};\n }\n\n let data;\n try {\n data = JSON.parse(el.textContent);\n } catch {\n return {};\n }\n\n if (typeof validate === 'function' && !validate(data)) {\n return {};\n }\n\n return data;\n}\n","/**\n * Lume-JS Persist Addon\n *\n * Keeps selected store keys in sync with localStorage/sessionStorage (or\n * any Storage-like object): hydrates them on call, then saves on change.\n *\n * Usage:\n * import { state } from \"lume-js\";\n * import { persist } from \"lume-js/addons\";\n *\n * const store = state({ todos: [], filter: 'all', draft: '' });\n *\n * // Hydrate + auto-save todos/filter; draft stays in-memory only\n * const stop = persist(store, 'my-app', { keys: ['todos', 'filter'] });\n *\n * Behavior:\n * - Hydration assigns stored values through the proxy, so subscribers and\n * bindings see them like any other write.\n * - Saves are coalesced to one storage write per microtask, and skipped\n * entirely when the serialized snapshot is unchanged.\n * - Storage failures (quota, unavailable, corrupted JSON, unserializable\n * values) are contained: a console warning, never a throw.\n *\n * @security Storage is same-origin but survives schema changes — hydration\n * only assigns keys you watch (never unknown keys from storage), and the\n * core set trap independently blocks prototype-polluting keys.\n *\n * @module addons/persist\n */\n\nimport { logWarn } from '../utils/log.js';\n\n/**\n * Read and parse the stored JSON blob. Returns a plain object, or null\n * when missing, corrupted, or not an object (warns on read errors).\n */\nfunction readStored(storage, storageKey) {\n try {\n const raw = storage.getItem(storageKey);\n if (!raw) return null;\n const data = JSON.parse(raw);\n return data && typeof data === 'object' && !Array.isArray(data) ? data : null;\n } catch {\n logWarn(`[Lume.js] persist(): could not read \"${storageKey}\" — starting fresh`);\n return null;\n }\n}\n\n/** Serialize the watched subset of the store. May throw (circular refs). */\nfunction serializeKeys(store, watched) {\n const out = {};\n for (const k of watched) out[k] = store[k];\n return JSON.stringify(out);\n}\n\n// storage object → Set of storage keys currently managed by a persist()\n// instance. Two instances on one entry silently overwrite each other's\n// subsets (each serializes only its own watched keys over the whole blob),\n// so the second registration gets a loud warning.\nconst activeEntries = new WeakMap();\n\n/**\n * Default storage resolution. Wrapped because accessing localStorage can\n * THROW (SecurityError) in cookie-blocked iframes and some privacy modes —\n * persist() must degrade to a warning, never crash the app at setup.\n */\nfunction defaultStorage() {\n try {\n return typeof localStorage !== 'undefined' ? localStorage : undefined;\n } catch {\n return undefined;\n }\n}\n\n/**\n * Sync store keys with a Storage object.\n *\n * @param {object} store - Reactive store created with state()\n * @param {string} storageKey - The storage entry name to read/write\n * @param {object} [options]\n * @param {string[]} [options.keys] - Keys to persist. Default: all own\n * non-$ keys of the store at call time.\n * @param {Storage} [options.storage] - Storage object. Default: localStorage.\n * Pass sessionStorage for per-tab persistence.\n * @returns {function} Dispose function — stops watching and saving.\n */\nexport function persist(store, storageKey, options = {}) {\n if (!store || typeof store.$subscribe !== 'function') {\n throw new Error('[Lume.js] persist() requires a reactive store from state()');\n }\n if (typeof storageKey !== 'string' || storageKey.length === 0) {\n throw new Error('[Lume.js] persist() requires a non-empty storage key');\n }\n\n const storage = options.storage !== undefined\n ? options.storage\n : defaultStorage();\n\n if (!storage || typeof storage.getItem !== 'function') {\n logWarn('[Lume.js] persist(): no storage available — persistence disabled');\n return () => {};\n }\n\n // An explicit array is respected as-is — including [] (persist nothing).\n // Only an absent/non-array option falls back to all own non-$ keys.\n const watched = Array.isArray(options.keys)\n ? options.keys.slice()\n : Object.keys(store).filter(k => !k.startsWith('$'));\n\n // Warn when another live persist() already manages this storage entry\n let entrySet = activeEntries.get(storage);\n if (!entrySet) {\n entrySet = new Set();\n activeEntries.set(storage, entrySet);\n }\n const ownsEntry = !entrySet.has(storageKey);\n if (ownsEntry) {\n entrySet.add(storageKey);\n } else {\n logWarn(\n `[Lume.js] persist(): \"${storageKey}\" is already managed by another persist() on this storage — ` +\n 'instances will overwrite each other\\'s data. Use a distinct key per store.'\n );\n }\n\n // ── Hydrate ────────────────────────────────────────────────────────────\n // Only watched keys are assigned — stale storage can't inject others.\n const stored = readStored(storage, storageKey);\n if (stored) {\n for (const k of watched) {\n if (Object.prototype.hasOwnProperty.call(stored, k)) {\n store[k] = stored[k];\n }\n }\n }\n\n // ── Save on change ─────────────────────────────────────────────────────\n // Remember what storage holds (post-hydration) so unchanged flushes —\n // including the hydration echo itself — skip the write.\n let lastWritten = null;\n try {\n lastWritten = serializeKeys(store, watched);\n } catch {\n // Unserializable initial state: first save attempt will warn.\n }\n\n let scheduled = false;\n let disposed = false;\n\n const flushSave = () => {\n scheduled = false;\n if (disposed) return;\n\n let json;\n try {\n json = serializeKeys(store, watched);\n } catch (err) {\n logWarn('[Lume.js] persist(): state not serializable — skipping save', err);\n return;\n }\n if (json === lastWritten) return;\n\n try {\n storage.setItem(storageKey, json);\n lastWritten = json;\n } catch (err) {\n logWarn('[Lume.js] persist(): could not write — storage full or unavailable?', err);\n }\n };\n\n const save = () => {\n if (scheduled) return;\n scheduled = true;\n queueMicrotask(flushSave);\n };\n\n const unsubs = watched.map(k => {\n let first = true;\n return store.$subscribe(k, () => {\n if (first) { first = false; return; } // skip $subscribe's immediate call\n save();\n });\n });\n\n return () => {\n disposed = true;\n if (ownsEntry) entrySet.delete(storageKey);\n while (unsubs.length) unsubs.pop()();\n };\n}\n","import { REACTIVE_BRAND } from \"../core/state.js\";\n\nexport { 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\";\nexport { persist } from \"./persist.js\";\n\n/**\n * Returns true if the value is a Lume reactive proxy created by state().\n *\n * Checks the shared reactive brand first (a registry symbol stamped by\n * state(), reliable across module copies), then falls back to duck-typing\n * ($subscribe) for proxies from older lume-js versions whose brand was not\n * shared. The brand check uses the `in` operator, which does not pass\n * through the proxy `get` trap — calling isReactive inside an effect does\n * not create a spurious dependency.\n *\n * @param {any} obj\n * @returns {boolean}\n */\nexport function isReactive(obj) {\n return !!(obj && typeof obj === 'object' &&\n (REACTIVE_BRAND in obj || typeof obj.$subscribe === 'function'));\n}\n"],"names":["container"],"mappings":"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"}
|
package/dist/handlers.min.mjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
const e={attr:"data-show",apply(e,t){e.hidden=!t}},t={attr:"data-classname",apply(e,t){e.className=t||""}};function a(e){return{attr:"data-"+e,apply(t,a){t.toggleAttribute(e,!!a)}}}function
|
|
1
|
+
const e={attr:"data-show",apply(e,t){e.hidden=!t}},t={attr:"data-classname",apply(e,t){e.className=t||""}};function a(e){return{attr:"data-"+e,apply(t,a){t.toggleAttribute(e,!!a)}}}function o(e){const t=e.startsWith("aria-")?e:"aria-"+e;return{attr:"data-"+t,apply(e,a){e.setAttribute(t,a?"true":"false")}}}function n(...e){return e.map(e=>({attr:"data-class-"+e,apply(t,a){t.classList.toggle(e,!!a)}}))}const r=/^(?:javascript:|vbscript:|data:text\/html)/,l=/[\u0000-\u0020\u007F]/g,s=new Set(["href","src","action","srcset","poster","formaction"]);function i(e){return{attr:"data-"+e,apply(t,a){if(null==a)return void t.removeAttribute(e);const o=a+"";s.has(e)&&r.test(o.replace(l,"").toLowerCase())?t.removeAttribute(e):t.setAttribute(e,o)}}}function c(e,...t){void 0!==console&&"function"==typeof console.warn&&console.warn(e,...t)}const d=new WeakMap;function p(...e){return e.map(e=>({attr:"data-on"+e,apply(t,a){let o=d.get(t);const n=o?o.get(e):void 0;n&&(t.removeEventListener(e,n),o.delete(e)),"function"==typeof a?(t.addEventListener(e,a),o||(o=new Map,d.set(t,o)),o.set(e,a)):null!=a&&c(`[Lume.js] on('${e}'): bound value is not a function — listener detached`)}}))}const u=["readonly","open","novalidate","formnovalidate","multiple","autofocus","autoplay","controls","loop","muted","defer","async","reversed","selected","inert","allowfullscreen"],m=["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"],f=["pressed","selected","disabled","checked","invalid","required","busy","modal","multiselectable","multiline","readonly","atomic"],b=["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"];function v(){return[e,...u.map(e=>a(e)),...m.map(e=>i(e)),...f.map(e=>o(e)),...b.map(e=>i("aria-"+e))]}const h=[a("readonly")],y=[o("pressed"),o("selected"),o("disabled")];export{y as a11yHandlers,o as ariaAttr,a as boolAttr,t as className,n as classToggle,h as formHandlers,v as htmlAttrs,p as on,e as show,i as stringAttr};
|