thunderous 2.3.4 → 2.3.6

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
@@ -87,11 +87,15 @@ var createSignal = (initVal, options) => {
87
87
  if (!isBatchingUpdates) {
88
88
  isBatchingUpdates = true;
89
89
  queueMicrotask(() => {
90
- for (const fn of updateQueue) {
91
- try {
92
- fn();
93
- } catch (error) {
94
- console.error("Error in subscriber:", { error, oldValue, newValue, fn });
90
+ while (updateQueue.size > 0) {
91
+ const updates = Array.from(updateQueue);
92
+ updateQueue.clear();
93
+ for (const fn of updates) {
94
+ try {
95
+ fn();
96
+ } catch (error) {
97
+ console.error("Error in subscriber:", { error, oldValue, newValue, fn });
98
+ }
95
99
  }
96
100
  }
97
101
  if (options?.debugMode === true || setterOptions?.debugMode === true) {
@@ -106,7 +110,6 @@ var createSignal = (initVal, options) => {
106
110
  }
107
111
  console.log("Signal set:", { oldValue, newValue, subscribers, label });
108
112
  }
109
- updateQueue.clear();
110
113
  isBatchingUpdates = false;
111
114
  });
112
115
  }
@@ -448,9 +451,7 @@ var evaluateBindings = (element, fragment) => {
448
451
  child.replaceWith(childFragment);
449
452
  }
450
453
  } else if (child instanceof Element) {
451
- const attrRemoveQueue = [];
452
- const attrSetQueue = [];
453
- for (const attr of child.attributes) {
454
+ for (const attr of [...child.attributes]) {
454
455
  const attrName = attr.name;
455
456
  if (SIGNAL_BINDING_REGEX.test(attr.value)) {
456
457
  const textList = attr.value.split(SIGNAL_BINDING_REGEX);
@@ -470,12 +471,12 @@ var evaluateBindings = (element, fragment) => {
470
471
  }
471
472
  }
472
473
  if (hasNull && newText === "null" || attrName.startsWith("prop:")) {
473
- attrRemoveQueue.push(attrName);
474
+ child.removeAttribute(attrName);
474
475
  } else {
475
- attrSetQueue.push([attrName, newText]);
476
+ child.setAttribute(attrName, newText);
476
477
  }
477
478
  if (attrName.startsWith("prop:")) {
478
- attrRemoveQueue.push(attrName);
479
+ child.removeAttribute(attrName);
479
480
  const propName = attrName.replace("prop:", "");
480
481
  const newValue = hasNull && newText === "null" ? null : newText;
481
482
  if (!(propName in child)) logPropertyWarning(propName, child);
@@ -502,27 +503,21 @@ var evaluateBindings = (element, fragment) => {
502
503
  }
503
504
  }
504
505
  if (uniqueKey !== "" && !attrName.startsWith("prop:")) {
505
- attrSetQueue.push([attrName, `this.__customCallbackFns.get('${uniqueKey}')(event)`]);
506
+ child.setAttribute(attrName, `this.__customCallbackFns.get('${uniqueKey}')(event)`);
506
507
  } else if (attrName.startsWith("prop:")) {
507
- attrRemoveQueue.push(attrName);
508
+ child.removeAttribute(attrName);
508
509
  const propName = attrName.replace("prop:", "");
509
510
  if (!(propName in child)) logPropertyWarning(propName, child);
510
511
  child[propName] = child.__customCallbackFns.get(uniqueKey);
511
512
  }
512
513
  });
513
514
  } else if (attrName.startsWith("prop:")) {
514
- attrRemoveQueue.push(attrName);
515
+ child.removeAttribute(attrName);
515
516
  const propName = attrName.replace("prop:", "");
516
517
  if (!(propName in child)) logPropertyWarning(propName, child);
517
518
  child[propName] = attr.value;
518
519
  }
519
520
  }
520
- for (const attrName of attrRemoveQueue) {
521
- child.removeAttribute(attrName);
522
- }
523
- for (const [name, value] of attrSetQueue) {
524
- child.setAttribute(name, value);
525
- }
526
521
  evaluateBindings(child, fragment);
527
522
  }
