@momentum-design/components 0.83.2 → 0.83.4

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.
@@ -10,6 +10,11 @@ import Component from '../../components/dialog';
10
10
  * If a `triggerId` is provided, the dialog will manage focus with that element, otherwise it will
11
11
  * remember the previously focused element before the dialog was opened.
12
12
  *
13
+ * The dialog is a controlled component, meaning it does not have its own state management for visibility.
14
+ * Use the `visible` property to control the visibility of the dialog.
15
+ * Use the `onClose` event to handle the close action of the dialog (fired when Close button is clicked
16
+ * or Escape is pressed).
17
+ *
13
18
  * Dialog component have 2 variants: default and promotional.
14
19
  *
15
20
  * **Accessibility notes for consuming (have to be explicitly set when you consume the component)**
@@ -30,6 +35,8 @@ import Component from '../../components/dialog';
30
35
  * @event hidden - (React: onHidden) Dispatched when the dialog is hidden
31
36
  * @event created - (React: onCreated) Dispatched when the dialog is created (added to the DOM)
32
37
  * @event destroyed - (React: onDestroyed) Dispatched when the dialog is destroyed (removed from the DOM)
38
+ * @event close - (React: onClose) Dispatched when the Close Button is clicked or Escape key is pressed
39
+ * (this does not hide the dialog)
33
40
  *
34
41
  * @cssproperty --mdc-dialog-primary-background-color - primary background color of the dialog
35
42
  * @cssproperty --mdc-dialog-border-color - border color of the dialog
@@ -53,5 +60,6 @@ declare const reactWrapper: import("@lit/react").ReactWebComponent<Component, {
53
60
  onHidden: EventName<Event>;
54
61
  onCreated: EventName<Event>;
55
62
  onDestroyed: EventName<Event>;
63
+ onClose: EventName<Event>;
56
64
  }>;
57
65
  export default reactWrapper;
@@ -12,6 +12,11 @@ import { TAG_NAME } from '../../components/dialog/dialog.constants';
12
12
  * If a `triggerId` is provided, the dialog will manage focus with that element, otherwise it will
13
13
  * remember the previously focused element before the dialog was opened.
14
14
  *
15
+ * The dialog is a controlled component, meaning it does not have its own state management for visibility.
16
+ * Use the `visible` property to control the visibility of the dialog.
17
+ * Use the `onClose` event to handle the close action of the dialog (fired when Close button is clicked
18
+ * or Escape is pressed).
19
+ *
15
20
  * Dialog component have 2 variants: default and promotional.
16
21
  *
17
22
  * **Accessibility notes for consuming (have to be explicitly set when you consume the component)**
@@ -32,6 +37,8 @@ import { TAG_NAME } from '../../components/dialog/dialog.constants';
32
37
  * @event hidden - (React: onHidden) Dispatched when the dialog is hidden
33
38
  * @event created - (React: onCreated) Dispatched when the dialog is created (added to the DOM)
34
39
  * @event destroyed - (React: onDestroyed) Dispatched when the dialog is destroyed (removed from the DOM)
40
+ * @event close - (React: onClose) Dispatched when the Close Button is clicked or Escape key is pressed
41
+ * (this does not hide the dialog)
35
42
  *
36
43
  * @cssproperty --mdc-dialog-primary-background-color - primary background color of the dialog
37
44
  * @cssproperty --mdc-dialog-border-color - border color of the dialog
@@ -59,6 +66,7 @@ const reactWrapper = createComponent({
59
66
  onHidden: 'hidden',
60
67
  onCreated: 'created',
61
68
  onDestroyed: 'destroyed',
69
+ onClose: 'close',
62
70
  },
63
71
  displayName: 'Dialog',
64
72
  });
@@ -1,20 +1,20 @@
1
- export { default as AlertChip } from './alertchip';
2
1
  export { default as Animation } from './animation';
3
2
  export { default as Appheader } from './appheader';
3
+ export { default as AlertChip } from './alertchip';
4
4
  export { default as Avatar } from './avatar';
5
5
  export { default as AvatarButton } from './avatarbutton';
6
6
  export { default as Badge } from './badge';
7
+ export { default as Brandvisual } from './brandvisual';
7
8
  export { default as Bullet } from './bullet';
8
9
  export { default as Button } from './button';
9
- export { default as Brandvisual } from './brandvisual';
10
- export { default as ButtonLink } from './buttonlink';
11
10
  export { default as ButtonGroup } from './buttongroup';
