@momentum-design/components 0.136.1 → 0.137.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/dist/browser/index.js +274 -270
- package/dist/browser/index.js.map +4 -4
- package/dist/components/datepicker/datepicker.component.js +6 -9
- package/dist/components/datepicker/datepicker.utils.d.ts +2 -2
- package/dist/components/datepicker/datepicker.utils.js +4 -3
- package/dist/components/focustrap/focustrap.component.d.ts +68 -0
- package/dist/components/focustrap/focustrap.component.js +122 -0
- package/dist/components/focustrap/focustrap.constants.d.ts +7 -0
- package/dist/components/focustrap/focustrap.constants.js +10 -0
- package/dist/components/focustrap/focustrap.styles.d.ts +2 -0
- package/dist/components/focustrap/focustrap.styles.js +9 -0
- package/dist/components/focustrap/focustrap.types.d.ts +13 -0
- package/dist/components/focustrap/focustrap.types.js +2 -0
- package/dist/components/focustrap/index.d.ts +7 -0
- package/dist/components/focustrap/index.js +4 -0
- package/dist/custom-elements.json +128 -0
- package/dist/index.d.ts +2 -1
- package/dist/index.js +2 -1
- package/dist/react/focustrap/index.d.ts +32 -0
- package/dist/react/focustrap/index.js +23 -0
- package/dist/react/index.d.ts +1 -0
- package/dist/react/index.js +1 -0
- package/dist/utils/mixins/focus/FocusTrapMixin.js +6 -0
- package/dist/utils/mixins/focus/FocusTrapStack.js +1 -0
- package/package.json +1 -1
|
@@ -198,11 +198,11 @@ class DatePicker extends FormInternalsMixin(DataAriaLabelMixin(FormfieldWrapper)
|
|
|
198
198
|
// AI-Assisted: keep input spinbuttons aligned with valid min/max date boundaries.
|
|
199
199
|
getParsedMin() {
|
|
200
200
|
var _a;
|
|
201
|
-
return this.min ? (_a = parseISO(this.min)) !== null && _a !== void 0 ? _a : undefined : undefined;
|
|
201
|
+
return this.min ? ((_a = parseISO(this.min)) !== null && _a !== void 0 ? _a : undefined) : undefined;
|
|
202
202
|
}
|
|
203
203
|
getParsedMax() {
|
|
204
204
|
var _a;
|
|
205
|
-
return this.max ? (_a = parseISO(this.max)) !== null && _a !== void 0 ? _a : undefined : undefined;
|
|
205
|
+
return this.max ? ((_a = parseISO(this.max)) !== null && _a !== void 0 ? _a : undefined) : undefined;
|
|
206
206
|
}
|
|
207
207
|
clampValueToRange(value) {
|
|
208
208
|
var _a, _b;
|
|
@@ -239,14 +239,10 @@ class DatePicker extends FormInternalsMixin(DataAriaLabelMixin(FormfieldWrapper)
|
|
|
239
239
|
max = Math.min(max, maxDt.getMonth() + 1);
|
|
240
240
|
}
|
|
241
241
|
else if (field === 'day' && currentMonth && currentYear) {
|
|
242
|
-
if (minDt &&
|
|
243
|
-
currentYear === minDt.getFullYear() &&
|
|
244
|
-
currentMonth === minDt.getMonth() + 1) {
|
|
242
|
+
if (minDt && currentYear === minDt.getFullYear() && currentMonth === minDt.getMonth() + 1) {
|
|
245
243
|
min = Math.max(min, minDt.getDate());
|
|
246
244
|
}
|
|
247
|
-
if (maxDt &&
|
|
248
|
-
currentYear === maxDt.getFullYear() &&
|
|
249
|
-
currentMonth === maxDt.getMonth() + 1) {
|
|
245
|
+
if (maxDt && currentYear === maxDt.getFullYear() && currentMonth === maxDt.getMonth() + 1) {
|
|
250
246
|
max = Math.min(max, maxDt.getDate());
|
|
251
247
|
}
|
|
252
248
|
}
|
|
@@ -688,7 +684,8 @@ class DatePicker extends FormInternalsMixin(DataAriaLabelMixin(FormfieldWrapper)
|
|
|
688
684
|
}
|
|
689
685
|
renderDefaultVariant() {
|
|
690
686
|
const displayText = this.getDisplayText();
|
|
691
|
-
const
|
|
687
|
+
const selectionMode = this.effectiveSelectionMode;
|
|
688
|
+
const placeholderText = getPlaceholder(this.locale, selectionMode === SELECTION_MODE.RANGE || selectionMode === SELECTION_MODE.WEEK);
|
|
692
689
|
return html `
|
|
693
690
|
<div
|
|
694
691
|
id="${TRIGGER_ID}"
|
|
@@ -20,9 +20,9 @@ declare function parseISODate(isoDate: string): DateParts | null;
|
|
|
20
20
|
*/
|
|
21
21
|
declare function buildISODate(month: string, day: string, year: string): string;
|
|
22
22
|
/**
|
|
23
|
-
* Gets the placeholder text for a date
|
|
23
|
+
* Gets the placeholder text for a date or date range based on locale order.
|
|
24
24
|
*/
|
|
25
|
-
declare function getPlaceholder(locale: string): string;
|
|
25
|
+
declare function getPlaceholder(locale: string, isRange?: boolean): string;
|
|
26
26
|
/**
|
|
27
27
|
* Gets the max value for a spinbutton field.
|
|
28
28
|
*/
|
|
@@ -44,12 +44,13 @@ function buildISODate(month, day, year) {
|
|
|
44
44
|
return toISODate(dt);
|
|
45
45
|
}
|
|
46
46
|
/**
|
|
47
|
-
* Gets the placeholder text for a date
|
|
47
|
+
* Gets the placeholder text for a date or date range based on locale order.
|
|
48
48
|
*/
|
|
49
|
-
function getPlaceholder(locale) {
|
|
49
|
+
function getPlaceholder(locale, isRange = false) {
|
|
50
50
|
const { order, separator } = getLocaleDateParts(locale);
|
|
51
51
|
const labels = { month: 'mm', day: 'dd', year: 'yyyy' };
|
|
52
|
-
|
|
52
|
+
const placeholder = order.map(field => labels[field]).join(` ${separator} `);
|
|
53
|
+
return isRange ? `${placeholder} - ${placeholder}` : placeholder;
|
|
53
54
|
}
|
|
54
55
|
/**
|
|
55
56
|
* Gets the max value for a spinbutton field.
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { CSSResult, PropertyValues } from 'lit';
|
|
2
|
+
import { Component } from '../../models';
|
|
3
|
+
declare const FocusTrap_base: import("../../utils/mixins/index.types").Constructor<Component & import("../../utils/mixins/focus/FocusTrapMixin").FocusTrapClassInterface> & typeof Component;
|
|
4
|
+
/**
|
|
5
|
+
* FocusTrap is a container component that manages keyboard focus within a specified region. It prevents focus from moving outside the container via Tab/Shift+Tab and optionally restores focus to the previously focused element when the trap is deactivated. Commonly used in modals, dialogs, popovers, and other overlay patterns to improve accessibility and user experience.
|
|
6
|
+
*
|
|
7
|
+
* **When to use**
|
|
8
|
+
*
|
|
9
|
+
* - **Custom Modals and dialogs** – Trap focus so users interact only with the dialog content.
|
|
10
|
+
* - **Custom Popovers and menus** – Keep focus within an overlay component.
|
|
11
|
+
* - **Custom Dropdown menus** – Ensure focus stays within menu options while open.
|
|
12
|
+
* - **Multi-step forms** – Control focus flow between steps or sub-sections.
|
|
13
|
+
* - **Accessible disclosure widgets** – Any collapsible or hidden content that should capture focus when revealed.
|
|
14
|
+
*
|
|
15
|
+
* **When not to use**
|
|
16
|
+
*
|
|
17
|
+
* - **Full-page navigation** – Do not trap focus on the main page; only use on transient overlays.
|
|
18
|
+
* - **Optional or advisory overlays** – If users can dismiss the overlay and continue with the page, consider whether focus trapping is necessary.
|
|
19
|
+
* - **Nested focus traps** – Avoid nesting multiple FocusTrap instances; the innermost trap will dominate focus behavior. MDC Dialog, popover and menu components already implement FocusTrap internally, so wrapping them in another FocusTrap is unnecessary and may cause unexpected behavior.
|
|
20
|
+
*
|
|
21
|
+
* @tagname mdc-focustrap
|
|
22
|
+
*
|
|
23
|
+
* @event focus-trap-activated - (React: onFocusTrapActivated) Fired when the focus trap is activated.
|
|
24
|
+
* @event focus-trap-deactivated - (React: onFocusTrapDeactivated) Fired when the focus trap is deactivated.
|
|
25
|
+
*
|
|
26
|
+
* @slot default - Default slot – place any focusable content here.
|
|
27
|
+
*/
|
|
28
|
+
declare class FocusTrap extends FocusTrap_base {
|
|
29
|
+
/**
|
|
30
|
+
* When `true`, disables keyboard focus trapping inside this component.
|
|
31
|
+
* When `false` (default), focus will be trapped.
|
|
32
|
+
*
|
|
33
|
+
* @default false
|
|
34
|
+
*/
|
|
35
|
+
trapDisabled: boolean;
|
|
36
|
+
/**
|
|
37
|
+
* Implements the abstract `focusTrap` required by `FocusTrapMixin`.
|
|
38
|
+
* Kept in sync with the public `trapDisabled` property (inverted).
|
|
39
|
+
* @internal
|
|
40
|
+
*/
|
|
41
|
+
protected focusTrap: boolean;
|
|
42
|
+
/**
|
|
43
|
+
* When `true`, disables restoring focus to the previously focused element
|
|
44
|
+
* when `trapDisabled` is set to `true` or the component is disconnected from the DOM.
|
|
45
|
+
* When `false` (default), focus will be restored.
|
|
46
|
+
* @default false
|
|
47
|
+
*/
|
|
48
|
+
restoreFocusDisabled: boolean;
|
|
49
|
+
/**
|
|
50
|
+
* When `true`, the first focusable element inside the container receives focus
|
|
51
|
+
* automatically when focus trapping is enabled (when `trapDisabled` is `false`).
|
|
52
|
+
*
|
|
53
|
+
* @default false
|
|
54
|
+
*/
|
|
55
|
+
autoFocus: boolean;
|
|
56
|
+
/** @internal – the element that had focus before the trap was activated */
|
|
57
|
+
private previouslyFocusedElement;
|
|
58
|
+
disconnectedCallback(): void;
|
|
59
|
+
protected updated(changedProperties: PropertyValues): void;
|
|
60
|
+
/**
|
|
61
|
+
* Restores focus to the element that was focused before the trap was activated,
|
|
62
|
+
* if `restoreFocusDisabled` is false.
|
|
63
|
+
*/
|
|
64
|
+
private restorePreviousFocus;
|
|
65
|
+
render(): import("lit-html").TemplateResult<1>;
|
|
66
|
+
static styles: Array<CSSResult>;
|
|
67
|
+
}
|
|
68
|
+
export default FocusTrap;
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
2
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
3
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
4
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
5
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
|
+
};
|
|
7
|
+
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
8
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
9
|
+
};
|
|
10
|
+
// AI-Assisted
|
|
11
|
+
import { html } from 'lit';
|
|
12
|
+
import { property } from 'lit/decorators.js';
|
|
13
|
+
import { Component } from '../../models';
|
|
14
|
+
import { FocusTrapMixin } from '../../utils/mixins/focus/FocusTrapMixin';
|
|
15
|
+
import { DEFAULTS } from './focustrap.constants';
|
|
16
|
+
import styles from './focustrap.styles';
|
|
17
|
+
/**
|
|
18
|
+
* @tagname mdc-focustrap
|
|
19
|
+
*
|
|
20
|
+
* @event focus-trap-activated - (React: onFocusTrapActivated) Fired when the focus trap is activated.
|
|
21
|
+
* @event focus-trap-deactivated - (React: onFocusTrapDeactivated) Fired when the focus trap is deactivated.
|
|
22
|
+
*
|
|
23
|
+
* @slot default - Default slot – place any focusable content here.
|
|
24
|
+
*/
|
|
25
|
+
class FocusTrap extends FocusTrapMixin(Component) {
|
|
26
|
+
constructor() {
|
|
27
|
+
super(...arguments);
|
|
28
|
+
/**
|
|
29
|
+
* When `true`, disables keyboard focus trapping inside this component.
|
|
30
|
+
* When `false` (default), focus will be trapped.
|
|
31
|
+
*
|
|
32
|
+
* @default false
|
|
33
|
+
*/
|
|
34
|
+
this.trapDisabled = DEFAULTS.TRAP_DISABLED;
|
|
35
|
+
/**
|
|
36
|
+
* Implements the abstract `focusTrap` required by `FocusTrapMixin`.
|
|
37
|
+
* Kept in sync with the public `trapDisabled` property (inverted).
|
|
38
|
+
* @internal
|
|
39
|
+
*/
|
|
40
|
+
this.focusTrap = true;
|
|
41
|
+
/**
|
|
42
|
+
* When `true`, disables restoring focus to the previously focused element
|
|
43
|
+
* when `trapDisabled` is set to `true` or the component is disconnected from the DOM.
|
|
44
|
+
* When `false` (default), focus will be restored.
|
|
45
|
+
* @default false
|
|
46
|
+
*/
|
|
47
|
+
this.restoreFocusDisabled = DEFAULTS.RESTORE_FOCUS_DISABLED;
|
|
48
|
+
/**
|
|
49
|
+
* When `true`, the first focusable element inside the container receives focus
|
|
50
|
+
* automatically when focus trapping is enabled (when `trapDisabled` is `false`).
|
|
51
|
+
*
|
|
52
|
+
* @default false
|
|
53
|
+
*/
|
|
54
|
+
this.autoFocus = DEFAULTS.AUTO_FOCUS;
|
|
55
|
+
/** @internal – the element that had focus before the trap was activated */
|
|
56
|
+
this.previouslyFocusedElement = null;
|
|
57
|
+
}
|
|
58
|
+
disconnectedCallback() {
|
|
59
|
+
const shouldRestoreFocus = !this.trapDisabled;
|
|
60
|
+
this.deactivateFocusTrap();
|
|
61
|
+
if (shouldRestoreFocus) {
|
|
62
|
+
this.restorePreviousFocus();
|
|
63
|
+
}
|
|
64
|
+
super.disconnectedCallback();
|
|
65
|
+
}
|
|
66
|
+
updated(changedProperties) {
|
|
67
|
+
var _a;
|
|
68
|
+
super.updated(changedProperties);
|
|
69
|
+
if (changedProperties.has('trapDisabled')) {
|
|
70
|
+
// Keep focusTrap in sync with trapDisabled (inverted)
|
|
71
|
+
this.focusTrap = !this.trapDisabled;
|
|
72
|
+
if (!this.trapDisabled) {
|
|
73
|
+
this.previouslyFocusedElement = (_a = document.activeElement) !== null && _a !== void 0 ? _a : null;
|
|
74
|
+
this.activateFocusTrap();
|
|
75
|
+
if (this.autoFocus) {
|
|
76
|
+
// Slotted children (e.g. mdc-input) may not have completed their own first render yet
|
|
77
|
+
// at this point, so findFocusable() would find nothing. Defer to the next animation
|
|
78
|
+
// frame, by which time all pending Lit updates have flushed.
|
|
79
|
+
requestAnimationFrame(() => {
|
|
80
|
+
if (this.autoFocus && !this.trapDisabled) {
|
|
81
|
+
this.setInitialFocus();
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
this.dispatchEvent(new CustomEvent('focus-trap-activated', { bubbles: true, composed: true }));
|
|
86
|
+
}
|
|
87
|
+
else {
|
|
88
|
+
this.deactivateFocusTrap();
|
|
89
|
+
this.restorePreviousFocus();
|
|
90
|
+
this.dispatchEvent(new CustomEvent('focus-trap-deactivated', { bubbles: true, composed: true }));
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Restores focus to the element that was focused before the trap was activated,
|
|
96
|
+
* if `restoreFocusDisabled` is false.
|
|
97
|
+
*/
|
|
98
|
+
restorePreviousFocus() {
|
|
99
|
+
if (!this.restoreFocusDisabled && this.previouslyFocusedElement) {
|
|
100
|
+
this.previouslyFocusedElement.focus({ preventScroll: true });
|
|
101
|
+
}
|
|
102
|
+
this.previouslyFocusedElement = null;
|
|
103
|
+
}
|
|
104
|
+
render() {
|
|
105
|
+
return html `<slot></slot>`;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
FocusTrap.styles = [...Component.styles, ...styles];
|
|
109
|
+
__decorate([
|
|
110
|
+
property({ type: Boolean, reflect: true, attribute: 'trap-disabled' }),
|
|
111
|
+
__metadata("design:type", Boolean)
|
|
112
|
+
], FocusTrap.prototype, "trapDisabled", void 0);
|
|
113
|
+
__decorate([
|
|
114
|
+
property({ type: Boolean, reflect: true, attribute: 'restore-focus-disabled' }),
|
|
115
|
+
__metadata("design:type", Boolean)
|
|
116
|
+
], FocusTrap.prototype, "restoreFocusDisabled", void 0);
|
|
117
|
+
__decorate([
|
|
118
|
+
property({ type: Boolean, reflect: true, attribute: 'auto-focus' }),
|
|
119
|
+
__metadata("design:type", Boolean)
|
|
120
|
+
], FocusTrap.prototype, "autoFocus", void 0);
|
|
121
|
+
export default FocusTrap;
|
|
122
|
+
// End AI-Assisted
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
// AI-Assisted
|
|
2
|
+
import utils from '../../utils/tag-name';
|
|
3
|
+
const TAG_NAME = utils.constructTagName('focustrap');
|
|
4
|
+
const DEFAULTS = {
|
|
5
|
+
TRAP_DISABLED: false,
|
|
6
|
+
RESTORE_FOCUS_DISABLED: false,
|
|
7
|
+
AUTO_FOCUS: false,
|
|
8
|
+
};
|
|
9
|
+
export { TAG_NAME, DEFAULTS };
|
|
10
|
+
// End AI-Assisted
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
interface Events {
|
|
2
|
+
/** Fired when the focus trap is activated (React: onFocusTrapActivated) */
|
|
3
|
+
onFocusTrapActivatedEvent: Event;
|
|
4
|
+
/** Fired when the focus trap is deactivated (React: onFocusTrapDeactivated) */
|
|
5
|
+
onFocusTrapDeactivatedEvent: Event;
|
|
6
|
+
}
|
|
7
|
+
declare global {
|
|
8
|
+
interface GlobalEventHandlersEventMap {
|
|
9
|
+
focustrapactivated: Event;
|
|
10
|
+
focustrapdeactivated: Event;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
export type { Events };
|
|
@@ -18189,6 +18189,134 @@
|
|
|
18189
18189
|
}
|
|
18190
18190
|
]
|
|
18191
18191
|
},
|
|
18192
|
+
{
|
|
18193
|
+
"kind": "javascript-module",
|
|
18194
|
+
"path": "components/focustrap/focustrap.component.js",
|
|
18195
|
+
"declarations": [
|
|
18196
|
+
{
|
|
18197
|
+
"kind": "class",
|
|
18198
|
+
"description": "",
|
|
18199
|
+
"name": "FocusTrap",
|
|
18200
|
+
"slots": [
|
|
18201
|
+
{
|
|
18202
|
+
"description": "Default slot – place any focusable content here.",
|
|
18203
|
+
"name": "default"
|
|
18204
|
+
}
|
|
18205
|
+
],
|
|
18206
|
+
"members": [
|
|
18207
|
+
{
|
|
18208
|
+
"kind": "field",
|
|
18209
|
+
"name": "autoFocus",
|
|
18210
|
+
"type": {
|
|
18211
|
+
"text": "boolean"
|
|
18212
|
+
},
|
|
18213
|
+
"description": "When `true`, the first focusable element inside the container receives focus\nautomatically when focus trapping is enabled (when `trapDisabled` is `false`).",
|
|
18214
|
+
"default": "false",
|
|
18215
|
+
"attribute": "auto-focus",
|
|
18216
|
+
"reflects": true
|
|
18217
|
+
},
|
|
18218
|
+
{
|
|
18219
|
+
"kind": "field",
|
|
18220
|
+
"name": "restoreFocusDisabled",
|
|
18221
|
+
"type": {
|
|
18222
|
+
"text": "boolean"
|
|
18223
|
+
},
|
|
18224
|
+
"description": "When `true`, disables restoring focus to the previously focused element\nwhen `trapDisabled` is set to `true` or the component is disconnected from the DOM.\nWhen `false` (default), focus will be restored.",
|
|
18225
|
+
"default": "false",
|
|
18226
|
+
"attribute": "restore-focus-disabled",
|
|
18227
|
+
"reflects": true
|
|
18228
|
+
},
|
|
18229
|
+
{
|
|
18230
|
+
"kind": "method",
|
|
18231
|
+
"name": "restorePreviousFocus",
|
|
18232
|
+
"privacy": "private",
|
|
18233
|
+
"description": "Restores focus to the element that was focused before the trap was activated,\nif `restoreFocusDisabled` is false."
|
|
18234
|
+
},
|
|
18235
|
+
{
|
|
18236
|
+
"kind": "field",
|
|
18237
|
+
"name": "trapDisabled",
|
|
18238
|
+
"type": {
|
|
18239
|
+
"text": "boolean"
|
|
18240
|
+
},
|
|
18241
|
+
"description": "When `true`, disables keyboard focus trapping inside this component.\nWhen `false` (default), focus will be trapped.",
|
|
18242
|
+
"default": "false",
|
|
18243
|
+
"attribute": "trap-disabled",
|
|
18244
|
+
"reflects": true
|
|
18245
|
+
}
|
|
18246
|
+
],
|
|
18247
|
+
"events": [
|
|
18248
|
+
{
|
|
18249
|
+
"name": "focus-trap-activated",
|
|
18250
|
+
"type": {
|
|
18251
|
+
"text": "CustomEvent"
|
|
18252
|
+
},
|
|
18253
|
+
"description": "(React: onFocusTrapActivated) Fired when the focus trap is activated.",
|
|
18254
|
+
"reactName": "onFocusTrapActivated"
|
|
18255
|
+
},
|
|
18256
|
+
{
|
|
18257
|
+
"name": "focus-trap-deactivated",
|
|
18258
|
+
"type": {
|
|
18259
|
+
"text": "CustomEvent"
|
|
18260
|
+
},
|
|
18261
|
+
"description": "(React: onFocusTrapDeactivated) Fired when the focus trap is deactivated.",
|
|
18262
|
+
"reactName": "onFocusTrapDeactivated"
|
|
18263
|
+
}
|
|
18264
|
+
],
|
|
18265
|
+
"attributes": [
|
|
18266
|
+
{
|
|
18267
|
+
"name": "trap-disabled",
|
|
18268
|
+
"type": {
|
|
18269
|
+
"text": "boolean"
|
|
18270
|
+
},
|
|
18271
|
+
"description": "When `true`, disables keyboard focus trapping inside this component.\nWhen `false` (default), focus will be trapped.",
|
|
18272
|
+
"default": "false",
|
|
18273
|
+
"fieldName": "trapDisabled"
|
|
18274
|
+
},
|
|
18275
|
+
{
|
|
18276
|
+
"name": "restore-focus-disabled",
|
|
18277
|
+
"type": {
|
|
18278
|
+
"text": "boolean"
|
|
18279
|
+
},
|
|
18280
|
+
"description": "When `true`, disables restoring focus to the previously focused element\nwhen `trapDisabled` is set to `true` or the component is disconnected from the DOM.\nWhen `false` (default), focus will be restored.",
|
|
18281
|
+
"default": "false",
|
|
18282
|
+
"fieldName": "restoreFocusDisabled"
|
|
18283
|
+
},
|
|
18284
|
+
{
|
|
18285
|
+
"name": "auto-focus",
|
|
18286
|
+
"type": {
|
|
18287
|
+
"text": "boolean"
|
|
18288
|
+
},
|
|
18289
|
+
"description": "When `true`, the first focusable element inside the container receives focus\nautomatically when focus trapping is enabled (when `trapDisabled` is `false`).",
|
|
18290
|
+
"default": "false",
|
|
18291
|
+
"fieldName": "autoFocus"
|
|
18292
|
+
}
|
|
18293
|
+
],
|
|
18294
|
+
"mixins": [
|
|
18295
|
+
{
|
|
18296
|
+
"name": "FocusTrapMixin",
|
|
18297
|
+
"module": "/src/utils/mixins/focus/FocusTrapMixin"
|
|
18298
|
+
}
|
|
18299
|
+
],
|
|
18300
|
+
"superclass": {
|
|
18301
|
+
"name": "Component",
|
|
18302
|
+
"module": "/src/models"
|
|
18303
|
+
},
|
|
18304
|
+
"tagName": "mdc-focustrap",
|
|
18305
|
+
"jsDoc": "/**\n * @tagname mdc-focustrap\n *\n * @event focus-trap-activated - (React: onFocusTrapActivated) Fired when the focus trap is activated.\n * @event focus-trap-deactivated - (React: onFocusTrapDeactivated) Fired when the focus trap is deactivated.\n *\n * @slot default - Default slot – place any focusable content here.\n */",
|
|
18306
|
+
"customElement": true
|
|
18307
|
+
}
|
|
18308
|
+
],
|
|
18309
|
+
"exports": [
|
|
18310
|
+
{
|
|
18311
|
+
"kind": "js",
|
|
18312
|
+
"name": "default",
|
|
18313
|
+
"declaration": {
|
|
18314
|
+
"name": "FocusTrap",
|
|
18315
|
+
"module": "components/focustrap/focustrap.component.js"
|
|
18316
|
+
}
|
|
18317
|
+
}
|
|
18318
|
+
]
|
|
18319
|
+
},
|
|
18192
18320
|
{
|
|
18193
18321
|
"kind": "javascript-module",
|
|
18194
18322
|
"path": "components/formfieldgroup/formfieldgroup.component.js",
|
package/dist/index.d.ts
CHANGED
|
@@ -91,6 +91,7 @@ import Banner from './components/banner';
|
|
|
91
91
|
import Buttonsimple from './components/buttonsimple';
|
|
92
92
|
import Verticaltablist from './components/verticaltablist';
|
|
93
93
|
import StatusMessage from './components/statusmessage';
|
|
94
|
+
import FocusTrap from './components/focustrap';
|
|
94
95
|
import type { AvatarSize } from './components/avatar/avatar.types';
|
|
95
96
|
import type { BadgeType } from './components/badge/badge.types';
|
|
96
97
|
import type { ColorType as ChipColorType } from './components/staticchip/staticchip.types';
|
|
@@ -121,6 +122,6 @@ import type { TimePickerChangeEvent, TimePickerInputEvent } from './components/t
|
|
|
121
122
|
import type { DatePickerChangeEvent, DatePickerInputEvent } from './components/datepicker/datepicker.types';
|
|
122
123
|
import type { CalendarDateSelectedEvent, CalendarMonthChangedEvent } from './components/calendar/calendar.types';
|
|
123
124
|
import type { ListBoxChangeEvent } from './components/listbox/listbox.types';
|
|
124
|
-
export { Accordion, AccordionButton, AccordionGroup, AlertChip, Animation, AnnouncementDialog, Appheader, Avatar, AvatarButton, Badge, Brandvisual, Bullet, Button, ButtonGroup, ButtonLink, Calendar, Card, CardButton, CardCheckbox, CardRadio, Checkbox, Chip, Coachmark, ControlTypeProvider, DatePicker, Dialog, Divider, FilterChip, FormfieldGroup, Icon, IconProvider, Illustration, IllustrationProvider, Input, InputChip, Link, LinkButton, Linksimple, List, Listheader, ListItem, Marker, MenuBar, MenuItem, MenuItemCheckbox, MenuItemRadio, MenuPopover, MenuSection, NavMenuItem, OptGroup, Option, Password, Popover, Presence, Progressbar, Progressspinner, Radio, RadioGroup, ResponsiveSettingsProvider, ScreenreaderAnnouncer, Searchfield, Searchpopover, Select, SelectListbox, SideNavigation, Skeleton, Spinner, StaticChip, StaticCheckbox, StaticRadio, StaticToggle, Stepper, StepperConnector, StepperItem, Tab, TabList, Text, Textarea, TimePicker, ThemeProvider, Toast, Toggle, Typewriter, ToggleTip, Tooltip, VirtualizedList, Combobox, Slider, ListBox, Banner, Buttonsimple, Verticaltablist, StatusMessage, };
|
|
125
|
+
export { Accordion, AccordionButton, AccordionGroup, AlertChip, Animation, AnnouncementDialog, Appheader, Avatar, AvatarButton, Badge, Brandvisual, Bullet, Button, ButtonGroup, ButtonLink, Calendar, Card, CardButton, CardCheckbox, CardRadio, Checkbox, Chip, Coachmark, ControlTypeProvider, DatePicker, Dialog, Divider, FilterChip, FormfieldGroup, Icon, IconProvider, Illustration, IllustrationProvider, Input, InputChip, Link, LinkButton, Linksimple, List, Listheader, ListItem, Marker, MenuBar, MenuItem, MenuItemCheckbox, MenuItemRadio, MenuPopover, MenuSection, NavMenuItem, OptGroup, Option, Password, Popover, Presence, Progressbar, Progressspinner, Radio, RadioGroup, ResponsiveSettingsProvider, ScreenreaderAnnouncer, Searchfield, Searchpopover, Select, SelectListbox, SideNavigation, Skeleton, Spinner, StaticChip, StaticCheckbox, StaticRadio, StaticToggle, Stepper, StepperConnector, StepperItem, Tab, TabList, Text, Textarea, TimePicker, ThemeProvider, Toast, Toggle, Typewriter, ToggleTip, Tooltip, VirtualizedList, Combobox, Slider, ListBox, Banner, Buttonsimple, Verticaltablist, StatusMessage, FocusTrap, };
|
|
125
126
|
export type { AvatarSize, BadgeType, ChipColorType, ButtonColor, ButtonVariant, IconButtonSize, MenuPopoverActionEvent, MenuPopoverChangeEvent, MenuSectionChangeEvent, PillButtonSize, PopoverPlacement, PresenceType, SkeletonVariant, SelectChangeEvent, SelectInputEvent, SpinnerSize, SpinnerVariant, SliderChangeEvent, StatusMessageSeverity, TextType, TypewriterType, InputInputEvent, InputChangeEvent, InputFocusEvent, InputBlurEvent, InputClearEvent, VirtualizedListScrollEvent, TablistChangeEvent, TextareaInputEvent, TextareaChangeEvent, TextareaFocusEvent, TextareaBlurEvent, TextareaLimitExceededEvent, ToggleOnChangeEvent, CheckboxOnChangeEvent, LinkButtonSize, TimePickerChangeEvent, TimePickerInputEvent, DatePickerChangeEvent, DatePickerInputEvent, CalendarDateSelectedEvent, CalendarMonthChangedEvent, VerticaltablistChangeEvent, ListBoxChangeEvent, };
|
|
126
127
|
export { BUTTON_COLORS, BUTTON_VARIANTS, ICON_BUTTON_SIZES, inMemoryCache, PILL_BUTTON_SIZES, SKELETON_VARIANTS, webAPIAssetsCache, };
|
package/dist/index.js
CHANGED
|
@@ -93,11 +93,12 @@ import Banner from './components/banner';
|
|
|
93
93
|
import Buttonsimple from './components/buttonsimple';
|
|
94
94
|
import Verticaltablist from './components/verticaltablist';
|
|
95
95
|
import StatusMessage from './components/statusmessage';
|
|
96
|
+
import FocusTrap from './components/focustrap';
|
|
96
97
|
// Constants / Utils Imports
|
|
97
98
|
import { BUTTON_COLORS, BUTTON_VARIANTS, ICON_BUTTON_SIZES, PILL_BUTTON_SIZES, } from './components/button/button.constants';
|
|
98
99
|
import { SKELETON_VARIANTS } from './components/skeleton/skeleton.constants';
|
|
99
100
|
import { inMemoryCache, webAPIAssetsCache } from './utils/assets-cache';
|
|
100
101
|
// Components Exports
|
|
101
|
-
export { Accordion, AccordionButton, AccordionGroup, AlertChip, Animation, AnnouncementDialog, Appheader, Avatar, AvatarButton, Badge, Brandvisual, Bullet, Button, ButtonGroup, ButtonLink, Calendar, Card, CardButton, CardCheckbox, CardRadio, Checkbox, Chip, Coachmark, ControlTypeProvider, DatePicker, Dialog, Divider, FilterChip, FormfieldGroup, Icon, IconProvider, Illustration, IllustrationProvider, Input, InputChip, Link, LinkButton, Linksimple, List, Listheader, ListItem, Marker, MenuBar, MenuItem, MenuItemCheckbox, MenuItemRadio, MenuPopover, MenuSection, NavMenuItem, OptGroup, Option, Password, Popover, Presence, Progressbar, Progressspinner, Radio, RadioGroup, ResponsiveSettingsProvider, ScreenreaderAnnouncer, Searchfield, Searchpopover, Select, SelectListbox, SideNavigation, Skeleton, Spinner, StaticChip, StaticCheckbox, StaticRadio, StaticToggle, Stepper, StepperConnector, StepperItem, Tab, TabList, Text, Textarea, TimePicker, ThemeProvider, Toast, Toggle, Typewriter, ToggleTip, Tooltip, VirtualizedList, Combobox, Slider, ListBox, Banner, Buttonsimple, Verticaltablist, StatusMessage, };
|
|
102
|
+
export { Accordion, AccordionButton, AccordionGroup, AlertChip, Animation, AnnouncementDialog, Appheader, Avatar, AvatarButton, Badge, Brandvisual, Bullet, Button, ButtonGroup, ButtonLink, Calendar, Card, CardButton, CardCheckbox, CardRadio, Checkbox, Chip, Coachmark, ControlTypeProvider, DatePicker, Dialog, Divider, FilterChip, FormfieldGroup, Icon, IconProvider, Illustration, IllustrationProvider, Input, InputChip, Link, LinkButton, Linksimple, List, Listheader, ListItem, Marker, MenuBar, MenuItem, MenuItemCheckbox, MenuItemRadio, MenuPopover, MenuSection, NavMenuItem, OptGroup, Option, Password, Popover, Presence, Progressbar, Progressspinner, Radio, RadioGroup, ResponsiveSettingsProvider, ScreenreaderAnnouncer, Searchfield, Searchpopover, Select, SelectListbox, SideNavigation, Skeleton, Spinner, StaticChip, StaticCheckbox, StaticRadio, StaticToggle, Stepper, StepperConnector, StepperItem, Tab, TabList, Text, Textarea, TimePicker, ThemeProvider, Toast, Toggle, Typewriter, ToggleTip, Tooltip, VirtualizedList, Combobox, Slider, ListBox, Banner, Buttonsimple, Verticaltablist, StatusMessage, FocusTrap, };
|
|
102
103
|
// Constants / Utils Exports
|
|
103
104
|
export { BUTTON_COLORS, BUTTON_VARIANTS, ICON_BUTTON_SIZES, inMemoryCache, PILL_BUTTON_SIZES, SKELETON_VARIANTS, webAPIAssetsCache, };
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { type EventName } from '@lit/react';
|
|
2
|
+
import Component from '../../components/focustrap';
|
|
3
|
+
import type { Events } from '../../components/focustrap/focustrap.types';
|
|
4
|
+
/**
|
|
5
|
+
* FocusTrap is a container component that manages keyboard focus within a specified region. It prevents focus from moving outside the container via Tab/Shift+Tab and optionally restores focus to the previously focused element when the trap is deactivated. Commonly used in modals, dialogs, popovers, and other overlay patterns to improve accessibility and user experience.
|
|
6
|
+
*
|
|
7
|
+
* **When to use**
|
|
8
|
+
*
|
|
9
|
+
* - **Custom Modals and dialogs** – Trap focus so users interact only with the dialog content.
|
|
10
|
+
* - **Custom Popovers and menus** – Keep focus within an overlay component.
|
|
11
|
+
* - **Custom Dropdown menus** – Ensure focus stays within menu options while open.
|
|
12
|
+
* - **Multi-step forms** – Control focus flow between steps or sub-sections.
|
|
13
|
+
* - **Accessible disclosure widgets** – Any collapsible or hidden content that should capture focus when revealed.
|
|
14
|
+
*
|
|
15
|
+
* **When not to use**
|
|
16
|
+
*
|
|
17
|
+
* - **Full-page navigation** – Do not trap focus on the main page; only use on transient overlays.
|
|
18
|
+
* - **Optional or advisory overlays** – If users can dismiss the overlay and continue with the page, consider whether focus trapping is necessary.
|
|
19
|
+
* - **Nested focus traps** – Avoid nesting multiple FocusTrap instances; the innermost trap will dominate focus behavior. MDC Dialog, popover and menu components already implement FocusTrap internally, so wrapping them in another FocusTrap is unnecessary and may cause unexpected behavior.
|
|
20
|
+
*
|
|
21
|
+
* @tagname mdc-focustrap
|
|
22
|
+
*
|
|
23
|
+
* @event focus-trap-activated - (React: onFocusTrapActivated) Fired when the focus trap is activated.
|
|
24
|
+
* @event focus-trap-deactivated - (React: onFocusTrapDeactivated) Fired when the focus trap is deactivated.
|
|
25
|
+
*
|
|
26
|
+
* @slot default - Default slot – place any focusable content here.
|
|
27
|
+
*/
|
|
28
|
+
declare const reactWrapper: import("@lit/react").ReactWebComponent<Component, {
|
|
29
|
+
onFocusTrapActivated: EventName<Events["onFocusTrapActivatedEvent"]>;
|
|
30
|
+
onFocusTrapDeactivated: EventName<Events["onFocusTrapDeactivatedEvent"]>;
|
|
31
|
+
}>;
|
|
32
|
+
export default reactWrapper;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { createComponent } from '@lit/react';
|
|
3
|
+
import Component from '../../components/focustrap';
|
|
4
|
+
import { TAG_NAME } from '../../components/focustrap/focustrap.constants';
|
|
5
|
+
/**
|
|
6
|
+
* @tagname mdc-focustrap
|
|
7
|
+
*
|
|
8
|
+
* @event focus-trap-activated - (React: onFocusTrapActivated) Fired when the focus trap is activated.
|
|
9
|
+
* @event focus-trap-deactivated - (React: onFocusTrapDeactivated) Fired when the focus trap is deactivated.
|
|
10
|
+
*
|
|
11
|
+
* @slot default - Default slot – place any focusable content here.
|
|
12
|
+
*/
|
|
13
|
+
const reactWrapper = createComponent({
|
|
14
|
+
tagName: TAG_NAME,
|
|
15
|
+
elementClass: Component,
|
|
16
|
+
react: React,
|
|
17
|
+
events: {
|
|
18
|
+
onFocusTrapActivated: 'focus-trap-activated',
|
|
19
|
+
onFocusTrapDeactivated: 'focus-trap-deactivated',
|
|
20
|
+
},
|
|
21
|
+
displayName: 'FocusTrap',
|
|
22
|
+
});
|
|
23
|
+
export default reactWrapper;
|
package/dist/react/index.d.ts
CHANGED
|
@@ -29,6 +29,7 @@ export { default as DatePicker } from './datepicker';
|
|
|
29
29
|
export { default as Dialog } from './dialog';
|
|
30
30
|
export { default as Divider } from './divider';
|
|
31
31
|
export { default as FilterChip } from './filterchip';
|
|
32
|
+
export { default as FocusTrap } from './focustrap';
|
|
32
33
|
export { default as FormfieldGroup } from './formfieldgroup';
|
|
33
34
|
export { default as FormfieldWrapper } from './formfieldwrapper';
|
|
34
35
|
export { default as Icon } from './icon';
|
package/dist/react/index.js
CHANGED
|
@@ -29,6 +29,7 @@ export { default as DatePicker } from './datepicker';
|
|
|
29
29
|
export { default as Dialog } from './dialog';
|
|
30
30
|
export { default as Divider } from './divider';
|
|
31
31
|
export { default as FilterChip } from './filterchip';
|
|
32
|
+
export { default as FocusTrap } from './focustrap';
|
|
32
33
|
export { default as FormfieldGroup } from './formfieldgroup';
|
|
33
34
|
export { default as FormfieldWrapper } from './formfieldwrapper';
|
|
34
35
|
export { default as Icon } from './icon';
|
|
@@ -148,6 +148,12 @@ export const FocusTrapMixin = (superClass) => {
|
|
|
148
148
|
}
|
|
149
149
|
const activeElement = this.getDeepActiveElement();
|
|
150
150
|
const activeIndex = this.findElement(activeElement);
|
|
151
|
+
// If focus currently sits outside this trap's focusable elements (e.g. on a sibling
|
|
152
|
+
// element in the document), do not hijack Tab navigation - let the browser's default
|
|
153
|
+
// behavior move focus naturally, including into the trap.
|
|
154
|
+
if (activeIndex === -1) {
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
151
157
|
const direction = event.shiftKey;
|
|
152
158
|
if (direction) {
|
|
153
159
|
this.focusTrapIndex = this.calculateNextIndex(activeIndex, -1);
|