528
523
  }
@@ -592,6 +587,7 @@ var css = (strings, ...values) => {
592
587
  };
593
588
 
594
589
  // src/custom-element.ts
590
+ var ANY_BINDING_REGEX = /(\{\{.+:.+\}\})/;
595
591
  var customElement = (render, options) => {
596
592
  const _options = { ...DEFAULT_RENDER_OPTIONS, ...options };
597
593
  const {
@@ -686,6 +682,46 @@ var customElement = (render, options) => {
686
682
  }
687
683
  }
688
684
  });
685
+ #getPropSignal = ((prop, { allowUndefined = false } = {}) => {
686
+ if (!(prop in this.#propSignals)) this.#propSignals[prop] = createSignal();
687
+ const [_getter, _setter] = this.#propSignals[prop];
688
+ let setFromProp = false;
689
+ const setter = (newValue) => {
690
+ if (!setFromProp) this[prop] = newValue;
691
+ _setter(newValue);
692
+ };
693
+ const getter = () => {
694
+ const value = _getter();
695
+ if (value === void 0 && !allowUndefined) {
696
+ const error = new Error(
697
+ `Error accessing property: "${prop}"
698
+ You must set an initial value before calling a property signal's getter.
699
+ `
700
+ );
701
+ console.error(error);
702
+ throw error;
703
+ }
704
+ return value;
705
+ };
706
+ getter.getter = true;
707
+ const descriptor = Object.getOwnPropertyDescriptor(this, prop);
708
+ if (descriptor === void 0) {
709
+ Object.defineProperty(this, prop, {
710
+ get: getter,
711
+ set: (newValue) => {
712
+ setFromProp = true;
713
+ _setter(newValue);
714
+ setFromProp = false;
715
+ }
716
+ });
717
+ }
718
+ const publicSignal = [getter, setter];
719
+ publicSignal.init = (value) => {
720
+ _setter(value);
721
+ return [getter, setter];
722
+ };
723
+ return publicSignal;
724
+ }).bind(this);
689
725
  #render() {
690
726
  const root = this.#shadowRoot ?? this;
691
727
  renderState.currentShadowRoot = this.#shadowRoot;
@@ -729,43 +765,7 @@ var customElement = (render, options) => {
729
765
  }
730
766
  ),
