@schukai/monster 4.153.0 → 4.154.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.
Files changed (52) hide show
  1. package/package.json +1 -1
  2. package/source/components/form/message-state-button.mjs +1 -1
  3. package/source/components/form/select-link.mjs +12 -1
  4. package/source/components/state/log.mjs +3 -7
  5. package/source/components/state/thread.mjs +3 -7
  6. package/source/components/time/day.mjs +0 -19
  7. package/source/components/time/month-calendar.mjs +5 -1
  8. package/source/data/buildmap.mjs +6 -5
  9. package/source/data/buildtree.mjs +0 -8
  10. package/source/data/datasource.mjs +3 -10
  11. package/source/data/diff.mjs +13 -12
  12. package/source/data/extend.mjs +23 -1
  13. package/source/data/pathfinder.mjs +228 -32
  14. package/source/data/transformer.mjs +2 -2
  15. package/source/dom/customcontrol.mjs +35 -9
  16. package/source/dom/customelement.mjs +293 -31
  17. package/source/dom/slotted.mjs +46 -31
  18. package/source/dom/template.mjs +4 -1
  19. package/source/dom/updater.mjs +57 -17
  20. package/source/types/base.mjs +7 -6
  21. package/source/types/basewithoptions.mjs +1 -8
  22. package/source/types/internal.mjs +4 -10
  23. package/source/types/node.mjs +1 -3
  24. package/source/types/observerlist.mjs +9 -17
  25. package/source/types/proxyobserver.mjs +51 -12
  26. package/source/types/tokenlist.mjs +13 -8
  27. package/source/util/clone.mjs +44 -14
  28. package/test/cases/browser-bundle.mjs +75 -0
  29. package/test/cases/components/time/month-calendar.mjs +115 -0
  30. package/test/cases/data/buildmap.mjs +14 -0
  31. package/test/cases/data/buildtree.mjs +49 -0
  32. package/test/cases/data/diff.mjs +86 -0
  33. package/test/cases/data/extend.mjs +52 -0
  34. package/test/cases/data/pathfinder-resolution-baseline.mjs +322 -0
  35. package/test/cases/data/pathfinder.mjs +163 -0
  36. package/test/cases/data/transformer.mjs +26 -0
  37. package/test/cases/dom/customcontrol.mjs +85 -0
  38. package/test/cases/dom/customelement-rebind-baseline.mjs +323 -0
  39. package/test/cases/dom/customelement-teardown.mjs +408 -0
  40. package/test/cases/dom/customelement.mjs +408 -0
  41. package/test/cases/dom/dimension.mjs +34 -10
  42. package/test/cases/dom/slotted-nodes.mjs +48 -1
  43. package/test/cases/dom/template.mjs +11 -0
  44. package/test/cases/dom/updater-remove.mjs +198 -0
  45. package/test/cases/dom/updater.mjs +118 -0
  46. package/test/cases/packaging.mjs +213 -0
  47. package/test/cases/types/base.mjs +50 -0
  48. package/test/cases/types/observerlist.mjs +103 -1
  49. package/test/cases/types/proxyobserver.mjs +189 -0
  50. package/test/cases/types/regex.mjs +4 -0
  51. package/test/cases/types/tokenlist.mjs +64 -1
  52. package/test/cases/util/clone.mjs +115 -0
@@ -377,6 +377,7 @@ function getInternal() {
377
377
  }
378
378
 
379
379
  const debounceValueSymbol = Symbol("debounceValue");
380
+ const pendingValueAttributeSymbol = Symbol("pendingValueAttribute");
380
381
 
381
382
  /**
382
383
  *
@@ -388,7 +389,17 @@ const debounceValueSymbol = Symbol("debounceValue");
388
389
  */