11
+ export { default as ButtonLink } from './buttonlink';
12
12
  export { default as Buttonsimple } from './buttonsimple';
13
13
  export { default as Card } from './card';
14
14
  export { default as CardButton } from './cardbutton';
15
15
  export { default as CardCheckbox } from './cardcheckbox';
16
- export { default as Checkbox } from './checkbox';
17
16
  export { default as CardRadio } from './cardradio';
17
+ export { default as Checkbox } from './checkbox';
18
18
  export { default as Chip } from './chip';
19
19
  export { default as Coachmark } from './coachmark';
20
20
  export { default as Dialog } from './dialog';
@@ -32,8 +32,8 @@ export { default as List } from './list';
32
32
  export { default as ListItem } from './listitem';
33
33
  export { default as Marker } from './marker';
34
34
  export { default as MenuBar } from './menubar';
35
- export { default as MenuItemCheckbox } from './menuitemcheckbox';
36
35
  export { default as MenuItem } from './menuitem';
36
+ export { default as MenuItemCheckbox } from './menuitemcheckbox';
37
37
  export { default as MenuItemRadio } from './menuitemradio';
38
38
  export { default as MenuPopover } from './menupopover';
39
39
  export { default as MenuSection } from './menusection';
@@ -1,20 +1,20 @@
1
- export { default as AlertChip } from './alertchip';
2
1
  export { default as Animation } from './animation';
3
2
  export { default as Appheader } from './appheader';
3
+ export { default as AlertChip } from './alertchip';
4
4
  export { default as Avatar } from './avatar';
5
5
  export { default as AvatarButton } from './avatarbutton';
6
6
  export { default as Badge } from './badge';
7
+ export { default as Brandvisual } from './brandvisual';
7
8
  export { default as Bullet } from './bullet';
8
9
  export { default as Button } from './button';
9
- export { default as Brandvisual } from './brandvisual';
10
- export { default as ButtonLink } from './buttonlink';
11
10
  export { default as ButtonGroup } from './buttongroup';
11
+ export { default as ButtonLink } from './buttonlink';
12
12
  export { default as Buttonsimple } from './buttonsimple';
13
13
  export { default as Card } from './card';
14
14
  export { default as CardButton } from './cardbutton';
15
15
  export { default as CardCheckbox } from './cardcheckbox';
16
- export { default as Checkbox } from './checkbox';
17
16
  export { default as CardRadio } from './cardradio';
17
+ export { default as Checkbox } from './checkbox';
18
18
  export { default as Chip } from './chip';
19
19
  export { default as Coachmark } from './coachmark';
20
20
  export { default as Dialog } from './dialog';
@@ -32,8 +32,8 @@ export { default as List } from './list';
32
32
  export { default as ListItem } from './listitem';
33
33
  export { default as Marker } from './marker';
34
34
  export { default as MenuBar } from './menubar';
35
- export { default as MenuItemCheckbox } from './menuitemcheckbox';
36
35
  export { default as MenuItem } from './menuitem';
36
+ export { default as MenuItemCheckbox } from './menuitemcheckbox';
37
37
  export { default as MenuItemRadio } from './menuitemradio';
38
38
  export { default as MenuPopover } from './menupopover';
39
39
  export { default as MenuSection } from './menusection';
@@ -2,9 +2,9 @@ import type { Component } from '../../models';
2
2
  import type { Constructor } from './index.types';
3
3
  export declare abstract class FocusTrapClassInterface {
4
4
  protected abstract focusTrap: boolean;
5
- enabledPreventScroll: boolean;
6
5
  setInitialFocus(elementIndexToReceiveFocus?: number): void;
7
6
  activateFocusTrap(): void;
8
7
  deactivateFocusTrap(): void;
8
+ private setIsFocusTrapActivated;
9
9
  }
10
- export declare const FocusTrapMixin: <T extends Constructor<Component>>(superClass: T) => Constructor<HTMLElement & FocusTrapClassInterface> & T;
10
+ export declare const FocusTrapMixin: <T extends Constructor<Component>>(superClass: T) => Constructor<Component & FocusTrapClassInterface> & T;
@@ -7,8 +7,69 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
7
7
  var __metadata = (this && this.__metadata) || function (k, v) {
8
8
  if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
9
9
  };
10
+ /* eslint-disable no-use-before-define */
10
11
  /* eslint-disable max-classes-per-file */
11
12
  import { property } from 'lit/decorators.js';
