thunderous 2.0.1 → 2.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -35,9 +35,9 @@ const myStyleSheet = css`
35
35
  }
36
36
  `;
37
37
 
38
- const MyElement = customElement(({ customCallback, refs, adoptStyleSheet }) => {
38
+ const MyElement = customElement(({ refs, adoptStyleSheet }) => {
39
39
  const [count, setCount] = createSignal(0);
40
- const increment = customCallback(() => setCount(count() + 1));
40
+ const increment = () => setCount(count() + 1);
41
41
  adoptStyleSheet(myStyleSheet);
42
42
  return html`
43
43
  <button onclick="${increment}">Increment</button>
package/dist/index.cjs CHANGED
@@ -271,19 +271,15 @@ var clientOnlyCallback = (fn) => {
271
271
  };
272
272
 
273
273
  // src/render.ts
274
+ var CALLBACK_BINDING_REGEX = /(\{\{callback:.+\}\})/;
275
+ var LEGACY_CALLBACK_BINDING_REGEX = /(this.getRootNode\(\).host.__customCallbackFns.get\('.+'\)\(event\))/;
276
+ var SIGNAL_BINDING_REGEX = /(\{\{signal:.+\}\})/;
277
+ var FRAGMENT_ATTRIBUTE = "___thunderous-fragment";
274
278
  var renderState = {
275
- currentShadowRoot: null
276
- };
277
- var clearHTML = (element) => {
278
- while (element.childNodes.length > 0) {
279
- element.childNodes[0].remove();
280
- }
281
- };
282
- var parseFragment = (htmlStr) => {
283
- const template = document.createElement("template");
284
- template.innerHTML = htmlStr;
285
- const fragment = renderState.currentShadowRoot === null ? template.content : renderState.currentShadowRoot.importNode?.(template.content, true) ?? template.content;
286
- return fragment;
279
+ currentShadowRoot: null,
280
+ signalMap: /* @__PURE__ */ new Map(),
281
+ callbackMap: /* @__PURE__ */ new Map(),
282
+ fragmentMap: /* @__PURE__ */ new Map()
287
283
  };
288
284
  var logValueError = (value) => {
289
285
  console.error(
@@ -296,7 +292,7 @@ var arrayToDocumentFragment = (array, parent) => {
296
292
  let count = 0;
297
293
  const keys = /* @__PURE__ */ new Set();
298
294
  for (const item of array) {
299
- const node = getNewNode(item, parent).cloneNode(true);
295
+ const node = createNewNode(item, parent);
300
296
  if (node instanceof DocumentFragment) {
301
297
  const child = node.firstElementChild;
302
298
  if (node.children.length > 1) {
@@ -328,150 +324,166 @@ var arrayToDocumentFragment = (array, parent) => {
328
324
  }
329
325
  return documentFragment;
330
326
  };
331
- var getNewNode = (value, parent) => {
327
+ var createNewNode = (value, parent) => {
332
328
  if (typeof value === "string") return new Text(value);
333
329
  if (Array.isArray(value)) return arrayToDocumentFragment(value, parent);
334
330
  if (value instanceof DocumentFragment) return value;
335
331
  return new Text("");
336
332
  };
337
- var html = (strings, ...values) => {
338
- let innerHTML = "";
339
- const signalMap = /* @__PURE__ */ new Map();
340
- const callbackMap = /* @__PURE__ */ new Map();
341
- const processValue = (value) => {
342
- if (!isServer && value instanceof DocumentFragment) {
343
- const tempDiv = document.createElement("div");
344
- tempDiv.append(value.cloneNode(true));
345
- return tempDiv.innerHTML;
333
+ var processValue = (value) => {
334
+ if (!isServer && value instanceof DocumentFragment) {
335
+ const uniqueKey = crypto.randomUUID();
336
+ renderState.fragmentMap.set(uniqueKey, value);
337
+ return `<div ${FRAGMENT_ATTRIBUTE}="${uniqueKey}"></div>`;
338
+ }
339
+ if (typeof value === "function" && "getter" in value && value.getter === true) {
340
+ const getter = value;
341
+ const uniqueKey = crypto.randomUUID();
342
+ renderState.signalMap.set(uniqueKey, getter);
343
+ let result = getter();
344
+ if (Array.isArray(result)) {
345
+ result = result.map((item) => processValue(item)).join("");
346
+ }
347
+ return isServer ? String(result) : `{{signal:${uniqueKey}}}`;
348
+ }
349
+ if (typeof value === "function") {
350
+ const uniqueKey = crypto.randomUUID();
351
+ renderState.callbackMap.set(uniqueKey, value);
352
+ return isServer ? String(value()) : `{{callback:${uniqueKey}}}`;
353
+ }
354
+ if (typeof value === "object" && value !== null) {
355
+ logValueError(value);
356
+ return "";
357
+ }
358
+ return String(value);
359
+ };
360
+ var evaluateBindings = (element, fragment) => {
361
+ for (const child of element.childNodes) {
362
+ if (child instanceof Text && SIGNAL_BINDING_REGEX.test(child.data)) {
363
+ const textList = child.data.split(SIGNAL_BINDING_REGEX);
364
+ const sibling = child.nextSibling;
365
+ textList.forEach((text, i) => {
366
+ const uniqueKey = text.replace(/\{\{signal:(.+)\}\}/, "$1");
367
+ const signal = uniqueKey !== text ? renderState.signalMap.get(uniqueKey) : void 0;
368
+ const newValue = signal !== void 0 ? signal() : text;
369
+ const newNode = createNewNode(newValue, element);
370
+ if (i === 0) {
371
+ child.replaceWith(newNode);
372
+ } else {
373
+ element.insertBefore(newNode, sibling);
374
+ }
375
+ if (signal !== void 0 && newNode instanceof Text) {
376
+ createEffect(() => {
377
+ newNode.data = signal();
378
+ });
379
+ } else if (signal !== void 0 && newNode instanceof DocumentFragment) {
380
+ let init = false;
381
+ createEffect(() => {
382
+ const result = signal();
383
+ const nextNode = createNewNode(result, element);
384
+ if (nextNode instanceof Text) {
385
+ throw new TypeError(
386
+ "Signal mismatch: expected DocumentFragment or Array<DocumentFragment>, but got Text"
387
+ );
388
+ }
389
+ let lastSibling = element.lastChild;
390
+ for (const child2 of element.children) {
391
+ const key = child2.getAttribute("key");
392
+ if (key === null) continue;
393
+ const matchingNode = nextNode.querySelector(`[key="${key}"]`);
394
+ if (init && matchingNode === null) {
395
+ child2.remove();
396
+ }
397
+ }
398
+ for (const child2 of nextNode.children) {
399
+ const key = child2.getAttribute("key");
400
+ const matchingNode = element.querySelector(`[key="${key}"]`);
401
+ if (matchingNode === null) continue;
402
+ matchingNode.__customCallbackFns = child2.__customCallbackFns;
403
+ for (const attr of child2.attributes) {
404
+ matchingNode.setAttribute(attr.name, attr.value);
405
+ }
406
+ matchingNode.replaceChildren(...child2.childNodes);
407
+ lastSibling = matchingNode.nextSibling;
408
+ child2.replaceWith(matchingNode);
409
+ }
410
+ element.insertBefore(nextNode, lastSibling);
411
+ if (!init) init = true;
412
+ });
413
+ }
414
+ });
346
415
  }
347
- if (typeof value === "function" && "getter" in value && value.getter === true) {
348
- const getter = value;
349
- const uniqueKey = crypto.randomUUID();
350
- signalMap.set(uniqueKey, getter);
351
- let result = getter();
352
- if (Array.isArray(result)) {
353
- result = result.map((item) => processValue(item)).join("");
416
+ if (child instanceof Element && child.hasAttribute(FRAGMENT_ATTRIBUTE)) {
417
+ const uniqueKey = child.getAttribute(FRAGMENT_ATTRIBUTE);
418
+ const childFragment = renderState.fragmentMap.get(uniqueKey);
419
+ if (childFragment !== void 0) {
420
+ child.replaceWith(childFragment);
421
+ }
422
+ } else if (child instanceof Element) {
423
+ for (const attr of child.attributes) {
424
+ if (SIGNAL_BINDING_REGEX.test(attr.value)) {
425
+ const textList = attr.value.split(SIGNAL_BINDING_REGEX);
426
+ createEffect(() => {
427
+ let newText = "";
428
+ let hasNull = false;
429
+ for (const text of textList) {
430
+ const uniqueKey = text.replace(/\{\{signal:(.+)\}\}/, "$1");
431
+ const signal = uniqueKey !== text ? renderState.signalMap.get(uniqueKey) : void 0;
432
+ const value = signal !== void 0 ? signal() : text;
433
+ if (value === null) hasNull = true;
434
+ newText += String(value);
435
+ }
436
+ if (hasNull && newText === "null") {
437
+ child.removeAttribute(attr.name);
438
+ } else {
439
+ child.setAttribute(attr.name, newText);
440
+ }
441
+ });
442
+ } else if (LEGACY_CALLBACK_BINDING_REGEX.test(attr.value)) {
443
+ const getRootNode = child.getRootNode.bind(child);
444
+ child.getRootNode = () => {
445
+ const rootNode = getRootNode();
446
+ return rootNode instanceof ShadowRoot ? rootNode : fragment;
447
+ };
448
+ } else if (CALLBACK_BINDING_REGEX.test(attr.value)) {
449
+ const textList = attr.value.split(CALLBACK_BINDING_REGEX);
450
+ createEffect(() => {
451
+ child.__customCallbackFns = child.__customCallbackFns ?? /* @__PURE__ */ new Map();
452
+ let uniqueKey = "";
453
+ for (const text of textList) {
454
+ const _uniqueKey = text.replace(/\{\{callback:(.+)\}\}/, "$1");
455
+ if (_uniqueKey !== text) uniqueKey = _uniqueKey;
456
+ const callback = uniqueKey !== text ? renderState.callbackMap.get(uniqueKey) : void 0;
457
+ if (callback !== void 0) {
458
+ child.__customCallbackFns.set(uniqueKey, callback);
459
+ }
460
+ }
461
+ if (uniqueKey !== "") {
462
+ child.setAttribute(attr.name, `this.__customCallbackFns.get('${uniqueKey}')(event)`);
463
+ }
464
+ });
465
+ }
354
466
  }
355
- return isServer ? String(result) : `{{signal:${uniqueKey}}}`;
356
- }
357
- if (typeof value === "function") {
358
- const uniqueKey = crypto.randomUUID();
359
- callbackMap.set(uniqueKey, value);
360
- return isServer ? String(value()) : `{{callback:${uniqueKey}}}`;
361
- }
362
- if (typeof value === "object" && value !== null) {
363
- logValueError(value);
364
- return "";
467
+ evaluateBindings(child, fragment);
365
468
  }
366
- return String(value);
367
- };
368
- strings.forEach((string, i) => {
469
+ }
470
+ };
471
+ var html = (strings, ...values) => {
472
+ const innerHTML = strings.reduce((innerHTML2, str, i) => {
369
473
  let value = values[i] ?? "";
370
474
  if (Array.isArray(value)) {
371
475
  value = value.map((item) => processValue(item)).join("");
372
476
  } else {
373
477
  value = processValue(value);
374
478
  }
375
- innerHTML += string + String(value === null ? "" : value);
376
- });
377
- if (isServer) {
378
- return innerHTML;
379
- }
380
- const fragment = parseFragment(innerHTML);
381
- const callbackBindingRegex = /(\{\{callback:.+\}\})/;
382
- const legacyCallbackBindingRegex = /(this.getRootNode\(\).host.__customCallbackFns.get\('.+'\)\(event\))/;
383
- const signalBindingRegex = /(\{\{signal:.+\}\})/;
384
- const parseChildren = (element) => {
385
- for (const child of element.childNodes) {
386
- if (child instanceof Text && signalBindingRegex.test(child.data)) {
387
- const textList = child.data.split(signalBindingRegex);
388
- const sibling = child.nextSibling;
389
- textList.forEach((text, i) => {
390
- const uniqueKey = text.replace(/\{\{signal:(.+)\}\}/, "$1");
391
- const signal = uniqueKey !== text ? signalMap.get(uniqueKey) : void 0;
392
- const newValue = signal !== void 0 ? signal() : text;
393
- const newNode = getNewNode(newValue, element);
394
- if (i === 0) {
395
- child.replaceWith(newNode);
396
- } else {
397
- element.insertBefore(newNode, sibling);
398
- }
399
- if (signal !== void 0 && newNode instanceof Text) {
400
- createEffect(() => {
401
- newNode.data = signal();
402
- });
403
- } else if (signal !== void 0 && newNode instanceof DocumentFragment) {
404
- createEffect(() => {
405
- const result = signal();
406
- const nextNode = getNewNode(result, element);
407
- if (nextNode instanceof Text) {
408
- throw new TypeError(
409
- "Signal mismatch: expected DocumentFragment or Array<DocumentFragment>, but got Text"
410
- );
411
- }
412
- let lastSibling = element.lastChild;
413
- for (const child2 of nextNode.children) {
414
- const key = child2.getAttribute("key");
415
- const matchingNode = element.querySelector(`[key="${key}"]`);
416
- if (matchingNode === null) continue;
417
- lastSibling = matchingNode.nextSibling;
418
- child2.replaceWith(matchingNode);
419
- }
420
- element.insertBefore(nextNode, lastSibling);
421
- });
422
- }
423
- });
424
- }
425
- if (child instanceof Element) {
426
- for (const attr of child.attributes) {
427
- if (signalBindingRegex.test(attr.value)) {
428
- const textList = attr.value.split(signalBindingRegex);
429
- createEffect(() => {
430
- let newText = "";
431
- let hasNull = false;
432
- for (const text of textList) {
433
- const uniqueKey = text.replace(/\{\{signal:(.+)\}\}/, "$1");
434
- const signal = uniqueKey !== text ? signalMap.get(uniqueKey) : void 0;
435
- const value = signal !== void 0 ? signal() : text;
436
- if (value === null) hasNull = true;
437
- newText += String(value);
438
- }
439
- if (hasNull && newText === "null") {
440
- child.removeAttribute(attr.name);
441
- } else {
442
- child.setAttribute(attr.name, newText);
443
- }
444
- });
445
- } else if (legacyCallbackBindingRegex.test(attr.value)) {
446
- const getRootNode = child.getRootNode.bind(child);
447
- child.getRootNode = () => {
448
- const rootNode = getRootNode();
449
- return rootNode instanceof ShadowRoot ? rootNode : fragment;
450
- };
451
- } else if (callbackBindingRegex.test(attr.value)) {
452
- const textList = attr.value.split(callbackBindingRegex);
453
- createEffect(() => {
454
- child.__customCallbackFns = child.__customCallbackFns ?? /* @__PURE__ */ new Map();
455
- let uniqueKey = "";
456
- for (const text of textList) {
457
- const _uniqueKey = text.replace(/\{\{callback:(.+)\}\}/, "$1");
458
- if (_uniqueKey !== text) uniqueKey = _uniqueKey;
459
- const callback = uniqueKey !== text ? callbackMap.get(uniqueKey) : void 0;
460
- if (callback !== void 0) {
461
- child.__customCallbackFns.set(uniqueKey, callback);
462
- }
463
- }
464
- if (uniqueKey !== "") {
465
- child.setAttribute(attr.name, `this.__customCallbackFns.get('${uniqueKey}')(event)`);
466
- }
467
- });
468
- }
469
- }
470
- parseChildren(child);
471
- }
472
- }
473
- };
474
- parseChildren(fragment);
479
+ innerHTML2 += str + String(value === null ? "" : value);
480
+ return innerHTML2;
481
+ }, "");
482
+ if (isServer) return innerHTML;
483
+ const template = document.createElement("template");
484
+ template.innerHTML = innerHTML;
485
+ const fragment = renderState.currentShadowRoot === null ? template.content : renderState.currentShadowRoot.importNode?.(template.content, true) ?? template.content;
486
+ evaluateBindings(fragment, fragment);
475
487
  return fragment;
476
488
  };
477
489
  var adoptedStylesSupported = typeof window !== "undefined" && window.ShadowRoot?.prototype.hasOwnProperty("adoptedStyleSheets") && window.CSSStyleSheet?.prototype.hasOwnProperty("replace");
@@ -721,8 +733,7 @@ You must set an initial value before calling a property signal's getter.
721
733
  for (const fn of this.#clientOnlyCallbackFns) {
722
734
  fn();
723
735
  }
724
- clearHTML(root);
725
- root.append(fragment);
736
+ root.replaceChildren(fragment);
726
737
  }
727
738
  static get formAssociated() {
728
739
  return formAssociated;
package/dist/index.d.cts CHANGED
@@ -170,6 +170,9 @@ declare const derived: <T>(fn: () => T) => SignalGetter<T>;
170
170
  */
171
171
  declare const createEffect: (fn: () => void) => void;
172
172
 
173
+ /**
174
+ * A tagged template function for creating DocumentFragment instances.
175
+ */
173
176
  declare const html: (strings: TemplateStringsArray, ...values: unknown[]) => DocumentFragment;
174
177
  declare const css: (strings: TemplateStringsArray, ...values: unknown[]) => Styles;
175
178
 
package/dist/index.d.ts CHANGED
@@ -170,6 +170,9 @@ declare const derived: <T>(fn: () => T) => SignalGetter<T>;
170
170
  */
171
171
  declare const createEffect: (fn: () => void) => void;
172
172
 
173
+ /**
174
+ * A tagged template function for creating DocumentFragment instances.
175
+ */
173
176
  declare const html: (strings: TemplateStringsArray, ...values: unknown[]) => DocumentFragment;
174
177
  declare const css: (strings: TemplateStringsArray, ...values: unknown[]) => Styles;
175
178
 
package/dist/index.js CHANGED
@@ -236,19 +236,15 @@ var clientOnlyCallback = (fn) => {
236
236
  };
237
237
 
238
238
  // src/render.ts
239
+ var CALLBACK_BINDING_REGEX = /(\{\{callback:.+\}\})/;
240
+ var LEGACY_CALLBACK_BINDING_REGEX = /(this.getRootNode\(\).host.__customCallbackFns.get\('.+'\)\(event\))/;
241
+ var SIGNAL_BINDING_REGEX = /(\{\{signal:.+\}\})/;
242
+ var FRAGMENT_ATTRIBUTE = "___thunderous-fragment";
239
243
  var renderState = {
240
- currentShadowRoot: null
241
- };
242
- var clearHTML = (element) => {
243
- while (element.childNodes.length > 0) {
244
- element.childNodes[0].remove();
245
- }
246
- };
247
- var parseFragment = (htmlStr) => {
248
- const template = document.createElement("template");
249
- template.innerHTML = htmlStr;
250
- const fragment = renderState.currentShadowRoot === null ? template.content : renderState.currentShadowRoot.importNode?.(template.content, true) ?? template.content;
251
- return fragment;
244
+ currentShadowRoot: null,
245
+ signalMap: /* @__PURE__ */ new Map(),
246
+ callbackMap: /* @__PURE__ */ new Map(),
247
+ fragmentMap: /* @__PURE__ */ new Map()
252
248
  };
253
249
  var logValueError = (value) => {
254
250
  console.error(
@@ -261,7 +257,7 @@ var arrayToDocumentFragment = (array, parent) => {
261
257
  let count = 0;
262
258
  const keys = /* @__PURE__ */ new Set();
263
259
  for (const item of array) {
264
- const node = getNewNode(item, parent).cloneNode(true);
260
+ const node = createNewNode(item, parent);
265
261
  if (node instanceof DocumentFragment) {
266
262
  const child = node.firstElementChild;
267
263
  if (node.children.length > 1) {
@@ -293,150 +289,166 @@ var arrayToDocumentFragment = (array, parent) => {
293
289
  }
294
290
  return documentFragment;
295
291
  };
296
- var getNewNode = (value, parent) => {
292
+ var createNewNode = (value, parent) => {
297
293
  if (typeof value === "string") return new Text(value);
298
294
  if (Array.isArray(value)) return arrayToDocumentFragment(value, parent);
299
295
  if (value instanceof DocumentFragment) return value;
300
296
  return new Text("");
301
297
  };
302
- var html = (strings, ...values) => {
303
- let innerHTML = "";
304
- const signalMap = /* @__PURE__ */ new Map();
305
- const callbackMap = /* @__PURE__ */ new Map();
306
- const processValue = (value) => {
307
- if (!isServer && value instanceof DocumentFragment) {
308
- const tempDiv = document.createElement("div");
309
- tempDiv.append(value.cloneNode(true));
310
- return tempDiv.innerHTML;
298
+ var processValue = (value) => {
299
+ if (!isServer && value instanceof DocumentFragment) {
300
+ const uniqueKey = crypto.randomUUID();
301
+ renderState.fragmentMap.set(uniqueKey, value);
302
+ return `<div ${FRAGMENT_ATTRIBUTE}="${uniqueKey}"></div>`;
303
+ }
304
+ if (typeof value === "function" && "getter" in value && value.getter === true) {
305
+ const getter = value;
306
+ const uniqueKey = crypto.randomUUID();
307
+ renderState.signalMap.set(uniqueKey, getter);
308
+ let result = getter();
309
+ if (Array.isArray(result)) {
310
+ result = result.map((item) => processValue(item)).join("");
311
+ }
312
+ return isServer ? String(result) : `{{signal:${uniqueKey}}}`;
313
+ }
314
+ if (typeof value === "function") {
315
+ const uniqueKey = crypto.randomUUID();
316
+ renderState.callbackMap.set(uniqueKey, value);
317
+ return isServer ? String(value()) : `{{callback:${uniqueKey}}}`;
318
+ }
319
+ if (typeof value === "object" && value !== null) {
320
+ logValueError(value);
321
+ return "";
322
+ }
323
+ return String(value);
324
+ };
325
+ var evaluateBindings = (element, fragment) => {
326
+ for (const child of element.childNodes) {
327
+ if (child instanceof Text && SIGNAL_BINDING_REGEX.test(child.data)) {
328
+ const textList = child.data.split(SIGNAL_BINDING_REGEX);
329
+ const sibling = child.nextSibling;
330
+ textList.forEach((text, i) => {
331
+ const uniqueKey = text.replace(/\{\{signal:(.+)\}\}/, "$1");
332
+ const signal = uniqueKey !== text ? renderState.signalMap.get(uniqueKey) : void 0;
333
+ const newValue = signal !== void 0 ? signal() : text;
334
+ const newNode = createNewNode(newValue, element);
335
+ if (i === 0) {
336
+ child.replaceWith(newNode);
337
+ } else {
338
+ element.insertBefore(newNode, sibling);
339
+ }
340
+ if (signal !== void 0 && newNode instanceof Text) {
341
+ createEffect(() => {
342
+ newNode.data = signal();
343
+ });
344
+ } else if (signal !== void 0 && newNode instanceof DocumentFragment) {
345
+ let init = false;
346
+ createEffect(() => {
347
+ const result = signal();
348
+ const nextNode = createNewNode(result, element);
349
+ if (nextNode instanceof Text) {
350
+ throw new TypeError(
351
+ "Signal mismatch: expected DocumentFragment or Array<DocumentFragment>, but got Text"
352
+ );
353
+ }
354
+ let lastSibling = element.lastChild;
355
+ for (const child2 of element.children) {
356
+ const key = child2.getAttribute("key");
357
+ if (key === null) continue;
358
+ const matchingNode = nextNode.querySelector(`[key="${key}"]`);
359
+ if (init && matchingNode === null) {
360
+ child2.remove();
361
+ }
362
+ }
363
+ for (const child2 of nextNode.children) {
364
+ const key = child2.getAttribute("key");
365
+ const matchingNode = element.querySelector(`[key="${key}"]`);
366
+ if (matchingNode === null) continue;
367
+ matchingNode.__customCallbackFns = child2.__customCallbackFns;
368
+ for (const attr of child2.attributes) {
369
+ matchingNode.setAttribute(attr.name, attr.value);
370
+ }
371
+ matchingNode.replaceChildren(...child2.childNodes);
372
+ lastSibling = matchingNode.nextSibling;
373
+ child2.replaceWith(matchingNode);
374
+ }
375
+ element.insertBefore(nextNode, lastSibling);
376
+ if (!init) init = true;
377
+ });
378
+ }
379
+ });
311
380
  }
312
- if (typeof value === "function" && "getter" in value && value.getter === true) {
313
- const getter = value;
314
- const uniqueKey = crypto.randomUUID();
315
- signalMap.set(uniqueKey, getter);
316
- let result = getter();
317
- if (Array.isArray(result)) {
318
- result = result.map((item) => processValue(item)).join("");
381
+ if (child instanceof Element && child.hasAttribute(FRAGMENT_ATTRIBUTE)) {
382
+ const uniqueKey = child.getAttribute(FRAGMENT_ATTRIBUTE);
383
+ const childFragment = renderState.fragmentMap.get(uniqueKey);
384
+ if (childFragment !== void 0) {
385
+ child.replaceWith(childFragment);
386
+ }
387
+ } else if (child instanceof Element) {
388
+ for (const attr of child.attributes) {
389
+ if (SIGNAL_BINDING_REGEX.test(attr.value)) {
390
+ const textList = attr.value.split(SIGNAL_BINDING_REGEX);
391
+ createEffect(() => {
392
+ let newText = "";
393
+ let hasNull = false;
394
+ for (const text of textList) {
395
+ const uniqueKey = text.replace(/\{\{signal:(.+)\}\}/, "$1");
396
+ const signal = uniqueKey !== text ? renderState.signalMap.get(uniqueKey) : void 0;
397
+ const value = signal !== void 0 ? signal() : text;
398
+ if (value === null) hasNull = true;
399
+ newText += String(value);
400
+ }
401
+ if (hasNull && newText === "null") {
402
+ child.removeAttribute(attr.name);
403
+ } else {
404
+ child.setAttribute(attr.name, newText);
405
+ }
406
+ });
407
+ } else if (LEGACY_CALLBACK_BINDING_REGEX.test(attr.value)) {
408
+ const getRootNode = child.getRootNode.bind(child);
409
+ child.getRootNode = () => {
410
+ const rootNode = getRootNode();
411
+ return rootNode instanceof ShadowRoot ? rootNode : fragment;
412
+ };
413
+ } else if (CALLBACK_BINDING_REGEX.test(attr.value)) {
414
+ const textList = attr.value.split(CALLBACK_BINDING_REGEX);
415
+ createEffect(() => {
416
+ child.__customCallbackFns = child.__customCallbackFns ?? /* @__PURE__ */ new Map();
417
+ let uniqueKey = "";
418
+ for (const text of textList) {
419
+ const _uniqueKey = text.replace(/\{\{callback:(.+)\}\}/, "$1");
420
+ if (_uniqueKey !== text) uniqueKey = _uniqueKey;
421
+ const callback = uniqueKey !== text ? renderState.callbackMap.get(uniqueKey) : void 0;
422
+ if (callback !== void 0) {
423
+ child.__customCallbackFns.set(uniqueKey, callback);
424
+ }
425
+ }
426
+ if (uniqueKey !== "") {
427
+ child.setAttribute(attr.name, `this.__customCallbackFns.get('${uniqueKey}')(event)`);
428
+ }
429
+ });
430
+ }
319
431
  }
320
- return isServer ? String(result) : `{{signal:${uniqueKey}}}`;
321
- }
322
- if (typeof value === "function") {
323
- const uniqueKey = crypto.randomUUID();
324
- callbackMap.set(uniqueKey, value);
325
- return isServer ? String(value()) : `{{callback:${uniqueKey}}}`;
326
- }
327
- if (typeof value === "object" && value !== null) {
328
- logValueError(value);
329
- return "";
432
+ evaluateBindings(child, fragment);
330
433
  }
331
- return String(value);
332
- };
333
- strings.forEach((string, i) => {
434
+ }
435
+ };
436
+ var html = (strings, ...values) => {
437
+ const innerHTML = strings.reduce((innerHTML2, str, i) => {
334
438
  let value = values[i] ?? "";
335
439
  if (Array.isArray(value)) {
336
440
  value = value.map((item) => processValue(item)).join("");
337
441
  } else {
338
442
  value = processValue(value);
339
443
  }
340
- innerHTML += string + String(value === null ? "" : value);
341
- });
342
- if (isServer) {
343
- return innerHTML;
344
- }
345
- const fragment = parseFragment(innerHTML);
346
- const callbackBindingRegex = /(\{\{callback:.+\}\})/;
347
- const legacyCallbackBindingRegex = /(this.getRootNode\(\).host.__customCallbackFns.get\('.+'\)\(event\))/;
348
- const signalBindingRegex = /(\{\{signal:.+\}\})/;
349
- const parseChildren = (element) => {
350
- for (const child of element.childNodes) {
351
- if (child instanceof Text && signalBindingRegex.test(child.data)) {
352
- const textList = child.data.split(signalBindingRegex);
353
- const sibling = child.nextSibling;
354
- textList.forEach((text, i) => {
355
- const uniqueKey = text.replace(/\{\{signal:(.+)\}\}/, "$1");
356
- const signal = uniqueKey !== text ? signalMap.get(uniqueKey) : void 0;
357
- const newValue = signal !== void 0 ? signal() : text;
358
- const newNode = getNewNode(newValue, element);
359
- if (i === 0) {
360
- child.replaceWith(newNode);
361
- } else {
362
- element.insertBefore(newNode, sibling);
363
- }
364
- if (signal !== void 0 && newNode instanceof Text) {
365
- createEffect(() => {
366
- newNode.data = signal();
367
- });
368
- } else if (signal !== void 0 && newNode instanceof DocumentFragment) {
369
- createEffect(() => {
370
- const result = signal();
371
- const nextNode = getNewNode(result, element);
372
- if (nextNode instanceof Text) {
373
- throw new TypeError(
374
- "Signal mismatch: expected DocumentFragment or Array<DocumentFragment>, but got Text"
375
- );
376
- }
377
- let lastSibling = element.lastChild;
378
- for (const child2 of nextNode.children) {
379
- const key = child2.getAttribute("key");
380
- const matchingNode = element.querySelector(`[key="${key}"]`);
381
- if (matchingNode === null) continue;
382
- lastSibling = matchingNode.nextSibling;
383
- child2.replaceWith(matchingNode);
384
- }
385
- element.insertBefore(nextNode, lastSibling);
386
- });
387
- }
388
- });
389
- }
390
- if (child instanceof Element) {
391
- for (const attr of child.attributes) {
392
- if (signalBindingRegex.test(attr.value)) {
393
- const textList = attr.value.split(signalBindingRegex);
394
- createEffect(() => {
395
- let newText = "";
396
- let hasNull = false;
397
- for (const text of textList) {
398
- const uniqueKey = text.replace(/\{\{signal:(.+)\}\}/, "$1");
399
- const signal = uniqueKey !== text ? signalMap.get(uniqueKey) : void 0;
400
- const value = signal !== void 0 ? signal() : text;
401
- if (value === null) hasNull = true;
402
- newText += String(value);
403
- }
404
- if (hasNull && newText === "null") {
405
- child.removeAttribute(attr.name);
406
- } else {
407
- child.setAttribute(attr.name, newText);
408
- }
409
- });
410
- } else if (legacyCallbackBindingRegex.test(attr.value)) {
411
- const getRootNode = child.getRootNode.bind(child);
412
- child.getRootNode = () => {
413
- const rootNode = getRootNode();
414
- return rootNode instanceof ShadowRoot ? rootNode : fragment;
415
- };
416
- } else if (callbackBindingRegex.test(attr.value)) {
417
- const textList = attr.value.split(callbackBindingRegex);
418
- createEffect(() => {
419
- child.__customCallbackFns = child.__customCallbackFns ?? /* @__PURE__ */ new Map();
420
- let uniqueKey = "";
421
- for (const text of textList) {
422
- const _uniqueKey = text.replace(/\{\{callback:(.+)\}\}/, "$1");
423
- if (_uniqueKey !== text) uniqueKey = _uniqueKey;
424
- const callback = uniqueKey !== text ? callbackMap.get(uniqueKey) : void 0;
425
- if (callback !== void 0) {
426
- child.__customCallbackFns.set(uniqueKey, callback);
427
- }
428
- }
429
- if (uniqueKey !== "") {
430
- child.setAttribute(attr.name, `this.__customCallbackFns.get('${uniqueKey}')(event)`);
431
- }
432
- });
433
- }
434
- }
435
- parseChildren(child);
436
- }
437
- }
438
- };
439
- parseChildren(fragment);
444
+ innerHTML2 += str + String(value === null ? "" : value);
445
+ return innerHTML2;
446
+ }, "");
447
+ if (isServer) return innerHTML;
448
+ const template = document.createElement("template");
449
+ template.innerHTML = innerHTML;
450
+ const fragment = renderState.currentShadowRoot === null ? template.content : renderState.currentShadowRoot.importNode?.(template.content, true) ?? template.content;
451
+ evaluateBindings(fragment, fragment);
440
452
  return fragment;
441
453
  };
442
454
  var adoptedStylesSupported = typeof window !== "undefined" && window.ShadowRoot?.prototype.hasOwnProperty("adoptedStyleSheets") && window.CSSStyleSheet?.prototype.hasOwnProperty("replace");
@@ -686,8 +698,7 @@ You must set an initial value before calling a property signal's getter.
686
698
  for (const fn of this.#clientOnlyCallbackFns) {
687
699
  fn();
688
700
  }
689
- clearHTML(root);
690
- root.append(fragment);
701
+ root.replaceChildren(fragment);
691
702
  }
692
703
  static get formAssociated() {
693
704
  return formAssociated;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "thunderous",
3
- "version": "2.0.1",
3
+ "version": "2.0.3",
4
4
  "description": "",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",