@m3e/core 1.0.0-rc.1 → 1.0.0-rc.3
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 +1 -2
- package/dist/a11y.js +6 -18
- package/dist/a11y.js.map +1 -1
- package/dist/a11y.min.js +5 -5
- package/dist/a11y.min.js.map +1 -1
- package/dist/css-custom-data.json +20 -0
- package/dist/custom-elements.json +3605 -3375
- package/dist/html-custom-data.json +36 -6
- package/dist/index.js +417 -33
- package/dist/index.js.map +1 -1
- package/dist/index.min.js +95 -84
- package/dist/index.min.js.map +1 -1
- package/dist/src/a11y/FocusTrapElement.d.ts +1 -1
- package/dist/src/a11y/FocusTrapElement.d.ts.map +1 -1
- package/dist/src/shared/controllers/PressedController.d.ts +2 -0
- package/dist/src/shared/controllers/PressedController.d.ts.map +1 -1
- package/dist/src/shared/mixins/AttachInternals.d.ts.map +1 -1
- package/dist/src/shared/mixins/Checked.d.ts.map +1 -1
- package/dist/src/shared/mixins/CheckedIndeterminate.d.ts.map +1 -1
- package/dist/src/shared/mixins/ConstraintValidation.d.ts.map +1 -1
- package/dist/src/shared/mixins/Dirty.d.ts.map +1 -1
- package/dist/src/shared/mixins/Disabled.d.ts.map +1 -1
- package/dist/src/shared/mixins/DisabledInteractive.d.ts.map +1 -1
- package/dist/src/shared/mixins/EventAttribute.d.ts.map +1 -1
- package/dist/src/shared/mixins/Focusable.d.ts.map +1 -1
- package/dist/src/shared/mixins/FormAssociated.d.ts.map +1 -1
- package/dist/src/shared/mixins/FormSubmitter.d.ts +4 -1
- package/dist/src/shared/mixins/FormSubmitter.d.ts.map +1 -1
- package/dist/src/shared/mixins/HtmlFor.d.ts.map +1 -1
- package/dist/src/shared/mixins/KeyboardClick.d.ts.map +1 -1
- package/dist/src/shared/mixins/Labelled.d.ts.map +1 -1
- package/dist/src/shared/mixins/LinkButton.d.ts +13 -3
- package/dist/src/shared/mixins/LinkButton.d.ts.map +1 -1
- package/dist/src/shared/mixins/ReadOnly.d.ts.map +1 -1
- package/dist/src/shared/mixins/Required.d.ts.map +1 -1
- package/dist/src/shared/mixins/RequiredConstraintValidation.d.ts.map +1 -1
- package/dist/src/shared/mixins/Role.d.ts.map +1 -1
- package/dist/src/shared/mixins/Selected.d.ts.map +1 -1
- package/dist/src/shared/mixins/Touched.d.ts.map +1 -1
- package/dist/src/shared/mixins/Vertical.d.ts.map +1 -1
- package/dist/src/shared/primitives/CollapsibleElement.d.ts +1 -1
- package/dist/src/shared/primitives/CollapsibleElement.d.ts.map +1 -1
- package/dist/src/shared/primitives/ElevationElement.d.ts +0 -1
- package/dist/src/shared/primitives/ElevationElement.d.ts.map +1 -1
- package/dist/src/shared/primitives/FocusRingElement.d.ts +0 -1
- package/dist/src/shared/primitives/FocusRingElement.d.ts.map +1 -1
- package/dist/src/shared/primitives/PseudoCheckboxElement.d.ts +0 -1
- package/dist/src/shared/primitives/PseudoCheckboxElement.d.ts.map +1 -1
- package/dist/src/shared/primitives/PseudoRadioElement.d.ts +0 -1
- package/dist/src/shared/primitives/PseudoRadioElement.d.ts.map +1 -1
- package/dist/src/shared/primitives/RippleElement.d.ts +0 -1
- package/dist/src/shared/primitives/RippleElement.d.ts.map +1 -1
- package/dist/src/shared/primitives/ScrollContainerElement.d.ts +1 -4
- package/dist/src/shared/primitives/ScrollContainerElement.d.ts.map +1 -1
- package/dist/src/shared/primitives/SlideElement.d.ts +1 -3
- package/dist/src/shared/primitives/SlideElement.d.ts.map +1 -1
- package/dist/src/shared/primitives/StateLayerElement.d.ts +0 -1
- package/dist/src/shared/primitives/StateLayerElement.d.ts.map +1 -1
- package/dist/src/shared/primitives/TextHighlightElement.d.ts +81 -0
- package/dist/src/shared/primitives/TextHighlightElement.d.ts.map +1 -0
- package/dist/src/shared/primitives/TextOverflowElement.d.ts +0 -1
- package/dist/src/shared/primitives/TextOverflowElement.d.ts.map +1 -1
- package/dist/src/shared/primitives/index.d.ts +1 -0
- package/dist/src/shared/primitives/index.d.ts.map +1 -1
- package/dist/src/shared/utils/scrollIntoViewIfNeeded.d.ts +2 -2
- package/dist/src/shared/utils/scrollIntoViewIfNeeded.d.ts.map +1 -1
- package/package.json +1 -1
- package/cem.config.mjs +0 -16
- package/demo/index.html +0 -58
- package/eslint.config.mjs +0 -21
- package/rollup.config.js +0 -132
- package/src/a11y/AriaDescriber.ts +0 -130
- package/src/a11y/FocusTrapElement.ts +0 -136
- package/src/a11y/InteractivityChecker.ts +0 -62
- package/src/a11y/LiveAnnouncer.ts +0 -143
- package/src/a11y/aria-reference.ts +0 -51
- package/src/a11y/index.ts +0 -8
- package/src/a11y/keycodes/KeyCode.ts +0 -128
- package/src/a11y/keycodes/ModifierKeys.ts +0 -5
- package/src/a11y/keycodes/getKeyCode.ts +0 -8
- package/src/a11y/keycodes/hasModifierKey.ts +0 -11
- package/src/a11y/keycodes/index.ts +0 -5
- package/src/a11y/keycodes/isModifierAllowed.ts +0 -12
- package/src/a11y/list-key/FocusKeyManager.ts +0 -25
- package/src/a11y/list-key/ListKeyManager.ts +0 -350
- package/src/a11y/list-key/ListManager.ts +0 -66
- package/src/a11y/list-key/RadioKeyManager.ts +0 -49
- package/src/a11y/list-key/RovingTabIndexManager.ts +0 -56
- package/src/a11y/list-key/SelectionManager.ts +0 -132
- package/src/a11y/list-key/Typeahead.ts +0 -131
- package/src/a11y/list-key/index.ts +0 -7
- package/src/a11y/visuallyHide.ts +0 -18
- package/src/anchoring/AnchorOptions.ts +0 -19
- package/src/anchoring/AnchorPosition.ts +0 -14
- package/src/anchoring/index.ts +0 -3
- package/src/anchoring/positionAnchor.ts +0 -53
- package/src/bidi/Directionality.ts +0 -85
- package/src/bidi/index.ts +0 -1
- package/src/index.ts +0 -1
- package/src/layout/Breakpoint.ts +0 -26
- package/src/layout/BreakpointObserver.ts +0 -44
- package/src/layout/index.ts +0 -2
- package/src/platform/Platform.ts +0 -67
- package/src/platform/index.ts +0 -1
- package/src/shared/controllers/FocusController.ts +0 -60
- package/src/shared/controllers/HoverController.ts +0 -140
- package/src/shared/controllers/IntersectionController.ts +0 -81
- package/src/shared/controllers/LongPressController.ts +0 -87
- package/src/shared/controllers/MonitorControllerBase.ts +0 -99
- package/src/shared/controllers/MutationController.ts +0 -89
- package/src/shared/controllers/PressedController.ts +0 -137
- package/src/shared/controllers/ResizeController.ts +0 -83
- package/src/shared/controllers/ScrollController.ts +0 -100
- package/src/shared/controllers/index.ts +0 -8
- package/src/shared/decorators/debounce.ts +0 -19
- package/src/shared/decorators/index.ts +0 -1
- package/src/shared/directives/index.ts +0 -1
- package/src/shared/directives/safeStyleMap.ts +0 -108
- package/src/shared/index.ts +0 -7
- package/src/shared/mixins/AttachInternals.ts +0 -48
- package/src/shared/mixins/Checked.ts +0 -50
- package/src/shared/mixins/CheckedIndeterminate.ts +0 -47
- package/src/shared/mixins/CheckedOrSelected.ts +0 -36
- package/src/shared/mixins/ConstraintValidation.ts +0 -213
- package/src/shared/mixins/Constructor.ts +0 -2
- package/src/shared/mixins/Dirty.ts +0 -71
- package/src/shared/mixins/Disabled.ts +0 -49
- package/src/shared/mixins/DisabledInteractive.ts +0 -78
- package/src/shared/mixins/EventAttribute.ts +0 -25
- package/src/shared/mixins/Focusable.ts +0 -52
- package/src/shared/mixins/FormAssociated.ts +0 -152
- package/src/shared/mixins/FormSubmitter.ts +0 -123
- package/src/shared/mixins/HtmlFor.ts +0 -89
- package/src/shared/mixins/KeyboardClick.ts +0 -46
- package/src/shared/mixins/Labelled.ts +0 -88
- package/src/shared/mixins/LinkButton.ts +0 -169
- package/src/shared/mixins/ReadOnly.ts +0 -48
- package/src/shared/mixins/Required.ts +0 -50
- package/src/shared/mixins/RequiredConstraintValidation.ts +0 -45
- package/src/shared/mixins/Role.ts +0 -134
- package/src/shared/mixins/Selected.ts +0 -50
- package/src/shared/mixins/Touched.ts +0 -71
- package/src/shared/mixins/Vertical.ts +0 -44
- package/src/shared/mixins/hasKeys.ts +0 -10
- package/src/shared/mixins/index.ts +0 -24
- package/src/shared/primitives/CollapsibleElement.ts +0 -227
- package/src/shared/primitives/ElevationElement.ts +0 -254
- package/src/shared/primitives/ElevationLevel.ts +0 -2
- package/src/shared/primitives/ElevationToken.ts +0 -18
- package/src/shared/primitives/FocusRingElement.ts +0 -199
- package/src/shared/primitives/FocusRingToken.ts +0 -24
- package/src/shared/primitives/PseudoCheckboxElement.ts +0 -116
- package/src/shared/primitives/PseudoRadioElement.ts +0 -83
- package/src/shared/primitives/RippleElement.ts +0 -289
- package/src/shared/primitives/RippleToken.ts +0 -15
- package/src/shared/primitives/ScrollContainerElement.ts +0 -151
- package/src/shared/primitives/ScrollDividers.ts +0 -2
- package/src/shared/primitives/SlideElement.ts +0 -128
- package/src/shared/primitives/StateLayerElement.ts +0 -191
- package/src/shared/primitives/StateLayerToken.ts +0 -16
- package/src/shared/primitives/TextOverflowElement.ts +0 -60
- package/src/shared/primitives/index.ts +0 -12
- package/src/shared/tokens/ColorToken.ts +0 -142
- package/src/shared/tokens/DensityToken.ts +0 -23
- package/src/shared/tokens/DesignToken.ts +0 -35
- package/src/shared/tokens/ElevationToken.ts +0 -115
- package/src/shared/tokens/MotionToken.ts +0 -107
- package/src/shared/tokens/ScrollbarToken.ts +0 -13
- package/src/shared/tokens/ShapeToken.ts +0 -138
- package/src/shared/tokens/StateToken.ts +0 -13
- package/src/shared/tokens/TypescaleToken.ts +0 -230
- package/src/shared/tokens/index.ts +0 -1
- package/src/shared/utils/getTextContent.ts +0 -31
- package/src/shared/utils/guid.ts +0 -11
- package/src/shared/utils/hasAssignedNodes.ts +0 -8
- package/src/shared/utils/index.ts +0 -5
- package/src/shared/utils/prefersReducedMotion.ts +0 -9
- package/src/shared/utils/scrollIntoViewIfNeeded.ts +0 -18
- package/tsconfig.json +0 -9
package/demo/index.html
DELETED
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
<!doctype html>
|
|
2
|
-
<html lang="en" style="overflow-y: auto">
|
|
3
|
-
<head>
|
|
4
|
-
<title>Core for M3E</title>
|
|
5
|
-
<meta charset="utf-8" />
|
|
6
|
-
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
7
|
-
<meta name="description" content="Core for M3E" />
|
|
8
|
-
<base href="./" />
|
|
9
|
-
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
|
10
|
-
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
|
11
|
-
<link
|
|
12
|
-
href="https://fonts.googleapis.com/css2?family=Roboto:ital,wght@0,100..900;1,100..900&display=swap"
|
|
13
|
-
rel="stylesheet"
|
|
14
|
-
/>
|
|
15
|
-
<link
|
|
16
|
-
href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,GRAD@24,400,0..1,0"
|
|
17
|
-
rel="stylesheet"
|
|
18
|
-
/>
|
|
19
|
-
<script type="importmap">
|
|
20
|
-
{
|
|
21
|
-
"imports": {
|
|
22
|
-
"lit": "https://cdn.jsdelivr.net/npm/lit@3.3.0/+esm",
|
|
23
|
-
"@m3e/core": "../../core/dist/index.min.js"
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
</script>
|
|
27
|
-
<script type="module" src="../../theme/dist/index.min.js"></script>
|
|
28
|
-
<script type="module" src="../dist/index.min.js"></script>
|
|
29
|
-
<style>
|
|
30
|
-
body {
|
|
31
|
-
font-family: "Roboto";
|
|
32
|
-
}
|
|
33
|
-
*:not(:defined) {
|
|
34
|
-
display: none;
|
|
35
|
-
}
|
|
36
|
-
</style>
|
|
37
|
-
</head>
|
|
38
|
-
<body>
|
|
39
|
-
<m3e-theme strong-focus>
|
|
40
|
-
<div
|
|
41
|
-
id="div"
|
|
42
|
-
style="
|
|
43
|
-
width: 300px;
|
|
44
|
-
height: 300px;
|
|
45
|
-
position: relative;
|
|
46
|
-
outline: none;
|
|
47
|
-
background-color: var(--md-sys-color-primary-container);
|
|
48
|
-
"
|
|
49
|
-
tabindex="0"
|
|
50
|
-
>
|
|
51
|
-
<m3e-focus-ring for="div"></m3e-focus-ring>
|
|
52
|
-
<m3e-elevation for="div" level="3"></m3e-elevation>
|
|
53
|
-
<m3e-state-layer for="div"></m3e-state-layer>
|
|
54
|
-
<m3e-ripple for="div"></m3e-ripple>
|
|
55
|
-
</div>
|
|
56
|
-
</m3e-theme>
|
|
57
|
-
</body>
|
|
58
|
-
</html>
|
package/eslint.config.mjs
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
import eslint from "@eslint/js";
|
|
2
|
-
import tseslint from "typescript-eslint";
|
|
3
|
-
import { fileURLToPath } from "url";
|
|
4
|
-
import { dirname } from "path";
|
|
5
|
-
|
|
6
|
-
export default tseslint.config(eslint.configs.recommended, tseslint.configs.recommended, {
|
|
7
|
-
languageOptions: {
|
|
8
|
-
parserOptions: {
|
|
9
|
-
project: true,
|
|
10
|
-
tsconfigRootDir: dirname(fileURLToPath(import.meta.url)),
|
|
11
|
-
},
|
|
12
|
-
},
|
|
13
|
-
rules: {
|
|
14
|
-
"@typescript-eslint/no-explicit-any": [
|
|
15
|
-
"error",
|
|
16
|
-
{
|
|
17
|
-
ignoreRestArgs: true,
|
|
18
|
-
},
|
|
19
|
-
],
|
|
20
|
-
},
|
|
21
|
-
});
|
package/rollup.config.js
DELETED
|
@@ -1,132 +0,0 @@
|
|
|
1
|
-
import resolve from "@rollup/plugin-node-resolve";
|
|
2
|
-
import terser from "@rollup/plugin-terser";
|
|
3
|
-
import typescript from "@rollup/plugin-typescript";
|
|
4
|
-
|
|
5
|
-
const banner = `/**
|
|
6
|
-
* @license MIT
|
|
7
|
-
* Copyright (c) 2025 matraic
|
|
8
|
-
* See LICENSE file in the project root for full license text.
|
|
9
|
-
*/`;
|
|
10
|
-
|
|
11
|
-
export default [
|
|
12
|
-
{
|
|
13
|
-
input: "src/index.ts",
|
|
14
|
-
output: [
|
|
15
|
-
{
|
|
16
|
-
file: "dist/index.js",
|
|
17
|
-
format: "esm",
|
|
18
|
-
sourcemap: true,
|
|
19
|
-
banner: banner,
|
|
20
|
-
},
|
|
21
|
-
{
|
|
22
|
-
file: "dist/index.min.js",
|
|
23
|
-
format: "esm",
|
|
24
|
-
sourcemap: true,
|
|
25
|
-
banner: banner,
|
|
26
|
-
plugins: [terser({ mangle: true })],
|
|
27
|
-
},
|
|
28
|
-
],
|
|
29
|
-
external: ["lit"],
|
|
30
|
-
plugins: [resolve(), typescript()],
|
|
31
|
-
},
|
|
32
|
-
{
|
|
33
|
-
input: "src/a11y/index.ts",
|
|
34
|
-
output: [
|
|
35
|
-
{
|
|
36
|
-
file: "dist/a11y.js",
|
|
37
|
-
format: "esm",
|
|
38
|
-
sourcemap: true,
|
|
39
|
-
banner: banner,
|
|
40
|
-
},
|
|
41
|
-
{
|
|
42
|
-
file: "dist/a11y.min.js",
|
|
43
|
-
format: "esm",
|
|
44
|
-
sourcemap: true,
|
|
45
|
-
banner: banner,
|
|
46
|
-
plugins: [terser({ mangle: true })],
|
|
47
|
-
},
|
|
48
|
-
],
|
|
49
|
-
external: ["lit"],
|
|
50
|
-
plugins: [resolve(), typescript()],
|
|
51
|
-
},
|
|
52
|
-
{
|
|
53
|
-
input: "src/anchoring/index.ts",
|
|
54
|
-
output: [
|
|
55
|
-
{
|
|
56
|
-
file: "dist/anchoring.js",
|
|
57
|
-
format: "esm",
|
|
58
|
-
sourcemap: true,
|
|
59
|
-
banner: banner,
|
|
60
|
-
},
|
|
61
|
-
{
|
|
62
|
-
file: "dist/anchoring.min.js",
|
|
63
|
-
format: "esm",
|
|
64
|
-
sourcemap: true,
|
|
65
|
-
banner: banner,
|
|
66
|
-
plugins: [terser({ mangle: true })],
|
|
67
|
-
},
|
|
68
|
-
],
|
|
69
|
-
external: ["lit"],
|
|
70
|
-
plugins: [resolve(), typescript()],
|
|
71
|
-
},
|
|
72
|
-
{
|
|
73
|
-
input: "src/bidi/index.ts",
|
|
74
|
-
output: [
|
|
75
|
-
{
|
|
76
|
-
file: "dist/bidi.js",
|
|
77
|
-
format: "esm",
|
|
78
|
-
sourcemap: true,
|
|
79
|
-
banner: banner,
|
|
80
|
-
},
|
|
81
|
-
{
|
|
82
|
-
file: "dist/bidi.min.js",
|
|
83
|
-
format: "esm",
|
|
84
|
-
sourcemap: true,
|
|
85
|
-
banner: banner,
|
|
86
|
-
plugins: [terser({ mangle: true })],
|
|
87
|
-
},
|
|
88
|
-
],
|
|
89
|
-
external: ["lit"],
|
|
90
|
-
plugins: [resolve(), typescript()],
|
|
91
|
-
},
|
|
92
|
-
{
|
|
93
|
-
input: "src/layout/index.ts",
|
|
94
|
-
output: [
|
|
95
|
-
{
|
|
96
|
-
file: "dist/layout.js",
|
|
97
|
-
format: "esm",
|
|
98
|
-
sourcemap: true,
|
|
99
|
-
banner: banner,
|
|
100
|
-
},
|
|
101
|
-
{
|
|
102
|
-
file: "dist/layout.min.js",
|
|
103
|
-
format: "esm",
|
|
104
|
-
sourcemap: true,
|
|
105
|
-
banner: banner,
|
|
106
|
-
plugins: [terser({ mangle: true })],
|
|
107
|
-
},
|
|
108
|
-
],
|
|
109
|
-
external: ["lit"],
|
|
110
|
-
plugins: [resolve(), typescript()],
|
|
111
|
-
},
|
|
112
|
-
{
|
|
113
|
-
input: "src/platform/index.ts",
|
|
114
|
-
output: [
|
|
115
|
-
{
|
|
116
|
-
file: "dist/platform.js",
|
|
117
|
-
format: "esm",
|
|
118
|
-
sourcemap: true,
|
|
119
|
-
banner: banner,
|
|
120
|
-
},
|
|
121
|
-
{
|
|
122
|
-
file: "dist/platform.min.js",
|
|
123
|
-
format: "esm",
|
|
124
|
-
sourcemap: true,
|
|
125
|
-
banner: banner,
|
|
126
|
-
plugins: [terser({ mangle: true })],
|
|
127
|
-
},
|
|
128
|
-
],
|
|
129
|
-
external: ["lit"],
|
|
130
|
-
plugins: [resolve(), typescript()],
|
|
131
|
-
},
|
|
132
|
-
];
|
|
@@ -1,130 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Adapted from Angular Material CDK ARIA Describer
|
|
3
|
-
* Source: https://github.com/angular/components/blob/main/src/cdk/a11y/aria-describer/aria-describer.ts
|
|
4
|
-
*
|
|
5
|
-
* @license MIT
|
|
6
|
-
* Copyright (c) 2025 Google LLC
|
|
7
|
-
* See LICENSE file in the project root for full license text.
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
import { isServer } from "lit";
|
|
11
|
-
|
|
12
|
-
import { addAriaReferencedId, removeAriaReferencedId } from "./aria-reference";
|
|
13
|
-
import { visuallyHide } from "./visuallyHide";
|
|
14
|
-
|
|
15
|
-
/** Utility for generating visually hidden elements that convey descriptive messages using `aria-describedby`. */
|
|
16
|
-
export class M3eAriaDescriber {
|
|
17
|
-
/** @private */ static #nextId = 0;
|
|
18
|
-
/** @private */ static #messageContainers = new Map<
|
|
19
|
-
ParentNode,
|
|
20
|
-
{
|
|
21
|
-
containerElement: HTMLElement;
|
|
22
|
-
messageRegistry: Map<string, { messageElement: Element; count: number }>;
|
|
23
|
-
}
|
|
24
|
-
>();
|
|
25
|
-
|
|
26
|
-
/**
|
|
27
|
-
* Adds an `aria-describedby` reference to a hidden element that contains the message.
|
|
28
|
-
* @param {Element} element The element for which to provide a visually hidden description.
|
|
29
|
-
* @param {string} message The message used to describe `element`.
|
|
30
|
-
* @param {string} [role="tooltip"] The ARIA role of the message.
|
|
31
|
-
*/
|
|
32
|
-
static describe(element: Element, message: string, role: string = "tooltip"): void {
|
|
33
|
-
if (isServer) {
|
|
34
|
-
return;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
const rootNode = element.getRootNode() as ParentNode;
|
|
38
|
-
if (!(rootNode instanceof ShadowRoot || rootNode instanceof Document)) {
|
|
39
|
-
return;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
let container = this.#messageContainers.get(rootNode);
|
|
43
|
-
if (!container) {
|
|
44
|
-
container = {
|
|
45
|
-
containerElement: this.#createMessageContainer(),
|
|
46
|
-
messageRegistry: new Map<string, { messageElement: Element; count: number }>(),
|
|
47
|
-
};
|
|
48
|
-
|
|
49
|
-
// Append to body if document, otherwise, append as a child of shadow root.
|
|
50
|
-
(rootNode instanceof Document ? rootNode.body : rootNode).appendChild(container.containerElement);
|
|
51
|
-
this.#messageContainers.set(rootNode, container);
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
const key = `${role}:${message}`;
|
|
55
|
-
let entry = container.messageRegistry.get(key);
|
|
56
|
-
if (!entry) {
|
|
57
|
-
entry = { messageElement: this.#createMessageElement(message, role), count: 0 };
|
|
58
|
-
container.containerElement.appendChild(entry.messageElement);
|
|
59
|
-
container.messageRegistry.set(key, entry);
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
entry.count++;
|
|
63
|
-
|
|
64
|
-
addAriaReferencedId(element, "aria-describedby", entry.messageElement.id);
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
/**
|
|
68
|
-
* Removes an `aria-describedby` reference to a hidden element that contains the message.
|
|
69
|
-
* @param {Element} element The element from which to remove a visually hidden description.
|
|
70
|
-
* @param {string} message The message used to describe `element`.
|
|
71
|
-
* @param {string} [role="tooltip"] The ARIA role of the message.
|
|
72
|
-
*/
|
|
73
|
-
static removeDescription(element: Element, message: string, role: string = "tooltip"): void {
|
|
74
|
-
if (isServer) {
|
|
75
|
-
return;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
const rootNode = element.getRootNode() as ParentNode;
|
|
79
|
-
const container = this.#messageContainers.get(rootNode);
|
|
80
|
-
if (!container) {
|
|
81
|
-
return;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
const key = `${role}:${message}`;
|
|
85
|
-
const entry = container.messageRegistry.get(key);
|
|
86
|
-
if (!entry) {
|
|
87
|
-
return;
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
entry.count--;
|
|
91
|
-
removeAriaReferencedId(element, "aria-describedby", entry.messageElement.id);
|
|
92
|
-
|
|
93
|
-
// If there are no more elements referencing the message, remove it from the DOM.
|
|
94
|
-
if (entry.count <= 0) {
|
|
95
|
-
entry.messageElement.remove();
|
|
96
|
-
container.messageRegistry.delete(key);
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
// If there are no more registered messages, remove the container from the DOM.
|
|
100
|
-
if (container.messageRegistry.size == 0) {
|
|
101
|
-
container.containerElement?.remove();
|
|
102
|
-
this.#messageContainers.delete(rootNode);
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
/** @private */
|
|
107
|
-
static #createMessageContainer(): HTMLElement {
|
|
108
|
-
const messageContainer = document.createElement("div");
|
|
109
|
-
messageContainer.classList.add("m3e-describedby-message-container");
|
|
110
|
-
visuallyHide(messageContainer.style);
|
|
111
|
-
return messageContainer;
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
/** @private */
|
|
115
|
-
static #createMessageElement(message: string, role: string): HTMLElement {
|
|
116
|
-
const messageElement = document.createElement("span");
|
|
117
|
-
messageElement.id = `m3e-describedby-message-${this.#nextId++}`;
|
|
118
|
-
messageElement.role = role;
|
|
119
|
-
messageElement.ariaAtomic = "true";
|
|
120
|
-
messageElement.innerText = message.trim();
|
|
121
|
-
return messageElement;
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
declare global {
|
|
126
|
-
/** Utility for generating visually hidden elements that convey descriptive messages using `aria-describedby`. */
|
|
127
|
-
var M3eAriaDescriber: M3eAriaDescriber;
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
globalThis.M3eAriaDescriber = M3eAriaDescriber;
|
|
@@ -1,136 +0,0 @@
|
|
|
1
|
-
import { css, CSSResultGroup, html, LitElement } from "lit";
|
|
2
|
-
import { customElement, query } from "lit/decorators.js";
|
|
3
|
-
|
|
4
|
-
import { Disabled } from "../shared/mixins/Disabled";
|
|
5
|
-
import { Role } from "../shared/mixins/Role";
|
|
6
|
-
|
|
7
|
-
import { M3eInteractivityChecker } from "./InteractivityChecker";
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* A non-visual element used to trap focus within nested content.
|
|
11
|
-
* @tag m3e-focus-trap
|
|
12
|
-
*
|
|
13
|
-
* @slot - Renders content for which to trap focus.
|
|
14
|
-
*
|
|
15
|
-
* @attr disabled - Disables the focus trap.
|
|
16
|
-
*/
|
|
17
|
-
@customElement("m3e-focus-trap")
|
|
18
|
-
export class M3eFocusTrapElement extends Disabled(Role(LitElement, "none")) {
|
|
19
|
-
/** The styles of the element. */
|
|
20
|
-
static override styles: CSSResultGroup = css`
|
|
21
|
-
:host {
|
|
22
|
-
display: contents;
|
|
23
|
-
}
|
|
24
|
-
.trap {
|
|
25
|
-
outline: none;
|
|
26
|
-
}
|
|
27
|
-
`;
|
|
28
|
-
|
|
29
|
-
/** @private */ @query(".trap") private readonly _firstTrap!: HTMLElement | null;
|
|
30
|
-
|
|
31
|
-
/** @inheritdoc */
|
|
32
|
-
protected override render(): unknown {
|
|
33
|
-
const trap = html`<div
|
|
34
|
-
class="trap"
|
|
35
|
-
.inert="${this.disabled}"
|
|
36
|
-
tabindex="0"
|
|
37
|
-
aria-hidden="true"
|
|
38
|
-
@focus="${this.#onFocus}"
|
|
39
|
-
></div>`;
|
|
40
|
-
return html`${trap}<slot></slot>${trap}`;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
/** @private */
|
|
44
|
-
#onFocus(e: FocusEvent): void {
|
|
45
|
-
const [first, last] = this.#getFirstAndLastFocusableChild();
|
|
46
|
-
const isFirst = e?.target === this._firstTrap;
|
|
47
|
-
const fromFirst = e.relatedTarget === first;
|
|
48
|
-
const fromLast = e.relatedTarget === last;
|
|
49
|
-
const fromOutside = !fromFirst && !fromLast;
|
|
50
|
-
|
|
51
|
-
if ((!isFirst && fromLast) || (isFirst && fromOutside)) {
|
|
52
|
-
first?.focus();
|
|
53
|
-
return;
|
|
54
|
-
}
|
|
55
|
-
if ((isFirst && fromFirst) || (!isFirst && fromOutside)) {
|
|
56
|
-
last?.focus();
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
/** @private */
|
|
61
|
-
#getFirstAndLastFocusableChild(): [HTMLElement | null, HTMLElement | null] {
|
|
62
|
-
let first: HTMLElement | null = null;
|
|
63
|
-
let last: HTMLElement | null = null;
|
|
64
|
-
|
|
65
|
-
this.#walkTree(this, [], (element, parents) => {
|
|
66
|
-
if (
|
|
67
|
-
M3eInteractivityChecker.isFocusable(element, parents) &&
|
|
68
|
-
element instanceof HTMLElement &&
|
|
69
|
-
element.tabIndex >= 0
|
|
70
|
-
) {
|
|
71
|
-
first = first ?? element;
|
|
72
|
-
last = element;
|
|
73
|
-
}
|
|
74
|
-
});
|
|
75
|
-
|
|
76
|
-
return [first, last];
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
/** @private */
|
|
80
|
-
#walkTree(
|
|
81
|
-
root: Element,
|
|
82
|
-
parents: readonly Element[],
|
|
83
|
-
callback: (element: Element, parents: readonly Element[]) => void
|
|
84
|
-
): void {
|
|
85
|
-
parents = [...parents, root];
|
|
86
|
-
|
|
87
|
-
for (const node of root.childNodes) {
|
|
88
|
-
if (node.nodeType !== Node.ELEMENT_NODE) continue;
|
|
89
|
-
const element = <HTMLElement>node;
|
|
90
|
-
|
|
91
|
-
callback(element, parents);
|
|
92
|
-
|
|
93
|
-
if (element.shadowRoot) {
|
|
94
|
-
this.#walkShadowRoot(element, parents, callback);
|
|
95
|
-
} else if (element instanceof HTMLSlotElement) {
|
|
96
|
-
this.#walkSlot(element, parents, callback);
|
|
97
|
-
} else if (element.hasChildNodes()) {
|
|
98
|
-
this.#walkTree(element, parents, callback);
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
/** @private */
|
|
104
|
-
#walkShadowRoot(
|
|
105
|
-
root: Element,
|
|
106
|
-
parents: readonly Element[],
|
|
107
|
-
callback: (element: Element, parents: readonly Element[]) => void
|
|
108
|
-
): void {
|
|
109
|
-
for (const node of root.shadowRoot?.childNodes ?? []) {
|
|
110
|
-
if (node.nodeType !== Node.ELEMENT_NODE) continue;
|
|
111
|
-
const element = <HTMLElement>node;
|
|
112
|
-
callback(element, parents);
|
|
113
|
-
if (!element.hasChildNodes()) continue;
|
|
114
|
-
this.#walkTree(element, parents, callback);
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
/** @private */
|
|
119
|
-
#walkSlot(
|
|
120
|
-
slot: HTMLSlotElement,
|
|
121
|
-
parents: readonly Element[],
|
|
122
|
-
callback: (element: Element, parents: readonly Element[]) => void
|
|
123
|
-
) {
|
|
124
|
-
for (const node of slot.assignedElements()) {
|
|
125
|
-
callback(node, parents);
|
|
126
|
-
if (!node.hasChildNodes()) continue;
|
|
127
|
-
this.#walkTree(node, parents, callback);
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
declare global {
|
|
133
|
-
interface HTMLElementTagNameMap {
|
|
134
|
-
"m3e-focus-trap": M3eFocusTrapElement;
|
|
135
|
-
}
|
|
136
|
-
}
|
|
@@ -1,62 +0,0 @@
|
|
|
1
|
-
/** Utility for checking the interactivity of an element, such as whether it is focusable. */
|
|
2
|
-
export class M3eInteractivityChecker {
|
|
3
|
-
/**
|
|
4
|
-
* Determines whether a given element can receive focus.
|
|
5
|
-
* @param {Element} element The element to test.
|
|
6
|
-
* @param {readonly Element[]} [parents = undefined] The known parent elements to test. The default value is `undefined`.
|
|
7
|
-
* @returns {boolean} Whether `element` can receive focus.
|
|
8
|
-
*/
|
|
9
|
-
static isFocusable(element: Element, parents?: readonly Element[]): boolean {
|
|
10
|
-
if (
|
|
11
|
-
element.matches(
|
|
12
|
-
":is(button,input,select,textarea,object,:is(a,area)[href],[tabindex],[contenteditable=true]):not(:disabled,[disabled])"
|
|
13
|
-
)
|
|
14
|
-
) {
|
|
15
|
-
return !this.#cannotFocusParent(parents);
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
if (
|
|
19
|
-
!element.localName.includes("-") ||
|
|
20
|
-
!element.matches(":not(:disabled,[disabled])") ||
|
|
21
|
-
element.getAttribute("aria-disabled") === "true"
|
|
22
|
-
) {
|
|
23
|
-
return false;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
if (element.shadowRoot?.delegatesFocus) {
|
|
27
|
-
return !this.#cannotFocusParent(parents);
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
return false;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
static #cannotFocusParent(parents?: readonly Element[]): boolean {
|
|
34
|
-
return parents?.some((x) => x.matches(":is([aria-hidden='true'],:disabled,[disabled])")) ?? false;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
/**
|
|
38
|
-
* Finds interactive elements that descend from the specified element.
|
|
39
|
-
* @param {HTMLElement} element The `HTMLElement` to search.
|
|
40
|
-
* @returns {HTMLElement[]} The interactive elements that descend from `element`.
|
|
41
|
-
*/
|
|
42
|
-
static findInteractiveElements(element: HTMLElement): HTMLElement[] {
|
|
43
|
-
const elements = new Array<HTMLElement>();
|
|
44
|
-
const walker = element.ownerDocument.createTreeWalker(element, NodeFilter.SHOW_ELEMENT);
|
|
45
|
-
|
|
46
|
-
for (let node = walker.nextNode(); node; node = walker.nextNode()) {
|
|
47
|
-
const element = <HTMLElement>walker.currentNode;
|
|
48
|
-
if (this.isFocusable(element)) {
|
|
49
|
-
elements.push(element);
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
return elements;
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
declare global {
|
|
58
|
-
/** Utility for checking the interactivity of an element, such as whether it is focusable. */
|
|
59
|
-
var M3eInteractivityChecker: M3eInteractivityChecker;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
globalThis.M3eInteractivityChecker = M3eInteractivityChecker;
|
|
@@ -1,143 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Adapted from Angular Material CDK Live Announcer
|
|
3
|
-
* Source: https://github.com/angular/components/blob/main/src/cdk/a11y/live-announcer/live-announcer.ts
|
|
4
|
-
*
|
|
5
|
-
* @license MIT
|
|
6
|
-
* Copyright (c) 2025 Google LLC
|
|
7
|
-
* See LICENSE file in the project root for full license text.
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
import { isServer } from "lit";
|
|
11
|
-
|
|
12
|
-
import { addAriaReferencedId } from "./aria-reference";
|
|
13
|
-
import { visuallyHide } from "./visuallyHide";
|
|
14
|
-
|
|
15
|
-
/** Specifies the possible politeness levels in which to announce messages. */
|
|
16
|
-
export type ARIALivePoliteness = "off" | "polite" | "assertive";
|
|
17
|
-
|
|
18
|
-
/** Utility for announcing messages to screen readers. */
|
|
19
|
-
export class M3eLiveAnnouncer {
|
|
20
|
-
/** @private */ static #nextId = 0;
|
|
21
|
-
/** @private */ static #liveElement?: HTMLElement;
|
|
22
|
-
/** @private */ static #previousTimeout: number;
|
|
23
|
-
/** @private */ static #currentPromise?: Promise<void>;
|
|
24
|
-
/** @private */ static #currentResolve?: () => void;
|
|
25
|
-
|
|
26
|
-
/**
|
|
27
|
-
* Announces the specified message to screen readers.
|
|
28
|
-
* @param {string} message The message to announce.
|
|
29
|
-
* @returns {Promise<void>} A `Promise` that resolves when `message` is added to the DOM.
|
|
30
|
-
*/
|
|
31
|
-
static announce(message: string): Promise<void>;
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
* Announces the specified message to screen readers.
|
|
35
|
-
* @param {string} message The message to announce.
|
|
36
|
-
* @param {ARIALivePoliteness | undefined} politeness The politeness in which to announce `message`.
|
|
37
|
-
* @returns {Promise<void>} A `Promise` that resolves when `message` is added to the DOM.
|
|
38
|
-
*/
|
|
39
|
-
static announce(message: string, politeness?: ARIALivePoliteness): Promise<void>;
|
|
40
|
-
|
|
41
|
-
/**
|
|
42
|
-
* Announces the specified message to screen readers.
|
|
43
|
-
* @param {string} message The message to announce.
|
|
44
|
-
* @param {number | undefined} duration The duration, in milliseconds, after which to clear the announcement. Note
|
|
45
|
-
* that this takes effect after the message has been added to the DOM, which can be up to
|
|
46
|
-
* 100ms after `announce` has been called.
|
|
47
|
-
* @returns {Promise<void>} A `Promise` that resolves when `message` is added to the DOM.
|
|
48
|
-
*/
|
|
49
|
-
static announce(message: string, duration?: number): Promise<void>;
|
|
50
|
-
|
|
51
|
-
/**
|
|
52
|
-
* Announces the specified message to screen readers.
|
|
53
|
-
* @param {string} message The message to announce.
|
|
54
|
-
* @param {ARIALivePoliteness | undefined} politeness The politeness in which to announce `message`.
|
|
55
|
-
* @param {number | undefined} duration The duration, in milliseconds, after which to clear the announcement. Note
|
|
56
|
-
* that this takes effect after the message has been added to the DOM, which can be up to 100ms after `announce` has been called.
|
|
57
|
-
* @returns {Promise<void>} A `Promise` that resolves when `message` is added to the DOM.
|
|
58
|
-
*/
|
|
59
|
-
static announce(message: string, politeness?: ARIALivePoliteness, duration?: number): Promise<void>;
|
|
60
|
-
|
|
61
|
-
static announce(message: string, ...args: any[]): Promise<void> {
|
|
62
|
-
if (isServer) {
|
|
63
|
-
return Promise.resolve();
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
this.#liveElement = this.#liveElement ?? this.#createLiveElement();
|
|
67
|
-
|
|
68
|
-
let politeness: ARIALivePoliteness | undefined;
|
|
69
|
-
let duration: number | undefined;
|
|
70
|
-
|
|
71
|
-
if (args.length === 1 && typeof args[0] === "number") {
|
|
72
|
-
duration = args[0];
|
|
73
|
-
} else {
|
|
74
|
-
[politeness, duration] = args;
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
this.clear();
|
|
78
|
-
clearTimeout(this.#previousTimeout);
|
|
79
|
-
|
|
80
|
-
this.#liveElement.setAttribute("aria-live", politeness ?? "polite");
|
|
81
|
-
|
|
82
|
-
for (const modal of document.querySelectorAll("m3e-dialog")) {
|
|
83
|
-
addAriaReferencedId(modal, "aria-owns", this.#liveElement.id);
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
this.#currentPromise = this.#currentPromise ?? new Promise((resolve) => (this.#currentResolve = resolve));
|
|
87
|
-
clearTimeout(this.#previousTimeout);
|
|
88
|
-
|
|
89
|
-
this.#previousTimeout = setTimeout(() => {
|
|
90
|
-
if (!this.#liveElement) {
|
|
91
|
-
return;
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
this.#liveElement.textContent = message;
|
|
95
|
-
if (duration !== undefined) {
|
|
96
|
-
this.#previousTimeout = setTimeout(() => this.clear(), duration);
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
this.#currentResolve?.();
|
|
100
|
-
this.#currentPromise = this.#currentResolve = undefined;
|
|
101
|
-
}, 100);
|
|
102
|
-
|
|
103
|
-
return this.#currentPromise;
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
/**
|
|
107
|
-
* Clears the current text from the announcer element. Can be used to prevent
|
|
108
|
-
* screen readers from reading the text out again while the user is going
|
|
109
|
-
* through page landmarks.
|
|
110
|
-
*/
|
|
111
|
-
static clear() {
|
|
112
|
-
if (this.#liveElement) {
|
|
113
|
-
this.#liveElement.textContent = "";
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
/** @private */
|
|
118
|
-
static #createLiveElement(): HTMLElement {
|
|
119
|
-
const prev = document.getElementsByClassName("m3e-live-announcer");
|
|
120
|
-
for (let i = 0; i < prev.length; i++) {
|
|
121
|
-
prev[i].remove();
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
const liveAnnouncer = document.createElement("div");
|
|
125
|
-
liveAnnouncer.classList.add("m3e-live-announcer");
|
|
126
|
-
liveAnnouncer.setAttribute("aria-atomic", "true");
|
|
127
|
-
liveAnnouncer.setAttribute("aria-live", "polite");
|
|
128
|
-
liveAnnouncer.id = `m3e-live-announcer-${this.#nextId++}`;
|
|
129
|
-
|
|
130
|
-
visuallyHide(liveAnnouncer.style);
|
|
131
|
-
|
|
132
|
-
document.body.append(liveAnnouncer);
|
|
133
|
-
|
|
134
|
-
return liveAnnouncer;
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
declare global {
|
|
139
|
-
/** Utility for announcing messages to screen readers. */
|
|
140
|
-
var M3eLiveAnnouncer: M3eLiveAnnouncer;
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
globalThis.M3eLiveAnnouncer = M3eLiveAnnouncer;
|