thunderous 2.0.1 → 2.0.2

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,151 @@ 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
+ createEffect(() => {
381
+ const result = signal();
382
+ const nextNode = createNewNode(result, element);
383
+ if (nextNode instanceof Text) {
384
+ throw new TypeError(
385
+ "Signal mismatch: expected DocumentFragment or Array<DocumentFragment>, but got Text"
386
+ );
387
+ }
388
+ let lastSibling = element.lastChild;
389
+ for (const child2 of nextNode.children) {
390
+ const key = child2.getAttribute("key");
391
+ const matchingNode = element.querySelector(`[key="${key}"]`);
392
+ if (matchingNode === null) continue;
393
+ lastSibling = matchingNode.nextSibling;
394
+ child2.replaceWith(matchingNode);
395
+ }
396
+ element.insertBefore(nextNode, lastSibling);
397
+ });
398
+ }
399
+ });
346
400
  }
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("");
401
+ if (child instanceof Element && child.hasAttribute(FRAGMENT_ATTRIBUTE)) {
402
+ const uniqueKey = child.getAttribute(FRAGMENT_ATTRIBUTE);
403
+ const childFragment = renderState.fragmentMap.get(uniqueKey);
404
+ if (childFragment !== void 0) {
405
+ child.replaceWith(childFragment);
406
+ }
407
+ } else if (child instanceof Element) {
408
+ for (const attr of child.attributes) {
409
+ if (SIGNAL_BINDING_REGEX.test(attr.value)) {
410
+ const textList = attr.value.split(SIGNAL_BINDING_REGEX);
411
+ createEffect(() => {
412
+ let newText = "";
413
+ let hasNull = false;
414
+ for (const text of textList) {
415
+ const uniqueKey = text.replace(/\{\{signal:(.+)\}\}/, "$1");
416
+ const signal = uniqueKey !== text ? renderState.signalMap.get(uniqueKey) : void 0;
417
+ const value = signal !== void 0 ? signal() : text;
418
+ if (value === null) hasNull = true;
419
+ newText += String(value);
420
+ }
421
+ if (hasNull && newText === "null") {
422
+ child.removeAttribute(attr.name);
423
+ } else {
424
+ child.setAttribute(attr.name, newText);
425
+ }
426
+ });
427
+ } else if (LEGACY_CALLBACK_BINDING_REGEX.test(attr.value)) {
428
+ const getRootNode = child.getRootNode.bind(child);
429
+ child.getRootNode = () => {
430
+ const rootNode = getRootNode();
431
+ return rootNode instanceof ShadowRoot ? rootNode : fragment;
432
+ };
433
+ } else if (CALLBACK_BINDING_REGEX.test(attr.value)) {
434
+ const textList = attr.value.split(CALLBACK_BINDING_REGEX);
435
+ createEffect(() => {
436
+ child.__customCallbackFns = child.__customCallbackFns ?? /* @__PURE__ */ new Map();
437
+ let uniqueKey = "";
438
+ for (const text of textList) {
439
+ const _uniqueKey = text.replace(/\{\{callback:(.+)\}\}/, "$1");
440
+ if (_uniqueKey !== text) uniqueKey = _uniqueKey;
441
+ const callback = uniqueKey !== text ? renderState.callbackMap.get(uniqueKey) : void 0;
442
+ if (callback !== void 0) {
443
+ child.__customCallbackFns.set(uniqueKey, callback);
444
+ }
445
+ }
446
+ if (uniqueKey !== "") {
447
+ child.setAttribute(attr.name, `this.__customCallbackFns.get('${uniqueKey}')(event)`);
448
+ }
449
+ });
450
+ }
354
451
  }
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}}}`;
452
+ evaluateBindings(child, fragment);
361
453
  }
362
- if (typeof value === "object" && value !== null) {
363
- logValueError(value);
364
- return "";
365
- }
366
- return String(value);
367
- };
368
- strings.forEach((string, i) => {
454
+ }
455
+ };
456
+ var html = (strings, ...values) => {
457
+ const innerHTML = strings.reduce((innerHTML2, str, i) => {
369
458
  let value = values[i] ?? "";
370
459
  if (Array.isArray(value)) {
371
460
  value = value.map((item) => processValue(item)).join("");
372
461
  } else {
373
462
  value = processValue(value);
374
463
  }
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);
464
+ innerHTML2 += str + String(value === null ? "" : value);
465
+ return innerHTML2;
466
+ }, "");
467
+ if (isServer) return innerHTML;
468
+ const template = document.createElement("template");
469
+ template.innerHTML = innerHTML;
470
+ const fragment = renderState.currentShadowRoot === null ? template.content : renderState.currentShadowRoot.importNode?.(template.content, true) ?? template.content;
471
+ evaluateBindings(fragment, fragment);
475
472
  return fragment;
476
473
  };
477
474
  var adoptedStylesSupported = typeof window !== "undefined" && window.ShadowRoot?.prototype.hasOwnProperty("adoptedStyleSheets") && window.CSSStyleSheet?.prototype.hasOwnProperty("replace");
@@ -721,8 +718,7 @@ You must set an initial value before calling a property signal's getter.
721
718
  for (const fn of this.#clientOnlyCallbackFns) {
722
719
  fn();
723
720
  }
724
- clearHTML(root);
725
- root.append(fragment);
721
+ root.replaceChildren(fragment);
726
722
  }
727
723
  static get formAssociated() {
728
724
  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,151 @@ 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
+ createEffect(() => {
346
+ const result = signal();
347
+ const nextNode = createNewNode(result, element);
348
+ if (nextNode instanceof Text) {
349
+ throw new TypeError(
350
+ "Signal mismatch: expected DocumentFragment or Array<DocumentFragment>, but got Text"
351
+ );
352
+ }
353
+ let lastSibling = element.lastChild;
354
+ for (const child2 of nextNode.children) {
355
+ const key = child2.getAttribute("key");
356
+ const matchingNode = element.querySelector(`[key="${key}"]`);
357
+ if (matchingNode === null) continue;
358
+ lastSibling = matchingNode.nextSibling;
359
+ child2.replaceWith(matchingNode);
360
+ }
361
+ element.insertBefore(nextNode, lastSibling);
362
+ });
363
+ }
364
+ });
311
365
  }
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("");
366
+ if (child instanceof Element && child.hasAttribute(FRAGMENT_ATTRIBUTE)) {
367
+ const uniqueKey = child.getAttribute(FRAGMENT_ATTRIBUTE);
368
+ const childFragment = renderState.fragmentMap.get(uniqueKey);
369
+ if (childFragment !== void 0) {
370
+ child.replaceWith(childFragment);
371
+ }
372
+ } else if (child instanceof Element) {
373
+ for (const attr of child.attributes) {
374
+ if (SIGNAL_BINDING_REGEX.test(attr.value)) {
375
+ const textList = attr.value.split(SIGNAL_BINDING_REGEX);
376
+ createEffect(() => {
377
+ let newText = "";
378
+ let hasNull = false;
379
+ for (const text of textList) {
380
+ const uniqueKey = text.replace(/\{\{signal:(.+)\}\}/, "$1");
381
+ const signal = uniqueKey !== text ? renderState.signalMap.get(uniqueKey) : void 0;
382
+ const value = signal !== void 0 ? signal() : text;
383
+ if (value === null) hasNull = true;
384
+ newText += String(value);
385
+ }
386
+ if (hasNull && newText === "null") {
387
+ child.removeAttribute(attr.name);
388
+ } else {
389
+ child.setAttribute(attr.name, newText);
390
+ }
391
+ });
392
+ } else if (LEGACY_CALLBACK_BINDING_REGEX.test(attr.value)) {
393
+ const getRootNode = child.getRootNode.bind(child);
394
+ child.getRootNode = () => {
395
+ const rootNode = getRootNode();
396
+ return rootNode instanceof ShadowRoot ? rootNode : fragment;
397
+ };
398
+ } else if (CALLBACK_BINDING_REGEX.test(attr.value)) {
399
+ const textList = attr.value.split(CALLBACK_BINDING_REGEX);
400
+ createEffect(() => {
401
+ child.__customCallbackFns = child.__customCallbackFns ?? /* @__PURE__ */ new Map();
402
+ let uniqueKey = "";
403
+ for (const text of textList) {
404
+ const _uniqueKey = text.replace(/\{\{callback:(.+)\}\}/, "$1");
405
+ if (_uniqueKey !== text) uniqueKey = _uniqueKey;
406
+ const callback = uniqueKey !== text ? renderState.callbackMap.get(uniqueKey) : void 0;
407
+ if (callback !== void 0) {
408
+ child.__customCallbackFns.set(uniqueKey, callback);
409
+ }
410
+ }
411
+ if (uniqueKey !== "") {
412
+ child.setAttribute(attr.name, `this.__customCallbackFns.get('${uniqueKey}')(event)`);
413
+ }
414
+ });
415
+ }
319
416
  }
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}}}`;
417
+ evaluateBindings(child, fragment);
326
418
  }
