uentropy 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js ADDED
@@ -0,0 +1,954 @@
1
+ // src/context.ts
2
+ function createContext() {
3
+ return {
4
+ data: null,
5
+ prefix: "en-",
6
+ watchers: /* @__PURE__ */ new Map(),
7
+ directives: /* @__PURE__ */ new Map(),
8
+ deps: {
9
+ isEvaluating: false,
10
+ set: /* @__PURE__ */ new Set(),
11
+ map: /* @__PURE__ */ new Map(),
12
+ versions: /* @__PURE__ */ new Map()
13
+ },
14
+ computedResultFns: /* @__PURE__ */ new WeakSet(),
15
+ batchQueue: null,
16
+ destroyed: false,
17
+ elementCache: /* @__PURE__ */ new Map(),
18
+ observer: null
19
+ };
20
+ }
21
+
22
+ // src/symbols.ts
23
+ var enPrefix = /* @__PURE__ */ Symbol("@en/prefix");
24
+
25
+ // src/utils.ts
26
+ function getKey(prop, prefix) {
27
+ return prefix === "" ? prop : `${prefix}.${prop}`;
28
+ }
29
+ function getParam(el, attrName, isParametric) {
30
+ if (!isParametric) return void 0;
31
+ const value = el.getAttribute(attrName);
32
+ if (!value) return void 0;
33
+ return value.slice(value.indexOf(":") + 1);
34
+ }
35
+ function isPrefixedObject(value) {
36
+ return typeof value === "object" && value !== null && enPrefix in value;
37
+ }
38
+ function clone(target) {
39
+ if (target === null || typeof target !== "object") return target;
40
+ if (target instanceof Date) {
41
+ return new Date(target.getTime());
42
+ }
43
+ if (target instanceof RegExp) {
44
+ return new RegExp(target.source, target.flags);
45
+ }
46
+ if (target instanceof Map) {
47
+ return new Map(
48
+ [...target].map(([k, v]) => [clone(k), clone(v)])
49
+ );
50
+ }
51
+ if (target instanceof Set) {
52
+ return new Set([...target].map((v) => clone(v)));
53
+ }
54
+ if (Array.isArray(target)) {
55
+ return target.map((v) => clone(v));
56
+ }
57
+ const result = Object.create(
58
+ Object.getPrototypeOf(target)
59
+ );
60
+ for (const key of Object.keys(target)) {
61
+ result[key] = clone(target[key]);
62
+ }
63
+ return result;
64
+ }
65
+
66
+ // src/computed.ts
67
+ var IS_COMPUTED = /* @__PURE__ */ Symbol("@en/computed");
68
+ function computed(fn) {
69
+ Object.defineProperty(fn, IS_COMPUTED, {
70
+ value: true,
71
+ enumerable: false,
72
+ configurable: true,
73
+ writable: false
74
+ });
75
+ return fn;
76
+ }
77
+ function clearDepsForKey(ctx, key) {
78
+ for (const [depKey, list] of ctx.deps.map) {
79
+ const filtered = list.filter((d) => d.key !== key);
80
+ if (filtered.length === 0) {
81
+ ctx.deps.map.delete(depKey);
82
+ } else {
83
+ ctx.deps.map.set(depKey, filtered);
84
+ }
85
+ }
86
+ }
87
+ function registerTrackedDeps(ctx, key, computedFn, parent, prop) {
88
+ const dependent = { key, computed: computedFn, parent, prop };
89
+ for (const dep of ctx.deps.set) {
90
+ const list = ctx.deps.map.get(dep) ?? [];
91
+ list.push(dependent);
92
+ ctx.deps.map.set(dep, list);
93
+ }
94
+ }
95
+ function setDependents(ctx, value, key, parent, prop) {
96
+ clearDepsForKey(ctx, key);
97
+ ctx.deps.isEvaluating = true;
98
+ ctx.deps.set.clear();
99
+ try {
100
+ value();
101
+ } catch {
102
+ } finally {
103
+ ctx.deps.isEvaluating = false;
104
+ }
105
+ registerTrackedDeps(ctx, key, value, parent, prop);
106
+ ctx.deps.set.clear();
107
+ }
108
+ function getDependentsOf(ctx, changedKey) {
109
+ const matched = [...ctx.deps.map.entries()].filter(
110
+ ([k]) => k === changedKey || k.startsWith(changedKey + ".") || changedKey.startsWith(k + ".")
111
+ ).flatMap(([, list]) => list);
112
+ const seen = /* @__PURE__ */ new Set();
113
+ return matched.filter((dep) => {
114
+ if (seen.has(dep.computed)) return false;
115
+ seen.add(dep.computed);
116
+ return true;
117
+ });
118
+ }
119
+ function removeDependentsFor(ctx, deletedKey) {
120
+ ctx.deps.map.delete(deletedKey);
121
+ for (const [k, list] of ctx.deps.map) {
122
+ const filtered = list.filter((d) => d.key !== deletedKey);
123
+ if (filtered.length === 0) {
124
+ ctx.deps.map.delete(k);
125
+ } else {
126
+ ctx.deps.map.set(k, filtered);
127
+ }
128
+ }
129
+ }
130
+ function bumpVersion(ctx, key) {
131
+ const v = (ctx.deps.versions.get(key) ?? 0) + 1;
132
+ ctx.deps.versions.set(key, v);
133
+ return v;
134
+ }
135
+ function isCurrentVersion(ctx, key, version) {
136
+ return ctx.deps.versions.get(key) === version;
137
+ }
138
+
139
+ // src/watchers.ts
140
+ function watch(ctx, key, watcher) {
141
+ const list = ctx.watchers.get(key) ?? [];
142
+ list.push(watcher);
143
+ ctx.watchers.set(key, list);
144
+ }
145
+ function unwatch(ctx, key, watcher) {
146
+ if (!key && !watcher) {
147
+ ctx.watchers.clear();
148
+ return;
149
+ }
150
+ if (key && !watcher) {
151
+ ctx.watchers.delete(key);
152
+ return;
153
+ }
154
+ const targets = key ? [[key, ctx.watchers.get(key) ?? []]] : [...ctx.watchers.entries()];
155
+ for (const [k, list] of targets) {
156
+ const filtered = list.filter((w) => w !== watcher);
157
+ if (filtered.length === 0) {
158
+ ctx.watchers.delete(k);
159
+ } else {
160
+ ctx.watchers.set(k, filtered);
161
+ }
162
+ }
163
+ }
164
+ function callWatchers(ctx, changedKey, changedValue, getValue2) {
165
+ for (const [watchedKey, watchers] of ctx.watchers) {
166
+ if (changedKey === watchedKey) {
167
+ watchers.forEach((cb) => cb(changedValue));
168
+ } else if (changedKey.startsWith(watchedKey + ".")) {
169
+ const parentValue = getValue2(watchedKey);
170
+ watchers.forEach((cb) => cb(parentValue));
171
+ }
172
+ }
173
+ }
174
+
175
+ // src/dom/queries.ts
176
+ function getValue(ctx, key) {
177
+ let parent = ctx.data;
178
+ let value = void 0;
179
+ let prop = "";
180
+ if (parent === null) {
181
+ return { parent, value, prop };
182
+ }
183
+ const parts = key.split(".");
184
+ for (let i = 0; i < parts.length; i++) {
185
+ prop = parts[i];
186
+ value = Reflect.get(parent, prop);
187
+ if (i < parts.length - 1) {
188
+ if (typeof value !== "object" || value === null) {
189
+ return { parent, value: void 0, prop };
190
+ }
191
+ parent = value;
192
+ }
193
+ }
194
+ return { parent, value, prop };
195
+ }
196
+
197
+ // src/directives/builtins.ts
198
+ function markDirective({ el, value, isDelete }) {
199
+ if (isDelete) {
200
+ removeElement(el);
201
+ return;
202
+ }
203
+ if (!(el instanceof HTMLElement)) return;
204
+ if (typeof value === "object" && value !== null) {
205
+ value = JSON.stringify(value);
206
+ }
207
+ el.textContent = typeof value === "string" ? value : String(value);
208
+ }
209
+ var modelListeners = /* @__PURE__ */ new WeakMap();
210
+ function modelDirective(ctx) {
211
+ return ({ el, value, key }) => {
212
+ if (!(el instanceof HTMLInputElement || el instanceof HTMLTextAreaElement || el instanceof HTMLSelectElement)) return;
213
+ if (el instanceof HTMLInputElement && el.type === "checkbox") {
214
+ el.checked = !!value;
215
+ } else if (el instanceof HTMLInputElement && el.type === "radio") {
216
+ el.checked = el.value === String(value);
217
+ } else {
218
+ const strVal = value === null || value === void 0 ? "" : String(value);
219
+ if (el.value !== strVal) el.value = strVal;
220
+ }
221
+ if (modelListeners.has(el)) return;
222
+ const isCheckbox = el instanceof HTMLInputElement && el.type === "checkbox";
223
+ const isRadio = el instanceof HTMLInputElement && el.type === "radio";
224
+ const isNumber = el instanceof HTMLInputElement && el.type === "number";
225
+ const isSelect = el instanceof HTMLSelectElement;
226
+ const eventName = isCheckbox || isRadio || isSelect ? "change" : "input";
227
+ const handler = () => {
228
+ const { parent, prop } = getValue(ctx, key);
229
+ if (!parent) return;
230
+ let incoming;
231
+ if (isCheckbox) incoming = el.checked;
232
+ else if (isNumber) incoming = el.valueAsNumber;
233
+ else incoming = el.value;
234
+ Reflect.set(parent, prop, incoming);
235
+ };
236
+ el.addEventListener(eventName, handler);
237
+ modelListeners.set(el, handler);
238
+ };
239
+ }
240
+ function createConditionalDirectives(ifOrIfNotFn) {
241
+ return {
242
+ if: {
243
+ cb: ({ el, value, key }) => ifOrIfNotFn(el, value, key, "if")
244
+ },
245
+ ifnot: {
246
+ cb: ({ el, value, key }) => ifOrIfNotFn(el, value, key, "ifnot")
247
+ }
248
+ };
249
+ }
250
+ function removeElement(el) {
251
+ const parent = el.parentElement;
252
+ if (!(el instanceof HTMLElement) || !parent) {
253
+ return el.remove();
254
+ }
255
+ const elMark = el.getAttribute("data-en-mark") ?? el.getAttribute("en-mark");
256
+ const parentMark = parent.getAttribute("data-en-mark") ?? parent.getAttribute("en-mark");
257
+ if (elMark && elMark === parentMark) {
258
+ return parent.remove();
259
+ }
260
+ el.remove();
261
+ }
262
+ var markEntry = { cb: markDirective };
263
+ function createModelEntry(ctx) {
264
+ return { cb: modelDirective(ctx) };
265
+ }
266
+
267
+ // src/directives/index.ts
268
+ function registerBuiltins(ctx) {
269
+ ctx.directives.set("mark", markEntry);
270
+ ctx.directives.set("model", createModelEntry(ctx));
271
+ }
272
+ function registerDirective(ctx, name, cb, isParametric = false) {
273
+ if (ctx.directives.has(name)) return;
274
+ ctx.directives.set(name, { cb, isParametric });
275
+ }
276
+
277
+ // src/core.ts
278
+ function bootstrapDirectives(ctx) {
279
+ registerBuiltins(ctx);
280
+ const conditionals = createConditionalDirectives(
281
+ (el, value, key, type) => ifOrIfNot(ctx, el, value, key, type)
282
+ );
283
+ ctx.directives.set("if", conditionals.if);
284
+ ctx.directives.set("ifnot", conditionals.ifnot);
285
+ }
286
+ function setupObserver(ctx) {
287
+ if (ctx.observer || typeof MutationObserver === "undefined") return;
288
+ ctx.observer = new MutationObserver(() => ctx.elementCache.clear());
289
+ ctx.observer.observe(document, { childList: true, subtree: true });
290
+ }
291
+ function queryAll(ctx, root, query) {
292
+ if (!(root instanceof Document)) {
293
+ return Array.from(root.querySelectorAll(query));
294
+ }
295
+ const cached = ctx.elementCache.get(query);
296
+ if (cached) return cached;
297
+ const result = Array.from(root.querySelectorAll(query));
298
+ ctx.elementCache.set(query, result);
299
+ return result;
300
+ }
301
+ function reactive(ctx, obj, prefix, parent, prop) {
302
+ if (obj === null) return obj;
303
+ const isObject = typeof obj === "object";
304
+ const isFunction = typeof obj === "function";
305
+ if (isFunction && parent) {
306
+ obj = obj.bind(parent);
307
+ }
308
+ if (isObject || isFunction) {
309
+ Object.defineProperty(obj, enPrefix, {
310
+ value: prefix,
311
+ enumerable: false,
312
+ writable: true,
313
+ configurable: true
314
+ });
315
+ }
316
+ if (isFunction && prop && parent) {
317
+ setDependents(ctx, obj, prefix, parent, prop);
318
+ return obj;
319
+ }
320
+ if (!isObject) return obj;
321
+ const proxied = new Proxy(
322
+ obj,
323
+ makeHandler(ctx)
324
+ );
325
+ for (const p of Object.keys(obj)) {
326
+ const childPrefix = getKey(p, prefix);
327
+ const value = obj[p];
328
+ obj[p] = reactive(
329
+ ctx,
330
+ value,
331
+ childPrefix,
332
+ proxied,
333
+ p
334
+ );
335
+ }
336
+ return proxied;
337
+ }
338
+ function makeHandler(ctx) {
339
+ return {
340
+ // ── get ───────────────────────────────────────────────────────────────────
341
+ get(target, prop, receiver) {
342
+ if (ctx.deps.isEvaluating && typeof prop === "string" && Object.getOwnPropertyDescriptor(target, prop)?.enumerable) {
343
+ ctx.deps.set.add(getKey(prop, target[enPrefix]));
344
+ }
345
+ const value = Reflect.get(target, prop, receiver);
346
+ if (typeof value === "function" && enPrefix in value && !ctx.computedResultFns.has(value)) {
347
+ const result = value();
348
+ if (result instanceof Promise) {
349
+ return result.then((v) => proxyComputed(ctx, v));
350
+ }
351
+ return proxyComputed(ctx, result);
352
+ }
353
+ return value;
354
+ },
355
+ // ── set ───────────────────────────────────────────────────────────────────
356
+ set(target, prop, value, receiver) {
357
+ if (typeof prop === "symbol") {
358
+ return Reflect.set(target, prop, value, receiver);
359
+ }
360
+ const key = getKey(prop, target[enPrefix]);
361
+ const reactiveValue = reactive(ctx, value, key, receiver, prop);
362
+ const success = Reflect.set(target, prop, reactiveValue, receiver);
363
+ scheduleUpdate(
364
+ ctx,
365
+ () => update(ctx, reactiveValue, key, false, receiver, prop)
366
+ );
367
+ scheduleUpdate(ctx, () => updateComputed(ctx, key));
368
+ return success;
369
+ },
370
+ // ── deleteProperty ────────────────────────────────────────────────────────
371
+ deleteProperty(target, prop) {
372
+ if (typeof prop === "symbol") {
373
+ return Reflect.deleteProperty(target, prop);
374
+ }
375
+ const key = getKey(prop, target[enPrefix]);
376
+ const success = Reflect.deleteProperty(target, prop);
377
+ update(ctx, void 0, key, true, target, prop);
378
+ removeDependentsFor(ctx, key);
379
+ return success;
380
+ },
381
+ // ── defineProperty ────────────────────────────────────────────────────────
382
+ defineProperty(target, prop, descriptor) {
383
+ if (prop === enPrefix && enPrefix in target && typeof descriptor.value === "string" && /\.\d+$/.test(descriptor.value)) {
384
+ return Reflect.set(target, prop, descriptor.value);
385
+ }
386
+ return Reflect.defineProperty(target, prop, descriptor);
387
+ }
388
+ };
389
+ }
390
+ function scheduleUpdate(ctx, fn) {
391
+ if (ctx.batchQueue !== null) {
392
+ ctx.batchQueue.push(fn);
393
+ } else {
394
+ fn();
395
+ }
396
+ }
397
+ function updateComputed(ctx, changedKey) {
398
+ const deps = getDependentsOf(ctx, changedKey);
399
+ for (const dep of deps) {
400
+ update(ctx, dep.computed, dep.key, false, dep.parent, dep.prop);
401
+ }
402
+ }
403
+ function update(ctx, value, key, isDelete, parent, prop, syncConfig) {
404
+ if (ctx.destroyed) return;
405
+ if (typeof value === "function" && !ctx.computedResultFns.has(value)) {
406
+ value = runComputed(ctx, value, key, parent, prop);
407
+ if (value === void 0) return;
408
+ }
409
+ if (value instanceof Promise) {
410
+ const version = bumpVersion(ctx, key);
411
+ value.then((v) => {
412
+ if (!isCurrentVersion(ctx, key, version)) return;
413
+ update(ctx, v, key, false, parent, prop, syncConfig);
414
+ });
415
+ return;
416
+ }
417
+ if (!syncConfig) {
418
+ callWatchers(ctx, key, value, (k) => getValue(ctx, k).value);
419
+ }
420
+ callDirectives(ctx, value, key, isDelete, parent, prop, void 0, void 0, syncConfig);
421
+ }
422
+ function runComputed(ctx, computedFn, key, parent, prop) {
423
+ const version = bumpVersion(ctx, key);
424
+ const result = computedFn();
425
+ if (result instanceof Promise) {
426
+ result.then((v) => {
427
+ if (!isCurrentVersion(ctx, key, version)) return;
428
+ const proxied = proxyComputed(ctx, v, key, parent, prop);
429
+ update(ctx, proxied, key, false, parent, prop);
430
+ }).catch(
431
+ (err) => console.error(`[entropy] Async computed error at "${key}":`, err)
432
+ );
433
+ return void 0;
434
+ }
435
+ return proxyComputed(ctx, result, key, parent, prop);
436
+ }
437
+ function proxyComputed(ctx, value, key, parent, prop) {
438
+ if (typeof value === "function") {
439
+ ctx.computedResultFns.add(value);
440
+ return value;
441
+ }
442
+ if (key === void 0 || parent === void 0 || prop === void 0) {
443
+ return clone(value);
444
+ }
445
+ return reactive(ctx, clone(value), key, parent, prop);
446
+ }
447
+ function callDirectives(ctx, value, key, isDelete, parent, prop, searchRoot, skipUpdateArrayElements, syncConfig) {
448
+ const isParentArray = Array.isArray(parent);
449
+ if (isParentArray && /^\d+$/.test(prop) && !skipUpdateArrayElements && syncConfig?.skipMark !== true) {
450
+ updateArrayItemElement(ctx, key, prop, value, parent);
451
+ } else if (isParentArray && prop === "length") {
452
+ sortArrayItemElements(ctx, parent);
453
+ }
454
+ if (isPrefixedObject(value)) {
455
+ if (Array.isArray(value) && syncConfig?.skipMark !== true) {
456
+ callDirectivesForArray(ctx, value, key, isDelete, parent, prop, searchRoot, syncConfig);
457
+ } else {
458
+ callDirectivesForObject(ctx, value, key, isDelete, parent, prop, searchRoot);
459
+ }
460
+ if (!Array.isArray(value)) {
461
+ callDirectivesForLeaf(ctx, value, key, isDelete, parent, prop, searchRoot, syncConfig);
462
+ }
463
+ return;
464
+ }
465
+ callDirectivesForLeaf(ctx, value, key, isDelete, parent, prop, searchRoot, syncConfig);
466
+ }
467
+ function callDirectivesForArray(ctx, value, key, isDelete, parent, prop, searchRoot, syncConfig) {
468
+ const placeholderKey = `${key}.#`;
469
+ const attrMark = ctx.prefix + "mark";
470
+ const query = `[${attrMark}="${placeholderKey}"]`;
471
+ let target = document;
472
+ if (syncConfig?.el.parentElement) {
473
+ target = syncConfig.el.parentElement;
474
+ }
475
+ const elsArrays = [];
476
+ queryAll(ctx, target, query).forEach((plc) => {
477
+ const els = initializeArrayElements(ctx, plc, placeholderKey, value);
478
+ elsArrays.push(els);
479
+ });
480
+ for (const els of elsArrays) {
481
+ for (const i in value) {
482
+ callDirectives(
483
+ ctx,
484
+ value[i],
485
+ getKey(i, key),
486
+ isDelete,
487
+ value,
488
+ i,
489
+ els[i] ?? void 0,
490
+ true
491
+ );
492
+ }
493
+ }
494
+ callDirectivesForLeaf(
495
+ ctx,
496
+ value.length,
497
+ getKey("length", key),
498
+ isDelete,
499
+ value,
500
+ "length",
501
+ searchRoot
502
+ );
503
+ }
504
+ function callDirectivesForObject(ctx, value, key, isDelete, parent, prop, searchRoot) {
505
+ for (const k in value) {
506
+ callDirectives(
507
+ ctx,
508
+ value[k],
509
+ getKey(k, key),
510
+ isDelete,
511
+ value,
512
+ k,
513
+ searchRoot
514
+ );
515
+ }
516
+ }
517
+ function callDirectivesForLeaf(ctx, value, key, isDelete, parent, prop, searchRoot, syncConfig) {
518
+ if (syncConfig) {
519
+ const { el, directive: attrSuffix, skipConditionals, skipMark } = syncConfig;
520
+ if (skipMark && attrSuffix === "mark" || skipConditionals && (attrSuffix === "if" || attrSuffix === "ifnot")) {
521
+ return;
522
+ }
523
+ const entry = ctx.directives.get(attrSuffix);
524
+ if (!entry) return;
525
+ const { cb, isParametric } = entry;
526
+ const param = getParam(el, ctx.prefix + attrSuffix, !!isParametric);
527
+ cb({ el, value, key, isDelete, parent, prop, param });
528
+ return;
529
+ }
530
+ const root = searchRoot ?? document;
531
+ for (const [attrSuffix, { cb, isParametric }] of ctx.directives) {
532
+ const attrName = ctx.prefix + attrSuffix;
533
+ const query = isParametric ? `[${attrName}^='${key}:']` : `[${attrName}='${key}']`;
534
+ queryAll(ctx, root, query).forEach((el) => {
535
+ const param = getParam(el, attrName, !!isParametric);
536
+ cb({ el, value, key, isDelete, parent, prop, param });
537
+ });
538
+ if (root instanceof Element && root.getAttribute(attrName) === key) {
539
+ const param = getParam(root, attrName, !!isParametric);
540
+ cb({ el: root, value, key, isDelete, parent, prop, param });
541
+ }
542
+ }
543
+ }
544
+ function ifOrIfNot(ctx, el, value, key, type) {
545
+ const isShow = type === "if" ? !!value : !value;
546
+ const isTemplate = el instanceof HTMLTemplateElement;
547
+ const attrType = ctx.prefix + type;
548
+ const attrMark = ctx.prefix + "mark";
549
+ if (isShow && isTemplate) {
550
+ const child = el.content.firstElementChild;
551
+ if (!child) return;
552
+ child.setAttribute(attrType, key);
553
+ syncNode(ctx, child, true);
554
+ el.replaceWith(child);
555
+ }
556
+ if (!isShow && !isTemplate) {
557
+ const temp = document.createElement("template");
558
+ temp.content.appendChild(el.cloneNode(true));
559
+ temp.setAttribute(attrType, key);
560
+ const mark = el.getAttribute(attrMark);
561
+ if (mark) temp.setAttribute(attrMark, mark);
562
+ el.replaceWith(temp);
563
+ }
564
+ }
565
+ function syncNode(ctx, el, isSyncRoot) {
566
+ Array.from(el.children).forEach((child) => syncNode(ctx, child, false));
567
+ syncDirectives(ctx, el, isSyncRoot, false);
568
+ }
569
+ function syncClone(ctx, clone2) {
570
+ Array.from(clone2.children).forEach((child) => syncClone(ctx, child));
571
+ syncDirectives(ctx, clone2, false, true);
572
+ }
573
+ function syncDirectives(ctx, el, skipConditionals, skipMark) {
574
+ for (const [name] of ctx.directives) {
575
+ if (skipMark && name === "mark") continue;
576
+ if (skipConditionals && (name === "if" || name === "ifnot")) continue;
577
+ const { isParametric } = ctx.directives.get(name);
578
+ const attrFull = ctx.prefix + name;
579
+ let key = el.getAttribute(attrFull);
580
+ if (isParametric) key = key?.split(":")[0] ?? null;
581
+ if (key?.endsWith(".#")) key = key.slice(0, -2);
582
+ if (key === null) continue;
583
+ const { value, parent, prop } = getValue(ctx, key);
584
+ if (!parent) continue;
585
+ update(ctx, value, key, false, parent, prop, {
586
+ directive: name,
587
+ el,
588
+ ...skipConditionals !== void 0 ? { skipConditionals } : {},
589
+ ...skipMark !== void 0 ? { skipMark } : {}
590
+ });
591
+ }
592
+ }
593
+ function updateArrayItemElement(ctx, key, idx, item, array) {
594
+ const attrMark = ctx.prefix + "mark";
595
+ const arrayItems = document.querySelectorAll(`[${attrMark}="${key}"]`);
596
+ if (arrayItems.length && !isPrefixedObject(item)) {
597
+ return;
598
+ }
599
+ const prefix = array[enPrefix] ?? "";
600
+ const placeholderKey = key.replace(/\d+$/, "#");
601
+ let itemReplaced = false;
602
+ Array.from(arrayItems).forEach((existingEl) => {
603
+ const cl = cloneFromPlaceholder(ctx, existingEl, placeholderKey, attrMark);
604
+ if (!cl) return;
605
+ initializeClone(ctx, idx, prefix, placeholderKey, cl);
606
+ existingEl.replaceWith(cl);
607
+ syncClone(ctx, cl);
608
+ itemReplaced = true;
609
+ });
610
+ if (itemReplaced) return;
611
+ Array.from(document.querySelectorAll(`[${attrMark}="${placeholderKey}"]`)).forEach((template) => {
612
+ if (!(template instanceof HTMLTemplateElement)) return;
613
+ const clone2 = template.content.firstElementChild?.cloneNode(true);
614
+ if (!(clone2 instanceof Element)) return;
615
+ initializeClone(ctx, idx, prefix, placeholderKey, clone2);
616
+ template.before(clone2);
617
+ syncClone(ctx, clone2);
618
+ });
619
+ }
620
+ function cloneFromPlaceholder(ctx, item, placeholderKey, attrMark) {
621
+ let placeholder = item.nextElementSibling;
622
+ while (placeholder) {
623
+ if (placeholder.getAttribute(attrMark) === placeholderKey) break;
624
+ placeholder = placeholder.nextElementSibling;
625
+ }
626
+ if (placeholder instanceof HTMLTemplateElement) {
627
+ const c = placeholder.content.firstElementChild?.cloneNode(true);
628
+ return c instanceof Element ? c : null;
629
+ }
630
+ if (placeholder?.getAttribute(attrMark) === placeholderKey) {
631
+ return placeholder.cloneNode(true);
632
+ }
633
+ return null;
634
+ }
635
+ function sortArrayItemElements(ctx, array) {
636
+ const attrMark = ctx.prefix + "mark";
637
+ const templateKey = getKey("#", array[enPrefix] ?? "");
638
+ Array.from(document.querySelectorAll(`[${attrMark}="${templateKey}"]`)).forEach((template) => {
639
+ const items = [];
640
+ let prev = template.previousElementSibling;
641
+ let isSorted = true;
642
+ let lastIdx = -1;
643
+ while (prev) {
644
+ const curr = prev;
645
+ prev = curr.previousElementSibling;
646
+ const k = curr.getAttribute(attrMark);
647
+ if (!k) continue;
648
+ if (k === templateKey) break;
649
+ if (k.replace(/\d+$/, "#") === templateKey) {
650
+ items.push(curr);
651
+ if (isSorted) {
652
+ const idx = Number(k.slice(k.lastIndexOf(".") + 1) ?? -1);
653
+ if (lastIdx !== -1 && lastIdx !== idx + 1) isSorted = false;
654
+ lastIdx = idx;
655
+ }
656
+ }
657
+ }
658
+ if (isSorted) return;
659
+ const sorted = [...items].sort((a, b) => {
660
+ const am = a.getAttribute(attrMark) ?? "";
661
+ const bm = b.getAttribute(attrMark) ?? "";
662
+ const ai = +(am.split(".").pop() ?? 0);
663
+ const bi = +(bm.split(".").pop() ?? 0);
664
+ return ai - bi;
665
+ });
666
+ sorted.forEach((el) => template.before(el));
667
+ });
668
+ }
669
+ function initializeArrayElements(ctx, plc, placeholderKey, array) {
670
+ const attrMark = ctx.prefix + "mark";
671
+ let prev = plc.previousElementSibling;
672
+ while (prev) {
673
+ const curr = prev;
674
+ prev = curr.previousElementSibling;
675
+ const k = curr.getAttribute(attrMark);
676
+ if (!k) continue;
677
+ if (k !== placeholderKey && k.replace(/\d+$/, "#") === placeholderKey) {
678
+ curr.remove();
679
+ } else {
680
+ break;
681
+ }
682
+ }
683
+ let template;
684
+ let placeholder;
685
+ if (plc instanceof HTMLTemplateElement) {
686
+ template = plc;
687
+ placeholder = plc.content.firstElementChild;
688
+ placeholder?.setAttribute(attrMark, placeholderKey);
689
+ } else {
690
+ placeholder = plc;
691
+ template = document.createElement("template");
692
+ template.content.appendChild(plc.cloneNode(true));
693
+ template.setAttribute(attrMark, placeholderKey);
694
+ plc.replaceWith(template);
695
+ }
696
+ if (!placeholder) {
697
+ console.warn(`[entropy] Empty template for key "${placeholderKey}"`);
698
+ return [];
699
+ }
700
+ const prefix = placeholderKey.slice(0, -2);
701
+ const elements = [];
702
+ for (const idx in array) {
703
+ if (Number.isNaN(+idx)) continue;
704
+ const clone2 = placeholder.cloneNode(true);
705
+ if (!(clone2 instanceof Element)) continue;
706
+ initializeClone(ctx, idx, prefix, placeholderKey, clone2);
707
+ template.before(clone2);
708
+ syncClone(ctx, clone2);
709
+ elements.push(clone2);
710
+ }
711
+ return elements;
712
+ }
713
+ function initializeClone(ctx, idx, prefix, placeholderKey, clone2) {
714
+ const key = getKey(idx, prefix);
715
+ for (const attrSuffix of ctx.directives.keys()) {
716
+ const attrName = ctx.prefix + attrSuffix;
717
+ rewriteKey(clone2, attrName, key, placeholderKey);
718
+ clone2.querySelectorAll(`[${attrName}]`).forEach((child) => {
719
+ rewriteKey(child, attrName, key, placeholderKey);
720
+ });
721
+ }
722
+ }
723
+ function rewriteKey(el, attrName, key, placeholderKey) {
724
+ const current = el.getAttribute(attrName);
725
+ if (current?.startsWith(placeholderKey)) {
726
+ el.setAttribute(attrName, key + current.slice(placeholderKey.length));
727
+ }
728
+ }
729
+ function batch(ctx, fn) {
730
+ ctx.batchQueue = [];
731
+ try {
732
+ fn();
733
+ } finally {
734
+ const queue = ctx.batchQueue ?? [];
735
+ ctx.batchQueue = null;
736
+ for (const task of queue) task();
737
+ }
738
+ }
739
+
740
+ // src/dom/components.ts
741
+ function registerTemplates(rootElement) {
742
+ const root = rootElement ?? document;
743
+ Array.from(root.querySelectorAll("template[name]")).forEach(
744
+ (template) => registerComponent(template)
745
+ );
746
+ }
747
+ function registerComponent(template) {
748
+ const name = template.getAttribute("name")?.toLowerCase();
749
+ if (!name || customElements.get(name)) return;
750
+ const ctor = class extends HTMLElement {
751
+ constructor() {
752
+ super();
753
+ const shadow = this.attachShadow({ mode: "open" });
754
+ Array.from(document.getElementsByTagName("style")).forEach((style) => {
755
+ shadow.appendChild(style.cloneNode(true));
756
+ });
757
+ Array.from(document.querySelectorAll('link[rel="stylesheet"]')).forEach(
758
+ (link) => shadow.appendChild(link.cloneNode(true))
759
+ );
760
+ Array.from(template.content.childNodes).forEach((child) => {
761
+ shadow.appendChild(child.cloneNode(true));
762
+ });
763
+ }
764
+ };
765
+ customElements.define(name, ctor);
766
+ }
767
+ async function loadTemplateFile(file) {
768
+ try {
769
+ const html = await fetch(file).then((r) => {
770
+ if (!r.ok) throw new Error(`HTTP ${r.status}`);
771
+ return r.text();
772
+ });
773
+ const wrapper = document.createElement("div");
774
+ wrapper.innerHTML = html;
775
+ registerTemplates(wrapper);
776
+ } catch (err) {
777
+ console.error(`[entropy] Failed to load template file "${file}":`, err);
778
+ }
779
+ }
780
+ function stitchTagTemplate(strings, ...values) {
781
+ return strings.reduce(
782
+ (acc, s, i) => acc + (values[i - 1] ?? "") + s
783
+ );
784
+ }
785
+ function wrapInDiv(html) {
786
+ const el = document.createElement("div");
787
+ el.innerHTML = html;
788
+ return el;
789
+ }
790
+
791
+ // src/instance.ts
792
+ var EntropyInstance = class {
793
+ constructor() {
794
+ this.readyStateHandler = null;
795
+ // ─── computed ──────────────────────────────────────────────────────────────
796
+ /**
797
+ * Marks a function as a computed value so entropy calls it reactively.
798
+ *
799
+ * @example
800
+ * ```ts
801
+ * data.fullName = en.computed(() => `${data.first} ${data.last}`);
802
+ * ```
803
+ */
804
+ this.computed = computed;
805
+ this.ctx = createContext();
806
+ bootstrapDirectives(this.ctx);
807
+ }
808
+ // ─── init ──────────────────────────────────────────────────────────────────
809
+ /**
810
+ * Initialises the reactive data object and begins listening for DOM changes.
811
+ * Returns the reactive data proxy – assign properties directly on it.
812
+ *
813
+ * @example
814
+ * ```ts
815
+ * const data = en.init();
816
+ * data.count = 0;
817
+ * ```
818
+ */
819
+ init() {
820
+ if (!this.ctx.data) {
821
+ this.ctx.data = reactive(this.ctx, {}, "");
822
+ }
823
+ registerTemplates();
824
+ setupObserver(this.ctx);
825
+ if (!this.readyStateHandler) {
826
+ this.readyStateHandler = () => {
827
+ if (document.readyState === "interactive") {
828
+ registerTemplates();
829
+ }
830
+ };
831
+ document.addEventListener("readystatechange", this.readyStateHandler);
832
+ }
833
+ return this.ctx.data;
834
+ }
835
+ // ─── watch ─────────────────────────────────────────────────────────────────
836
+ /**
837
+ * Registers a watcher for `key`. Called with the new value whenever `key`
838
+ * or any of its children changes.
839
+ *
840
+ * @example
841
+ * ```ts
842
+ * en.watch('user.name', newName => console.log('Name changed:', newName));
843
+ * ```
844
+ */
845
+ watch(key, watcher) {
846
+ watch(this.ctx, key, watcher);
847
+ }
848
+ /**
849
+ * Removes watchers. See parameter combinations in the source for details.
850
+ */
851
+ unwatch(key, watcher) {
852
+ unwatch(this.ctx, key, watcher);
853
+ }
854
+ // ─── directive ─────────────────────────────────────────────────────────────
855
+ /**
856
+ * Registers a custom directive.
857
+ *
858
+ * @example
859
+ * ```ts
860
+ * en.directive('color', ({ el, value }) => {
861
+ * (el as HTMLElement).style.color = String(value);
862
+ * });
863
+ * ```
864
+ */
865
+ directive(name, cb, isParametric = false) {
866
+ registerDirective(this.ctx, name, cb, isParametric);
867
+ }
868
+ // ─── prefix ────────────────────────────────────────────────────────────────
869
+ /**
870
+ * Overrides the default attribute prefix (`en-`).
871
+ * Must be called before `init()`.
872
+ *
873
+ * @example
874
+ * ```ts
875
+ * en.prefix('data-en');
876
+ * const data = en.init();
877
+ * // now use data-en-mark="..." in your HTML
878
+ * ```
879
+ */
880
+ prefix(value = "en") {
881
+ this.ctx.prefix = value.endsWith("-") ? value : value + "-";
882
+ }
883
+ // ─── batch ─────────────────────────────────────────────────────────────────
884
+ /**
885
+ * Batches multiple reactive changes into a single DOM update pass.
886
+ *
887
+ * @example
888
+ * ```ts
889
+ * en.batch(() => {
890
+ * data.firstName = 'Jane';
891
+ * data.lastName = 'Doe';
892
+ * });
893
+ * ```
894
+ */
895
+ batch(fn) {
896
+ batch(this.ctx, fn);
897
+ }
898
+ // ─── load ──────────────────────────────────────────────────────────────────
899
+ /**
900
+ * Fetches one or more HTML files and registers any `<template name="...">`
901
+ * elements found inside them as custom components.
902
+ *
903
+ * @example
904
+ * ```ts
905
+ * await en.load(['components/card.html', 'components/modal.html']);
906
+ * ```
907
+ */
908
+ async load(files) {
909
+ const list = Array.isArray(files) ? files : [files];
910
+ await Promise.all(list.map((f) => loadTemplateFile(f)));
911
+ }
912
+ register(root, ...args) {
913
+ if (Array.isArray(root)) {
914
+ root = stitchTagTemplate(root, ...args);
915
+ }
916
+ if (typeof root === "string") {
917
+ root = wrapInDiv(root);
918
+ }
919
+ registerTemplates(root);
920
+ }
921
+ // ─── destroy ───────────────────────────────────────────────────────────────
922
+ /**
923
+ * Tears down the instance: removes event listeners, clears watchers,
924
+ * and prevents further DOM updates.
925
+ */
926
+ destroy() {
927
+ this.ctx.destroyed = true;
928
+ if (this.readyStateHandler) {
929
+ document.removeEventListener(
930
+ "readystatechange",
931
+ this.readyStateHandler
932
+ );
933
+ this.readyStateHandler = null;
934
+ }
935
+ this.ctx.watchers.clear();
936
+ this.ctx.deps.map.clear();
937
+ this.ctx.deps.versions.clear();
938
+ this.ctx.elementCache.clear();
939
+ this.ctx.observer?.disconnect();
940
+ this.ctx.observer = null;
941
+ this.ctx.data = null;
942
+ }
943
+ };
944
+ function createInstance() {
945
+ return new EntropyInstance();
946
+ }
947
+
948
+ // src/index.ts
949
+ var en = new EntropyInstance();
950
+ var index_default = en;
951
+
952
+ export { EntropyInstance, computed, createInstance, index_default as default };
953
+ //# sourceMappingURL=index.js.map
954
+ //# sourceMappingURL=index.js.map