@schukai/monster 4.152.0 → 4.153.1
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 +24 -1
- package/package.json +1 -1
- package/source/components/content/viewer/message.mjs +1 -0
- package/source/components/form/select.mjs +2 -0
- package/source/components/form/util/fetch.mjs +6 -7
- package/source/components/state/log.mjs +7 -0
- package/source/components/state/thread.mjs +11 -0
- package/source/components/time/month-calendar.mjs +80 -30
- package/source/data/buildmap.mjs +3 -2
- package/source/data/buildtree.mjs +0 -8
- package/source/data/pathfinder.mjs +48 -19
- package/source/data/transformer.mjs +2 -2
- package/source/dom/customcontrol.mjs +35 -9
- package/source/dom/customelement.mjs +21 -2
- package/source/dom/resource.mjs +19 -5
- package/source/dom/sanitize-html.mjs +34 -29
- package/source/dom/slotted.mjs +46 -31
- package/source/dom/template.mjs +4 -1
- package/source/dom/updater.mjs +51 -15
- package/source/types/node.mjs +1 -3
- package/source/types/proxyobserver.mjs +32 -10
- package/test/cases/components/datatable/drag-scroll.mjs +28 -1
- package/test/cases/components/form/button-bar.mjs +9 -6
- package/test/cases/components/form/confirm-button.mjs +4 -4
- package/test/cases/components/form/control-bar.mjs +73 -72
- package/test/cases/components/form/input-group.mjs +3 -1
- package/test/cases/components/form/password.mjs +2 -2
- package/test/cases/components/form/popper-button.mjs +2 -2
- package/test/cases/components/form/select-sort-callback.mjs +57 -0
- package/test/cases/components/form/select.mjs +2 -2
- package/test/cases/components/form/sheet.mjs +13 -1
- package/test/cases/components/layout/popper.mjs +3 -1
- package/test/cases/components/layout/tabs.mjs +4 -1
- package/test/cases/components/navigation/site-navigation.mjs +12 -26
- package/test/cases/components/navigation/table-of-content.mjs +3 -0
- package/test/cases/components/state/ticker-lifecycle.mjs +47 -0
- package/test/cases/components/time/month-calendar.mjs +100 -0
- package/test/cases/data/buildmap.mjs +14 -0
- package/test/cases/data/buildtree.mjs +49 -0
- package/test/cases/data/pathfinder.mjs +34 -0
- package/test/cases/data/transformer.mjs +33 -0
- package/test/cases/dom/customcontrol.mjs +87 -6
- package/test/cases/dom/customelement.mjs +54 -0
- package/test/cases/dom/ready.mjs +3 -3
- package/test/cases/dom/resource-availability.mjs +46 -0
- package/test/cases/dom/resourcemanager.mjs +12 -6
- package/test/cases/dom/sanitize-html.mjs +71 -0
- package/test/cases/dom/slotted-nodes.mjs +48 -1
- package/test/cases/dom/template.mjs +11 -0
- package/test/cases/dom/updater.mjs +119 -1
- package/test/cases/types/proxyobserver.mjs +75 -0
- package/test/cases/util/processing.mjs +3 -3
|
@@ -16,39 +16,44 @@ export function sanitizeHtml(htmlString, options = {}) {
|
|
|
16
16
|
);
|
|
17
17
|
|
|
18
18
|
const blockedTags = options.blockedTags || [];
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
19
|
+
const animationTags = new Set([
|
|
20
|
+
"animate",
|
|
21
|
+
"set",
|
|
22
|
+
"animatemotion",
|
|
23
|
+
"animatetransform",
|
|
24
|
+
"animatecolor",
|
|
25
|
+
"discard",
|
|
26
|
+
]);
|
|
27
|
+
const urlAttrs = new Set(["href", "xlink:href", "action", "formaction"]);
|
|
28
|
+
// biome-ignore lint/suspicious/noControlCharactersInRegex: URL schemes must be checked after removing control characters.
|
|
29
|
+
const urlControlCharacters = /[\u0000-\u0020\u007f-\u009f]/g;
|
|
30
|
+
|
|
31
|
+
function sanitizeTree(root) {
|
|
32
|
+
for (const tag of blockedTags) {
|
|
33
|
+
for (const el of root.querySelectorAll(tag)) el.remove();
|
|
34
|
+
}
|
|
35
|
+
for (const el of root.querySelectorAll("*")) {
|
|
36
|
+
// SVG animations can change a safe URL after the attribute check.
|
|
37
|
+
if (animationTags.has(el.localName.toLowerCase())) {
|
|
38
|
+
el.remove();
|
|
39
|
+
continue;
|
|
37
40
|
}
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
if (urlAttrs.includes(name)) {
|
|
41
|
-
const val = value.trim().toLowerCase();
|
|
42
|
-
if (
|
|
43
|
-
val.startsWith("javascript:") ||
|
|
44
|
-
val.startsWith("data:") ||
|
|
45
|
-
val.startsWith("vbscript:")
|
|
46
|
-
) {
|
|
41
|
+
for (const { name, value } of [...el.attributes]) {
|
|
42
|
+
if (/^on/i.test(name)) {
|
|
47
43
|
el.removeAttribute(name);
|
|
44
|
+
} else if (urlAttrs.has(name.toLowerCase())) {
|
|
45
|
+
const normalized = value.replace(urlControlCharacters, "").toLowerCase();
|
|
46
|
+
if (/^(javascript|data|vbscript):/.test(normalized)) {
|
|
47
|
+
el.removeAttribute(name);
|
|
48
|
+
}
|
|
48
49
|
}
|
|
49
50
|
}
|
|
50
|
-
|
|
51
|
-
|
|
51
|
+
if (el.localName === "template" && el.content) {
|
|
52
|
+
sanitizeTree(el.content);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
sanitizeTree(doc);
|
|
52
57
|
|
|
53
58
|
return doc.body.innerHTML;
|
|
54
59
|
}
|
package/source/dom/slotted.mjs
CHANGED
|
@@ -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 (!
|
|
34
|
+
if (!(shadowRoot instanceof ShadowRoot)) {
|
|
34
35
|
return result;
|
|
35
36
|
}
|
|
36
37
|
|
|
37
|
-
|
|
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
|
|
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
|
-
|
|
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 (!(
|
|
83
|
+
if (!(shadowRoot instanceof ShadowRoot)) {
|
|
87
84
|
return result;
|
|
88
85
|
}
|
|
89
86
|
|
|
90
|
-
|
|
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
|
|
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
|
+
}
|
package/source/dom/template.mjs
CHANGED
|
@@ -156,7 +156,10 @@ export function findDocumentTemplate(id, currentNode) {
|
|
|
156
156
|
)
|
|
157
157
|
) {
|
|
158
158
|
if (currentNode instanceof Node) {
|
|
159
|
-
if (
|
|
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
|
|
package/source/dom/updater.mjs
CHANGED
|
@@ -803,17 +803,34 @@ function parseIntArray(val) {
|
|
|
803
803
|
* @private
|
|
804
804
|
*/
|
|
805
805
|
function retrieveFromBindings() {
|
|
806
|
-
if (
|
|
806
|
+
if (
|
|
807
|
+
this[internalSymbol].element.matches(`[${ATTRIBUTE_UPDATER_BIND}]`) &&
|
|
808
|
+
!isUncheckedRadio(this[internalSymbol].element)
|
|
809
|
+
) {
|
|
807
810
|
retrieveAndSetValue.call(this, this[internalSymbol].element);
|
|
808
811
|
}
|
|
809
812
|
|
|
810
813
|
for (const [, element] of this[internalSymbol].element
|
|
811
814
|
.querySelectorAll(`[${ATTRIBUTE_UPDATER_BIND}]`)
|
|
812
815
|
.entries()) {
|
|
816
|
+
if (isUncheckedRadio(element)) continue;
|
|
813
817
|
retrieveAndSetValue.call(this, element);
|
|
814
818
|
}
|
|
815
819
|
}
|
|
816
820
|
|
|
821
|
+
/**
|
|
822
|
+
* @param {HTMLElement} element
|
|
823
|
+
* @return {boolean}
|
|
824
|
+
* @private
|
|
825
|
+
*/
|
|
826
|
+
function isUncheckedRadio(element) {
|
|
827
|
+
return (
|
|
828
|
+
element instanceof HTMLInputElement &&
|
|
829
|
+
element.type === "radio" &&
|
|
830
|
+
element.checked === false
|
|
831
|
+
);
|
|
832
|
+
}
|
|
833
|
+
|
|
817
834
|
/**
|
|
818
835
|
* @private
|
|
819
836
|
* @license AGPLv3
|
|
@@ -1201,7 +1218,10 @@ function getSlots(container, cache) {
|
|
|
1201
1218
|
return cache.get(container);
|
|
1202
1219
|
}
|
|
1203
1220
|
|
|
1204
|
-
const slots = container.querySelectorAll("slot");
|
|
1221
|
+
const slots = Array.from(container.querySelectorAll("slot"));
|
|
1222
|
+
if (container.localName === "slot") {
|
|
1223
|
+
slots.unshift(container);
|
|
1224
|
+
}
|
|
1205
1225
|
cache.set(container, slots);
|
|
1206
1226
|
return slots;
|
|
1207
1227
|
}
|
|
@@ -1565,6 +1585,9 @@ function replaceKeyedPatchedChildren(
|
|
|
1565
1585
|
const nextNode = preparePatchedNode(reused, renderedValue);
|
|
1566
1586
|
nextNode[patchNodeKeySymbol] = normalizedKey;
|
|
1567
1587
|
if (nextNode !== reused && reused.parentNode === element) {
|
|
1588
|
+
if (reused instanceof HTMLElement) {
|
|
1589
|
+
teardownManagedSubtree(reused);
|
|
1590
|
+
}
|
|
1568
1591
|
element.replaceChild(nextNode, reused);
|
|
1569
1592
|
}
|
|
1570
1593
|
nextNodes.push(nextNode);
|
|
@@ -1591,15 +1614,11 @@ function replaceKeyedPatchedChildren(
|
|
|
1591
1614
|
}
|
|
1592
1615
|
}
|
|
1593
1616
|
|
|
1594
|
-
for (
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
if (element.lastChild !== node) {
|
|
1601
|
-
element.appendChild(node);
|
|
1602
|
-
}
|
|
1617
|
+
for (let index = 0; index < nextNodes.length; index++) {
|
|
1618
|
+
const node = nextNodes[index];
|
|
1619
|
+
const current = element.childNodes[index] ?? null;
|
|
1620
|
+
if (current === node) continue;
|
|
1621
|
+
element.insertBefore(node, current);
|
|
1603
1622
|
}
|
|
1604
1623
|
}
|
|
1605
1624
|
|
|
@@ -1851,8 +1870,7 @@ function runUpdateAttributes(
|
|
|
1851
1870
|
if (
|
|
1852
1871
|
!element.hasAttribute(ATTRIBUTE_UPDATER_SELECT_THIS) &&
|
|
1853
1872
|
!attributes.includes(`path:${current}`) &&
|
|
1854
|
-
!attributes
|
|
1855
|
-
!attributes.startsWith("i18n:")
|
|
1873
|
+
!hasUnconditionalBinding(attributes)
|
|
1856
1874
|
) {
|
|
1857
1875
|
continue;
|
|
1858
1876
|
}
|
|
@@ -1933,8 +1951,7 @@ function runUpdateProperties(
|
|
|
1933
1951
|
if (
|
|
1934
1952
|
!element.hasAttribute(ATTRIBUTE_UPDATER_SELECT_THIS) &&
|
|
1935
1953
|
!properties.includes(`path:${current}`) &&
|
|
1936
|
-
!properties
|
|
1937
|
-
!properties.startsWith("i18n:")
|
|
1954
|
+
!hasUnconditionalBinding(properties)
|
|
1938
1955
|
) {
|
|
1939
1956
|
continue;
|
|
1940
1957
|
}
|
|
@@ -1981,6 +1998,9 @@ function runUpdateProperties(
|
|
|
1981
1998
|
|
|
1982
1999
|
function handleInputControlAttributeUpdate(element, name, value) {
|
|
1983
2000
|
if (element instanceof HTMLSelectElement) {
|
|
2001
|
+
if (name !== "value") {
|
|
2002
|
+
return;
|
|
2003
|
+
}
|
|
1984
2004
|
switch (element.type) {
|
|
1985
2005
|
case "select-multiple":
|
|
1986
2006
|
const selectedValues = isArray(value)
|
|
@@ -2054,6 +2074,22 @@ function handleInputControlAttributeUpdate(element, name, value) {
|
|
|
2054
2074
|
}
|
|
2055
2075
|
}
|
|
2056
2076
|
|
|
2077
|
+
/**
|
|
2078
|
+
* Check whether an attribute/property definition contains a command that is
|
|
2079
|
+
* independent from the changed subject path.
|
|
2080
|
+
* @param {string} definitions
|
|
2081
|
+
* @return {boolean}
|
|
2082
|
+
* @private
|
|
2083
|
+
*/
|
|
2084
|
+
function hasUnconditionalBinding(definitions) {
|
|
2085
|
+
return definitions.split(",").some((definition) => {
|
|
2086
|
+
const separator = definition.indexOf(" ");
|
|
2087
|
+
if (separator < 0) return false;
|
|
2088
|
+
const command = trimSpaces(definition.substring(separator + 1));
|
|
2089
|
+
return command.startsWith("static:") || command.startsWith("i18n:");
|
|
2090
|
+
});
|
|
2091
|
+
}
|
|
2092
|
+
|
|
2057
2093
|
/**
|
|
2058
2094
|
* @private
|
|
2059
2095
|
* @param {HTMLElement|*} element
|
package/source/types/node.mjs
CHANGED
|
@@ -199,8 +199,6 @@ class Node extends Base {
|
|
|
199
199
|
* @return {setChildLevelAndParent}
|
|
200
200
|
*/
|
|
201
201
|
function setChildLevelAndParent(node, operand, visitedNodes) {
|
|
202
|
-
const self = this;
|
|
203
|
-
|
|
204
202
|
if (visitedNodes.has(node)) {
|
|
205
203
|
throw new Error(
|
|
206
204
|
"the node has already been visited and cannot be traversed again",
|
|
@@ -217,7 +215,7 @@ function setChildLevelAndParent(node, operand, visitedNodes) {
|
|
|
217
215
|
child[treeStructureSymbol].parent = node;
|
|
218
216
|
child[treeStructureSymbol].level =
|
|
219
217
|
node[treeStructureSymbol].level + operand;
|
|
220
|
-
setChildLevelAndParent.call(
|
|
218
|
+
setChildLevelAndParent.call(node, child, operand, visitedNodes);
|
|
221
219
|
});
|
|
222
220
|
return this;
|
|
223
221
|
}
|
|
@@ -156,6 +156,15 @@ function getHandler() {
|
|
|
156
156
|
}
|
|
157
157
|
|
|
158
158
|
const value = Reflect.get(target, key, receiver);
|
|
159
|
+
const descriptor = Reflect.getOwnPropertyDescriptor(target, key);
|
|
160
|
+
if (
|
|
161
|
+
descriptor &&
|
|
162
|
+
descriptor.configurable === false &&
|
|
163
|
+
"value" in descriptor &&
|
|
164
|
+
descriptor.writable === false
|
|
165
|
+
) {
|
|
166
|
+
return value;
|
|
167
|
+
}
|
|
159
168
|
|
|
160
169
|
if (typeof key === "symbol") {
|
|
161
170
|
return value;
|
|
@@ -233,16 +242,12 @@ function getHandler() {
|
|
|
233
242
|
|
|
234
243
|
// https://262.ecma-international.org/9.0/#sec-proxy-object-internal-methods-and-internal-slots-delete-p
|
|
235
244
|
deleteProperty: function (target, key) {
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
proxy.observers.notify(proxy);
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
return true;
|
|
245
|
+
const hadOwnProperty = Object.hasOwn(target, key);
|
|
246
|
+
const result = Reflect.deleteProperty(target, key);
|
|
247
|
+
if (result && hadOwnProperty && typeof key !== "symbol") {
|
|
248
|
+
proxy.observers.notify(proxy);
|
|
244
249
|
}
|
|
245
|
-
return
|
|
250
|
+
return result;
|
|
246
251
|
},
|
|
247
252
|
|
|
248
253
|
// https://262.ecma-international.org/9.0/#sec-proxy-object-internal-methods-and-internal-slots-defineownproperty-p-desc
|
|
@@ -277,5 +282,22 @@ function getHandler() {
|
|
|
277
282
|
* @private
|
|
278
283
|
*/
|
|
279
284
|
function isNativeObjectWithInternalSlots(value) {
|
|
280
|
-
|
|
285
|
+
if (
|
|
286
|
+
value instanceof Date ||
|
|
287
|
+
value instanceof RegExp ||
|
|
288
|
+
value instanceof Map ||
|
|
289
|
+
value instanceof Set ||
|
|
290
|
+
value instanceof WeakMap ||
|
|
291
|
+
value instanceof WeakSet ||
|
|
292
|
+
value instanceof Promise ||
|
|
293
|
+
value instanceof ArrayBuffer ||
|
|
294
|
+
ArrayBuffer.isView(value)
|
|
295
|
+
) {
|
|
296
|
+
return true;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
return (
|
|
300
|
+
typeof SharedArrayBuffer === "function" &&
|
|
301
|
+
value instanceof SharedArrayBuffer
|
|
302
|
+
);
|
|
281
303
|
}
|
|
@@ -6,6 +6,21 @@ import { ResizeObserverMock } from "../../../util/resize-observer.mjs";
|
|
|
6
6
|
const expect = chai.expect;
|
|
7
7
|
chai.use(chaiDom);
|
|
8
8
|
|
|
9
|
+
// Supply geometry before the first layout can populate the measurement cache.
|
|
10
|
+
function installColumnBarGeometry(parentWidth) {
|
|
11
|
+
const original = HTMLElement.prototype.getBoundingClientRect;
|
|
12
|
+
HTMLElement.prototype.getBoundingClientRect = function () {
|
|
13
|
+
if (this.localName === "monster-column-bar") return {width:0};
|
|
14
|
+
if (this.getRootNode().host?.localName === "monster-column-bar") {
|
|
15
|
+
const role = this.getAttribute("data-monster-role");
|
|
16
|
+
if (role === "control") return {width:parentWidth()};
|
|
17
|
+
if (role === "settings-button" || this.localName === "li") return {width:20};
|
|
18
|
+
}
|
|
19
|
+
return original.call(this);
|
|
20
|
+
};
|
|
21
|
+
return () => { HTMLElement.prototype.getBoundingClientRect = original; };
|
|
22
|
+
}
|
|
23
|
+
|
|
9
24
|
function dispatchPointerEvent(target, type, options = {}) {
|
|
10
25
|
const event = new Event(type, {
|
|
11
26
|
bubbles: true,
|
|
@@ -35,6 +50,9 @@ function dispatchPointerEvent(target, type, options = {}) {
|
|
|
35
50
|
|
|
36
51
|
function mockScrollableElement(element, { clientWidth, scrollWidth, scrollLeft = 0 }) {
|
|
37
52
|
let currentScrollLeft = scrollLeft;
|
|
53
|
+
// Synthetic events have no browser-owned active pointer to capture.
|
|
54
|
+
element.setPointerCapture = () => {};
|
|
55
|
+
element.releasePointerCapture = () => {};
|
|
38
56
|
|
|
39
57
|
Object.defineProperty(element, "clientWidth", {
|
|
40
58
|
configurable: true,
|
|
@@ -447,6 +465,7 @@ describe("Datatable drag scroll", function () {
|
|
|
447
465
|
});
|
|
448
466
|
|
|
449
467
|
it("defers column bar resize updates to the next animation frame", async function () {
|
|
468
|
+
let restoreGeometry = () => {};
|
|
450
469
|
const OriginalResizeObserver = window.ResizeObserver;
|
|
451
470
|
const originalGlobalResizeObserver = globalThis.ResizeObserver;
|
|
452
471
|
const originalRequestAnimationFrame = window.requestAnimationFrame;
|
|
@@ -468,6 +487,7 @@ describe("Datatable drag scroll", function () {
|
|
|
468
487
|
const mocks = document.getElementById("mocks");
|
|
469
488
|
const wrapper = document.createElement("div");
|
|
470
489
|
let parentWidth = 400;
|
|
490
|
+
restoreGeometry = installColumnBarGeometry(() => parentWidth);
|
|
471
491
|
|
|
472
492
|
Object.defineProperty(wrapper, "getBoundingClientRect", {
|
|
473
493
|
configurable: true,
|
|
@@ -499,6 +519,7 @@ describe("Datatable drag scroll", function () {
|
|
|
499
519
|
"[data-monster-role=dots]",
|
|
500
520
|
);
|
|
501
521
|
const dots = Array.from(dotsContainer.querySelectorAll("li"));
|
|
522
|
+
dots.forEach(dot => { dot.style.margin = "0"; });
|
|
502
523
|
|
|
503
524
|
expect(control).to.exist;
|
|
504
525
|
expect(settingsButton).to.exist;
|
|
@@ -519,7 +540,7 @@ describe("Datatable drag scroll", function () {
|
|
|
519
540
|
});
|
|
520
541
|
});
|
|
521
542
|
|
|
522
|
-
await new Promise((resolve) => setTimeout(resolve,
|
|
543
|
+
await new Promise((resolve) => setTimeout(resolve, 30));
|
|
523
544
|
|
|
524
545
|
expect(
|
|
525
546
|
dotsContainer.querySelector(".dots-overflow-indicator"),
|
|
@@ -551,6 +572,7 @@ describe("Datatable drag scroll", function () {
|
|
|
551
572
|
dotsContainer.querySelector(".dots-overflow-indicator"),
|
|
552
573
|
).to.exist;
|
|
553
574
|
} finally {
|
|
575
|
+
restoreGeometry();
|
|
554
576
|
window.ResizeObserver = OriginalResizeObserver;
|
|
555
577
|
globalThis.ResizeObserver = originalGlobalResizeObserver;
|
|
556
578
|
window.requestAnimationFrame = originalRequestAnimationFrame;
|
|
@@ -559,6 +581,7 @@ describe("Datatable drag scroll", function () {
|
|
|
559
581
|
});
|
|
560
582
|
|
|
561
583
|
it("coalesces column bar dot updates and ignores overflow indicator mutations", async function () {
|
|
584
|
+
let restoreGeometry = () => {};
|
|
562
585
|
const OriginalResizeObserver = window.ResizeObserver;
|
|
563
586
|
const originalGlobalResizeObserver = globalThis.ResizeObserver;
|
|
564
587
|
const originalRequestAnimationFrame = window.requestAnimationFrame;
|
|
@@ -580,6 +603,7 @@ describe("Datatable drag scroll", function () {
|
|
|
580
603
|
const mocks = document.getElementById("mocks");
|
|
581
604
|
const wrapper = document.createElement("div");
|
|
582
605
|
let parentWidth = 80;
|
|
606
|
+
restoreGeometry = installColumnBarGeometry(() => parentWidth);
|
|
583
607
|
|
|
584
608
|
Object.defineProperty(wrapper, "getBoundingClientRect", {
|
|
585
609
|
configurable: true,
|
|
@@ -612,6 +636,7 @@ describe("Datatable drag scroll", function () {
|
|
|
612
636
|
"[data-monster-role=dots]",
|
|
613
637
|
);
|
|
614
638
|
const dots = Array.from(dotsContainer.querySelectorAll("li"));
|
|
639
|
+
dots.forEach(dot => { dot.style.margin = "0"; });
|
|
615
640
|
|
|
616
641
|
Object.defineProperty(control, "getBoundingClientRect", {
|
|
617
642
|
configurable: true,
|
|
@@ -628,6 +653,7 @@ describe("Datatable drag scroll", function () {
|
|
|
628
653
|
});
|
|
629
654
|
});
|
|
630
655
|
|
|
656
|
+
await new Promise((resolve) => setTimeout(resolve, 30));
|
|
631
657
|
return { columnBar, control, dotsContainer };
|
|
632
658
|
};
|
|
633
659
|
|
|
@@ -669,6 +695,7 @@ describe("Datatable drag scroll", function () {
|
|
|
669
695
|
|
|
670
696
|
expect(scheduledCallbacks.length).to.equal(0);
|
|
671
697
|
} finally {
|
|
698
|
+
restoreGeometry();
|
|
672
699
|
window.ResizeObserver = OriginalResizeObserver;
|
|
673
700
|
globalThis.ResizeObserver = originalGlobalResizeObserver;
|
|
674
701
|
window.requestAnimationFrame = originalRequestAnimationFrame;
|
|
@@ -104,6 +104,9 @@ describe("ButtonBar", function () {
|
|
|
104
104
|
"Weitere Aktionen",
|
|
105
105
|
);
|
|
106
106
|
|
|
107
|
+
// The empty fixture has no overflow; expose the switch for the focus check.
|
|
108
|
+
switchButton.hidden = false;
|
|
109
|
+
switchButton.classList.remove("hidden");
|
|
107
110
|
bar.showDialog();
|
|
108
111
|
expect(switchButton.getAttribute("aria-expanded")).to.equal("true");
|
|
109
112
|
switchButton.dispatchEvent(
|
|
@@ -403,7 +406,7 @@ describe("ButtonBar", function () {
|
|
|
403
406
|
it("should allow themes to space main and overflow button groups", function () {
|
|
404
407
|
const cssText = ButtonBar.getCSSStyleSheet()
|
|
405
408
|
.flatMap((styleSheet) => Array.from(styleSheet.cssRules))
|
|
406
|
-
.map((rule) => rule.cssText)
|
|
409
|
+
.map((rule) => rule.cssText.replace(/="([\w-]+)"/g, "=$1").replace(/>\s+\[/g, ">["))
|
|
407
410
|
.join("\n");
|
|
408
411
|
|
|
409
412
|
expect(cssText).to.contain("slot[name=popper]");
|
|
@@ -424,7 +427,7 @@ describe("ButtonBar", function () {
|
|
|
424
427
|
it("should stack a nested button bar inside a parent overflow item", function () {
|
|
425
428
|
const cssText = ButtonBar.getCSSStyleSheet()
|
|
426
429
|
.flatMap((styleSheet) => Array.from(styleSheet.cssRules))
|
|
427
|
-
.map((rule) => rule.cssText)
|
|
430
|
+
.map((rule) => rule.cssText.replace(/="([\w-]+)"/g, "=$1").replace(/>\s+\[/g, ">["))
|
|
428
431
|
.join("\n");
|
|
429
432
|
|
|
430
433
|
expect(cssText).to.match(
|
|
@@ -517,11 +520,11 @@ describe("ButtonBar", function () {
|
|
|
517
520
|
|
|
518
521
|
expect(buttons[1].style.marginLeft).to.equal("");
|
|
519
522
|
expect(buttons[0].style.borderRadius).to.equal("8px");
|
|
520
|
-
expect(buttons[0].style.borderTopRightRadius).to.equal("");
|
|
521
|
-
expect(buttons[0].style.borderBottomRightRadius).to.equal("");
|
|
523
|
+
expect(buttons[0].style.borderTopRightRadius || buttons[0].style.borderRadius).to.equal("8px");
|
|
524
|
+
expect(buttons[0].style.borderBottomRightRadius || buttons[0].style.borderRadius).to.equal("8px");
|
|
522
525
|
expect(buttons[1].style.borderRadius).to.equal("8px");
|
|
523
|
-
expect(buttons[1].style.borderTopLeftRadius).to.equal("");
|
|
524
|
-
expect(buttons[1].style.borderBottomLeftRadius).to.equal("");
|
|
526
|
+
expect(buttons[1].style.borderTopLeftRadius || buttons[1].style.borderRadius).to.equal("8px");
|
|
527
|
+
expect(buttons[1].style.borderBottomLeftRadius || buttons[1].style.borderRadius).to.equal("8px");
|
|
525
528
|
} finally {
|
|
526
529
|
window.requestAnimationFrame = originalRequestAnimationFrame;
|
|
527
530
|
globalThis.requestAnimationFrame = originalGlobalRequestAnimationFrame;
|
|
@@ -150,7 +150,7 @@ describe('ConfirmButton', function () {
|
|
|
150
150
|
|
|
151
151
|
const cssText = ConfirmButton.getCSSStyleSheet()
|
|
152
152
|
.flatMap((styleSheet) => Array.from(styleSheet.cssRules))
|
|
153
|
-
.map((rule) => rule.cssText)
|
|
153
|
+
.map((rule) => rule.cssText.replace(/="([\w-]+)"/g, "=$1").replace(/>\s+\[/g, ">["))
|
|
154
154
|
.join("\n");
|
|
155
155
|
|
|
156
156
|
expect(cssText).to.contain('div[data-monster-role=decision]');
|
|
@@ -162,14 +162,14 @@ describe('ConfirmButton', function () {
|
|
|
162
162
|
|
|
163
163
|
const cssText = ConfirmButton.getCSSStyleSheet()
|
|
164
164
|
.flatMap((styleSheet) => Array.from(styleSheet.cssRules))
|
|
165
|
-
.map((rule) => rule.cssText)
|
|
165
|
+
.map((rule) => rule.cssText.replace(/="([\w-]+)"/g, "=$1").replace(/>\s+\[/g, ">["))
|
|
166
166
|
.join("\n");
|
|
167
167
|
|
|
168
168
|
expect(cssText).to.contain('div[data-monster-role=decision]');
|
|
169
169
|
expect(cssText).to.contain('max-width: 100%');
|
|
170
170
|
expect(cssText).to.contain('min-width: 0px');
|
|
171
171
|
expect(cssText).to.contain('flex-wrap: wrap');
|
|
172
|
-
expect(cssText).to.contain('div[data-monster-role=decision]>*');
|
|
172
|
+
expect(cssText.replace(/\s*>\s*/g, '>')).to.contain('div[data-monster-role=decision]>*');
|
|
173
173
|
expect(cssText).to.contain('flex: 1 1 max-content');
|
|
174
174
|
|
|
175
175
|
});
|
|
@@ -178,7 +178,7 @@ describe('ConfirmButton', function () {
|
|
|
178
178
|
|
|
179
179
|
const cssText = ConfirmButton.getCSSStyleSheet()
|
|
180
180
|
.flatMap((styleSheet) => Array.from(styleSheet.cssRules))
|
|
181
|
-
.map((rule) => rule.cssText)
|
|
181
|
+
.map((rule) => rule.cssText.replace(/="([\w-]+)"/g, "=$1").replace(/>\s+\[/g, ">["))
|
|
182
182
|
.join("\n");
|
|
183
183
|
|
|
184
184
|
expect(cssText).to.contain(':host(monster-confirm-button)');
|