@blockquote-web-components/blockquote-dialog 1.0.0 → 1.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/README.md +30 -63
- package/package.json +4 -3
- package/src/BlockquoteDialog.js +26 -2
- package/src/dom-utils.js +0 -270
package/README.md
CHANGED
|
@@ -30,27 +30,28 @@ Inert content outside an active dialog is typically visually obscured or dimmed
|
|
|
30
30
|
|
|
31
31
|
##### Fields
|
|
32
32
|
|
|
33
|
-
| Name | Privacy | Type | Default | Description
|
|
34
|
-
| ----------------------------- | ------- | --------- | ----------- |
|
|
35
|
-
| `treewalker` | | | |
|
|
36
|
-
| `dialogRef` | | | |
|
|
37
|
-
| `open` | public | `boolean` | | Opens the dialog when set to \`true\` and closes it when set to \`false\`.
|
|
38
|
-
| `_slotTpl` | | | |
|
|
39
|
-
| `_labeledbyTpl` | | | |
|
|
40
|
-
| `_contentTpl` | | | |
|
|
41
|
-
| `_scrollerTpl` | | | |
|
|
42
|
-
| `_firstNodeFocusTrapTpl` | | | |
|
|
43
|
-
| `_lastNodeFocusTrapTpl` | | | |
|
|
44
|
-
| `_isConnectedCallbackResolve` | | | |
|
|
45
|
-
| `_isConnectedCallback` | | | |
|
|
46
|
-
| `_firstFocusableChild` | | | `undefined` |
|
|
47
|
-
| `_lastFocusableChild` | | | `undefined` |
|
|
48
|
-
| `_nextClickIsFromContent` | | `boolean` | `false` |
|
|
49
|
-
| `_overflowRoot` | | | |
|
|
50
|
-
| `type` | public | `string` | `'alert'` | The type of dialog for accessibility. Set this to \`alert\` to announce a
dialog as an alert dialog.
|
|
51
|
-
| `label` | public | `string` | `''` |
|
|
52
|
-
| `labelledby` | public | `string` | `''` |
|
|
53
|
-
| `
|
|
33
|
+
| Name | Privacy | Type | Default | Description | Inherited From |
|
|
34
|
+
| ----------------------------- | ------- | --------- | ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -------------- |
|
|
35
|
+
| `treewalker` | | | | | |
|
|
36
|
+
| `dialogRef` | | | | | |
|
|
37
|
+
| `open` | public | `boolean` | | Opens the dialog when set to \`true\` and closes it when set to \`false\`. | |
|
|
38
|
+
| `_slotTpl` | | | | | |
|
|
39
|
+
| `_labeledbyTpl` | | | | | |
|
|
40
|
+
| `_contentTpl` | | | | | |
|
|
41
|
+
| `_scrollerTpl` | | | | | |
|
|
42
|
+
| `_firstNodeFocusTrapTpl` | | | | | |
|
|
43
|
+
| `_lastNodeFocusTrapTpl` | | | | | |
|
|
44
|
+
| `_isConnectedCallbackResolve` | | | | | |
|
|
45
|
+
| `_isConnectedCallback` | | | | | |
|
|
46
|
+
| `_firstFocusableChild` | | | `undefined` | | |
|
|
47
|
+
| `_lastFocusableChild` | | | `undefined` | | |
|
|
48
|
+
| `_nextClickIsFromContent` | | `boolean` | `false` | | |
|
|
49
|
+
| `_overflowRoot` | | | | | |
|
|
50
|
+
| `type` | public | `string` | `'alert'` | The type of dialog for accessibility. Set this to \`alert\` to announce a
dialog as an alert dialog. | |
|
|
51
|
+
| `label` | public | `string` | `''` | The 'label' attribute will be used as the 'aria-label' for the dialog | |
|
|
52
|
+
| `labelledby` | public | `string` | `''` | The 'labelledby' attribute will be used as the 'aria-labelledby' for the dialog.
It will also be used to create a slot with the same 'id' and 'name'.
This slot is hidden by default and its 'name' and 'id' should correspond to the 'slot' attribute of an element in the Light DOM.
This connects the 'slot', 'name', and 'id' attributes of a slot to be used with ARIA relationships. | |
|
|
53
|
+
| `labelledbyVisible` | public | `boolean` | `false` | The 'labelledbyVisible' attribute will control the visibility of the slot created by 'labelledby'.
By default, it is set to 'hidden'. | |
|
|
54
|
+
| `returnValue` | public | | | Gets or sets the dialog's return value, usually to indicate which button
a user pressed to close it.

https\://developer.mozilla.org/en-US/docs/Web/API/HTMLDialogElement/returnValue returnValue | |
|
|
54
55
|
|
|
55
56
|
##### Methods
|
|
56
57
|
|
|
@@ -78,12 +79,14 @@ Inert content outside an active dialog is typically visually obscured or dimmed
|
|
|
78
79
|
|
|
79
80
|
##### Attributes
|
|
80
81
|
|
|
81
|
-
| Name
|
|
82
|
-
|
|
|
83
|
-
| `open`
|
|
84
|
-
| `label`
|
|
85
|
-
| `labelledby`
|
|
86
|
-
| `
|
|
82
|
+
| Name | Field | Inherited From |
|
|
83
|
+
| --------------------- | ----------------- | -------------- |
|
|
84
|
+
| `open` | open | |
|
|
85
|
+
| `label` | label | |
|
|
86
|
+
| `labelledby` | labelledby | |
|
|
87
|
+
| `labelledbyVisible` | | |
|
|
88
|
+
| `type` | type | |
|
|
89
|
+
| `labelledby-visibile` | labelledbyVisible | |
|
|
87
90
|
|
|
88
91
|
##### Slots
|
|
89
92
|
|
|
@@ -109,42 +112,6 @@ Inert content outside an active dialog is typically visually obscured or dimmed
|
|
|
109
112
|
| ---- | ------------------ | ---------------- | ----------------------- | ------- |
|
|
110
113
|
| `js` | `BlockquoteDialog` | BlockquoteDialog | src/BlockquoteDialog.js | |
|
|
111
114
|
|
|
112
|
-
### `src/dom-utils.js`:
|
|
113
|
-
|
|
114
|
-
#### Functions
|
|
115
|
-
|
|
116
|
-
| Name | Description | Parameters | Return |
|
|
117
|
-
| ---------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------- | ----------------------------------------------------- |
|
|
118
|
-
| `redispatchEvent` | Re-dispatches an event from the provided element.