731
767
  propSignals: new Proxy({}, {
732
- get: (_, prop) => {
733
- if (!(prop in this.#propSignals)) this.#propSignals[prop] = createSignal();
734
- const [_getter, _setter] = this.#propSignals[prop];
735
- let setFromProp = false;
736
- const setter = (newValue) => {
737
- if (!setFromProp) this[prop] = newValue;
738
- _setter(newValue);
739
- };
740
- const getter = () => {
741
- const value = _getter();
742
- if (value === void 0) {
743
- const error = new Error(
744
- `Error accessing property: "${prop}"
745
- You must set an initial value before calling a property signal's getter.
746
- `
747
- );
748
- console.error(error);
749
- throw error;
750
- }
751
- return value;
752
- };
753
- getter.getter = true;
754
- Object.defineProperty(this, prop, {
755
- get: getter,
756
- set: (newValue) => {
757
- setFromProp = true;
758
- _setter(newValue);
759
- setFromProp = false;
760
- }
761
- });
762
- const publicSignal = [getter, setter];
763
- publicSignal.init = (value) => {
764
- _setter(value);
765
- return [getter, setter];
766
- };
767
- return publicSignal;
768
- },
768
+ get: (_, prop) => this.#getPropSignal(prop),
769
769
  set: () => {
770
770
  console.error("Signals must be assigned via setters.");
771
771
  return false;
@@ -822,6 +822,27 @@ You must set an initial value before calling a property signal's getter.
822
822
  constructor() {
823
823
  try {
824
824
  super();
825
+ if (!Object.prototype.hasOwnProperty.call(this, "__customCallbackFns")) {
826
+ this.__customCallbackFns = /* @__PURE__ */ new Map();
827
+ }
828
+ for (const attr of this.attributes) {
829
+ this.#attrSignals[attr.name] = createSignal(attr.value);
830
+ }
831
+ for (const [attrName, attr] of this.#attributesAsPropertiesMap) {
832
+ if (!(attrName in this.#attrSignals)) this.#attrSignals[attrName] = createSignal(null);
833
+ const propName = attr.prop;
834
+ const [getter] = this.#getPropSignal(propName, { allowUndefined: true });
835
+ createEffect(() => {
836
+ const value = getter();
837
+ if (value === void 0) return;
838
+ if (value !== null) {
839
+ this.setAttribute(attrName, String(value));
840
+ } else {
841
+ this.removeAttribute(attrName);
842
+ }
843
+ });
844
+ }
845
+ this.#render();
825
846
  } catch (error) {
826
847
  const _error = new Error(
827
848
  "Error instantiating element:\nThis usually occurs if you have errors in the function body of your component. Check prior logs for possible causes.\n",
@@ -830,41 +851,6 @@ You must set an initial value before calling a property signal's getter.
830
851
  console.error(_error);
831
852
  throw _error;
832
853
  }
833
- if (!Object.prototype.hasOwnProperty.call(this, "__customCallbackFns")) {
834
- this.__customCallbackFns = /* @__PURE__ */ new Map();
835
- }
836
- for (const [attrName, attr] of this.#attributesAsPropertiesMap) {
837
- this.#attrSignals[attrName] = createSignal(null);
838
- Object.defineProperty(this, attr.prop, {
839
- get: () => {
840
- if (!(attrName in this.#attrSignals)) this.#attrSignals[attrName] = createSignal(null);
841
- const [getter] = this.#attrSignals[attrName];
842
- const raw = getter();
843
- const rawOnly = raw !== null && attr.value === null;
844
- const value = rawOnly ? attr.coerce(raw) : attr.value;
845
- return value === null ? null : value;
846
- },
847
- set: (newValue) => {
848
- const oldValue = attr.value;
849
- attr.value = newValue;
850
- if (!(attrName in this.#attrSignals)) this.#attrSignals[attrName] = createSignal(null);
851
- const [, attrSetter] = this.#attrSignals[attrName];
852
- const [, propSetter] = this.#propSignals[attrName];
853
- const attrValue = newValue === null ? null : String(newValue);
854
- if (String(oldValue) === attrValue) return;
855
- attrSetter(attrValue);
856
- propSetter(newValue);
857
- if (attrValue === null) this.removeAttribute(attrName);
858
- else this.setAttribute(attrName, attrValue);
859
- },
860
- configurable: true,
861
- enumerable: true
862
- });
863
- }
864
- for (const attr of this.attributes) {
865
- this.#attrSignals[attr.name] = createSignal(attr.value);
866
- }
867
- this.#render();
868
854
  }
869
855
  connectedCallback() {
870
856
  if (this.#observer !== null) {
@@ -882,12 +868,19 @@ You must set an initial value before calling a property signal's getter.
882
868
  fn();
883
869
  }
884
870
  }
871
+ #attributesBusy = false;
885
872
  attributeChangedCallback(name, oldValue, newValue) {
873
+ if (this.#attributesBusy || ANY_BINDING_REGEX.test(newValue ?? "")) return;
886
874
  const [, attrSetter] = this.#attrSignals[name] ?? [];
887
875
  attrSetter?.(newValue);
888
876
  const prop = this.#attributesAsPropertiesMap.get(name);
889
877
  if (prop !== void 0) {
890
- this[prop.prop] = newValue === null ? null : prop.coerce(newValue);
878
+ const propName = prop.prop;
879
+ this.#attributesBusy = true;
880
+ const [, propSetter] = this.#getPropSignal(propName);
881
+ const propValue = newValue === null ? null : prop.coerce(newValue);
882
+ propSetter(propValue);
883
+ this.#attributesBusy = false;
891
884
  }
892
885
  for (const fn of this.#attributeChangedFns) {
893
886
  fn(name, oldValue, newValue);
package/dist/index.js CHANGED
@@ -52,11 +52,15 @@ var createSignal = (initVal, options) => {
52
52
  if (!isBatchingUpdates) {
53
53
  isBatchingUpdates = true;
54
54
  queueMicrotask(() => {
55
- for (const fn of updateQueue) {
56
- try {
57
- fn();
58
- } catch (error) {
59
- console.error("Error in subscriber:", { error, oldValue, newValue, fn });
55
+ while (updateQueue.size > 0) {
56
+ const updates = Array.from(updateQueue);
57
+ updateQueue.clear();
58
+ for (const fn of updates) {
59
+ try {
60
+ fn();
61
+ } catch (error) {
62
+ console.error("Error in subscriber:", { error, oldValue, newValue, fn });
63
+ }
60
64
  }
61
65
  }
62
66
  if (options?.debugMode === true || setterOptions?.debugMode === true) {
@@ -71,7 +75,6 @@ var createSignal = (initVal, options) => {
71
75
  }
72
76
  console.log("Signal set:", { oldValue, newValue, subscribers, label });
73
77
  }
74
- updateQueue.clear();
75
78
  isBatchingUpdates = false;
76
79
  });
77
80
  }
@@ -413,9 +416,7 @@ var evaluateBindings = (element, fragment) => {
413
416
  child.replaceWith(childFragment);
414
417
  }
415
418
  } else if (child instanceof Element) {
416
- const attrRemoveQueue = [];
417
- const attrSetQueue = [];
418
- for (const attr of child.attributes) {
419
+ for (const attr of [...child.attributes]) {
419
420
  const attrName = attr.name;
420
421
  if (SIGNAL_BINDING_REGEX.test(attr.value)) {
421
422
  const textList = attr.value.split(SIGNAL_BINDING_REGEX);
@@ -435,12 +436,12 @@ var evaluateBindings = (element, fragment) => {
435
436
  }
436
437
  }
437
438
  if (hasNull && newText === "null" || attrName.startsWith("prop:")) {
438
- attrRemoveQueue.push(attrName);
439
+ child.removeAttribute(attrName);
439
440
  } else {
440
- attrSetQueue.push([attrName, newText]);
441
+ child.setAttribute(attrName, newText);
441
442
  }
442
443
  if (attrName.startsWith("prop:")) {
443
- attrRemoveQueue.push(attrName);
444
+ child.removeAttribute(attrName);
444
445
  const propName = attrName.replace("prop:", "");
445
446
  const newValue = hasNull && newText === "null" ? null : newText;
446
447
  if (!(propName in child)) logPropertyWarning(propName, child);
@@ -467,27 +468,21 @@ var evaluateBindings = (element, fragment) => {
467
468
  }
468
469
  }
469
470
  if (uniqueKey !== "" && !attrName.startsWith("prop:")) {
470
- attrSetQueue.push([attrName, `this.__customCallbackFns.get('${uniqueKey}')(event)`]);
471
+ child.setAttribute(attrName, `this.__customCallbackFns.get('${uniqueKey}')(event)`);
471
472
  } else if (attrName.startsWith("prop:")) {
472
- attrRemoveQueue.push(attrName);
473
+ child.removeAttribute(attrName);
473
474
  const propName = attrName.replace("prop:", "");
474
475
  if (!(propName in child)) logPropertyWarning(propName, child);
475
476
  child[propName] = child.__customCallbackFns.get(uniqueKey);
476
477
  }
477
478
  });
478
479
  } else if (attrName.startsWith("prop:")) {
479
- attrRemoveQueue.push(attrName);
480
+ child.removeAttribute(attrName);
480
481
  const propName = attrName.replace("prop:", "");
481
482
  if (!(propName in child)) logPropertyWarning(propName, child);
482
483
  child[propName] = attr.value;
483
484
  }
484
485
  }
485
- for (const attrName of attrRemoveQueue) {
486
- child.removeAttribute(attrName);
487
- }
488
- for (const [name, value] of attrSetQueue) {
489
- child.setAttribute(name, value);
490
- }
491
486
  evaluateBindings(child, fragment);
492
487
  }
493
488
  }
@@ -557,6 +552,7 @@ var css = (strings, ...values) => {
557
552
  };
558
553
 
559
554
  // src/custom-element.ts
555
+ var ANY_BINDING_REGEX = /(\{\{.+:.+\}\})/;
560
556
  var customElement = (render, options) => {
561
557
  const _options = { ...DEFAULT_RENDER_OPTIONS, ...options };
562
558
  const {
@@ -651,6 +647,46 @@ var customElement = (render, options) => {
651
647
  }
652
648
  }
653
649
  });
650
+ #getPropSignal = ((prop, { allowUndefined = false } = {}) => {
651
+ if (!(prop in this.#propSignals)) this.#propSignals[prop] = createSignal();
652
+ const [_getter, _setter] = this.#propSignals[prop];
653
+ let setFromProp = false;
654
+ const setter = (newValue) => {
655
+ if (!setFromProp) this[prop] = newValue;
656
+ _setter(newValue);
657
+ };
658
+ const getter = () => {
659
+ const value = _getter();
660
+ if (value === void 0 && !allowUndefined) {
661
+ const error = new Error(
662
+ `Error accessing property: "${prop}"
663
+ You must set an initial value before calling a property signal's getter.
664
+ `
665
+ );
666
+ console.error(error);
667
+ throw error;
668
+ }
669
+ return value;
670
+ };
671
+ getter.getter = true;
672
+ const descriptor = Object.getOwnPropertyDescriptor(this, prop);
673
+ if (descriptor === void 0) {
674
+ Object.defineProperty(this, prop, {
675
+ get: getter,
676
+ set: (newValue) => {
677
+ setFromProp = true;
678
+ _setter(newValue);
679
+ setFromProp = false;
680
+ }
681
+ });
682
+ }
683
+ const publicSignal = [getter, setter];
684
+ publicSignal.init = (value) => {
685
+ _setter(value);
686
+ return [getter, setter];
687
+ };
688
+ return publicSignal;
689
+ }).bind(this);
654
690
  #render() {
655
691
  const root = this.#shadowRoot ?? this;
656
692
  renderState.currentShadowRoot = this.#shadowRoot;
@@ -694,43 +730,7 @@ var customElement = (render, options) => {
694
730
  }
695
731
  ),
696
732
  propSignals: new Proxy({}, {
697
- get: (_, prop) => {
698
- if (!(prop in this.#propSignals)) this.#propSignals[prop] = createSignal();
699
- const [_getter, _setter] = this.#propSignals[prop];
700
- let setFromProp = false;
701
- const setter = (newValue) => {
702
- if (!setFromProp) this[prop] = newValue;
703
- _setter(newValue);
704
- };
705
- const getter = () => {
706
- const value = _getter();
707
- if (value === void 0) {
708
- const error = new Error(
709
- `Error accessing property: "${prop}"
710
- You must set an initial value before calling a property signal's getter.
711
- `
712
- );
713
- console.error(error);
714
- throw error;
715
- }
716
- return value;
717
- };
718
- getter.getter = true;
719
- Object.defineProperty(this, prop, {
720
- get: getter,
721
- set: (newValue) => {
722
- setFromProp = true;
723
- _setter(newValue);
724
- setFromProp = false;
725
- }
726
- });
727
- const publicSignal = [getter, setter];
728
- publicSignal.init = (value) => {
729
- _setter(value);
730
- return [getter, setter];
731
- };
732
- return publicSignal;
733
- },
733
+ get: (_, prop) => this.#getPropSignal(prop),
734
734
  set: () => {
735
735
  console.error("Signals must be assigned via setters.");
736
736
  return false;
@@ -787,6 +787,27 @@ You must set an initial value before calling a property signal's getter.
787
787
  constructor() {
788
788
  try {
789
789
  super();
790
+ if (!Object.prototype.hasOwnProperty.call(this, "__customCallbackFns")) {
791
+ this.__customCallbackFns = /* @__PURE__ */ new Map();
792
+ }
793
+ for (const attr of this.attributes) {
794
+ this.#attrSignals[attr.name] = createSignal(attr.value);
795
+ }
796
+ for (const [attrName, attr] of this.#attributesAsPropertiesMap) {
797
+ if (!(attrName in this.#attrSignals)) this.#attrSignals[attrName] = createSignal(null);
798
+ const propName = attr.prop;
799
+ const [getter] = this.#getPropSignal(propName, { allowUndefined: true });
800
+ createEffect(() => {
801
+ const value = getter();
802
+ if (value === void 0) return;
803
+ if (value !== null) {
804
+ this.setAttribute(attrName, String(value));
805
+ } else {
806
+ this.removeAttribute(attrName);
807
+ }
808
+ });
809
+ }
810
+ this.#render();
790
811
  } catch (error) {
791
812
  const _error = new Error(
792
813
  "Error instantiating element:\nThis usually occurs if you have errors in the function body of your component. Check prior logs for possible causes.\n",
@@ -795,41 +816,6 @@ You must set an initial value before calling a property signal's getter.
795
816
  console.error(_error);
796
817
  throw _error;
797
818
  }
798
- if (!Object.prototype.hasOwnProperty.call(this, "__customCallbackFns")) {
799
- this.__customCallbackFns = /* @__PURE__ */ new Map();
800
- }
801
- for (const [attrName, attr] of this.#attributesAsPropertiesMap) {
802
- this.#attrSignals[attrName] = createSignal(null);
803
- Object.defineProperty(this, attr.prop, {
804
- get: () => {
805
- if (!(attrName in this.#attrSignals)) this.#attrSignals[attrName] = createSignal(null);
806
- const [getter] = this.#attrSignals[attrName];
807
- const raw = getter();
808
- const rawOnly = raw !== null && attr.value === null;
809
- const value = rawOnly ? attr.coerce(raw) : attr.value;
810
- return value === null ? null : value;
811
- },
812
- set: (newValue) => {
813
- const oldValue = attr.value;
814
- attr.value = newValue;
815
- if (!(attrName in this.#attrSignals)) this.#attrSignals[attrName] = createSignal(null);
816
- const [, attrSetter] = this.#attrSignals[attrName];
817
- const [, propSetter] = this.#propSignals[attrName];
818
- const attrValue = newValue === null ? null : String(newValue);
819
- if (String(oldValue) === attrValue) return;
820
- attrSetter(attrValue);
821
- propSetter(newValue);
822
- if (attrValue === null) this.removeAttribute(attrName);
823
- else this.setAttribute(attrName, attrValue);
824
- },
825
- configurable: true,
826
- enumerable: true
827
- });
828
- }
829
- for (const attr of this.attributes) {
830
- this.#attrSignals[attr.name] = createSignal(attr.value);
831
- }
832
- this.#render();
833
819
  }
834
820
  connectedCallback() {
835
821
  if (this.#observer !== null) {
@@ -847,12 +833,19 @@ You must set an initial value before calling a property signal's getter.
847
833
  fn();
848
834
  }
849
835
  }
836
+ #attributesBusy = false;
850
837
  attributeChangedCallback(name, oldValue, newValue) {
838
+ if (this.#attributesBusy || ANY_BINDING_REGEX.test(newValue ?? "")) return;
851
839
  const [, attrSetter] = this.#attrSignals[name] ?? [];
852
840
  attrSetter?.(newValue);
853
841
  const prop = this.#attributesAsPropertiesMap.get(name);
854
842
  if (prop !== void 0) {
855
- this[prop.prop] = newValue === null ? null : prop.coerce(newValue);
843
+ const propName = prop.prop;
844
+ this.#attributesBusy = true;
845
+ const [, propSetter] = this.#getPropSignal(propName);
846
+ const propValue = newValue === null ? null : prop.coerce(newValue);
847
+ propSetter(propValue);
848
+ this.#attributesBusy = false;
856
849
  }
857
850
  for (const fn of this.#attributeChangedFns) {
858
851
  fn(name, oldValue, newValue);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "thunderous",
3
- "version": "2.3.4",
3
+ "version": "2.3.6",
4
4
  "description": "",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",