@blockquote-web-components/blockquote-dialog 1.0.0 → 1.0.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/package.json +3 -2
- package/src/BlockquoteDialog.js +1 -1
- package/src/dom-utils.js +0 -270
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@blockquote-web-components/blockquote-dialog",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.1",
|
|
4
4
|
"description": "Webcomponent blockquote-dialog following open-wc recommendations",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"lit",
|
|
@@ -151,6 +151,7 @@
|
|
|
151
151
|
"dependencies": {
|
|
152
152
|
"@blockquote-web-components/blockquote-directive-ariaidref-slot": "^1.0.0",
|
|
153
153
|
"@blockquote-web-components/blockquote-mixin-slot-content": "^1.5.0",
|
|
154
|
+
"@blockquote/dev-utilities": "^1.0.0",
|
|
154
155
|
"@material/web": "^1.4.1",
|
|
155
156
|
"lit": "^3.1.3"
|
|
156
157
|
},
|
|
@@ -164,5 +165,5 @@
|
|
|
164
165
|
"access": "public"
|
|
165
166
|
},
|
|
166
167
|
"customElements": "custom-elements.json",
|
|
167
|
-
"gitHead": "
|
|
168
|
+
"gitHead": "8446f853a540f46dccab76aaa4da98d646b56a5a"
|
|
168
169
|
}
|
package/src/BlockquoteDialog.js
CHANGED
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
isFocusable,
|
|
8
8
|
getFirstAndLastFocusableChildren,
|
|
9
9
|
walkComposedTree,
|
|
10
|
-
} from '
|
|
10
|
+
} from '@blockquote/dev-utilities';
|
|
11
11
|
import { styles } from './styles/blockquote-dialog-styles.css.js';
|
|
12
12
|
import { styles as animations } from './styles/blockqoute-dialog-animations-styles.css.js';
|
|
13
13
|
|
package/src/dom-utils.js
DELETED
|
@@ -1,270 +0,0 @@
|
|
|
1
|
-
/* c8 ignore start */
|
|
2
|
-
/**
|
|
3
|
-
* Re-dispatches an event from the provided element.
|
|
4
|
-
* @author @material/web
|
|
5
|
-
* @see https://github.com/material-components/material-web/blob/main/internal/events/redispatch-event.ts
|
|
6
|
-
* @param {Element} element - The element to dispatch the event from.
|
|
7
|
-
* @param {Event} event - The event to re-dispatch.
|
|
8
|
-
* @param {Object} [options={}] - An object with properties to override in the new event.
|
|
9
|
-
* @returns {boolean} - Whether or not the event was dispatched (if cancelable).
|
|
10
|
-
*/
|
|
11
|
-
|
|
12
|
-
const redispatchEventFromEvent = (element, event, options = {}) => {
|
|
13
|
-
// For bubbling events in SSR light DOM (or composed), stop their propagation
|
|
14
|
-
// and dispatch the copy.
|
|
15
|
-
if (event.bubbles && (!element.shadowRoot || event.composed)) {
|
|
16
|
-
event.stopPropagation();
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
const copy = Reflect.construct(event.constructor, [event.type, { ...event, ...options }]);
|
|
20
|
-
const dispatched = element.dispatchEvent(copy);
|
|
21
|
-
if (!dispatched) {
|
|
22
|
-
event.preventDefault();
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
return dispatched;
|
|
26
|
-
};
|
|
27
|
-
|
|
28
|
-
/**
|
|
29
|
-
* Re-dispatches an event from the provided element.
|
|
30
|
-
*
|
|
31
|
-
* This function is useful for forwarding non-composed events, such as `change`
|
|
32
|
-
* events.
|
|
33
|
-
*
|
|
34
|
-
* @example
|
|
35
|
-
* class MyDialog extends LitElement {
|
|
36
|
-
* render() {
|
|
37
|
-
* return html`<dialog @close=${this.redispatchEvent}>...</dialog>`;
|
|
38
|
-
* }
|
|
39
|
-
*
|
|
40
|
-
* protected redispatchEvent(ev: Event) {
|
|
41
|
-
* redispatchEvent(this, ev, { cancelable: true });
|
|
42
|
-
* }
|
|
43
|
-
* }
|
|
44
|
-
*
|
|
45
|
-
* @param {Element} element - The element to dispatch the event from.
|
|
46
|
-
* @param {Event|string} event - The event to re-dispatch. If it's a string, a new Event is created.
|
|
47
|
-
* @param {Object} [options={}] - An object with properties to override in the new event.
|
|
48
|
-
* @returns {boolean} - Whether or not the event was dispatched (if cancelable).
|
|
49
|
-
*/
|
|
50
|
-
export const redispatchEvent = (element, event, options = {}) => {
|
|
51
|
-
if (typeof event === 'string') {
|
|
52
|
-
const eventType = event;
|
|
53
|
-
const newEvent = new CustomEvent(eventType);
|
|
54
|
-
return redispatchEventFromEvent(element, newEvent, options);
|
|
55
|
-
}
|
|
56
|
-
return redispatchEventFromEvent(element, event, options);
|
|
57
|
-
};
|
|
58
|
-
|
|
59
|
-
/**
|
|
60
|
-
* Checks if an element should be ignored.
|
|
61
|
-
* @param {Element} element - The DOM element to check.
|
|
62
|
-
* @param {Array} [exceptions=['dialog', '[popover]']] - Array of Elements to ignore when checking the element.
|
|
63
|
-
* @returns {boolean} True if the element should be ignored by a screen reader, false otherwise.
|
|
64
|
-
*/
|
|
65
|
-
export const isElementInvisible = (element, exceptions = ['dialog', '[popover]']) => {
|
|
66
|
-
if (!element || !(element instanceof HTMLElement)) {
|
|
67
|
-
return false;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
if (element.matches(exceptions.join(','))) {
|
|
71
|
-
return false;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
const computedStyle = window.getComputedStyle(element);
|
|
75
|
-
const isStyleHidden = computedStyle.display === 'none' || computedStyle.visibility === 'hidden';
|
|
76
|
-
const isAttributeHidden = element.matches('[disabled], [hidden], [inert], [aria-hidden="true"]');
|
|
77
|
-
|
|
78
|
-
return isStyleHidden || isAttributeHidden;
|
|
79
|
-
};
|
|
80
|
-
|
|
81
|
-
/**
|
|
82
|
-
* Checks if an element is focusable. An element is considered focusable if it matches
|
|
83
|
-
* standard focusable elements criteria (such as buttons, inputs, etc., that are not disabled
|
|
84
|
-
* and do not have a negative tabindex) or is a custom element with a shadow root that delegates focus.
|
|
85
|
-
*
|
|
86
|
-
* @param {Element} element - The DOM element to check for focusability.
|
|
87
|
-
* @returns {boolean} True if the element is focusable, false otherwise.
|
|
88
|
-
*/
|
|
89
|
-
export const isFocusable = element => {
|
|
90
|
-
if (!(element instanceof HTMLElement)) {
|
|
91
|
-
return false;
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
// https://stackoverflow.com/a/30753870/76472
|
|
95
|
-
const knownFocusableElements = `a[href],area[href],button:not([disabled]),details,iframe,object,input:not([disabled]),select:not([disabled]),textarea:not([disabled]),[contentEditable="true"],[tabindex]:not([tabindex^="-"]),audio[controls],video[controls]`;
|
|
96
|
-
|
|
97
|
-
if (element.matches(knownFocusableElements)) {
|
|
98
|
-
return true;
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
const isDisabledCustomElement =
|
|
102
|
-
element.localName.includes('-') && element.matches('[disabled], [aria-disabled="true"]');
|
|
103
|
-
if (isDisabledCustomElement) {
|
|
104
|
-
return false;
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
return element.shadowRoot?.delegatesFocus ?? false;
|
|
108
|
-
};
|
|
109
|
-
|
|
110
|
-
/**
|
|
111
|
-
* Retrieves the first and last focusable children of a node using a TreeWalker.
|
|
112
|
-
*
|
|
113
|
-
* @param {IterableIterator<HTMLElement>} walker - The TreeWalker object used to traverse the node's children.
|
|
114
|
-
* @returns {[first: HTMLElement|null, last: HTMLElement|null]} An object containing the first and last focusable children. If no focusable children are found, `null` is returned for both.
|
|
115
|
-
*/
|
|
116
|
-
export const getFirstAndLastFocusableChildren = walker => {
|
|
117
|
-
let firstFocusableChild = null;
|
|
118
|
-
let lastFocusableChild = null;
|
|
119
|
-
|
|
120
|
-
for (const currentNode of walker) {
|
|
121
|
-
if (!firstFocusableChild) {
|
|
122
|
-
firstFocusableChild = currentNode;
|
|
123
|
-
}
|
|
124
|
-
lastFocusableChild = currentNode;
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
return [firstFocusableChild, lastFocusableChild];
|
|
128
|
-
};
|
|
129
|
-
|
|
130
|
-
/**
|
|
131
|
-
* Traverse the composed tree from the root, selecting elements that meet the provided filter criteria.
|
|
132
|
-
* You can pass [NodeFilter](https://developer.mozilla.org/en-US/docs/Web/API/NodeFilter) or 0 to retrieve all nodes.
|
|
133
|
-
* @author Jan Miksovsky
|
|
134
|
-
* @see https://github.com/JanMiksovsky/elix/blob/main/src/core/dom.js
|
|
135
|
-
* @param {Node} node - The root node for traversal.
|
|
136
|
-
* @param {number} [whatToShow=0] - NodeFilter code for node types to include.
|
|
137
|
-
* @param {function} [filter=(n: Node) => true] - Filters nodes. Child nodes are considered even if parent does not satisfy the filter.
|
|
138
|
-
* @param {function} [skipNode=(n: Node) => false] - Determines whether to skip a node and its children.
|
|
139
|
-
* @returns {IterableIterator<Node>} An iterator yielding nodes meeting the filter criteria.
|
|
140
|
-
*/
|
|
141
|
-
export function* walkComposedTree(
|
|
142
|
-
node,
|
|
143
|
-
whatToShow = 0,
|
|
144
|
-
filter = () => true,
|
|
145
|
-
skipNode = () => false,
|
|
146
|
-
) {
|
|
147
|
-
if ((whatToShow && node.nodeType !== whatToShow) || skipNode(node)) {
|
|
148
|
-
return;
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
if (filter(node)) {
|
|
152
|
-
yield node;
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
const children =
|
|
156
|
-
// eslint-disable-next-line no-nested-ternary
|
|
157
|
-
node instanceof HTMLElement && node.shadowRoot
|
|
158
|
-
? node.shadowRoot.children
|
|
159
|
-
: node instanceof HTMLSlotElement
|
|
160
|
-
? node.assignedNodes({ flatten: true })
|
|
161
|
-
: node.childNodes;
|
|
162
|
-
|
|
163
|
-
for (const child of children) {
|
|
164
|
-
yield* walkComposedTree(child, whatToShow, filter, skipNode);
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
/**
|
|
169
|
-
* Returns the deepest active element, considering Shadow DOM subtrees
|
|
170
|
-
* @param {Document | ShadowRoot} root - The root element to start the search from.
|
|
171
|
-
* @returns {Element} The deepest active element or body element if no active element is found.
|
|
172
|
-
*/
|
|
173
|
-
export const getDeepActiveElement = (root = document) => {
|
|
174
|
-
const activeEl = root.activeElement;
|
|
175
|
-
if (activeEl) {
|
|
176
|
-
if (activeEl.shadowRoot) {
|
|
177
|
-
return getDeepActiveElement(activeEl.shadowRoot) ?? activeEl;
|
|
178
|
-
}
|
|
179
|
-
return activeEl;
|
|
180
|
-
}
|
|
181
|
-
return document.body;
|
|
182
|
-
};
|
|
183
|
-
|
|
184
|
-
/**
|
|
185
|
-
* Returns true if the first node contains the second, even if the second node
|
|
186
|
-
* is in a shadow tree.
|
|
187
|
-
*
|
|
188
|
-
* The standard Node.contains() function does not account for Shadow DOM, and
|
|
189
|
-
* returns false if the supplied target node is sitting inside a shadow tree
|
|
190
|
-
* within the container.
|
|
191
|
-
*
|
|
192
|
-
* @param {Node} container - The container to search within.
|
|
193
|
-
* @param {Node} target - The node that may be inside the container.
|
|
194
|
-
* @returns {boolean} - True if the container contains the target node.
|
|
195
|
-
*/
|
|
196
|
-
export const deepContains = (container, target) => {
|
|
197
|
-
/** @type {any} */
|
|
198
|
-
let current = target;
|
|
199
|
-
while (current) {
|
|
200
|
-
const parent = current.assignedSlot || current.parentNode || current.host;
|
|
201
|
-
if (parent === container) {
|
|
202
|
-
return true;
|
|
203
|
-
}
|
|
204
|
-
current = parent;
|
|
205
|
-
}
|
|
206
|
-
return false;
|
|
207
|
-
};
|
|
208
|
-
|
|
209
|
-
/**
|
|
210
|
-
* Search a list element for the item that contains the specified target.
|
|
211
|
-
*
|
|
212
|
-
* When dealing with UI events (e.g., mouse clicks) that may occur in
|
|
213
|
-
* subelements inside a list item, you can use this routine to obtain the
|
|
214
|
-
* containing list item.
|
|
215
|
-
*
|
|
216
|
-
* @author Jan Miksovsky
|
|
217
|
-
* @see https://github.com/JanMiksovsky/elix/blob/main/src/core/dom.js
|
|
218
|
-
* @param {NodeList|Node[]} items - A list element containing a set of items
|
|
219
|
-
* @param {Node} target - A target element that may or may not be an item in the
|
|
220
|
-
* list.
|
|
221
|
-
* @returns {number} - The index of the list child that is or contains the
|
|
222
|
-
* indicated target node. Returns -1 if not found.
|
|
223
|
-
*/
|
|
224
|
-
export const indexOfItemContainingTarget = (items, target) =>
|
|
225
|
-
Array.prototype.findIndex.call(
|
|
226
|
-
items,
|
|
227
|
-
(/** @type Node */ item) => item === target || deepContains(item, target),
|
|
228
|
-
);
|
|
229
|
-
|
|
230
|
-
/**
|
|
231
|
-
* Return the ancestors of the given node in the composed tree.
|
|
232
|
-
*
|
|
233
|
-
* In the composed tree, the ancestor of a node assigned to a slot is that slot,
|
|
234
|
-
* not the node's DOM ancestor. The ancestor of a shadow root is its host.
|
|
235
|
-
*
|
|
236
|
-
* @author Jan Miksovsky
|
|
237
|
-
* @see https://github.com/JanMiksovsky/elix/blob/main/src/core/dom.js
|
|
238
|
-
* @param {Node} node
|
|
239
|
-
* @returns {Iterable<Node>}
|
|
240
|
-
*/
|
|
241
|
-
export function* composedAncestors(node) {
|
|
242
|
-
for (let /** @type {Node|null} */ current = node; current; ) {
|
|
243
|
-
const next =
|
|
244
|
-
// eslint-disable-next-line no-nested-ternary
|
|
245
|
-
current instanceof HTMLElement && current.assignedSlot
|
|
246
|
-
? current.assignedSlot
|
|
247
|
-
: current instanceof ShadowRoot
|
|
248
|
-
? current.host
|
|
249
|
-
: current.parentNode;
|
|
250
|
-
if (next) {
|
|
251
|
-
yield next;
|
|
252
|
-
}
|
|
253
|
-
current = next;
|
|
254
|
-
}
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
/**
|
|
258
|
-
* Checks if a click event occurred inside a given bounding rectangle.
|
|
259
|
-
*
|
|
260
|
-
* @param {DOMRect} rect - The bounding rectangle, typically obtained from `element.getBoundingClientRect()`.
|
|
261
|
-
* @param {PointerEvent} ev - The click event.
|
|
262
|
-
* @returns {boolean} True if the click occurred inside the rectangle, false otherwise.
|
|
263
|
-
*/
|
|
264
|
-
export const isClickInsideRect = (rect, ev) => {
|
|
265
|
-
const { top, left, height, width } = rect;
|
|
266
|
-
const { clientX, clientY } = ev;
|
|
267
|
-
return clientY >= top && clientY <= top + height && clientX >= left && clientX <= left + width;
|
|
268
|
-
};
|
|
269
|
-
|
|
270
|
-
export const randomID = (length = 10) => Math.random().toString(36).substring(2, length);
|