327
- if (typeof value === "object" && value !== null) {
328
- logValueError(value);
329
- return "";
330
- }
331
- return String(value);
332
- };
333
- strings.forEach((string, i) => {
419
+ }
420
+ };
421
+ var html = (strings, ...values) => {
422
+ const innerHTML = strings.reduce((innerHTML2, str, i) => {
334
423
  let value = values[i] ?? "";
335
424
  if (Array.isArray(value)) {
336
425
  value = value.map((item) => processValue(item)).join("");
337
426
  } else {
338
427
  value = processValue(value);
339
428
  }
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);
429
+ innerHTML2 += str + String(value === null ? "" : value);
430
+ return innerHTML2;
431
+ }, "");
432
+ if (isServer) return innerHTML;
433
+ const template = document.createElement("template");
434
+ template.innerHTML = innerHTML;
435
+ const fragment = renderState.currentShadowRoot === null ? template.content : renderState.currentShadowRoot.importNode?.(template.content, true) ?? template.content;
436
+ evaluateBindings(fragment, fragment);
440
437
  return fragment;
441
438
  };
442
439
  var adoptedStylesSupported = typeof window !== "undefined" && window.ShadowRoot?.prototype.hasOwnProperty("adoptedStyleSheets") && window.CSSStyleSheet?.prototype.hasOwnProperty("replace");
@@ -686,8 +683,7 @@ You must set an initial value before calling a property signal's getter.
686
683
  for (const fn of this.#clientOnlyCallbackFns) {
687
684
  fn();
688
685
  }
689
- clearHTML(root);
690
- root.append(fragment);
686
+ root.replaceChildren(fragment);
691
687
  }
692
688
  static get formAssociated() {
693
689
  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.2",
4
4
  "description": "",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",