thunderous 2.0.7 → 2.1.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.cjs CHANGED
@@ -136,6 +136,14 @@ var createEffect = (fn) => {
136
136
 
137
137
  // src/utilities.ts
138
138
  var NOOP = () => void 0;
139
+ var queryComment = (node, comment) => {
140
+ for (const child of node.childNodes) {
141
+ if (child.nodeType === Node.COMMENT_NODE && child.nodeValue === comment) {
142
+ return child;
143
+ }
144
+ }
145
+ return null;
146
+ };
139
147
 
140
148
  // src/server-side.ts
141
149
  var isServer = typeof window === "undefined";
@@ -287,12 +295,19 @@ var logValueError = (value) => {
287
295
  value
288
296
  );
289
297
  };
290
- var arrayToDocumentFragment = (array, parent) => {
298
+ var logPropertyWarning = (propName, element) => {
299
+ console.warn(
300
+ `Property "${propName}" does not exist on element:`,
301
+ element,
302
+ "\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."
303
+ );
304
+ };
305
+ var arrayToDocumentFragment = (array, parent, uniqueKey) => {
291
306
  const documentFragment = new DocumentFragment();
292
307
  let count = 0;
293
308
  const keys = /* @__PURE__ */ new Set();
294
309
  for (const item of array) {
295
- const node = createNewNode(item, parent);
310
+ const node = createNewNode(item, parent, uniqueKey);
296
311
  if (node instanceof DocumentFragment) {
297
312
  const child = node.firstElementChild;
298
313
  if (node.children.length > 1) {
@@ -322,11 +337,13 @@ var arrayToDocumentFragment = (array, parent) => {
322
337
  }
323
338
  documentFragment.append(node);
324
339
  }
340
+ const comment = document.createComment(uniqueKey);
341
+ documentFragment.append(comment);
325
342
  return documentFragment;
326
343
  };
327
- var createNewNode = (value, parent) => {
344
+ var createNewNode = (value, parent, uniqueKey) => {
328
345
  if (typeof value === "string") return new Text(value);
329
- if (Array.isArray(value)) return arrayToDocumentFragment(value, parent);
346
+ if (Array.isArray(value)) return arrayToDocumentFragment(value, parent, uniqueKey);
330
347
  if (value instanceof DocumentFragment) return value;
331
348
  return new Text("");
332
349
  };
@@ -366,7 +383,7 @@ var evaluateBindings = (element, fragment) => {
366
383
  const uniqueKey = text.replace(/\{\{signal:(.+)\}\}/, "$1");
367
384
  const signal = uniqueKey !== text ? renderState.signalMap.get(uniqueKey) : void 0;
368
385
  const newValue = signal !== void 0 ? signal() : text;
369
- const newNode = createNewNode(newValue, element);
386
+ const newNode = createNewNode(newValue, element, uniqueKey);
370
387
  if (i === 0) {
371
388
  child.replaceWith(newNode);
372
389
  } else {
@@ -380,13 +397,12 @@ var evaluateBindings = (element, fragment) => {
380
397
  let init = false;
381
398
  createEffect(() => {
382
399
  const result = signal();
383
- const nextNode = createNewNode(result, element);
400
+ const nextNode = createNewNode(result, element, uniqueKey);
384
401
  if (nextNode instanceof Text) {
385
402
  throw new TypeError(
386
403
  "Signal mismatch: expected DocumentFragment or Array<DocumentFragment>, but got Text"
387
404
  );
388
405
  }
389
- let lastSibling = element.lastChild;
390
406
  for (const child2 of element.children) {
391
407
  const key = child2.getAttribute("key");
392
408
  if (key === null) continue;
@@ -395,6 +411,7 @@ var evaluateBindings = (element, fragment) => {
395
411
  child2.remove();
396
412
  }
397
413
  }
414
+ let anchor = queryComment(element, uniqueKey);
398
415
  for (const child2 of nextNode.children) {
399
416
  const key = child2.getAttribute("key");
400
417
  const matchingNode = element.querySelector(`[key="${key}"]`);
@@ -404,10 +421,12 @@ var evaluateBindings = (element, fragment) => {
404
421
  matchingNode.setAttribute(attr.name, attr.value);
405
422
  }
406
423
  matchingNode.replaceChildren(...child2.childNodes);
407
- lastSibling = matchingNode.nextSibling;
424
+ anchor = matchingNode.nextSibling;
408
425
  child2.replaceWith(matchingNode);
409
426
  }
410
- element.insertBefore(nextNode, lastSibling);
427
+ const nextAnchor = queryComment(nextNode, uniqueKey);
428
+ nextAnchor?.remove();
429
+ element.insertBefore(nextNode, anchor);
411
430
  if (!init) init = true;
412
431
  });
413
432
  }
@@ -421,22 +440,31 @@ var evaluateBindings = (element, fragment) => {
421
440
  }
422
441
  } else if (child instanceof Element) {
423
442
  for (const attr of child.attributes) {
443
+ const attrName = attr.name;
424
444
  if (SIGNAL_BINDING_REGEX.test(attr.value)) {
425
445
  const textList = attr.value.split(SIGNAL_BINDING_REGEX);
426
446
  createEffect(() => {
427
447
  let newText = "";
428
448
  let hasNull = false;
449
+ let signal;
429
450
  for (const text of textList) {
430
451
  const uniqueKey = text.replace(/\{\{signal:(.+)\}\}/, "$1");
431
- const signal = uniqueKey !== text ? renderState.signalMap.get(uniqueKey) : void 0;
452
+ signal = uniqueKey !== text ? renderState.signalMap.get(uniqueKey) : void 0;
432
453
  const value = signal !== void 0 ? signal() : text;
433
454
  if (value === null) hasNull = true;
434
455
  newText += String(value);
435
456
  }
436
- if (hasNull && newText === "null") {
437
- child.removeAttribute(attr.name);
457
+ if (hasNull && newText === "null" || attrName.startsWith("prop:")) {
458
+ child.removeAttribute(attrName);
438
459
  } else {
439
- child.setAttribute(attr.name, newText);
460
+ child.setAttribute(attrName, newText);
461
+ }
462
+ if (attrName.startsWith("prop:")) {
463
+ child.removeAttribute(attrName);
464
+ const propName = attrName.replace("prop:", "");
465
+ if (!(propName in child)) logPropertyWarning(propName, child);
466
+ const newValue = hasNull && newText === "null" ? null : newText;
467
+ child[propName] = signal !== void 0 ? signal() : newValue;
440
468
  }
441
469
  });
442
470
  } else if (LEGACY_CALLBACK_BINDING_REGEX.test(attr.value)) {
@@ -458,10 +486,20 @@ var evaluateBindings = (element, fragment) => {
458
486
  child.__customCallbackFns.set(uniqueKey, callback);
459
487
  }
460
488
  }
461
- if (uniqueKey !== "") {
462
- child.setAttribute(attr.name, `this.__customCallbackFns.get('${uniqueKey}')(event)`);
489
+ if (uniqueKey !== "" && !attrName.startsWith("prop:")) {
490
+ child.setAttribute(attrName, `this.__customCallbackFns.get('${uniqueKey}')(event)`);
491
+ } else if (attrName.startsWith("prop:")) {
492
+ child.removeAttribute(attrName);
493
+ const propName = attrName.replace("prop:", "");
494
+ if (!(propName in child)) logPropertyWarning(propName, child);
495
+ child[propName] = child.__customCallbackFns.get(uniqueKey);
463
496
  }
464
497
  });
498
+ } else if (attrName.startsWith("prop:")) {
499
+ child.removeAttribute(attrName);
500
+ const propName = attrName.replace("prop:", "");
501
+ if (!(propName in child)) logPropertyWarning(propName, child);
502
+ child[propName] = attr.value;
465
503
  }
466
504
  }
467
505
  evaluateBindings(child, fragment);
package/dist/index.d.cts CHANGED
@@ -2,7 +2,7 @@ declare global {
2
2
  interface DocumentFragment {
3
3
  host: HTMLElement;
4
4
  }
5
- interface Element {
5
+ interface Node {
6
6
  __customCallbackFns?: Map<string, AnyFn>;
7
7
  }
8
8
  interface CustomElementRegistry {
package/dist/index.d.ts CHANGED
@@ -2,7 +2,7 @@ declare global {
2
2
  interface DocumentFragment {
3
3
  host: HTMLElement;
4
4
  }
5
- interface Element {
5
+ interface Node {
6
6
  __customCallbackFns?: Map<string, AnyFn>;
7
7
  }
8
8
  interface CustomElementRegistry {
package/dist/index.js CHANGED
@@ -101,6 +101,14 @@ var createEffect = (fn) => {
101
101
 
102
102
  // src/utilities.ts
103
103
  var NOOP = () => void 0;
104
+ var queryComment = (node, comment) => {
105
+ for (const child of node.childNodes) {
106
+ if (child.nodeType === Node.COMMENT_NODE && child.nodeValue === comment) {
107
+ return child;
108
+ }
109
+ }
110
+ return null;
111
+ };
104
112
 
105
113
  // src/server-side.ts
106
114
  var isServer = typeof window === "undefined";
@@ -252,12 +260,19 @@ var logValueError = (value) => {
252
260
  value
253
261
  );
254
262
  };
255
- var arrayToDocumentFragment = (array, parent) => {
263
+ var logPropertyWarning = (propName, element) => {
264
+ console.warn(
265
+ `Property "${propName}" does not exist on element:`,
266
+ element,
267
+ "\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."
268
+ );
269
+ };
270
+ var arrayToDocumentFragment = (array, parent, uniqueKey) => {
256
271
  const documentFragment = new DocumentFragment();
257
272
  let count = 0;
258
273
  const keys = /* @__PURE__ */ new Set();
259
274
  for (const item of array) {
260
- const node = createNewNode(item, parent);
275
+ const node = createNewNode(item, parent, uniqueKey);
261
276
  if (node instanceof DocumentFragment) {
262
277
  const child = node.firstElementChild;
263
278
  if (node.children.length > 1) {
@@ -287,11 +302,13 @@ var arrayToDocumentFragment = (array, parent) => {
287
302
  }
288
303
  documentFragment.append(node);
289
304
  }
305
+ const comment = document.createComment(uniqueKey);
306
+ documentFragment.append(comment);
290
307
  return documentFragment;
291
308
  };
292
- var createNewNode = (value, parent) => {
309
+ var createNewNode = (value, parent, uniqueKey) => {
293
310
  if (typeof value === "string") return new Text(value);
294
- if (Array.isArray(value)) return arrayToDocumentFragment(value, parent);
311
+ if (Array.isArray(value)) return arrayToDocumentFragment(value, parent, uniqueKey);
295
312
  if (value instanceof DocumentFragment) return value;
296
313
  return new Text("");
297
314
  };
@@ -331,7 +348,7 @@ var evaluateBindings = (element, fragment) => {
331
348
  const uniqueKey = text.replace(/\{\{signal:(.+)\}\}/, "$1");
332
349
  const signal = uniqueKey !== text ? renderState.signalMap.get(uniqueKey) : void 0;
333
350
  const newValue = signal !== void 0 ? signal() : text;
334
- const newNode = createNewNode(newValue, element);
351
+ const newNode = createNewNode(newValue, element, uniqueKey);
335
352
  if (i === 0) {
336
353
  child.replaceWith(newNode);
337
354
  } else {
@@ -345,13 +362,12 @@ var evaluateBindings = (element, fragment) => {
345
362
  let init = false;
346
363
  createEffect(() => {
347
364
  const result = signal();
348
- const nextNode = createNewNode(result, element);
365
+ const nextNode = createNewNode(result, element, uniqueKey);
349
366
  if (nextNode instanceof Text) {
350
367
  throw new TypeError(
351
368
  "Signal mismatch: expected DocumentFragment or Array<DocumentFragment>, but got Text"
352
369
  );
353
370
  }
354
- let lastSibling = element.lastChild;
355
371
  for (const child2 of element.children) {
356
372
  const key = child2.getAttribute("key");
357
373
  if (key === null) continue;
@@ -360,6 +376,7 @@ var evaluateBindings = (element, fragment) => {
360
376
  child2.remove();
361
377
  }
362
378
  }
379
+ let anchor = queryComment(element, uniqueKey);
363
380
  for (const child2 of nextNode.children) {
364
381
  const key = child2.getAttribute("key");
365
382
  const matchingNode = element.querySelector(`[key="${key}"]`);
@@ -369,10 +386,12 @@ var evaluateBindings = (element, fragment) => {
369
386
  matchingNode.setAttribute(attr.name, attr.value);
370
387
  }
371
388
  matchingNode.replaceChildren(...child2.childNodes);
372
- lastSibling = matchingNode.nextSibling;
389
+ anchor = matchingNode.nextSibling;
373
390
  child2.replaceWith(matchingNode);
374
391
  }
375
- element.insertBefore(nextNode, lastSibling);
392
+ const nextAnchor = queryComment(nextNode, uniqueKey);
393
+ nextAnchor?.remove();
394
+ element.insertBefore(nextNode, anchor);
376
395
  if (!init) init = true;
377
396
  });
378
397
  }
@@ -386,22 +405,31 @@ var evaluateBindings = (element, fragment) => {
386
405
  }
387
406
  } else if (child instanceof Element) {
388
407
  for (const attr of child.attributes) {
408
+ const attrName = attr.name;
389
409
  if (SIGNAL_BINDING_REGEX.test(attr.value)) {
390
410
  const textList = attr.value.split(SIGNAL_BINDING_REGEX);
391
411
  createEffect(() => {
392
412
  let newText = "";
393
413
  let hasNull = false;
414
+ let signal;
394
415
  for (const text of textList) {
395
416
  const uniqueKey = text.replace(/\{\{signal:(.+)\}\}/, "$1");
396
- const signal = uniqueKey !== text ? renderState.signalMap.get(uniqueKey) : void 0;
417
+ signal = uniqueKey !== text ? renderState.signalMap.get(uniqueKey) : void 0;
397
418
  const value = signal !== void 0 ? signal() : text;
398
419
  if (value === null) hasNull = true;
399
420
  newText += String(value);
400
421
  }
401
- if (hasNull && newText === "null") {
402
- child.removeAttribute(attr.name);
422
+ if (hasNull && newText === "null" || attrName.startsWith("prop:")) {
423
+ child.removeAttribute(attrName);
403
424
  } else {
404
- child.setAttribute(attr.name, newText);
425
+ child.setAttribute(attrName, newText);
426
+ }
427
+ if (attrName.startsWith("prop:")) {
428
+ child.removeAttribute(attrName);
429
+ const propName = attrName.replace("prop:", "");
430
+ if (!(propName in child)) logPropertyWarning(propName, child);
431
+ const newValue = hasNull && newText === "null" ? null : newText;
432
+ child[propName] = signal !== void 0 ? signal() : newValue;
405
433
  }
406
434
  });
407
435
  } else if (LEGACY_CALLBACK_BINDING_REGEX.test(attr.value)) {
@@ -423,10 +451,20 @@ var evaluateBindings = (element, fragment) => {
423
451
  child.__customCallbackFns.set(uniqueKey, callback);
424
452
  }
425
453
  }
426
- if (uniqueKey !== "") {
427
- child.setAttribute(attr.name, `this.__customCallbackFns.get('${uniqueKey}')(event)`);
454
+ if (uniqueKey !== "" && !attrName.startsWith("prop:")) {
455
+ child.setAttribute(attrName, `this.__customCallbackFns.get('${uniqueKey}')(event)`);
456
+ } else if (attrName.startsWith("prop:")) {
457
+ child.removeAttribute(attrName);
458
+ const propName = attrName.replace("prop:", "");
459
+ if (!(propName in child)) logPropertyWarning(propName, child);
460
+ child[propName] = child.__customCallbackFns.get(uniqueKey);
428
461
  }
429
462
  });
463
+ } else if (attrName.startsWith("prop:")) {
464
+ child.removeAttribute(attrName);
465
+ const propName = attrName.replace("prop:", "");
466
+ if (!(propName in child)) logPropertyWarning(propName, child);
467
+ child[propName] = attr.value;
430
468
  }
431
469
  }
432
470
  evaluateBindings(child, fragment);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "thunderous",
3
- "version": "2.0.7",
3
+ "version": "2.1.0",
4
4
  "description": "",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",
@@ -35,7 +35,10 @@
35
35
  "www": "cd www && npm run dev",
36
36
  "build": "tsup src/index.ts --format cjs,esm --dts --no-clean",
37
37
  "test": "find src -name '*.test.ts' | xargs c8 node --import tsx --test",
38
- "lint": "tsc && eslint ."
38
+ "lint": "tsc && eslint .",
39
+ "version:patch": "npm run build && npm version patch && cd demo && npm version patch && cd ../www && npm version patch && git add -A && git commit -m 'bump version'",
40
+ "version:minor": "npm run build && npm version minor && cd demo && npm version minor && cd ../www && npm version minor && git add -A && git commit -m 'bump version'",
41
+ "version:major": "npm run build && npm version major && cd demo && npm version major && cd ../www && npm version major && git add -A && git commit -m 'bump version'"
39
42
  },
40
43
  "devDependencies": {
41
44
  "@types/dompurify": "^3.2.0",