@momentum-design/components 0.136.0 → 0.137.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.
@@ -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,7 @@
1
+ declare const TAG_NAME: "mdc-focustrap";
2
+ declare const DEFAULTS: {
3
+ TRAP_DISABLED: boolean;
4
+ RESTORE_FOCUS_DISABLED: boolean;
5
+ AUTO_FOCUS: boolean;
6
+ };
7
+ export { TAG_NAME, DEFAULTS };
@@ -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,2 @@
1
+ declare const _default: import("lit").CSSResult[];
2
+ export default _default;
@@ -0,0 +1,9 @@
1
+ // AI-Assisted
2
+ import { css } from 'lit';
3
+ const styles = css `
4
+ :host {
5
+ display: contents;
6
+ }
7
+ `;
8
+ export default [styles];
9
+ // 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 };
@@ -0,0 +1,2 @@
1
+ export {};
2
+ // End AI-Assisted
@@ -0,0 +1,7 @@
1
+ import FocusTrap from './focustrap.component';
2
+ declare global {
3
+ interface HTMLElementTagNameMap {
4
+ ['mdc-focustrap']: FocusTrap;
5
+ }
6
+ }
7
+ export default FocusTrap;
@@ -0,0 +1,4 @@
1
+ import FocusTrap from './focustrap.component';
2
+ import { TAG_NAME } from './focustrap.constants';
3
+ FocusTrap.register(TAG_NAME);
4
+ export default FocusTrap;
@@ -111,7 +111,7 @@ declare class Toast extends Toast_base {
111
111
  private updateDetailedSlotPresence;
112
112
  private updateFooterButtonsPresence;
113
113
  protected firstUpdated(changedProperties: PropertyValues): Promise<void>;
114
- protected updated(changedProperties: PropertyValues): void;
114
+ protected updated(changedProperties: PropertyValues): Promise<void>;
115
115
  protected renderIcon(iconName: string): import("lit-html").TemplateResult<1> | typeof nothing;
116
116
  private canRenderToggleButton;
117
117
  private shouldRenderToggleButton;
@@ -122,15 +122,17 @@ class Toast extends FooterMixin(Component) {
122
122
  super.firstUpdated(changedProperties);
123
123
  this.updateDetailedSlotPresence();
124
124
  this.updateDataExpanded();
125
- await this.updateComplete;
126
- if (hasOverflowMixin(this.headerTextElement)) {
127
- this.hasOverflowingHeaderText = this.headerTextElement.isHeightOverflowing();
128
- }
129
125
  }
130
- updated(changedProperties) {
126
+ async updated(changedProperties) {
131
127
  if (changedProperties.has('showMoreText') || changedProperties.has('showLessText')) {
132
128
  this.updateDataExpanded();
133
129
  }
130
+ if (changedProperties.has('headerText')) {
131
+ await this.updateComplete;
132
+ if (hasOverflowMixin(this.headerTextElement)) {
133
+ this.hasOverflowingHeaderText = this.headerTextElement.isHeightOverflowing();
134
+ }
135
+ }
134
136
  }
135
137
  renderIcon(iconName) {
136
138
  if (!iconName)
@@ -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;
@@ -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';
@@ -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);
@@ -22,6 +22,7 @@ export class FocusTrapStack {
22
22
  static removeKeydownListener() {
23
23
  if (this.currentKeydownListener) {
24
24
  document.removeEventListener('keydown', this.currentKeydownListener);
25
+ this.currentKeydownListener = null;
25
26
  }
26
27
  }
27
28
  /**
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@momentum-design/components",
3
3
  "packageManager": "yarn@3.2.4",
4
- "version": "0.136.0",
4
+ "version": "0.137.0",
5
5
  "engines": {
6
6
  "node": ">=24.0.0",
7
7
  "npm": ">=8.0.0"