thunderous 2.3.13 → 2.4.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/dist/index.cjs +196 -81
- package/dist/index.d.cts +5 -1
- package/dist/index.d.ts +5 -1
- package/dist/index.js +1155 -5
- package/package.json +4 -5
package/dist/index.cjs
CHANGED
|
@@ -49,13 +49,14 @@ var DEFAULT_RENDER_OPTIONS = {
|
|
|
49
49
|
};
|
|
50
50
|
|
|
51
51
|
// src/signals.ts
|
|
52
|
-
var
|
|
52
|
+
var sym = null;
|
|
53
|
+
var effects = /* @__PURE__ */ new WeakMap();
|
|
53
54
|
var createSignal = (initVal, options) => {
|
|
54
55
|
const subscribers = /* @__PURE__ */ new Set();
|
|
55
56
|
let value = initVal;
|
|
56
57
|
const getter = (getterOptions) => {
|
|
57
|
-
if (
|
|
58
|
-
subscribers.add(
|
|
58
|
+
if (sym !== null) {
|
|
59
|
+
subscribers.add(sym);
|
|
59
60
|
}
|
|
60
61
|
if (options?.debugMode === true || getterOptions?.debugMode === true) {
|
|
61
62
|
let label = "anonymous signal";
|
|
@@ -67,7 +68,11 @@ var createSignal = (initVal, options) => {
|
|
|
67
68
|
} else if (getterOptions?.label !== void 0) {
|
|
68
69
|
label = getterOptions.label;
|
|
69
70
|
}
|
|
70
|
-
console.log("Signal retrieved:", {
|
|
71
|
+
console.log("Signal retrieved:", {
|
|
72
|
+
value,
|
|
73
|
+
subscribers: Array.from(subscribers).map((sym2) => effects.get(sym2)),
|
|
74
|
+
label
|
|
75
|
+
});
|
|
71
76
|
}
|
|
72
77
|
return value;
|
|
73
78
|
};
|
|
@@ -84,15 +89,29 @@ var createSignal = (initVal, options) => {
|
|
|
84
89
|
const isObject = typeof newValue === "object" && newValue !== null;
|
|
85
90
|
if (!isObject && value === newValue) return;
|
|
86
91
|
if (isObject && typeof value === "object" && value !== null) {
|
|
87
|
-
|
|
92
|
+
const isPlainObject = (obj) => typeof obj === "object" && obj !== null && Object.getPrototypeOf(obj) === Object.prototype;
|
|
93
|
+
if (isPlainObject(value) && isPlainObject(newValue)) {
|
|
94
|
+
if (JSON.stringify(value) === JSON.stringify(newValue)) return;
|
|
95
|
+
}
|
|
88
96
|
}
|
|
89
97
|
const oldValue = value;
|
|
90
98
|
value = newValue;
|
|
91
|
-
for (const
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
99
|
+
for (const sym2 of subscribers) {
|
|
100
|
+
const effectRef = effects.get(sym2);
|
|
101
|
+
if (effectRef !== void 0) {
|
|
102
|
+
try {
|
|
103
|
+
effectRef.fn({
|
|
104
|
+
lastValue: effectRef.value,
|
|
105
|
+
destroy: () => {
|
|
106
|
+
effects.delete(sym2);
|
|
107
|
+
queueMicrotask(() => subscribers.delete(sym2));
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
} catch (error) {
|
|
111
|
+
console.error("Error in subscriber:", { error, oldValue, newValue, fn: effectRef.fn });
|
|
112
|
+
}
|
|
113
|
+
} else {
|
|
114
|
+
queueMicrotask(() => subscribers.delete(sym2));
|
|
96
115
|
}
|
|
97
116
|
}
|
|
98
117
|
if (options?.debugMode === true || setterOptions?.debugMode === true) {
|
|
@@ -105,7 +124,12 @@ var createSignal = (initVal, options) => {
|
|
|
105
124
|
} else if (setterOptions?.label !== void 0) {
|
|
106
125
|
label = setterOptions.label;
|
|
107
126
|
}
|
|
108
|
-
console.log("Signal set:", {
|
|
127
|
+
console.log("Signal set:", {
|
|
128
|
+
oldValue,
|
|
129
|
+
newValue,
|
|
130
|
+
subscribers: Array.from(subscribers).map((sym2) => effects.get(sym2)),
|
|
131
|
+
label
|
|
132
|
+
});
|
|
109
133
|
}
|
|
110
134
|
};
|
|
111
135
|
return [getter, setter];
|
|
@@ -121,21 +145,33 @@ var derived = (fn, options) => {
|
|
|
121
145
|
});
|
|
122
146
|
return getter;
|
|
123
147
|
};
|
|
124
|
-
var createEffect = (fn) => {
|
|
125
|
-
|
|
148
|
+
var createEffect = (fn, value) => {
|
|
149
|
+
const privateSym = sym = Symbol();
|
|
150
|
+
effects.set(sym, { fn, value });
|
|
126
151
|
try {
|
|
127
|
-
fn(
|
|
152
|
+
fn({
|
|
153
|
+
lastValue: value,
|
|
154
|
+
destroy: () => {
|
|
155
|
+
effects.delete(privateSym);
|
|
156
|
+
}
|
|
157
|
+
});
|
|
128
158
|
} catch (error) {
|
|
129
159
|
console.error("Error in effect:", { error, fn });
|
|
130
160
|
}
|
|
131
|
-
|
|
161
|
+
sym = null;
|
|
132
162
|
};
|
|
133
163
|
|
|
134
164
|
// src/utilities.ts
|
|
135
165
|
var NOOP = () => void 0;
|
|
136
166
|
var queryComment = (node, comment) => {
|
|
137
|
-
|
|
138
|
-
|
|
167
|
+
const walker = document.createTreeWalker(node, NodeFilter.SHOW_COMMENT, {
|
|
168
|
+
acceptNode: (n) => n.nodeValue === comment ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_SKIP
|
|
169
|
+
});
|
|
170
|
+
return walker.nextNode();
|
|
171
|
+
};
|
|
172
|
+
var queryChildren = (children, selector) => {
|
|
173
|
+
for (const child of children) {
|
|
174
|
+
if (child instanceof Element && child.matches(selector)) {
|
|
139
175
|
return child;
|
|
140
176
|
}
|
|
141
177
|
}
|
|
@@ -291,6 +327,7 @@ var renderState = {
|
|
|
291
327
|
signalMap: /* @__PURE__ */ new Map(),
|
|
292
328
|
callbackMap: /* @__PURE__ */ new Map(),
|
|
293
329
|
fragmentMap: /* @__PURE__ */ new Map(),
|
|
330
|
+
childrenMap: /* @__PURE__ */ new Map(),
|
|
294
331
|
propertyMap: /* @__PURE__ */ new Map(),
|
|
295
332
|
registry: typeof customElements !== "undefined" ? customElements : {}
|
|
296
333
|
};
|
|
@@ -301,21 +338,27 @@ var logPropertyWarning = (propName, element) => {
|
|
|
301
338
|
"\n\nThunderous will attempt to set the property anyway, but this may result in unexpected behavior. Please make sure the property exists on the element prior to setting it."
|
|
302
339
|
);
|
|
303
340
|
};
|
|
304
|
-
var
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
341
|
+
var asNodeList = (value, parent) => {
|
|
342
|
+
if (typeof value === "string") return [new Text(value)];
|
|
343
|
+
if (value instanceof DocumentFragment) return [...value.children];
|
|
344
|
+
if (Array.isArray(value)) {
|
|
345
|
+
const nodeList = [];
|
|
346
|
+
let count = 0;
|
|
347
|
+
const keys = /* @__PURE__ */ new Set();
|
|
348
|
+
for (const item of value) {
|
|
349
|
+
const cachedItem = item instanceof DocumentFragment ? renderState.childrenMap.get(item) : void 0;
|
|
350
|
+
const children = cachedItem ?? asNodeList(item, parent);
|
|
351
|
+
if (cachedItem === void 0 && item instanceof DocumentFragment) {
|
|
352
|
+
renderState.childrenMap.set(item, children);
|
|
353
|
+
}
|
|
354
|
+
if (children.length > 1) {
|
|
313
355
|
console.error(
|
|
314
356
|
"When rendering arrays, fragments must contain only one top-level element at a time. Error occured in:",
|
|
315
357
|
parent
|
|
316
358
|
);
|
|
317
359
|
}
|
|
318
|
-
|
|
360
|
+
const child = children[0];
|
|
361
|
+
if (child === null || !(child instanceof Element)) continue;
|
|
319
362
|
let key = child.getAttribute("key");
|
|
320
363
|
if (key === null) {
|
|
321
364
|
console.warn(
|
|
@@ -333,18 +376,11 @@ var arrayToDocumentFragment = (array, parent, uniqueKey) => {
|
|
|
333
376
|
}
|
|
334
377
|
keys.add(key);
|
|
335
378
|
count++;
|
|
379
|
+
nodeList.push(...children);
|
|
336
380
|
}
|
|
337
|
-
|
|
381
|
+
return nodeList;
|
|
338
382
|
}
|
|
339
|
-
|
|
340
|
-
documentFragment.append(comment);
|
|
341
|
-
return documentFragment;
|
|
342
|
-
};
|
|
343
|
-
var createNewNode = (value, parent, uniqueKey) => {
|
|
344
|
-
if (typeof value === "string") return new Text(value);
|
|
345
|
-
if (Array.isArray(value)) return arrayToDocumentFragment(value, parent, uniqueKey);
|
|
346
|
-
if (value instanceof DocumentFragment) return value;
|
|
347
|
-
return new Text("");
|
|
383
|
+
return [new Text()];
|
|
348
384
|
};
|
|
349
385
|
var processValue = (value) => {
|
|
350
386
|
if (!isServer && value instanceof DocumentFragment) {
|
|
@@ -370,62 +406,139 @@ var processValue = (value) => {
|
|
|
370
406
|
return String(value);
|
|
371
407
|
};
|
|
372
408
|
var evaluateBindings = (element, fragment) => {
|
|
373
|
-
for (const child of element.childNodes) {
|
|
409
|
+
for (const child of [...element.childNodes]) {
|
|
374
410
|
if (child instanceof Text && SIGNAL_BINDING_REGEX.test(child.data)) {
|
|
375
411
|
const textList = child.data.split(SIGNAL_BINDING_REGEX);
|
|
376
|
-
const
|
|
412
|
+
const nextSibling = child.nextSibling;
|
|
413
|
+
const prevSibling = child.previousSibling;
|
|
377
414
|
textList.forEach((text, i) => {
|
|
378
|
-
const uniqueKey = text.replace(/\{\{signal:(.+)\}\}/, "$1");
|
|
379
|
-
const signal = uniqueKey !==
|
|
415
|
+
const uniqueKey = SIGNAL_BINDING_REGEX.test(text) ? text.replace(/\{\{signal:(.+)\}\}/, "$1") : void 0;
|
|
416
|
+
const signal = uniqueKey !== void 0 ? renderState.signalMap.get(uniqueKey) : void 0;
|
|
380
417
|
const newValue = signal !== void 0 ? signal() : text;
|
|
381
|
-
const
|
|
418
|
+
const initialChildren = asNodeList(newValue, element);
|
|
382
419
|
if (i === 0) {
|
|
383
|
-
child.replaceWith(
|
|
420
|
+
child.replaceWith(...initialChildren);
|
|
384
421
|
} else {
|
|
385
|
-
element
|
|
422
|
+
const endAnchor2 = queryComment(element, `${uniqueKey}:end`) ?? nextSibling;
|
|
423
|
+
if (endAnchor2 !== null) {
|
|
424
|
+
endAnchor2.before(...initialChildren);
|
|
425
|
+
} else {
|
|
426
|
+
element.append(...initialChildren);
|
|
427
|
+
}
|
|
386
428
|
}
|
|
387
|
-
if (
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
} else
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
429
|
+
if (uniqueKey === void 0) return;
|
|
430
|
+
const startAnchor = document.createComment(`${uniqueKey}:start`);
|
|
431
|
+
if (prevSibling !== null) {
|
|
432
|
+
prevSibling.after(startAnchor);
|
|
433
|
+
} else {
|
|
434
|
+
element.prepend(startAnchor);
|
|
435
|
+
}
|
|
436
|
+
const endAnchor = document.createComment(`${uniqueKey}:end`);
|
|
437
|
+
if (nextSibling !== null) {
|
|
438
|
+
nextSibling.before(endAnchor);
|
|
439
|
+
} else {
|
|
440
|
+
element.append(endAnchor);
|
|
441
|
+
}
|
|
442
|
+
const bindText = (node, signal2) => {
|
|
443
|
+
createEffect(({ destroy }) => {
|
|
444
|
+
const result = signal2();
|
|
445
|
+
if (Array.isArray(result)) {
|
|
446
|
+
destroy();
|
|
447
|
+
bindArray(signal2);
|
|
448
|
+
return;
|
|
402
449
|
}
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
if (init && matchingNode === null) {
|
|
408
|
-
child2.remove();
|
|
409
|
-
}
|
|
450
|
+
if (result instanceof DocumentFragment) {
|
|
451
|
+
destroy();
|
|
452
|
+
bindFragment(signal2);
|
|
453
|
+
return;
|
|
410
454
|
}
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
455
|
+
node.data = result === null ? "" : String(result);
|
|
456
|
+
});
|
|
457
|
+
};
|
|
458
|
+
const bindArray = (signal2) => {
|
|
459
|
+
createEffect(
|
|
460
|
+
({ lastValue: oldChildren, destroy }) => {
|
|
461
|
+
const result = signal2();
|
|
462
|
+
console.trace("Binding array:", {
|
|
463
|
+
result: result.map((node) => node.cloneNode(true)),
|
|
464
|
+
oldChildren
|
|
465
|
+
});
|
|
466
|
+
const newChildren = asNodeList(result, element);
|
|
467
|
+
const firstChild = newChildren[0];
|
|
468
|
+
if (!Array.isArray(result) && newChildren.length === 1 && firstChild instanceof DocumentFragment) {
|
|
469
|
+
destroy();
|
|
470
|
+
bindFragment(signal2);
|
|
471
|
+
return;
|
|
472
|
+
}
|
|
473
|
+
if (newChildren.length === 1 && firstChild instanceof Text) {
|
|
474
|
+
destroy();
|
|
475
|
+
bindText(firstChild, signal2);
|
|
476
|
+
return;
|
|
419
477
|
}
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
478
|
+
while (startAnchor.nextSibling !== endAnchor) {
|
|
479
|
+
startAnchor.nextSibling?.remove();
|
|
480
|
+
}
|
|
481
|
+
startAnchor.after(...newChildren);
|
|
482
|
+
if (oldChildren === null) return newChildren;
|
|
483
|
+
for (const persistedChild of oldChildren) {
|
|
484
|
+
if (persistedChild instanceof Element) {
|
|
485
|
+
const key = persistedChild.getAttribute("key");
|
|
486
|
+
if (key === null) continue;
|
|
487
|
+
const newChild = queryChildren(newChildren, `[key="${key}"]`);
|
|
488
|
+
if (newChild === null) {
|
|
489
|
+
persistedChild.remove();
|
|
490
|
+
continue;
|
|
491
|
+
}
|
|
492
|
+
for (const attr of [...persistedChild.attributes]) {
|
|
493
|
+
if (!newChild.hasAttribute(attr.name)) persistedChild.removeAttribute(attr.name);
|
|
494
|
+
}
|
|
495
|
+
for (const newAttr of [...newChild.attributes]) {
|
|
496
|
+
const oldAttrValue = persistedChild.getAttribute(newAttr.name);
|
|
497
|
+
if (oldAttrValue?.startsWith("this.__customCallbackFns")) continue;
|
|
498
|
+
persistedChild.setAttribute(newAttr.name, newAttr.value);
|
|
499
|
+
}
|
|
500
|
+
newChild.replaceWith(persistedChild);
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
return newChildren;
|
|
504
|
+
},
|
|
505
|
+
null
|
|
506
|
+
);
|
|
507
|
+
};
|
|
508
|
+
const bindFragment = (signal2) => {
|
|
509
|
+
const initialFragment = signal2();
|
|
510
|
+
renderState.childrenMap.set(initialFragment, [...initialFragment.childNodes]);
|
|
511
|
+
createEffect(({ destroy }) => {
|
|
512
|
+
const result = signal2();
|
|
513
|
+
const cachedChildren = renderState.childrenMap.get(initialFragment);
|
|
514
|
+
const children = cachedChildren ?? asNodeList(result, element);
|
|
515
|
+
if (Array.isArray(result)) {
|
|
516
|
+
destroy();
|
|
517
|
+
bindArray(signal2);
|
|
518
|
+
return;
|
|
519
|
+
}
|
|
520
|
+
if (result instanceof Text) {
|
|
521
|
+
const children2 = asNodeList(result, element);
|
|
522
|
+
const text2 = children2[0];
|
|
523
|
+
destroy();
|
|
524
|
+
bindText(text2, signal2);
|
|
525
|
+
return;
|
|
526
|
+
}
|
|
527
|
+
while (startAnchor.nextSibling !== endAnchor) {
|
|
528
|
+
startAnchor.nextSibling?.remove();
|
|
423
529
|
}
|
|
424
|
-
|
|
425
|
-
nextAnchor?.remove();
|
|
426
|
-
element.insertBefore(nextNode, anchor);
|
|
427
|
-
if (!init) init = true;
|
|
530
|
+
startAnchor.after(...children);
|
|
428
531
|
});
|
|
532
|
+
};
|
|
533
|
+
if (signal !== void 0) {
|
|
534
|
+
if (Array.isArray(newValue)) {
|
|
535
|
+
bindArray(signal);
|
|
536
|
+
} else if (initialChildren instanceof DocumentFragment) {
|
|
537
|
+
bindFragment(signal);
|
|
538
|
+
} else {
|
|
539
|
+
const initialChild = initialChildren[0];
|
|
540
|
+
bindText(initialChild, signal);
|
|
541
|
+
}
|
|
429
542
|
}
|
|
430
543
|
});
|
|
431
544
|
}
|
|
@@ -510,6 +623,7 @@ var evaluateBindings = (element, fragment) => {
|
|
|
510
623
|
);
|
|
511
624
|
return;
|
|
512
625
|
}
|
|
626
|
+
if (!(propName in child)) logPropertyWarning(propName, child);
|
|
513
627
|
child[propName] = child.__customCallbackFns.get(uniqueKey);
|
|
514
628
|
}
|
|
515
629
|
});
|
|
@@ -524,6 +638,7 @@ var evaluateBindings = (element, fragment) => {
|
|
|
524
638
|
);
|
|
525
639
|
return;
|
|
526
640
|
}
|
|
641
|
+
if (!(propName in child)) logPropertyWarning(propName, child);
|
|
527
642
|
child[propName] = attr.value;
|
|
528
643
|
}
|
|
529
644
|
}
|
package/dist/index.d.cts
CHANGED
|
@@ -112,6 +112,10 @@ type AnyFn = (...args: any[]) => any;
|
|
|
112
112
|
|
|
113
113
|
type HTMLCustomElement<T extends Record<PropertyKey, unknown>> = Omit<HTMLElement, keyof T> & T;
|
|
114
114
|
|
|
115
|
+
// Again, flexible typing is necessary to support these generics
|
|
116
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
117
|
+
type Effect<T = any> = (args: { lastValue: T; destroy: () => void }) => T | void;
|
|
118
|
+
|
|
115
119
|
/**
|
|
116
120
|
* Create a custom element that can be defined for use in the DOM.
|
|
117
121
|
* @example
|
|
@@ -174,7 +178,7 @@ declare const derived: <T>(fn: () => T, options?: SignalOptions) => SignalGetter
|
|
|
174
178
|
* });
|
|
175
179
|
* ```
|
|
176
180
|
*/
|
|
177
|
-
declare const createEffect: (fn:
|
|
181
|
+
declare const createEffect: <T = unknown>(fn: Effect<T>, value?: T) => void;
|
|
178
182
|
|
|
179
183
|
/**
|
|
180
184
|
* A tagged template function for creating DocumentFragment instances.
|
package/dist/index.d.ts
CHANGED
|
@@ -112,6 +112,10 @@ type AnyFn = (...args: any[]) => any;
|
|
|
112
112
|
|
|
113
113
|
type HTMLCustomElement<T extends Record<PropertyKey, unknown>> = Omit<HTMLElement, keyof T> & T;
|
|
114
114
|
|
|
115
|
+
// Again, flexible typing is necessary to support these generics
|
|
116
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
117
|
+
type Effect<T = any> = (args: { lastValue: T; destroy: () => void }) => T | void;
|
|
118
|
+
|
|
115
119
|
/**
|
|
116
120
|
* Create a custom element that can be defined for use in the DOM.
|
|
117
121
|
* @example
|
|
@@ -174,7 +178,7 @@ declare const derived: <T>(fn: () => T, options?: SignalOptions) => SignalGetter
|
|
|
174
178
|
* });
|
|
175
179
|
* ```
|
|
176
180
|
*/
|
|
177
|
-
declare const createEffect: (fn:
|
|
181
|
+
declare const createEffect: <T = unknown>(fn: Effect<T>, value?: T) => void;
|
|
178
182
|
|
|
179
183
|
/**
|
|
180
184
|
* A tagged template function for creating DocumentFragment instances.
|