389
390
  function initValueAttributeObserver() {
390
391
  const self = this;
391
- this[attributeObserverSymbol]["value"] = () => {
392
+ this[attributeObserverSymbol]["value"] = (newValue, oldValue) => {
393
+ if (!self[pendingValueAttributeSymbol]) {
394
+ self[pendingValueAttributeSymbol] = {
395
+ oldValue: normalizeAttributeValue(oldValue),
396
+ newValue: normalizeAttributeValue(newValue),
397
+ };
398
+ } else {
399
+ self[pendingValueAttributeSymbol].newValue =
400
+ normalizeAttributeValue(newValue);
401
+ }
402
+
392
403
  if (self[debounceValueSymbol] instanceof DeadMansSwitch) {
393
404
  try {
394
405
  self[debounceValueSymbol].touch();
@@ -402,18 +413,33 @@ function initValueAttributeObserver() {
402
413
  }
403
414
 
404
415
  self[debounceValueSymbol] = new DeadMansSwitch(10, () => {
405
- const oldValue = self.getAttribute("value");
406
- const newValue = self.value;
407
-
408
- if (oldValue !== newValue) {
409
- setTimeout(() => {
410
- self.value = self.getAttribute("value") ?? "";
411
- }, 0);
412
- }
416
+ const pending = self[pendingValueAttributeSymbol];
417
+ delete self[pendingValueAttributeSymbol];
418
+ delete self[debounceValueSymbol];
419
+ if (!pending) return;
420
+
421
+ setTimeout(() => {
422
+ const currentValue = normalizeAttributeValue(self.value);
423
+ if (
424
+ currentValue === pending.oldValue &&
425
+ currentValue !== pending.newValue
426
+ ) {
427
+ self.value = pending.newValue;
428
+ }
429
+ }, 0);
413
430
  });
414
431
  };
415
432
  }
416
433
 
434
+ /**
435
+ * @param {*} value
436
+ * @return {string}
437
+ * @private
438
+ */
439
+ function normalizeAttributeValue(value) {
440
+ return value === null || value === undefined ? "" : String(value);
441
+ }
442
+
417
443
  const disabledSyncSymbol = Symbol("disabledSync");
418
444
  const disabledAttributeSyncSymbol = Symbol("disabledAttributeSync");
419
445
 
@@ -36,7 +36,11 @@ import {
36
36
  validateObject,
37
37
  } from "../types/validate.mjs";
38
38
  import { clone } from "../util/clone.mjs";
39
- import { getLinkedObjects, hasObjectLink } from "./attributes.mjs";
39
+ import {
40
+ getLinkedObjects,
41
+ hasObjectLink,
42
+ removeObjectLink,
43
+ } from "./attributes.mjs";
40
44
  import {
41
45
  ATTRIBUTE_DISABLED,
42
46
  ATTRIBUTE_OPTIONS,
@@ -119,6 +123,8 @@ const updateCloneDataSymbol = Symbol("@schukai/monster/dom/@@updateCloneData");
119
123
  const scriptHostElementSymbol = Symbol("scriptHostElement");
120
124
  const optionDefaultsSymbol = Symbol("optionDefaults");
121
125
  const managedShadowRootSymbol = Symbol("managedShadowRoot");
126
+ const assembledOnceSymbol = Symbol("assembledOnce");
127
+ const teardownCallbacksSymbol = Symbol("teardownCallbacks");
122
128
  const visibilityStateSymbol = Symbol("visibilityState");
123
129
  let hostVisibilityStyleSheet = null;
124
130
 
@@ -358,7 +364,7 @@ class CustomElement extends HTMLElement {
358
364
  * @property {Function} templateFormatter.marker.open=null Specifies the opening marker for the templates.
359
365
  * @property {Function} templateFormatter.marker.close=null Specifies the closing marker for the templates.
360
366
  * @property {Boolean} templateFormatter.i18n=false Specifies whether the templates should be formatted with i18n.
361
- * @property {Boolean} eventProcessing=false Specifies whether the control processes events.
367
+ * @property {Boolean} eventProcessing=false Specifies whether the control processes events. A bound input writes its value into the updater snapshot, not into the options. The synchronisation is one way, options to snapshot, so getOption() does not observe user input; read it with getInternalUpdateCloneData().
362
368
  * @property {Object} updater Specifies updater options.
363
369
  * @property {Boolean} updater.batchUpdates=false Batches updater content/attribute updates per diff.
364
370
  * @since 1.8.0
@@ -540,16 +546,9 @@ class CustomElement extends HTMLElement {
540
546
  * @since 1.10.0
541
547
  */
542
548
  getOption(path, defaultValue = undefined) {
543
- let value;
544
-
545
- try {
546
- value = new Pathfinder(
547
- this[internalSymbol].getRealSubject()["options"],
548
- ).getVia(path);
549
- } catch (e) {}
550
-
551
- if (value === undefined) return defaultValue;
552
- return value;
549
+ return new Pathfinder(
550
+ this[internalSymbol].getRealSubject()["options"],
551
+ ).getVia(path, defaultValue);
553
552
  }
554
553
 
555
554
  /**
@@ -699,6 +698,7 @@ class CustomElement extends HTMLElement {
699
698
  [assembleMethodSymbol]() {
700
699
  let elements;
701
700
  let nodeList;
701
+
702
702
  // Extract options from attributes and set them
703
703
  const AttributeOptions = getOptionsFromAttributes.call(this);
704
704
  if (
@@ -734,7 +734,15 @@ class CustomElement extends HTMLElement {
734
734
  // Initialize the custom elements inside the shadow root and the slotted elements
735
735
  initFromCallbackHost.call(this);
736
736
  try {
737
- nodeList = new Set([...elements, ...getSlottedElements.call(this)]);
737
+ nodeList = new Set([
738
+ ...elements,
739
+ ...getSlottedElements.call(
740
+ this,
741
+ undefined,
742
+ undefined,
743
+ getManagedShadowRoot.call(this),
744
+ ),
745
+ ]);
738
746
  } catch (e) {
739
747
  nodeList = elements;
740
748
  }
@@ -761,13 +769,40 @@ class CustomElement extends HTMLElement {
761
769
  );
762
770
  // Attach a mutation observer to observe changes to the attributes of the element
763
771
  attachAttributeChangeMutationObserver.call(this);
772
+ attachChildMutationObserver.call(this);
764
773
 
765
774
  if (isFunction(this[internalSymbol]?.syncDisabledState)) {
766
- this[internalSymbol].syncDisabledState();
775
+ this[internalSymbol].syncDisabledState(true);
767
776
  }
768
777
 
769
778
  syncVisibilityState.call(this);
770
779
 
780
+ this[assembledOnceSymbol] = true;
781
+
782
+ return this;
783
+ }
784
+
785
+ /**
786
+ * Registers a callback that runs when the element is disconnected.
787
+ *
788
+ * The registration is consumed by the first teardown, so a callback that is
789
+ * needed again after a reconnect has to be registered again. A callback that
790
+ * throws does not stop the remaining teardown; the error is reported through
791
+ * the error attribute.
792
+ *
793
+ * @param {function} callback
794
+ * @return {CustomElement}
795
+ * @throws {TypeError} value is not a function
796
+ * @since 4.154.0
797
+ */
798
+ registerTeardown(callback) {
799
+ validateFunction(callback);
800
+
801
+ if (!isArray(this[teardownCallbacksSymbol])) {
802
+ this[teardownCallbacksSymbol] = [];
803
+ }
804
+
805
+ this[teardownCallbacksSymbol].push(callback);
771
806
  return this;
772
807
  }
773
808
 
@@ -775,7 +810,15 @@ class CustomElement extends HTMLElement {
775
810
  * You know what you are doing? This function is only for advanced users.
776
811
  * The result is a clone of the internal data.
777
812
  *
813
+ * This snapshot is the object the updaters are bound to. It is the only
814
+ * place where values typed into a `data-monster-bind` input end up when
815
+ * `eventProcessing` is enabled, because the synchronisation runs one way
816
+ * from the options into the snapshot and never back. A reconnect rebinds to
817
+ * the same snapshot, so that state survives.
818
+ *
778
819
  * @return {*}
820
+ * @see Monster.DOM.CustomElement#getOption
821
+ * @since 3.56.0
779
822
  */
780
823
  getInternalUpdateCloneData() {
781
824
  return clone(this[updateCloneDataSymbol]);
@@ -790,19 +833,31 @@ class CustomElement extends HTMLElement {
790
833
  * @see https://developer.mozilla.org/en-US/docs/Web/API/Element/connectedCallback
791
834
  */
792
835
  connectedCallback() {
793
- // Check if the object has already been initialized
794
- if (!hasObjectLink(this, customElementUpdaterLinkSymbol)) {
795
- try {
796
- initOptionsFromAttributes(
797
- this,
798
- this[internalSymbol].getSubject()?.["options"],
799
- );
800
- } catch (e) {
801
- addErrorAttribute(this, e);
802
- }
803
- // If not, call the assembleMethod to initialize the object
804
- this[assembleMethodSymbol]();
836
+ // Already bound, nothing to do. Moving a connected element inside its own
837
+ // parent fires disconnect and connect, so this runs often.
838
+ if (hasObjectLink(this, customElementUpdaterLinkSymbol)) {
839
+ return;
840
+ }
841
+
842
+ try {
843
+ initOptionsFromAttributes(
844
+ this,
845
+ this[internalSymbol].getSubject()?.["options"],
846
+ );
847
+ } catch (e) {
848
+ addErrorAttribute(this, e);
849
+ }
850
+
851
+ // The assemble method is a one shot: derived classes override it and
852
+ // rebuild their own state in there, for example state/log.mjs resets its
853
+ // entries array and its entry maps. After a disconnect the rendered DOM
854
+ // and that state are still intact, so a reconnect only relinks.
855
+ if (this[assembledOnceSymbol] === true) {
856
+ rebindAfterReconnect.call(this);
857
+ return;
805
858
  }
859
+
860
+ this[assembleMethodSymbol]();
806
861
  }
807
862
 
808
863
  /**
@@ -811,7 +866,16 @@ class CustomElement extends HTMLElement {
811
866
  * @return {void}
812
867
  * @since 1.7.0
813
868
  */
814
- disconnectedCallback() {}
869
+ disconnectedCallback() {
870
+ runTeardownCallbacks.call(this);
871
+ disposeLinkedUpdaters.call(this);
872
+ detachAttributeChangeMutationObserver.call(this);
873
+ detachChildMutationObserver.call(this);
874
+
875
+ if (isFunction(this[internalSymbol]?.disconnectDisabledObservers)) {
876
+ this[internalSymbol].disconnectDisabledObservers();
877
+ }
878
+ }
815
879
 
816
880
  /**
817
881
  * The custom element has been moved into a new document (e.g. someone called document.adoptNode(el)).
@@ -967,6 +1031,168 @@ function initFromCallbackHost() {
967
1031
  callControlCallback.call(this, callBackFunctionName);
968
1032
  }
969
1033
 
1034
+ /**
1035
+ * Relinks a previously assembled element after it was reconnected.
1036
+ *
1037
+ * Deliberately does not touch the shadow root, the rendered markup, the options
1038
+ * snapshot or anything a derived assemble method built. It only recreates what
1039
+ * the teardown released: the updaters and the observers.
1040
+ *
1041
+ * @private
1042
+ * @this CustomElement
1043
+ */
1044
+ function rebindAfterReconnect() {
1045
+ let elements = getManagedShadowRoot.call(this)?.childNodes;
1046
+ if (!(elements instanceof NodeList)) {
1047
+ elements = this.childNodes;
1048
+ }
1049
+
1050
+ let nodeList;
1051
+ try {
1052
+ nodeList = new Set([
1053
+ ...elements,
1054
+ ...getSlottedElements.call(
1055
+ this,
1056
+ undefined,
1057
+ undefined,
1058
+ getManagedShadowRoot.call(this),
1059
+ ),
1060
+ ]);
1061
+ } catch {
1062
+ nodeList = elements;
1063
+ }
1064
+
1065
+ const cfg = {};
1066
+ if (this.getOption("eventProcessing") === true) {
1067
+ cfg.eventProcessing = true;
1068
+ }
1069
+ if (this.getOption("updater.batchUpdates") === true) {
1070
+ cfg.batchUpdates = true;
1071
+ }
1072
+
1073
+ // Binds to the same snapshot object on purpose: it carries the runtime state
1074
+ // the updaters produced, including values written back by event processing,
1075
+ // which the component options never receive.
1076
+ addObjectWithUpdaterToElement.call(
1077
+ this,
1078
+ nodeList,
1079
+ customElementUpdaterLinkSymbol,
1080
+ this[updateCloneDataSymbol],
1081
+ cfg,
1082
+ );
1083
+
1084
+ attachAttributeChangeMutationObserver.call(this);
1085
+ attachChildMutationObserver.call(this);
1086
+
1087
+ if (isFunction(this[internalSymbol]?.syncDisabledState)) {
1088
+ this[internalSymbol].syncDisabledState(true);
1089
+ }
1090
+
1091
+ syncVisibilityState.call(this);
1092
+ }
1093
+
1094
+ /**
1095
+ * @private
1096
+ * @this CustomElement
1097
+ */
1098
+ function runTeardownCallbacks() {
1099
+ const callbacks = this[teardownCallbacksSymbol];
1100
+ if (!isArray(callbacks) || callbacks.length === 0) {
1101
+ return;
1102
+ }
1103
+
1104
+ // taken first, so a callback that re-registers during teardown does not run
1105
+ // in the same pass
1106
+ this[teardownCallbacksSymbol] = [];
1107
+
1108
+ for (const callback of callbacks) {
1109
+ try {
1110
+ callback.call(this);
1111
+ } catch (e) {
1112
+ addErrorAttribute(this, e);
1113
+ }
1114
+ }
1115
+ }
1116
+
1117
+ /**
1118
+ * @private
1119
+ * @this CustomElement
1120
+ */
1121
+ function disposeLinkedUpdaters() {
1122
+ if (!hasObjectLink(this, customElementUpdaterLinkSymbol)) {
1123
+ return;
1124
+ }
1125
+
1126
+ try {
1127
+ for (const group of getLinkedObjects(
1128
+ this,
1129
+ customElementUpdaterLinkSymbol,
1130
+ )) {
1131
+ if (!(group instanceof Set)) {
1132
+ continue;
1133
+ }
1134
+
1135
+ for (const updater of group) {
1136
+ if (typeof updater?.dispose !== "function") {
1137
+ continue;
1138
+ }
1139
+
1140
+ try {
1141
+ updater.dispose();
1142
+ } catch (e) {
1143
+ addErrorAttribute(this, e);
1144
+ }
1145
+ }
1146
+ }
1147
+
1148
+ removeObjectLink(this, customElementUpdaterLinkSymbol);
1149
+ } catch (e) {
1150
+ addErrorAttribute(this, e);
1151
+ }
1152
+ }
1153
+
1154
+ /**
1155
+ * The symbol is deleted, not only disconnected: the attach guard treats a
1156
+ * present symbol as an active observer and would otherwise never replace it.
1157
+ *
1158
+ * @private
1159
+ * @this CustomElement
1160
+ */
1161
+ function detachAttributeChangeMutationObserver() {
1162
+ const observer = this[attributeMutationObserverSymbol];
1163
+ if (observer) {
1164
+ try {
1165
+ observer.disconnect();
1166
+ } catch (e) {
1167
+ addErrorAttribute(this, e);
1168
+ }
1169
+ }
1170
+
1171
+ delete this[attributeMutationObserverSymbol];
1172
+ }
1173
+
1174
+ /**
1175
+ * @private
1176
+ * @this CustomElement
1177
+ */
1178
+ function detachChildMutationObserver() {
1179
+ const observer = this[childMutationObserverSymbol];
1180
+ if (observer) {
1181
+ try {
1182
+ observer.disconnect();
1183
+ } catch (e) {
1184
+ addErrorAttribute(this, e);
1185
+ }
1186
+ }
1187
+
1188
+ delete this[childMutationObserverSymbol];
1189
+
1190
+ if (this[childMutationTimerSymbol]) {
1191
+ clearTimeout(this[childMutationTimerSymbol]);
1192
+ this[childMutationTimerSymbol] = null;
1193
+ }
1194
+ }
1195
+
970
1196
  /**
971
1197
  * This method is called when the element is first created.
972
1198
  *
@@ -1095,12 +1321,21 @@ function containChildNode(node) {
1095
1321
  return true;
1096
1322
  }
1097
1323
 
1098
- for (const [, e] of Object.entries(this.childNodes)) {
1099
- if (e.contains(node)) {
1324
+ // Node.contains() already covers the whole light DOM subtree, so the only
1325
+ // thing left to search is the shadow root of nested elements, which no
1326
+ // contains() call can see.
1327
+ for (const child of this.childNodes) {
1328
+ const shadowRoot = child?.shadowRoot;
1329
+ if (
1330
+ shadowRoot instanceof ShadowRoot &&
1331
+ containChildNode.call(shadowRoot, node)
1332
+ ) {
1100
1333
  return true;
1101
1334
  }
1102
1335
 
1103
- containChildNode.call(e, node);
1336
+ if (child.childNodes?.length && containChildNode.call(child, node)) {
1337
+ return true;
1338
+ }
1104
1339
  }
1105
1340
 
1106
1341
  return false;
@@ -1133,7 +1368,12 @@ function initOptionObserver() {
1133
1368
  targets.add(element);
1134
1369
  }
1135
1370
  }
1136
- for (const element of getSlottedElements.call(self, disabledQuery)) {
1371
+ for (const element of getSlottedElements.call(
1372
+ self,
1373
+ disabledQuery,
1374
+ undefined,
1375
+ getManagedShadowRoot.call(self),
1376
+ )) {
1137
1377
  targets.add(element);
1138
1378
  }
1139
1379
  } catch (e) {}
@@ -1220,6 +1460,8 @@ function initOptionObserver() {
1220
1460
  self.attachObserver(new Observer(syncDisabledState));
1221
1461
 
1222
1462
  self[internalSymbol].syncDisabledState = syncDisabledState;
1463
+ self[internalSymbol].disconnectDisabledObservers =
1464
+ disconnectDisabledObservers;
1223
1465
 
1224
1466
  let updaterSyncRevision = 0;
1225
1467
  self.attachObserver(
@@ -1293,6 +1535,17 @@ function syncUpdaterSubject(target, source) {
1293
1535
  return;
1294
1536
  }
1295
1537
 
1538
+ // Empty option objects are also used as refresh markers by editable
1539
+ // datasource components. Keep their established merge behavior while
1540
+ // allowing non-empty replacement objects to remove stale keys.
1541
+ if (Object.keys(source).length > 0) {
1542
+ for (const key of Object.keys(target)) {
1543
+ if (!Object.hasOwn(source, key)) {
1544
+ delete target[key];
1545
+ }
1546
+ }
1547
+ }
1548
+
1296
1549
  for (const [key, value] of Object.entries(source)) {
1297
1550
  syncUpdaterValue(target, key, value);
1298
1551
  }
@@ -1679,6 +1932,15 @@ function initShadowRoot() {
1679
1932
  let template;
1680
1933
  let html;
1681
1934
 
1935
+ // A rebind keeps the rendered shadow DOM. Calling attachShadow() again would
1936
+ // throw, after which the main template used to be appended into the light
1937
+ // DOM and a second updater set was bound to the same host.
1938
+ const existing = getManagedShadowRoot.call(this);
1939
+ if (existing instanceof ShadowRoot) {
1940
+ this[managedShadowRootSymbol] = existing;
1941
+ return this;
1942
+ }
1943
+
1682
1944
  try {
1683
1945
  template = findDocumentTemplate(this.constructor.getTag());
1684
1946
  } catch (e) {
@@ -27,37 +27,33 @@ export { getSlottedElements, getSlottedNodes };
27
27
  * @since 3.33.0
28
28
  * @throws {Error} query must be a string
29
29
  */
30
- function getSlottedNodes(query, name) {
30
+ function getSlottedNodes(query, name, managedShadowRoot) {
31
31
  const result = new Set();
32
+ const shadowRoot = getShadowRoot(this, managedShadowRoot);
32
33
 
33
- if (!this.shadowRoot) {
34
+ if (!(shadowRoot instanceof ShadowRoot)) {
34
35
  return result;
35
36
  }
36
37
 
37
- let selector = "slot";
38
- if (name !== undefined) {
39
- if (name === null) {
40
- selector += ":not([name])";
41
- } else {
42
- selector += `[name=${validateString(name)}]`;
43
- }
44
- }
45
-
46
- const slots = this.shadowRoot.querySelectorAll(selector);
38
+ const slots = getSlotsByName(shadowRoot, name);
47
39
 
48
- for (const [, slot] of Object.entries(slots)) {
40
+ for (const slot of slots) {
49
41
  slot.assignedNodes().forEach(function (node) {
50
42
  if (node === null || node === undefined) {
51
43
  return;
52
44
  }
53
45
 
54
46
  if (isString(query)) {
55
- node.querySelectorAll(query).forEach(function (n) {
56
- result.add(n);
57
- });
58
-
59
- if (node.matches(query)) {
47
+ if (query.length === 0) {
60
48
  result.add(node);
49
+ } else if (node instanceof Element) {
50
+ node.querySelectorAll(query).forEach(function (n) {
51
+ result.add(n);
52
+ });
53
+
54
+ if (node.matches(query)) {
55
+ result.add(node);
56
+ }
61
57
  }
62
58
  } else if (query !== undefined) {
63
59
  throw new Error("query must be a string");
@@ -80,25 +76,17 @@ function getSlottedNodes(query, name) {
80
76
  * @since 1.23.0
81
77
  * @throws {Error} query must be a string
82
78
  */
83
- function getSlottedElements(query, name) {
79
+ function getSlottedElements(query, name, managedShadowRoot) {
84
80
  const result = new Set();
81
+ const shadowRoot = getShadowRoot(this, managedShadowRoot);
85
82
 
86
- if (!(this.shadowRoot instanceof ShadowRoot)) {
83
+ if (!(shadowRoot instanceof ShadowRoot)) {
87
84
  return result;
88
85
  }
89
86
 
90
- let selector = "slot";
91
- if (name !== undefined) {
92
- if (name === null) {
93
- selector += ":not([name])";
94
- } else {
95
- selector += `[name=${validateString(name)}]`;
96
- }
97
- }
98
-
99
- const slots = this.shadowRoot.querySelectorAll(selector);
87
+ const slots = getSlotsByName(shadowRoot, name);
100
88
 
101
- for (const [, slot] of Object.entries(slots)) {
89
+ for (const slot of slots) {
102
90
  slot.assignedElements().forEach(function (node) {
103
91
  if (
104
92
  !(node instanceof HTMLElement) &&
@@ -129,3 +117,30 @@ function getSlottedElements(query, name) {
129
117
 
130
118
  return result;
131
119
  }
120
+
121
+ /**
122
+ * @param {*} host
123
+ * @param {*} managedShadowRoot
124
+ * @return {ShadowRoot|null}
125
+ * @private
126
+ */
127
+ function getShadowRoot(host, managedShadowRoot) {
128
+ if (managedShadowRoot instanceof ShadowRoot) return managedShadowRoot;
129
+ return host?.shadowRoot instanceof ShadowRoot ? host.shadowRoot : null;
130
+ }
131
+
132
+ /**
133
+ * Filter slots by their DOM name instead of interpolating an unsafe selector.
134
+ * @param {ShadowRoot} shadowRoot
135
+ * @param {string|undefined|null} name
136
+ * @return {HTMLSlotElement[]}
137
+ * @private
138
+ */
139
+ function getSlotsByName(shadowRoot, name) {
140
+ if (name !== undefined && name !== null) validateString(name);
141
+ return Array.from(shadowRoot.querySelectorAll("slot")).filter((slot) => {
142
+ if (name === undefined) return true;
143
+ if (name === null) return slot.name === "";
144
+ return slot.name === name;
145
+ });
146
+ }
@@ -156,7 +156,10 @@ export function findDocumentTemplate(id, currentNode) {
156
156
  )
157
157
  ) {
158
158
  if (currentNode instanceof Node) {
159
- if (currentNode.hasAttribute(ATTRIBUTE_TEMPLATE_PREFIX)) {
159
+ if (
160
+ currentNode instanceof Element &&
161
+ currentNode.hasAttribute(ATTRIBUTE_TEMPLATE_PREFIX)
162
+ ) {
160
163
  prefixID = currentNode.getAttribute(ATTRIBUTE_TEMPLATE_PREFIX);
161
164
  }
162
165