This function is useful for forwarding non-composed events, such as \`change\`
events. | `element: Element, event: Event\|string, options: Object` | `boolean` |
|
|
119
|
-
| `isElementInvisible` | Checks if an element should be ignored. | `element: Element, exceptions: Array` | `boolean` |
|
|
120
|
-
| `isFocusable` | Checks if an element is focusable. An element is considered focusable if it matches
standard focusable elements criteria (such as buttons, inputs, etc., that are not disabled
and do not have a negative tabindex) or is a custom element with a shadow root that delegates focus. | `element: Element` | `boolean` |
|
|
121
|
-
| `getFirstAndLastFocusableChildren` | Retrieves the first and last focusable children of a node using a TreeWalker. | `walker: IterableIterator<HTMLElement>` | `[first: HTMLElement\|null, last: HTMLElement\|null]` |
|
|
122
|
-
| `walkComposedTree` | Traverse the composed tree from the root, selecting elements that meet the provided filter criteria.
You can pass \[NodeFilter]\(https\://developer.mozilla.org/en-US/docs/Web/API/NodeFilter) or 0 to retrieve all nodes. | `node: Node, whatToShow: number, filter: function, skipNode: function` | `IterableIterator<Node>` |
|
|
123
|
-
| `getDeepActiveElement` | Returns the deepest active element, considering Shadow DOM subtrees | `root: Document \| ShadowRoot` | `Element` |
|
|
124
|
-
| `deepContains` | Returns true if the first node contains the second, even if the second node
is in a shadow tree.

The standard Node.contains() function does not account for Shadow DOM, and
returns false if the supplied target node is sitting inside a shadow tree
within the container. | `container: Node, target: Node` | `boolean` |
|
|
125
|
-
| `indexOfItemContainingTarget` | Search a list element for the item that contains the specified target.

When dealing with UI events (e.g., mouse clicks) that may occur in
subelements inside a list item, you can use this routine to obtain the
containing list item. | `items: NodeList\|Node[], target: Node` | `number` |
|
|
126
|
-
| `composedAncestors` | Return the ancestors of the given node in the composed tree.

In the composed tree, the ancestor of a node assigned to a slot is that slot,
not the node's DOM ancestor. The ancestor of a shadow root is its host. | `node: Node` | `Iterable<Node>` |
|
|
127
|
-
| `isClickInsideRect` | Checks if a click event occurred inside a given bounding rectangle. | `rect: DOMRect, ev: PointerEvent` | `boolean` |
|
|
128
|
-
| `randomID` | | `length` | |
|
|
129
|
-
|
|
130
|
-
<hr/>
|
|
131
|
-
|
|
132
|
-
#### Exports
|
|
133
|
-
|
|
134
|
-
| Kind | Name | Declaration | Module | Package |
|
|
135
|
-
| ---- | ---------------------------------- | -------------------------------- | ---------------- | ------- |
|
|
136
|
-
| `js` | `redispatchEvent` | redispatchEvent | src/dom-utils.js | |
|
|
137
|
-
| `js` | `isElementInvisible` | isElementInvisible | src/dom-utils.js | |
|
|
138
|
-
| `js` | `isFocusable` | isFocusable | src/dom-utils.js | |
|
|
139
|
-
| `js` | `getFirstAndLastFocusableChildren` | getFirstAndLastFocusableChildren | src/dom-utils.js | |
|
|
140
|
-
| `js` | `walkComposedTree` | walkComposedTree | src/dom-utils.js | |
|
|
141
|
-
| `js` | `getDeepActiveElement` | getDeepActiveElement | src/dom-utils.js | |
|
|
142
|
-
| `js` | `deepContains` | deepContains | src/dom-utils.js | |
|
|
143
|
-
| `js` | `indexOfItemContainingTarget` | indexOfItemContainingTarget | src/dom-utils.js | |
|
|
144
|
-
| `js` | `composedAncestors` | composedAncestors | src/dom-utils.js | |
|
|
145
|
-
| `js` | `isClickInsideRect` | isClickInsideRect | src/dom-utils.js | |
|
|
146
|
-
| `js` | `randomID` | randomID | src/dom-utils.js | |
|
|
147
|
-
|
|
148
115
|
### `src/index.js`:
|
|
149
116
|
|
|
150
117
|
#### Exports
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@blockquote-web-components/blockquote-dialog",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"description": "Webcomponent blockquote-dialog following open-wc recommendations",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"lit",
|
|
@@ -103,7 +103,7 @@
|
|
|
103
103
|
"SwitchCase": 1,
|
|
104
104
|
"ignoredNodes": [
|
|
105
105
|
"PropertyDefinition",
|
|
106
|
-
"TemplateLiteral >
|
|
106
|
+
"TemplateLiteral > *"
|
|
107
107
|
]
|
|
108
108
|
}
|
|
109
109
|
],
|
|
@@ -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": "d0a77e49014065e4265fa11d4736e65671f53479"
|
|
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
|
|
|
@@ -44,6 +44,7 @@ import { styles as animations } from './styles/blockqoute-dialog-animations-styl
|
|
|
44
44
|
* @attribute open
|
|
45
45
|
* @attribute label
|
|
46
46
|
* @attribute labelledby
|
|
47
|
+
* @attribute labelledbyVisible
|
|
47
48
|
* @attribute type
|
|
48
49
|
* @fires open
|
|
49
50
|
* @fires close
|
|
@@ -84,14 +85,32 @@ export class BlockquoteDialog extends LitElement {
|
|
|
84
85
|
attribute: false,
|
|
85
86
|
},
|
|
86
87
|
|
|
88
|
+
/**
|
|
89
|
+
* The 'label' attribute will be used as the 'aria-label' for the dialog
|
|
90
|
+
*/
|
|
87
91
|
label: {
|
|
88
92
|
type: String,
|
|
89
93
|
},
|
|
90
94
|
|
|
95
|
+
/**
|
|
96
|
+
* The 'labelledby' attribute will be used as the 'aria-labelledby' for the dialog.
|
|
97
|
+
* It will also be used to create a slot with the same 'id' and 'name'.
|
|
98
|
+
* This slot is hidden by default and its 'name' and 'id' should correspond to the 'slot' attribute of an element in the Light DOM.
|
|
99
|
+
* This connects the 'slot', 'name', and 'id' attributes of a slot to be used with ARIA relationships.
|
|
100
|
+
*/
|
|
91
101
|
labelledby: {
|
|
92
102
|
type: String,
|
|
93
103
|
},
|
|
94
104
|
|
|
105
|
+
/**
|
|
106
|
+
* The 'labelledbyVisible' attribute will control the visibility of the slot created by 'labelledby'.
|
|
107
|
+
* By default, it is set to 'hidden'.
|
|
108
|
+
*/
|
|
109
|
+
labelledbyVisible: {
|
|
110
|
+
type: String,
|
|
111
|
+
attribute: 'labelledby-visibile',
|
|
112
|
+
},
|
|
113
|
+
|
|
95
114
|
/**
|
|
96
115
|
* The type of dialog for accessibility. Set this to `alert` to announce a
|
|
97
116
|
* dialog as an alert dialog.
|
|
@@ -131,6 +150,7 @@ export class BlockquoteDialog extends LitElement {
|
|
|
131
150
|
this.type = 'alert';
|
|
132
151
|
this.label = '';
|
|
133
152
|
this.labelledby = '';
|
|
153
|
+
this.labelledbyVisible = false;
|
|
134
154
|
|
|
135
155
|
if (!isServer) {
|
|
136
156
|
this.addEventListener('submit', this._handleSubmit);
|
|
@@ -210,7 +230,11 @@ export class BlockquoteDialog extends LitElement {
|
|
|
210
230
|
}
|
|
211
231
|
|
|
212
232
|
get _labeledbyTpl() {
|
|
213
|
-
return html`
|
|
233
|
+
return html`
|
|
234
|
+
${this.labelledby
|
|
235
|
+
? blockquoteDirectiveAriaidrefSlot(this.labelledby, this.labelledbyVisible)
|
|
236
|
+
: ''}
|
|
237
|
+
`;
|
|
214
238
|
}
|
|
215
239
|
|
|
216
240
|
get _contentTpl() {
|
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);
|