@momentum-design/components 0.27.6 → 0.28.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 +314 -131
- package/dist/browser/index.js.map +4 -4
- package/dist/components/button/button.styles.js +1 -0
- package/dist/components/buttonsimple/buttonsimple.component.d.ts +5 -0
- package/dist/components/buttonsimple/buttonsimple.component.js +10 -0
- package/dist/components/modalcontainer/modalcontainer.component.d.ts +9 -3
- package/dist/components/modalcontainer/modalcontainer.component.js +26 -15
- package/dist/components/modalcontainer/modalcontainer.constants.d.ts +2 -5
- package/dist/components/modalcontainer/modalcontainer.constants.js +3 -6
- package/dist/components/modalcontainer/modalcontainer.styles.js +6 -6
- package/dist/components/modalcontainer/modalcontainer.types.d.ts +1 -2
- package/dist/components/popover/index.d.ts +9 -0
- package/dist/components/popover/index.js +6 -0
- package/dist/components/popover/popover.component.d.ts +256 -0
- package/dist/components/popover/popover.component.js +675 -0
- package/dist/components/popover/popover.constants.d.ts +44 -0
- package/dist/components/popover/popover.constants.js +46 -0
- package/dist/components/popover/popover.events.d.ts +34 -0
- package/dist/components/popover/popover.events.js +47 -0
- package/dist/components/popover/popover.stack.d.ts +45 -0
- package/dist/components/popover/popover.stack.js +55 -0
- package/dist/components/popover/popover.styles.d.ts +2 -0
- package/dist/components/popover/popover.styles.js +109 -0
- package/dist/components/popover/popover.types.d.ts +4 -0
- package/dist/components/popover/popover.types.js +1 -0
- package/dist/components/popover/popover.utils.d.ts +48 -0
- package/dist/components/popover/popover.utils.js +156 -0
- package/dist/custom-elements.json +4135 -2187
- package/dist/index.d.ts +2 -1
- package/dist/index.js +2 -1
- package/dist/react/index.d.ts +2 -1
- package/dist/react/index.js +2 -1
- package/dist/react/popover/index.d.ts +24 -0
- package/dist/react/popover/index.js +33 -0
- package/dist/utils/mixins/DataAriaDescribedbyMixin.d.ts +6 -0
- package/dist/utils/mixins/DataAriaDescribedbyMixin.js +30 -0
- package/dist/utils/mixins/DataAriaLabelledbyMixin.d.ts +6 -0
- package/dist/utils/mixins/DataAriaLabelledbyMixin.js +29 -0
- package/dist/utils/mixins/FocusTrapMixin.d.ts +9 -0
- package/dist/utils/mixins/FocusTrapMixin.js +323 -0
- package/package.json +2 -1
package/dist/index.d.ts
CHANGED
@@ -6,6 +6,7 @@ import Badge from './components/badge';
|
|
6
6
|
import Presence from './components/presence';
|
7
7
|
import Text from './components/text';
|
8
8
|
import Button from './components/button';
|
9
|
+
import Popover from './components/popover';
|
9
10
|
import Bullet from './components/bullet';
|
10
11
|
import Marker from './components/marker';
|
11
12
|
import Divider from './components/divider';
|
@@ -23,6 +24,6 @@ import Spinner from './components/spinner';
|
|
23
24
|
import RadioGroup from './components/radiogroup';
|
24
25
|
import type { TextType } from './components/text/text.types';
|
25
26
|
import type { SpinnerSize, SpinnerVariant } from './components/spinner/spinner.types';
|
26
|
-
export { ThemeProvider, Icon, IconProvider, Avatar, Badge, Presence, Text, Button, Bullet, Marker, Divider, AvatarButton, Input, Link, Toggle, Checkbox, Radio, VirtualizedList, Tab, FormfieldGroup, Spinner, RadioGroup, };
|
27
|
+
export { ThemeProvider, Icon, IconProvider, Avatar, Badge, Presence, Text, Button, Popover, Bullet, Marker, Divider, AvatarButton, Input, Link, Toggle, Checkbox, Radio, VirtualizedList, Tab, FormfieldGroup, Spinner, RadioGroup, };
|
27
28
|
export type { TextType, SpinnerSize, SpinnerVariant, };
|
28
29
|
export { inMemoryCache, webAPIIconsCache };
|
package/dist/index.js
CHANGED
@@ -6,6 +6,7 @@ import Badge from './components/badge';
|
|
6
6
|
import Presence from './components/presence';
|
7
7
|
import Text from './components/text';
|
8
8
|
import Button from './components/button';
|
9
|
+
import Popover from './components/popover';
|
9
10
|
import Bullet from './components/bullet';
|
10
11
|
import Marker from './components/marker';
|
11
12
|
import Divider from './components/divider';
|
@@ -21,5 +22,5 @@ import FormfieldGroup from './components/formfieldgroup';
|
|
21
22
|
import { inMemoryCache, webAPIIconsCache } from './utils/icon-cache';
|
22
23
|
import Spinner from './components/spinner';
|
23
24
|
import RadioGroup from './components/radiogroup';
|
24
|
-
export { ThemeProvider, Icon, IconProvider, Avatar, Badge, Presence, Text, Button, Bullet, Marker, Divider, AvatarButton, Input, Link, Toggle, Checkbox, Radio, VirtualizedList, Tab, FormfieldGroup, Spinner, RadioGroup, };
|
25
|
+
export { ThemeProvider, Icon, IconProvider, Avatar, Badge, Presence, Text, Button, Popover, Bullet, Marker, Divider, AvatarButton, Input, Link, Toggle, Checkbox, Radio, VirtualizedList, Tab, FormfieldGroup, Spinner, RadioGroup, };
|
25
26
|
export { inMemoryCache, webAPIIconsCache };
|
package/dist/react/index.d.ts
CHANGED
@@ -5,15 +5,16 @@ export { default as Bullet } from './bullet';
|
|
5
5
|
export { default as Button } from './button';
|
6
6
|
export { default as Buttonsimple } from './buttonsimple';
|
7
7
|
export { default as Checkbox } from './checkbox';
|
8
|
+
export { default as Divider } from './divider';
|
8
9
|
export { default as FormfieldGroup } from './formfieldgroup';
|
9
10
|
export { default as FormfieldWrapper } from './formfieldwrapper';
|
10
|
-
export { default as Divider } from './divider';
|
11
11
|
export { default as Icon } from './icon';
|
12
12
|
export { default as IconProvider } from './iconprovider';
|
13
13
|
export { default as Input } from './input';
|
14
14
|
export { default as Link } from './link';
|
15
15
|
export { default as Marker } from './marker';
|
16
16
|
export { default as Modalcontainer } from './modalcontainer';
|
17
|
+
export { default as Popover } from './popover';
|
17
18
|
export { default as Presence } from './presence';
|
18
19
|
export { default as Radio } from './radio';
|
19
20
|
export { default as RadioGroup } from './radiogroup';
|
package/dist/react/index.js
CHANGED
@@ -5,15 +5,16 @@ export { default as Bullet } from './bullet';
|
|
5
5
|
export { default as Button } from './button';
|
6
6
|
export { default as Buttonsimple } from './buttonsimple';
|
7
7
|
export { default as Checkbox } from './checkbox';
|
8
|
+
export { default as Divider } from './divider';
|
8
9
|
export { default as FormfieldGroup } from './formfieldgroup';
|
9
10
|
export { default as FormfieldWrapper } from './formfieldwrapper';
|
10
|
-
export { default as Divider } from './divider';
|
11
11
|
export { default as Icon } from './icon';
|
12
12
|
export { default as IconProvider } from './iconprovider';
|
13
13
|
export { default as Input } from './input';
|
14
14
|
export { default as Link } from './link';
|
15
15
|
export { default as Marker } from './marker';
|
16
16
|
export { default as Modalcontainer } from './modalcontainer';
|
17
|
+
export { default as Popover } from './popover';
|
17
18
|
export { default as Presence } from './presence';
|
18
19
|
export { default as Radio } from './radio';
|
19
20
|
export { default as RadioGroup } from './radiogroup';
|
@@ -0,0 +1,24 @@
|
|
1
|
+
import Component from '../../components/popover';
|
2
|
+
/**
|
3
|
+
* Popover component is a lightweight floating UI element that displays additional content when triggered.
|
4
|
+
* It can be used for tooltips, dropdowns, or contextual menus.
|
5
|
+
* The popover automatically positions itself based on available space and
|
6
|
+
* supports dynamic height adjustments with scrollable content when needed。
|
7
|
+
*
|
8
|
+
* @dependency mdc-button
|
9
|
+
* @dependency mdc-modalcontainer
|
10
|
+
*
|
11
|
+
* @tagname mdc-popover
|
12
|
+
*
|
13
|
+
* @cssproperty --mdc-popover-arrow-border-radius - radius of the arrow border
|
14
|
+
* @cssproperty --mdc-popover-arrow-border - border of the arrow
|
15
|
+
* @cssproperty --mdc-popover-primary-background-color - primary background color of the popover
|
16
|
+
* @cssproperty --mdc-popover-inverted-background-color - inverted background color of the popover
|
17
|
+
* @cssproperty --mdc-popover-inverted-border-color - inverted border color of the popover
|
18
|
+
* @cssproperty --mdc-popover-inverted-text-color - inverted text color of the popover
|
19
|
+
*
|
20
|
+
* @slot - Default slot for modal container
|
21
|
+
*
|
22
|
+
*/
|
23
|
+
declare const reactWrapper: import("@lit/react").ReactWebComponent<Component, {}>;
|
24
|
+
export default reactWrapper;
|
@@ -0,0 +1,33 @@
|
|
1
|
+
import * as React from 'react';
|
2
|
+
import { createComponent } from '@lit/react';
|
3
|
+
import Component from '../../components/popover';
|
4
|
+
import { TAG_NAME } from '../../components/popover/popover.constants';
|
5
|
+
/**
|
6
|
+
* Popover component is a lightweight floating UI element that displays additional content when triggered.
|
7
|
+
* It can be used for tooltips, dropdowns, or contextual menus.
|
8
|
+
* The popover automatically positions itself based on available space and
|
9
|
+
* supports dynamic height adjustments with scrollable content when needed。
|
10
|
+
*
|
11
|
+
* @dependency mdc-button
|
12
|
+
* @dependency mdc-modalcontainer
|
13
|
+
*
|
14
|
+
* @tagname mdc-popover
|
15
|
+
*
|
16
|
+
* @cssproperty --mdc-popover-arrow-border-radius - radius of the arrow border
|
17
|
+
* @cssproperty --mdc-popover-arrow-border - border of the arrow
|
18
|
+
* @cssproperty --mdc-popover-primary-background-color - primary background color of the popover
|
19
|
+
* @cssproperty --mdc-popover-inverted-background-color - inverted background color of the popover
|
20
|
+
* @cssproperty --mdc-popover-inverted-border-color - inverted border color of the popover
|
21
|
+
* @cssproperty --mdc-popover-inverted-text-color - inverted text color of the popover
|
22
|
+
*
|
23
|
+
* @slot - Default slot for modal container
|
24
|
+
*
|
25
|
+
*/
|
26
|
+
const reactWrapper = createComponent({
|
27
|
+
tagName: TAG_NAME,
|
28
|
+
elementClass: Component,
|
29
|
+
react: React,
|
30
|
+
events: {},
|
31
|
+
displayName: 'Popover',
|
32
|
+
});
|
33
|
+
export default reactWrapper;
|
@@ -0,0 +1,6 @@
|
|
1
|
+
import { LitElement } from 'lit';
|
2
|
+
import type { Constructor } from './index.types';
|
3
|
+
export interface DataAriaDescribedbyMixinInterface {
|
4
|
+
dataAriaDescribedby: string | null;
|
5
|
+
}
|
6
|
+
export declare const DataAriaDescribedbyMixin: <T extends Constructor<LitElement>>(superClass: T) => Constructor<DataAriaDescribedbyMixinInterface> & T;
|
@@ -0,0 +1,30 @@
|
|
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
|
+
import { property } from 'lit/decorators.js';
|
11
|
+
export const DataAriaDescribedbyMixin = (superClass) => {
|
12
|
+
class InnerMixinClass extends superClass {
|
13
|
+
constructor() {
|
14
|
+
super(...arguments);
|
15
|
+
/**
|
16
|
+
* Defines an ID reference to one or more elements that provide additional
|
17
|
+
* descriptive information for the current element.
|
18
|
+
* The `aria-describedby` attribute to be set for accessibility.
|
19
|
+
* @default null
|
20
|
+
*/
|
21
|
+
this.dataAriaDescribedby = null;
|
22
|
+
}
|
23
|
+
}
|
24
|
+
__decorate([
|
25
|
+
property({ type: String, reflect: true, attribute: 'data-aria-describedby' }),
|
26
|
+
__metadata("design:type", Object)
|
27
|
+
], InnerMixinClass.prototype, "dataAriaDescribedby", void 0);
|
28
|
+
// Cast return type to your mixin's interface intersected with the superClass type
|
29
|
+
return InnerMixinClass;
|
30
|
+
};
|
@@ -0,0 +1,6 @@
|
|
1
|
+
import { LitElement } from 'lit';
|
2
|
+
import type { Constructor } from './index.types';
|
3
|
+
export interface DataAriaLabelledbyMixinInterface {
|
4
|
+
dataAriaLabelledby: string | null;
|
5
|
+
}
|
6
|
+
export declare const DataAriaLabelledbyMixin: <T extends Constructor<LitElement>>(superClass: T) => Constructor<DataAriaLabelledbyMixinInterface> & T;
|
@@ -0,0 +1,29 @@
|
|
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
|
+
import { property } from 'lit/decorators.js';
|
11
|
+
export const DataAriaLabelledbyMixin = (superClass) => {
|
12
|
+
class InnerMixinClass extends superClass {
|
13
|
+
constructor() {
|
14
|
+
super(...arguments);
|
15
|
+
/**
|
16
|
+
* Defines an ID reference to one or more elements that label the current element.
|
17
|
+
* The `aria-labelledby` attribute to be set for accessibility.
|
18
|
+
* @default null
|
19
|
+
*/
|
20
|
+
this.dataAriaLabelledby = null;
|
21
|
+
}
|
22
|
+
}
|
23
|
+
__decorate([
|
24
|
+
property({ type: String, reflect: true, attribute: 'data-aria-labelledby' }),
|
25
|
+
__metadata("design:type", Object)
|
26
|
+
], InnerMixinClass.prototype, "dataAriaLabelledby", void 0);
|
27
|
+
// Cast return type to your mixin's interface intersected with the superClass type
|
28
|
+
return InnerMixinClass;
|
29
|
+
};
|
@@ -0,0 +1,9 @@
|
|
1
|
+
import type { Constructor } from './index.types';
|
2
|
+
export interface FocusTrapClassInterface {
|
3
|
+
enabledFocusTrap: boolean;
|
4
|
+
enabledPreventScroll: boolean;
|
5
|
+
setFocusableElements(): void;
|
6
|
+
setInitialFocus(prefferableElement?: number): void;
|
7
|
+
deactivateFocusTrap(): void;
|
8
|
+
}
|
9
|
+
export declare const FocusTrapMixin: <T extends Constructor<HTMLElement>>(superClass: T) => Constructor<HTMLElement & FocusTrapClassInterface> & T;
|
@@ -0,0 +1,323 @@
|
|
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
|
+
import { property } from 'lit/decorators.js';
|
11
|
+
import { DEFAULTS as POPOVER_DEFAULTS } from '../../components/popover/popover.constants';
|
12
|
+
export const FocusTrapMixin = (superClass) => {
|
13
|
+
class FocusTrap extends superClass {
|
14
|
+
constructor(...args) {
|
15
|
+
super(...args);
|
16
|
+
/**
|
17
|
+
* Determines whether the focus trap is enabled.
|
18
|
+
* If true, focus will be restricted to the content within this component.
|
19
|
+
* @default false
|
20
|
+
*/
|
21
|
+
this.enabledFocusTrap = POPOVER_DEFAULTS.FOCUS_TRAP;
|
22
|
+
/**
|
23
|
+
* Prevent outside scrolling when popover show.
|
24
|
+
* @default false
|
25
|
+
*/
|
26
|
+
this.enabledPreventScroll = POPOVER_DEFAULTS.PREVENT_SCROLL;
|
27
|
+
/** @internal */
|
28
|
+
this.focusTrapIndex = -1;
|
29
|
+
/** @internal */
|
30
|
+
this.focusableElements = [];
|
31
|
+
this.shouldWrapFocus = () => true;
|
32
|
+
this.addEventListener('keydown', this.handleKeydown);
|
33
|
+
}
|
34
|
+
/**
|
35
|
+
* Deactivate the focus trap.
|
36
|
+
*/
|
37
|
+
deactivateFocusTrap() {
|
38
|
+
this.enabledFocusTrap = false;
|
39
|
+
this.enabledPreventScroll = false;
|
40
|
+
this.focusTrapIndex = -1;
|
41
|
+
document.body.style.overflow = '';
|
42
|
+
}
|
43
|
+
/**
|
44
|
+
* Checks if the element has no client rectangles (not visible in the viewport).
|
45
|
+
*
|
46
|
+
* @param element - The element to check.
|
47
|
+
* @returns True if the element has no client rectangles.
|
48
|
+
*/
|
49
|
+
hasNoClientRects(element) {
|
50
|
+
return element.getClientRects().length === 0;
|
51
|
+
}
|
52
|
+
/**
|
53
|
+
* Checks if the element has zero dimensions (width and height are both 0).
|
54
|
+
*
|
55
|
+
* @param element - The element to check.
|
56
|
+
* @returns True if the element has zero dimensions.
|
57
|
+
*/
|
58
|
+
hasZeroDimensions(element) {
|
59
|
+
const { width, height } = element.getBoundingClientRect();
|
60
|
+
const { offsetWidth, offsetHeight } = element;
|
61
|
+
return offsetWidth + offsetHeight + height + width === 0;
|
62
|
+
}
|
63
|
+
/**
|
64
|
+
* Determines if the element is not visible in the DOM.
|
65
|
+
*
|
66
|
+
* @param element - The element to check.
|
67
|
+
* @returns True if the element is not visible.
|
68
|
+
*/
|
69
|
+
isNotVisible(element) {
|
70
|
+
return this.hasZeroDimensions(element) || this.hasNoClientRects(element);
|
71
|
+
}
|
72
|
+
/**
|
73
|
+
* Checks if the element has inline styles that make it hidden.
|
74
|
+
*
|
75
|
+
* @param element - The element to check.
|
76
|
+
* @returns True if the element has inline styles that make it hidden.
|
77
|
+
*/
|
78
|
+
hasHiddenStyle(element) {
|
79
|
+
const { display, opacity, visibility } = element.style;
|
80
|
+
return display === 'none' || opacity === '0' || visibility === 'hidden' || visibility === 'collapse';
|
81
|
+
}
|
82
|
+
/**
|
83
|
+
* Checks if the element is hidden by a computed style.
|
84
|
+
*
|
85
|
+
* @param element - The element to check.
|
86
|
+
* @returns True if the element is hidden by a computed style.
|
87
|
+
*/
|
88
|
+
hasComputedHidden(element) {
|
89
|
+
const computedStyle = getComputedStyle(element);
|
90
|
+
return computedStyle.visibility === 'hidden' || computedStyle.height === '0' || computedStyle.display === 'none';
|
91
|
+
}
|
92
|
+
/**
|
93
|
+
* Checks if the element is hidden from the user.
|
94
|
+
*
|
95
|
+
* @param element - The element to check.
|
96
|
+
* @returns True if the element is hidden.
|
97
|
+
*/
|
98
|
+
isHidden(element) {
|
99
|
+
return (element.hasAttribute('hidden')
|
100
|
+
|| element.getAttribute('aria-hidden') === 'true'
|
101
|
+
|| this.hasHiddenStyle(element)
|
102
|
+
|| this.isNotVisible(element)
|
103
|
+
|| this.hasComputedHidden(element));
|
104
|
+
}
|
105
|
+
/**
|
106
|
+
* Checks if the element is not tabbable.
|
107
|
+
*
|
108
|
+
* @param element - The element to check.
|
109
|
+
* @returns True if the element is not tabbable.
|
110
|
+
*/
|
111
|
+
isNotTabbable(element) {
|
112
|
+
return element.getAttribute('tabindex') === '-1';
|
113
|
+
}
|
114
|
+
/**
|
115
|
+
* Checks if the element is interactive.
|
116
|
+
*
|
117
|
+
* @param element - The element to check.
|
118
|
+
* @returns True if the element is interactive.
|
119
|
+
*/
|
120
|
+
isInteractiveElement(element) {
|
121
|
+
const interactiveTags = new Set(['BUTTON', 'DETAILS', 'EMBED', 'IFRAME', 'SELECT', 'TEXTAREA']);
|
122
|
+
if (interactiveTags.has(element.tagName)) {
|
123
|
+
return true;
|
124
|
+
}
|
125
|
+
if (element instanceof HTMLAnchorElement && element.hasAttribute('href')) {
|
126
|
+
return true;
|
127
|
+
}
|
128
|
+
if (element instanceof HTMLInputElement && element.type !== 'hidden') {
|
129
|
+
return true;
|
130
|
+
}
|
131
|
+
if ((element instanceof HTMLAudioElement || element instanceof HTMLVideoElement)
|
132
|
+
&& element.hasAttribute('controls')) {
|
133
|
+
return true;
|
134
|
+
}
|
135
|
+
if ((element instanceof HTMLImageElement || element instanceof HTMLObjectElement)
|
136
|
+
&& element.hasAttribute('usemap')) {
|
137
|
+
return true;
|
138
|
+
}
|
139
|
+
if (element.hasAttribute('tabindex') && element.tabIndex > -1) {
|
140
|
+
return true;
|
141
|
+
}
|
142
|
+
return false;
|
143
|
+
}
|
144
|
+
/**
|
145
|
+
* Checks if the element is focusable.
|
146
|
+
*
|
147
|
+
* @param element - The element to check.
|
148
|
+
* @returns True if the element is focusable.
|
149
|
+
*/
|
150
|
+
isFocusable(element) {
|
151
|
+
if (this.isHidden(element) || this.isNotTabbable(element)) {
|
152
|
+
return false;
|
153
|
+
}
|
154
|
+
return this.isInteractiveElement(element);
|
155
|
+
}
|
156
|
+
/**
|
157
|
+
* Recursively finds all focusable elements within the given root and its descendants.
|
158
|
+
*
|
159
|
+
* @param root - The root element to search for focusable elements.
|
160
|
+
* @param matches - The set of focusable elements.
|
161
|
+
* @returns The list of focusable elements.
|
162
|
+
*/
|
163
|
+
findFocusable(root, matches = new Set()) {
|
164
|
+
if (root instanceof HTMLElement && this.isFocusable(root)) {
|
165
|
+
matches.add(root);
|
166
|
+
}
|
167
|
+
const children = Array.from(root.children);
|
168
|
+
children.forEach((child) => {
|
169
|
+
const element = child;
|
170
|
+
if (this.isFocusable(element)) {
|
171
|
+
matches.add(element);
|
172
|
+
}
|
173
|
+
if (element.shadowRoot) {
|
174
|
+
this.findFocusable(element.shadowRoot, matches);
|
175
|
+
}
|
176
|
+
else if (element.tagName === 'SLOT') {
|
177
|
+
const assignedNodes = element.assignedElements({ flatten: true });
|
178
|
+
assignedNodes.forEach((node) => {
|
179
|
+
if (node instanceof HTMLElement) {
|
180
|
+
this.findFocusable(node, matches);
|
181
|
+
}
|
182
|
+
});
|
183
|
+
}
|
184
|
+
else {
|
185
|
+
this.findFocusable(element, matches);
|
186
|
+
}
|
187
|
+
});
|
188
|
+
return [...matches];
|
189
|
+
}
|
190
|
+
/**
|
191
|
+
* Updates the list of focusable elements within the component's shadow root.
|
192
|
+
*/
|
193
|
+
setFocusableElements() {
|
194
|
+
if (!this.shadowRoot)
|
195
|
+
return;
|
196
|
+
this.focusableElements = this.findFocusable(this.shadowRoot, new Set());
|
197
|
+
}
|
198
|
+
/**
|
199
|
+
* Sets the initial focus within the container.
|
200
|
+
*
|
201
|
+
* @param prefferableElement - The index of the prefferable element to focus.
|
202
|
+
*/
|
203
|
+
setInitialFocus(prefferableElement = 0) {
|
204
|
+
if (this.focusableElements.length === 0)
|
205
|
+
return;
|
206
|
+
if (this.enabledPreventScroll) {
|
207
|
+
document.body.style.overflow = 'hidden';
|
208
|
+
}
|
209
|
+
if (this.focusableElements[prefferableElement]) {
|
210
|
+
this.focusTrapIndex = prefferableElement;
|
211
|
+
this.focusableElements[prefferableElement].focus();
|
212
|
+
}
|
213
|
+
}
|
214
|
+
/**
|
215
|
+
* Calculates the next index for the focus trap.
|
216
|
+
*
|
217
|
+
* @param currentIndex - The current index.
|
218
|
+
* @param step - The step to calculate the next index.
|
219
|
+
* @returns The next index.
|
220
|
+
*/
|
221
|
+
calculateNextIndex(currentIndex, step) {
|
222
|
+
const { length } = this.focusableElements;
|
223
|
+
const wrapFocus = this.shouldWrapFocus();
|
224
|
+
if (currentIndex === -1) {
|
225
|
+
return step > 0 ? 0 : length - 1;
|
226
|
+
}
|
227
|
+
let nextIndex = currentIndex + step;
|
228
|
+
if (wrapFocus) {
|
229
|
+
if (nextIndex < 0)
|
230
|
+
nextIndex = length - 1;
|
231
|
+
if (nextIndex >= length)
|
232
|
+
nextIndex = 0;
|
233
|
+
}
|
234
|
+
else {
|
235
|
+
if (nextIndex < 0)
|
236
|
+
nextIndex = 0;
|
237
|
+
if (nextIndex >= length)
|
238
|
+
nextIndex = length - 1;
|
239
|
+
}
|
240
|
+
return nextIndex;
|
241
|
+
}
|
242
|
+
/**
|
243
|
+
* Returns the deepest active element in the shadow DOM.
|
244
|
+
*
|
245
|
+
* @returns The deepest active element.
|
246
|
+
*/
|
247
|
+
getDeepActiveElement() {
|
248
|
+
var _a;
|
249
|
+
let host = document.activeElement || document.body;
|
250
|
+
while (host instanceof HTMLElement && ((_a = host.shadowRoot) === null || _a === void 0 ? void 0 : _a.activeElement)) {
|
251
|
+
host = host.shadowRoot.activeElement;
|
252
|
+
}
|
253
|
+
return host || document.body;
|
254
|
+
}
|
255
|
+
/**
|
256
|
+
* Finds the index of the active element within the focusable elements.
|
257
|
+
*
|
258
|
+
* @param activeElement - The active element.
|
259
|
+
* @returns The index of the active element.
|
260
|
+
*/
|
261
|
+
findElement(activeElement) {
|
262
|
+
return this.focusableElements.findIndex((element) => this.isEqualFocusNode(activeElement, element));
|
263
|
+
}
|
264
|
+
/**
|
265
|
+
* Checks if the active element is equal to the given element.
|
266
|
+
*
|
267
|
+
* @param activeElement - The active element.
|
268
|
+
* @param element - The element to compare.
|
269
|
+
* @returns True if the active element is equal to the given element.
|
270
|
+
*/
|
271
|
+
isEqualFocusNode(activeElement, element) {
|
272
|
+
if (activeElement.nodeType >= 0) {
|
273
|
+
return element.isEqualNode(activeElement) && element === activeElement;
|
274
|
+
}
|
275
|
+
return false;
|
276
|
+
}
|
277
|
+
/**
|
278
|
+
* Traps focus within the container.
|
279
|
+
*
|
280
|
+
* @param direction - The direction of the focus trap.
|
281
|
+
* If true, the focus will be trapped in the previous element.
|
282
|
+
*/
|
283
|
+
trapFocus(direction) {
|
284
|
+
if (this.focusableElements.length === 0)
|
285
|
+
return;
|
286
|
+
const activeElement = this.getDeepActiveElement();
|
287
|
+
const activeIndex = this.findElement(activeElement);
|
288
|
+
if (direction) {
|
289
|
+
this.focusTrapIndex = this.calculateNextIndex(activeIndex, -1);
|
290
|
+
}
|
291
|
+
else {
|
292
|
+
this.focusTrapIndex = this.calculateNextIndex(activeIndex, 1);
|
293
|
+
}
|
294
|
+
const nextElement = this.focusableElements[this.focusTrapIndex];
|
295
|
+
if (nextElement) {
|
296
|
+
nextElement.focus();
|
297
|
+
}
|
298
|
+
}
|
299
|
+
/**
|
300
|
+
* Traps focus within the container.
|
301
|
+
*
|
302
|
+
* @param event - The keyboard event.
|
303
|
+
*/
|
304
|
+
handleKeydown(event) {
|
305
|
+
if (!this.enabledFocusTrap || !this.focusableElements.length) {
|
306
|
+
return;
|
307
|
+
}
|
308
|
+
if (event.key === 'Tab') {
|
309
|
+
event.preventDefault();
|
310
|
+
this.trapFocus(event.shiftKey);
|
311
|
+
}
|
312
|
+
}
|
313
|
+
}
|
314
|
+
__decorate([
|
315
|
+
property({ type: Boolean }),
|
316
|
+
__metadata("design:type", Boolean)
|
317
|
+
], FocusTrap.prototype, "enabledFocusTrap", void 0);
|
318
|
+
__decorate([
|
319
|
+
property({ type: Boolean }),
|
320
|
+
__metadata("design:type", Boolean)
|
321
|
+
], FocusTrap.prototype, "enabledPreventScroll", void 0);
|
322
|
+
return FocusTrap;
|
323
|
+
};
|
package/package.json
CHANGED
@@ -28,6 +28,7 @@
|
|
28
28
|
"!**/*.tsbuildinfo"
|
29
29
|
],
|
30
30
|
"dependencies": {
|
31
|
+
"@floating-ui/dom": "^1.6.12",
|
31
32
|
"@lit/context": "^1.1.2",
|
32
33
|
"@lit/react": "^1.0.5",
|
33
34
|
"@momentum-design/fonts": "*",
|
@@ -37,5 +38,5 @@
|
|
37
38
|
"lit": "^3.2.0",
|
38
39
|
"uuid": "^11.0.5"
|
39
40
|
},
|
40
|
-
"version": "0.
|
41
|
+
"version": "0.28.1"
|
41
42
|
}
|