13
+ /**
14
+ * FocusTrapStack manages a stack of active focus traps,
15
+ * ensuring only one focus trap is active at a time.
16
+ *
17
+ * This also makes sure there is only one keydown listener active at a time,
18
+ * which is necessary to handle focus trapping correctly.
19
+ */
20
+ class FocusTrapStack {
21
+ static get stackArray() {
22
+ return Array.from(this.stack);
23
+ }
24
+ static addKeydownListener(keydownListener) {
25
+ this.currentKeydownListener = keydownListener;
26
+ document.addEventListener('keydown', keydownListener);
27
+ }
28
+ static removeKeydownListener() {
29
+ if (this.currentKeydownListener) {
30
+ document.removeEventListener('keydown', this.currentKeydownListener);
31
+ }
32
+ }
33
+ /**
34
+ * Activates a focus trap by adding it to the stack.
35
+ * It deactivates all other traps in the stack to ensure only one trap is active
36
+ *
37
+ * @param trap - The focus trap to activate.
38
+ */
39
+ static activate(trap) {
40
+ // Deactivate all other traps
41
+ this.stackArray.forEach((activeTrap) => {
42
+ if (activeTrap !== trap) {
43
+ activeTrap.setIsFocusTrapActivated(false);
44
+ }
45
+ });
46
+ this.stack.add(trap);
47
+ // remove the current keydown listener if it exists
48
+ // and add a new one for the current trap
49
+ this.removeKeydownListener();
50
+ this.addKeydownListener(trap.handleTabKeydown.bind(trap));
51
+ }
52
+ /**
53
+ * Deactivates a focus trap by removing it from the stack.
54
+ * Activates the previous trap in the stack if any.
55
+ *
56
+ * @param trap - The focus trap to deactivate.
57
+ */
58
+ static deactivate(trap) {
59
+ this.stack.delete(trap);
60
+ this.removeKeydownListener();
61
+ // activate the previous trap in the stack if any
62
+ if (this.stack.size > 0) {
63
+ const lastTrap = this.stackArray.pop();
64
+ if (lastTrap) {
65
+ lastTrap.setIsFocusTrapActivated(true);
66
+ this.addKeydownListener(lastTrap.handleTabKeydown.bind(lastTrap));
67
+ }
68
+ }
69
+ }
70
+ }
71
+ FocusTrapStack.stack = new Set();
72
+ FocusTrapStack.currentKeydownListener = null;
12
73
  export const FocusTrapMixin = (superClass) => {
13
74
  class FocusTrap extends superClass {
14
75
  constructor() {
@@ -21,11 +82,6 @@ export const FocusTrapMixin = (superClass) => {
21
82
  * @default true
22
83
  */
23
84
  this.shouldFocusTrapWrap = true;
24
- /**
25
- * Prevent outside scrolling when element is shown.
26
- * @default false
27
- */
28
- this.enabledPreventScroll = false;
29
85
  /** @internal */
30
86
  this.focusTrapIndex = -1;
31
87
  /** @internal */
@@ -33,38 +89,23 @@ export const FocusTrapMixin = (superClass) => {
33
89
  /** @internal */
34
90
  this.isFocusTrapActivated = false;
35
91
  }
36
- connectedCallback() {
37
- super.connectedCallback();
38
- document.addEventListener('keydown', this.handleTabKeydown.bind(this));
39
- }
40
- disconnectedCallback() {
41
- super.disconnectedCallback();
42
- document.removeEventListener('keydown', this.handleTabKeydown.bind(this));
43
- }
44
- async updated(changedProperties) {
45
- super.updated(changedProperties);
46
- if (changedProperties.has('focusTrap')) {
47
- if (!this.focusTrap) {
48
- this.deactivateFocusTrap();
49
- }
50
- }
92
+ setIsFocusTrapActivated(isActivated) {
93
+ this.isFocusTrapActivated = isActivated;
51
94
  }
52
95
  /**
53
96
  * Activate the focus trap
54
- * This calculates the focusable elements within the component's shadow root
55
97
  */
56
98
  activateFocusTrap() {
57
- this.isFocusTrapActivated = !!this.focusTrap;
99
+ this.setIsFocusTrapActivated(true);
100
+ FocusTrapStack.activate(this);
58
101
  }
59
102
  /**
60
103
  * Deactivate the focus trap.
61
104
  */
62
105
  deactivateFocusTrap() {
63
- this.isFocusTrapActivated = false;
106
+ this.setIsFocusTrapActivated(false);
107
+ FocusTrapStack.deactivate(this);
64
108
  this.focusTrapIndex = -1;
65
- this.enabledPreventScroll = false;
66
- // todo: this should not override the body overflow style, but reset it instead
67
- document.body.style.overflow = '';
68
109
  }
69
110
  /**
70
111
  * Checks if the element has no client rectangles (not visible in the viewport).
@@ -233,12 +274,9 @@ export const FocusTrapMixin = (superClass) => {
233
274
  if (this.focusableElements.length === 0 || !this.focusTrap) {
234
275
  return;
235
276
  }
236
- if (this.enabledPreventScroll) {
237
- document.body.style.overflow = 'hidden';
238
- }
239
277
  if (this.focusableElements[elementIndexToReceiveFocus]) {
240
278
  this.focusTrapIndex = elementIndexToReceiveFocus;
241
- this.focusableElements[elementIndexToReceiveFocus].focus();
279
+ this.focusableElements[elementIndexToReceiveFocus].focus({ preventScroll: true });
242
280
  }
243
281
  }
244
282
  /**
@@ -325,7 +363,7 @@ export const FocusTrapMixin = (superClass) => {
325
363
  }
326
364
  const nextElement = this.focusableElements[this.focusTrapIndex];
327
365
  if (nextElement) {
328
- nextElement.focus();
366
+ nextElement.focus({ preventScroll: true });
329
367
  }
330
368
  }
331
369
  /**
@@ -333,6 +371,7 @@ export const FocusTrapMixin = (superClass) => {
333
371
  *
334
372
  * @param event - The keyboard event.
335
373
  */
374
+ // @ts-ignore - this is a method which will be called in the stack
336
375
  handleTabKeydown(event) {
337
376
  if (!this.isFocusTrapActivated) {
338
377
  return;
@@ -347,9 +386,5 @@ export const FocusTrapMixin = (superClass) => {
347
386
  property({ type: Boolean, reflect: true, attribute: 'should-focus-trap-wrap' }),
348
387
  __metadata("design:type", Boolean)
349
388
  ], FocusTrap.prototype, "shouldFocusTrapWrap", void 0);
350
- __decorate([
351
- property({ type: Boolean }),
352
- __metadata("design:type", Boolean)
353
- ], FocusTrap.prototype, "enabledPreventScroll", void 0);
354
389
  return FocusTrap;
355
390
  };
@@ -0,0 +1,9 @@
1
+ import { LitElement } from 'lit';
2
+ import type { Constructor } from './index.types';
3
+ import type { Component } from '../../models';
4
+ export declare abstract class PreventScrollMixinInterface {
5
+ protected abstract preventScroll?: boolean;
6
+ protected activatePreventScroll(): void;
7
+ protected deactivatePreventScroll(): void;
8
+ }
9
+ export declare const PreventScrollMixin: <T extends Constructor<LitElement>>(superClass: T) => Constructor<Component & PreventScrollMixinInterface> & T;
@@ -0,0 +1,32 @@
1
+ export const PreventScrollMixin = (superClass) => {
2
+ class PreventScroll extends superClass {
3
+ constructor() {
4
+ super(...arguments);
5
+ /**
6
+ * @internal
7
+ */
8
+ this.isPreventScrollActive = false;
9
+ /**
10
+ * @internal
11
+ */
12
+ this.previousDocumentBodyStyleOverflow = '';
13
+ }
14
+ activatePreventScroll() {
15
+ if (this.preventScroll && !this.isPreventScrollActive) {
16
+ this.isPreventScrollActive = true;
17
+ // Store the previous body overflow style
18
+ this.previousDocumentBodyStyleOverflow = document.body.style.overflow;
19
+ // Set body overflow to hidden to prevent scrolling
20
+ document.body.style.overflow = 'hidden';
21
+ }
22
+ }
23
+ deactivatePreventScroll() {
24
+ if (this.isPreventScrollActive) {
25
+ this.isPreventScrollActive = false;
26
+ // Restore the previous body overflow style
27
+ document.body.style.overflow = this.previousDocumentBodyStyleOverflow;
28
+ }
29
+ }
30
+ }
31
+ return PreventScroll;
32
+ };
package/package.json CHANGED
@@ -41,5 +41,5 @@
41
41
  "lottie-web": "^5.12.2",
42
42
  "uuid": "^11.0.5"
43
43
  },
44
- "version": "0.83.2"
44
+ "version": "0.83.4"
45
45
  }