@momentum-design/components 0.122.6 → 0.122.7
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 +428 -409
- package/dist/browser/index.js.map +4 -4
- package/dist/components/list/list.component.d.ts +12 -17
- package/dist/components/list/list.component.js +29 -39
- package/dist/components/listitem/listitem.component.d.ts +10 -0
- package/dist/components/listitem/listitem.component.js +7 -0
- package/dist/components/virtualizedlist/virtualizedlist.component.d.ts +244 -41
- package/dist/components/virtualizedlist/virtualizedlist.component.js +597 -78
- package/dist/components/virtualizedlist/virtualizedlist.constants.d.ts +7 -4
- package/dist/components/virtualizedlist/virtualizedlist.constants.js +7 -4
- package/dist/components/virtualizedlist/virtualizedlist.styles.js +17 -3
- package/dist/components/virtualizedlist/virtualizedlist.types.d.ts +12 -10
- package/dist/components/virtualizedlist/virtualizedlist.utils.d.ts +11 -0
- package/dist/components/virtualizedlist/virtualizedlist.utils.js +23 -0
- package/dist/custom-elements.json +976 -305
- package/dist/react/index.d.ts +2 -2
- package/dist/react/index.js +2 -2
- package/dist/react/virtualizedlist/index.d.ts +44 -6
- package/dist/react/virtualizedlist/index.js +44 -6
- package/dist/utils/mixins/AutoFocusOnMountMixin.js +2 -2
- package/dist/utils/mixins/ListNavigationMixin.d.ts +5 -2
- package/dist/utils/mixins/ListNavigationMixin.js +77 -68
- package/dist/utils/mixins/lifecycle/LifeCycleMixin.js +4 -0
- package/dist/utils/mixins/lifecycle/lifecycle.contants.d.ts +1 -0
- package/dist/utils/mixins/lifecycle/lifecycle.contants.js +1 -0
- package/dist/utils/range.d.ts +40 -0
- package/dist/utils/range.js +66 -0
- package/dist/utils/virtualIndexArray.d.ts +27 -0
- package/dist/utils/virtualIndexArray.js +42 -0
- package/package.json +2 -2
- package/dist/components/virtualizedlist/virtualizedlist.helper.test.d.ts +0 -22
- package/dist/components/virtualizedlist/virtualizedlist.helper.test.js +0 -82
package/dist/react/index.d.ts
CHANGED
|
@@ -9,10 +9,10 @@ export { default as Avatar } from './avatar';
|
|
|
9
9
|
export { default as AvatarButton } from './avatarbutton';
|
|
10
10
|
export { default as Badge } from './badge';
|
|
11
11
|
export { default as Banner } from './banner';
|
|
12
|
-
export { default as Brandvisual } from './brandvisual';
|
|
13
12
|
export { default as Bullet } from './bullet';
|
|
14
13
|
export { default as Button } from './button';
|
|
15
14
|
export { default as ButtonGroup } from './buttongroup';
|
|
15
|
+
export { default as Brandvisual } from './brandvisual';
|
|
16
16
|
export { default as ButtonLink } from './buttonlink';
|
|
17
17
|
export { default as Buttonsimple } from './buttonsimple';
|
|
18
18
|
export { default as Card } from './card';
|
|
@@ -77,10 +77,10 @@ export { default as StepperConnector } from './stepperconnector';
|
|
|
77
77
|
export { default as StepperItem } from './stepperitem';
|
|
78
78
|
export { default as Tab } from './tab';
|
|
79
79
|
export { default as TabList } from './tablist';
|
|
80
|
-
export { default as Text } from './text';
|
|
81
80
|
export { default as Textarea } from './textarea';
|
|
82
81
|
export { default as ThemeProvider } from './themeprovider';
|
|
83
82
|
export { default as Toast } from './toast';
|
|
83
|
+
export { default as Text } from './text';
|
|
84
84
|
export { default as Toggle } from './toggle';
|
|
85
85
|
export { default as ToggleTip } from './toggletip';
|
|
86
86
|
export { default as Tooltip } from './tooltip';
|
package/dist/react/index.js
CHANGED
|
@@ -9,10 +9,10 @@ export { default as Avatar } from './avatar';
|
|
|
9
9
|
export { default as AvatarButton } from './avatarbutton';
|
|
10
10
|
export { default as Badge } from './badge';
|
|
11
11
|
export { default as Banner } from './banner';
|
|
12
|
-
export { default as Brandvisual } from './brandvisual';
|
|
13
12
|
export { default as Bullet } from './bullet';
|
|
14
13
|
export { default as Button } from './button';
|
|
15
14
|
export { default as ButtonGroup } from './buttongroup';
|
|
15
|
+
export { default as Brandvisual } from './brandvisual';
|
|
16
16
|
export { default as ButtonLink } from './buttonlink';
|
|
17
17
|
export { default as Buttonsimple } from './buttonsimple';
|
|
18
18
|
export { default as Card } from './card';
|
|
@@ -77,10 +77,10 @@ export { default as StepperConnector } from './stepperconnector';
|
|
|
77
77
|
export { default as StepperItem } from './stepperitem';
|
|
78
78
|
export { default as Tab } from './tab';
|
|
79
79
|
export { default as TabList } from './tablist';
|
|
80
|
-
export { default as Text } from './text';
|
|
81
80
|
export { default as Textarea } from './textarea';
|
|
82
81
|
export { default as ThemeProvider } from './themeprovider';
|
|
83
82
|
export { default as Toast } from './toast';
|
|
83
|
+
export { default as Text } from './text';
|
|
84
84
|
export { default as Toggle } from './toggle';
|
|
85
85
|
export { default as ToggleTip } from './toggletip';
|
|
86
86
|
export { default as Tooltip } from './tooltip';
|
|
@@ -2,23 +2,61 @@ import { type EventName } from '@lit/react';
|
|
|
2
2
|
import Component from '../../components/virtualizedlist';
|
|
3
3
|
import type { Events } from '../../components/virtualizedlist/virtualizedlist.types';
|
|
4
4
|
/**
|
|
5
|
-
* `mdc-virtualizedlist` component
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
5
|
+
* `mdc-virtualizedlist` is an extension of the `mdc-list` component that adds virtualization capabilities using
|
|
6
|
+
* the Tanstack Virtual library.
|
|
7
|
+
*
|
|
8
|
+
* This component is thin wrapper around the Tanstack libray to provide additional funtionalities such as
|
|
9
|
+
* keyboard navigation, focus management, scroll anchoring and accessibility features.
|
|
10
|
+
*
|
|
10
11
|
* Please refer to [Tanstack Virtual Docs](https://tanstack.com/virtual/latest) for more in depth documentation.
|
|
11
12
|
*
|
|
13
|
+
* ## Setup
|
|
14
|
+
*
|
|
15
|
+
* `virtualizerProps` is a required prop that requires at least two properties to be set: `count` and `estimateSize`.
|
|
16
|
+
* `count` is the total number of items in the list, and `estimateSize` is a function that returns the estimated
|
|
17
|
+
* size (in pixels) of each item in the list. `getItemKey` is also strongly recommended to be set to provide unique
|
|
18
|
+
* keys for each item in the list.
|
|
19
|
+
*
|
|
20
|
+
* ### Render list items
|
|
21
|
+
*
|
|
22
|
+
* To keep the component framework-agnostic, the rendering of the list items is left to the consumer.
|
|
23
|
+
*
|
|
24
|
+
* We need to render only the visible items. The list of visible items are provided by the `virtualitemschange` event.
|
|
25
|
+
* The event detail contains the `virtualItems` array, which contains the information for the rendering.
|
|
26
|
+
* List items must have an `data-index` attribute, the indexes are in the `virtualItems` list.
|
|
27
|
+
*
|
|
28
|
+
* ## Best practices
|
|
29
|
+
*
|
|
30
|
+
* ### List updates
|
|
31
|
+
*
|
|
32
|
+
* Tanstack needs only the count of the items in the list and the size of each item to perform virtualization.
|
|
33
|
+
* List updates happens when
|
|
34
|
+
* - when `virtualizerProps` property of the component instance changes
|
|
35
|
+
* - when `observe-size-changes` is set and the item's size changes (it uses ResizeObserver internally)
|
|
36
|
+
* - when `component.visualiser.measure` called manually.
|
|
37
|
+
*
|
|
38
|
+
* ### Header
|
|
39
|
+
*
|
|
40
|
+
* To add a header to the list, use the `mdc-listheader` component and place it in the `list-header` slot.
|
|
41
|
+
*
|
|
42
|
+
* ### Lists with dynamic content
|
|
43
|
+
*
|
|
44
|
+
* Unique keys for the list items are critical for dynamically changing list items or item's content.
|
|
45
|
+
* If the key change with the content it will cause scrollbar and content shuttering.
|
|
46
|
+
*
|
|
12
47
|
* @tagname mdc-virtualizedlist
|
|
13
48
|
*
|
|
14
49
|
* @event scroll - (React: onScroll) Event that gets called when user scrolls inside of list.
|
|
50
|
+
* @event virtualitemschange - (React: onVirtualItemsChange) Event that gets called when the virtual items change.
|
|
15
51
|
*
|
|
16
|
-
* @slot -
|
|
52
|
+
* @slot default - This is a default/unnamed slot, where listitems can be placed.
|
|
53
|
+
* @slot list-header - This slot is used to pass a header for the list, which can be a `mdc-listheader` component.
|
|
17
54
|
*
|
|
18
55
|
* @csspart container - The container of the virtualized list.
|
|
19
56
|
* @csspart scroll - The scrollable area of the virtualized list.
|
|
20
57
|
*/
|
|
21
58
|
declare const reactWrapper: import("@lit/react").ReactWebComponent<Component, {
|
|
59
|
+
onVirtualItemsChange: EventName<Events["onVirtualItemsChangeEvent"]>;
|
|
22
60
|
onScroll: EventName<Events["onScrollEvent"]>;
|
|
23
61
|
}>;
|
|
24
62
|
export default reactWrapper;
|
|
@@ -3,18 +3,55 @@ import { createComponent } from '@lit/react';
|
|
|
3
3
|
import Component from '../../components/virtualizedlist';
|
|
4
4
|
import { TAG_NAME } from '../../components/virtualizedlist/virtualizedlist.constants';
|
|
5
5
|
/**
|
|
6
|
-
* `mdc-virtualizedlist` component
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
6
|
+
* `mdc-virtualizedlist` is an extension of the `mdc-list` component that adds virtualization capabilities using
|
|
7
|
+
* the Tanstack Virtual library.
|
|
8
|
+
*
|
|
9
|
+
* This component is thin wrapper around the Tanstack libray to provide additional funtionalities such as
|
|
10
|
+
* keyboard navigation, focus management, scroll anchoring and accessibility features.
|
|
11
|
+
*
|
|
11
12
|
* Please refer to [Tanstack Virtual Docs](https://tanstack.com/virtual/latest) for more in depth documentation.
|
|
12
13
|
*
|
|
14
|
+
* ## Setup
|
|
15
|
+
*
|
|
16
|
+
* `virtualizerProps` is a required prop that requires at least two properties to be set: `count` and `estimateSize`.
|
|
17
|
+
* `count` is the total number of items in the list, and `estimateSize` is a function that returns the estimated
|
|
18
|
+
* size (in pixels) of each item in the list. `getItemKey` is also strongly recommended to be set to provide unique
|
|
19
|
+
* keys for each item in the list.
|
|
20
|
+
*
|
|
21
|
+
* ### Render list items
|
|
22
|
+
*
|
|
23
|
+
* To keep the component framework-agnostic, the rendering of the list items is left to the consumer.
|
|
24
|
+
*
|
|
25
|
+
* We need to render only the visible items. The list of visible items are provided by the `virtualitemschange` event.
|
|
26
|
+
* The event detail contains the `virtualItems` array, which contains the information for the rendering.
|
|
27
|
+
* List items must have an `data-index` attribute, the indexes are in the `virtualItems` list.
|
|
28
|
+
*
|
|
29
|
+
* ## Best practices
|
|
30
|
+
*
|
|
31
|
+
* ### List updates
|
|
32
|
+
*
|
|
33
|
+
* Tanstack needs only the count of the items in the list and the size of each item to perform virtualization.
|
|
34
|
+
* List updates happens when
|
|
35
|
+
* - when `virtualizerProps` property of the component instance changes
|
|
36
|
+
* - when `observe-size-changes` is set and the item's size changes (it uses ResizeObserver internally)
|
|
37
|
+
* - when `component.visualiser.measure` called manually.
|
|
38
|
+
*
|
|
39
|
+
* ### Header
|
|
40
|
+
*
|
|
41
|
+
* To add a header to the list, use the `mdc-listheader` component and place it in the `list-header` slot.
|
|
42
|
+
*
|
|
43
|
+
* ### Lists with dynamic content
|
|
44
|
+
*
|
|
45
|
+
* Unique keys for the list items are critical for dynamically changing list items or item's content.
|
|
46
|
+
* If the key change with the content it will cause scrollbar and content shuttering.
|
|
47
|
+
*
|
|
13
48
|
* @tagname mdc-virtualizedlist
|
|
14
49
|
*
|
|
15
50
|
* @event scroll - (React: onScroll) Event that gets called when user scrolls inside of list.
|
|
51
|
+
* @event virtualitemschange - (React: onVirtualItemsChange) Event that gets called when the virtual items change.
|
|
16
52
|
*
|
|
17
|
-
* @slot -
|
|
53
|
+
* @slot default - This is a default/unnamed slot, where listitems can be placed.
|
|
54
|
+
* @slot list-header - This slot is used to pass a header for the list, which can be a `mdc-listheader` component.
|
|
18
55
|
*
|
|
19
56
|
* @csspart container - The container of the virtualized list.
|
|
20
57
|
* @csspart scroll - The scrollable area of the virtualized list.
|
|
@@ -24,6 +61,7 @@ const reactWrapper = createComponent({
|
|
|
24
61
|
elementClass: Component,
|
|
25
62
|
react: React,
|
|
26
63
|
events: {
|
|
64
|
+
onVirtualItemsChange: 'virtualitemschange',
|
|
27
65
|
onScroll: 'scroll',
|
|
28
66
|
},
|
|
29
67
|
displayName: 'VirtualizedList',
|
|
@@ -15,7 +15,7 @@ export const AutoFocusOnMountMixin = (superClass) => {
|
|
|
15
15
|
/**
|
|
16
16
|
* @internal
|
|
17
17
|
*/
|
|
18
|
-
this.elementToAutoFocus =
|
|
18
|
+
this.elementToAutoFocus = null;
|
|
19
19
|
/**
|
|
20
20
|
* This property indicates whether the element should receive focus automatically when it is mounted.
|
|
21
21
|
*
|
|
@@ -30,7 +30,7 @@ export const AutoFocusOnMountMixin = (superClass) => {
|
|
|
30
30
|
if (this.autoFocusOnMount) {
|
|
31
31
|
// wait for the element to be fully updated before focusing
|
|
32
32
|
await this.updateComplete;
|
|
33
|
-
this.elementToAutoFocus.focus();
|
|
33
|
+
(this.elementToAutoFocus || this).focus();
|
|
34
34
|
}
|
|
35
35
|
}
|
|
36
36
|
}
|
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
import type { Component } from '../../models';
|
|
2
|
+
import type { BaseArray } from '../virtualIndexArray';
|
|
2
3
|
import type { Constructor } from './index.types';
|
|
3
4
|
export declare abstract class ListNavigationMixinInterface {
|
|
4
5
|
protected loop: 'true' | 'false';
|
|
5
6
|
protected propagateAllKeyEvents: boolean;
|
|
6
7
|
protected initialFocus: number;
|
|
7
|
-
protected abstract get navItems(): HTMLElement
|
|
8
|
-
protected resetTabIndexes(index: number): void;
|
|
8
|
+
protected abstract get navItems(): BaseArray<HTMLElement>;
|
|
9
|
+
protected resetTabIndexes(index: number, focusElement?: boolean): void;
|
|
9
10
|
protected resetTabIndexAndSetFocus(newIndex: number, oldIndex?: number, focusNewItem?: boolean): void;
|
|
11
|
+
protected setInitialFocus(): void;
|
|
12
|
+
protected handleNavigationKeyDown(event: KeyboardEvent): void;
|
|
10
13
|
}
|
|
11
14
|
/**
|
|
12
15
|
* This mixin extends the passed class with list like navigation capabilities.
|
|
@@ -47,64 +47,6 @@ export const ListNavigationMixin = (superClass) => {
|
|
|
47
47
|
* @internal
|
|
48
48
|
*/
|
|
49
49
|
this.initialFocus = 0;
|
|
50
|
-
/**
|
|
51
|
-
* Handles keydown events for navigation within the list.
|
|
52
|
-
* It allows users to navigate through the list items using arrow keys, home, and end keys.
|
|
53
|
-
* The navigation is based on the current focused item and the direction of the layout (RTL or LTR).
|
|
54
|
-
*
|
|
55
|
-
* By default, it will stop propagation for key events which are handled by the component.
|
|
56
|
-
* Check the mixin options to change this behavior.
|
|
57
|
-
*
|
|
58
|
-
* @param event - The keyboard event triggered by user interaction.
|
|
59
|
-
* @internal
|
|
60
|
-
*/
|
|
61
|
-
this.handleNavigationKeyDown = (event) => {
|
|
62
|
-
const keysToHandle = new Set([KEYS.ARROW_DOWN, KEYS.ARROW_UP, KEYS.HOME, KEYS.END]);
|
|
63
|
-
const isRtl = window.getComputedStyle(this).direction === 'rtl';
|
|
64
|
-
const targetKey = this.resolveDirectionKey(event.key, isRtl);
|
|
65
|
-
if (!keysToHandle.has(targetKey)) {
|
|
66
|
-
return;
|
|
67
|
-
}
|
|
68
|
-
const target = event.target;
|
|
69
|
-
const currentIndex = this.getCurrentIndex(target);
|
|
70
|
-
if (currentIndex === -1)
|
|
71
|
-
return;
|
|
72
|
-
this.resetTabIndexes(currentIndex);
|
|
73
|
-
switch (targetKey) {
|
|
74
|
-
case KEYS.HOME: {
|
|
75
|
-
// Move focus to the first item
|
|
76
|
-
this.resetTabIndexAndSetFocus(0, currentIndex);
|
|
77
|
-
break;
|
|
78
|
-
}
|
|
79
|
-
case KEYS.END: {
|
|
80
|
-
// Move focus to the last item
|
|
81
|
-
this.resetTabIndexAndSetFocus(this.navItems.length - 1, currentIndex);
|
|
82
|
-
break;
|
|
83
|
-
}
|
|
84
|
-
case KEYS.ARROW_DOWN: {
|
|
85
|
-
// Move focus to the next item
|
|
86
|
-
const eolIndex = this.shouldLoop() ? 0 : currentIndex;
|
|
87
|
-
const newIndex = currentIndex + 1 === this.navItems.length ? eolIndex : currentIndex + 1;
|
|
88
|
-
this.resetTabIndexAndSetFocus(newIndex, currentIndex);
|
|
89
|
-
break;
|
|
90
|
-
}
|
|
91
|
-
case KEYS.ARROW_UP: {
|
|
92
|
-
// Move focus to the prev item
|
|
93
|
-
const eolIndex = this.shouldLoop() ? this.navItems.length - 1 : currentIndex;
|
|
94
|
-
const newIndex = currentIndex - 1 === -1 ? eolIndex : currentIndex - 1;
|
|
95
|
-
this.resetTabIndexAndSetFocus(newIndex, currentIndex);
|
|
96
|
-
break;
|
|
97
|
-
}
|
|
98
|
-
default:
|
|
99
|
-
break;
|
|
100
|
-
}
|
|
101
|
-
// When the component consume any of the pressed key, we need to stop propagation
|
|
102
|
-
// to prevent the event from bubbling up and being handled by parent components which might use the same key.
|
|
103
|
-
if (!this.propagateAllKeyEvents) {
|
|
104
|
-
event.stopPropagation();
|
|
105
|
-
event.preventDefault();
|
|
106
|
-
}
|
|
107
|
-
};
|
|
108
50
|
/**
|
|
109
51
|
* Handles click events on the navigation items.
|
|
110
52
|
* It retrieves the index of the clicked item and resets the tabindex accordingly.
|
|
@@ -118,11 +60,11 @@ export const ListNavigationMixin = (superClass) => {
|
|
|
118
60
|
if (newIndex !== -1) {
|
|
119
61
|
// When user clicked on a focusable element inside the item, we update the navigation index, but
|
|
120
62
|
// keep the focus on the clicked element.
|
|
121
|
-
const focusNewItem = !(this.navItems
|
|
63
|
+
const focusNewItem = !(this.navItems.at(newIndex) !== target && document.activeElement === event.target);
|
|
122
64
|
this.resetTabIndexAndSetFocus(newIndex, undefined, focusNewItem);
|
|
123
65
|
}
|
|
124
66
|
};
|
|
125
|
-
this.addEventListener('keydown', this.handleNavigationKeyDown);
|
|
67
|
+
this.addEventListener('keydown', this.handleNavigationKeyDown.bind(this));
|
|
126
68
|
this.addEventListener('click', this.handleNavigationClick);
|
|
127
69
|
}
|
|
128
70
|
/**
|
|
@@ -132,9 +74,70 @@ export const ListNavigationMixin = (superClass) => {
|
|
|
132
74
|
*/
|
|
133
75
|
async firstUpdated(changedProperties) {
|
|
134
76
|
super.firstUpdated(changedProperties);
|
|
77
|
+
this.setInitialFocus();
|
|
78
|
+
}
|
|
79
|
+
setInitialFocus() {
|
|
135
80
|
const indexToFocus = Math.max(Math.min(this.initialFocus, this.navItems.length - 1), 0);
|
|
136
81
|
this.resetTabIndexAndSetFocus(indexToFocus, undefined, false);
|
|
137
82
|
}
|
|
83
|
+
/**
|
|
84
|
+
* Handles keydown events for navigation within the list.
|
|
85
|
+
* It allows users to navigate through the list items using arrow keys, home, and end keys.
|
|
86
|
+
* The navigation is based on the current focused item and the direction of the layout (RTL or LTR).
|
|
87
|
+
*
|
|
88
|
+
* By default, it will stop propagation for key events which are handled by the component.
|
|
89
|
+
* Check the mixin options to change this behavior.
|
|
90
|
+
*
|
|
91
|
+
* @param event - The keyboard event triggered by user interaction.
|
|
92
|
+
* @internal
|
|
93
|
+
*/
|
|
94
|
+
handleNavigationKeyDown(event) {
|
|
95
|
+
const keysToHandle = new Set([KEYS.ARROW_DOWN, KEYS.ARROW_UP, KEYS.HOME, KEYS.END]);
|
|
96
|
+
const isRtl = window.getComputedStyle(this).direction === 'rtl';
|
|
97
|
+
const targetKey = this.resolveDirectionKey(event.key, isRtl);
|
|
98
|
+
if (!keysToHandle.has(targetKey)) {
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
const target = event.target;
|
|
102
|
+
const currentIndex = this.getCurrentIndex(target);
|
|
103
|
+
if (currentIndex === -1)
|
|
104
|
+
return;
|
|
105
|
+
this.resetTabIndexes(currentIndex);
|
|
106
|
+
switch (targetKey) {
|
|
107
|
+
case KEYS.HOME: {
|
|
108
|
+
// Move focus to the first item
|
|
109
|
+
this.resetTabIndexAndSetFocus(0, currentIndex);
|
|
110
|
+
break;
|
|
111
|
+
}
|
|
112
|
+
case KEYS.END: {
|
|
113
|
+
// Move focus to the last item
|
|
114
|
+
this.resetTabIndexAndSetFocus(this.navItems.length - 1, currentIndex);
|
|
115
|
+
break;
|
|
116
|
+
}
|
|
117
|
+
case KEYS.ARROW_DOWN: {
|
|
118
|
+
// Move focus to the next item
|
|
119
|
+
const eolIndex = this.shouldLoop() ? 0 : currentIndex;
|
|
120
|
+
const newIndex = currentIndex + 1 === this.navItems.length ? eolIndex : currentIndex + 1;
|
|
121
|
+
this.resetTabIndexAndSetFocus(newIndex, currentIndex);
|
|
122
|
+
break;
|
|
123
|
+
}
|
|
124
|
+
case KEYS.ARROW_UP: {
|
|
125
|
+
// Move focus to the prev item
|
|
126
|
+
const eolIndex = this.shouldLoop() ? this.navItems.length - 1 : currentIndex;
|
|
127
|
+
const newIndex = currentIndex - 1 === -1 ? eolIndex : currentIndex - 1;
|
|
128
|
+
this.resetTabIndexAndSetFocus(newIndex, currentIndex);
|
|
129
|
+
break;
|
|
130
|
+
}
|
|
131
|
+
default:
|
|
132
|
+
break;
|
|
133
|
+
}
|
|
134
|
+
// When the component consume any of the pressed key, we need to stop propagation
|
|
135
|
+
// to prevent the event from bubbling up and being handled by parent components which might use the same key.
|
|
136
|
+
if (!this.propagateAllKeyEvents) {
|
|
137
|
+
event.stopPropagation();
|
|
138
|
+
event.preventDefault();
|
|
139
|
+
}
|
|
140
|
+
}
|
|
138
141
|
/**
|
|
139
142
|
* Retrieves the current index of the item that triggered the event.
|
|
140
143
|
*
|
|
@@ -152,14 +155,20 @@ export const ListNavigationMixin = (superClass) => {
|
|
|
152
155
|
* Reset all tabindex to -1 and set the tabindex of the current item to 0
|
|
153
156
|
*
|
|
154
157
|
* @param index - The index of the currently focused item.
|
|
158
|
+
* @param focusElement - Call focus() on the current item or not.
|
|
155
159
|
*/
|
|
156
|
-
resetTabIndexes(index) {
|
|
160
|
+
resetTabIndexes(index, focusElement = true) {
|
|
157
161
|
var _a;
|
|
158
162
|
if (this.navItems.length > 0) {
|
|
159
163
|
this.navItems.forEach(item => item.setAttribute('tabindex', '-1'));
|
|
160
|
-
const currentIndex = this.navItems
|
|
161
|
-
this.navItems
|
|
162
|
-
(
|
|
164
|
+
const currentIndex = this.navItems.at(index) ? index : 0;
|
|
165
|
+
const currentItem = (_a = this.navItems.at(currentIndex)) !== null && _a !== void 0 ? _a : this.navItems.find(Boolean);
|
|
166
|
+
if (currentItem) {
|
|
167
|
+
currentItem.setAttribute('tabindex', '0');
|
|
168
|
+
if (focusElement) {
|
|
169
|
+
currentItem.focus();
|
|
170
|
+
}
|
|
171
|
+
}
|
|
163
172
|
}
|
|
164
173
|
}
|
|
165
174
|
/**
|
|
@@ -171,21 +180,21 @@ export const ListNavigationMixin = (superClass) => {
|
|
|
171
180
|
* @returns - This method does not return anything.
|
|
172
181
|
*/
|
|
173
182
|
resetTabIndexAndSetFocus(newIndex, oldIndex, focusNewItem = true) {
|
|
183
|
+
var _a;
|
|
174
184
|
const { navItems } = this;
|
|
175
185
|
if (navItems.length === 0)
|
|
176
186
|
return;
|
|
177
187
|
// Ensure newIndex is valid
|
|
178
|
-
const
|
|
179
|
-
const newItem = navItems[newIdx];
|
|
188
|
+
const newItem = (_a = navItems.at(newIndex)) !== null && _a !== void 0 ? _a : navItems.find(Boolean);
|
|
180
189
|
if (newIndex === oldIndex && newItem && newItem.getAttribute('tabindex') === '0') {
|
|
181
190
|
return;
|
|
182
191
|
}
|
|
183
192
|
if (oldIndex === undefined) {
|
|
184
193
|
navItems.forEach(item => item.setAttribute('tabindex', '-1'));
|
|
185
194
|
}
|
|
186
|
-
else if (navItems
|
|
195
|
+
else if (navItems.at(oldIndex)) {
|
|
187
196
|
// Reset tabindex of the old item
|
|
188
|
-
navItems
|
|
197
|
+
navItems.at(oldIndex).setAttribute('tabindex', '-1');
|
|
189
198
|
}
|
|
190
199
|
newItem.setAttribute('tabindex', '0');
|
|
191
200
|
if (focusNewItem) {
|
|
@@ -26,6 +26,10 @@ export const LifeCycleMixin = (superClass) => {
|
|
|
26
26
|
connectedCallback() {
|
|
27
27
|
super.connectedCallback();
|
|
28
28
|
this.dispatchEvent(new Event(LIFE_CYCLE_EVENTS.CREATED, { bubbles: true, composed: true }));
|
|
29
|
+
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
|
30
|
+
this.updateComplete.then(() => {
|
|
31
|
+
this.dispatchEvent(new Event(LIFE_CYCLE_EVENTS.FIRST_UPDATE_COMPLETED, { bubbles: true, composed: true }));
|
|
32
|
+
});
|
|
29
33
|
}
|
|
30
34
|
disconnectedCallback() {
|
|
31
35
|
super.disconnectedCallback();
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
interface RangeOptions {
|
|
2
|
+
includeEnd?: boolean;
|
|
3
|
+
}
|
|
4
|
+
/**
|
|
5
|
+
* Represents a numeric interval between two numbers.
|
|
6
|
+
*
|
|
7
|
+
* Start is inclusive, end is exclusive by default.
|
|
8
|
+
*/
|
|
9
|
+
export declare class Interval implements Iterable<number> {
|
|
10
|
+
/** The start of the Interval (inclusive). */
|
|
11
|
+
readonly start: number;
|
|
12
|
+
/** The end of the Interval (exclusive) by default. */
|
|
13
|
+
readonly end: number;
|
|
14
|
+
/** Whether the end is inclusive. Default is false (exclusive). */
|
|
15
|
+
readonly includeEnd: boolean;
|
|
16
|
+
constructor(start: number, end: number, options?: RangeOptions);
|
|
17
|
+
/**
|
|
18
|
+
* Checks if a number is within the Interval.
|
|
19
|
+
* @param value - The number to check.
|
|
20
|
+
*/
|
|
21
|
+
includes(value: number): boolean;
|
|
22
|
+
/**
|
|
23
|
+
* Returns an iterator for the Interval.
|
|
24
|
+
*
|
|
25
|
+
* If the start is greater than the end, it decrements by step.
|
|
26
|
+
* The iteration stops when the current number reaches or exceeds the end (or equals if includeEnd is true).
|
|
27
|
+
*
|
|
28
|
+
* @param step - The step between each number in the Interval. Default is the step defined in the constructor.
|
|
29
|
+
* It must be a positive non-zero number.
|
|
30
|
+
* @returns An iterator that yields numbers from start to end, incremented by step.
|
|
31
|
+
*/
|
|
32
|
+
iter(step?: number): Iterator<number>;
|
|
33
|
+
/**
|
|
34
|
+
* Returns the default iterator for the Interval.
|
|
35
|
+
*
|
|
36
|
+
* it steps by 1.
|
|
37
|
+
*/
|
|
38
|
+
[Symbol.iterator](): Iterator<number, any, any>;
|
|
39
|
+
}
|
|
40
|
+
export {};
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
const DEFAULT_OPTIONS = {
|
|
2
|
+
includeEnd: false,
|
|
3
|
+
};
|
|
4
|
+
/**
|
|
5
|
+
* Represents a numeric interval between two numbers.
|
|
6
|
+
*
|
|
7
|
+
* Start is inclusive, end is exclusive by default.
|
|
8
|
+
*/
|
|
9
|
+
export class Interval {
|
|
10
|
+
constructor(start, end, options = {}) {
|
|
11
|
+
const opt = { ...DEFAULT_OPTIONS, ...options };
|
|
12
|
+
this.start = start;
|
|
13
|
+
this.end = end;
|
|
14
|
+
this.includeEnd = opt.includeEnd;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Checks if a number is within the Interval.
|
|
18
|
+
* @param value - The number to check.
|
|
19
|
+
*/
|
|
20
|
+
includes(value) {
|
|
21
|
+
const { start, end, includeEnd } = this;
|
|
22
|
+
const min = Math.min(start, end);
|
|
23
|
+
const max = Math.max(start, end);
|
|
24
|
+
return includeEnd ? value >= min && value <= max : value >= min && value < max;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Returns an iterator for the Interval.
|
|
28
|
+
*
|
|
29
|
+
* If the start is greater than the end, it decrements by step.
|
|
30
|
+
* The iteration stops when the current number reaches or exceeds the end (or equals if includeEnd is true).
|
|
31
|
+
*
|
|
32
|
+
* @param step - The step between each number in the Interval. Default is the step defined in the constructor.
|
|
33
|
+
* It must be a positive non-zero number.
|
|
34
|
+
* @returns An iterator that yields numbers from start to end, incremented by step.
|
|
35
|
+
*/
|
|
36
|
+
iter(step = 1) {
|
|
37
|
+
if (step <= 0) {
|
|
38
|
+
throw new Error('Step must be a positive non-zero number');
|
|
39
|
+
}
|
|
40
|
+
const stepper = step * (this.start <= this.end ? 1 : -1);
|
|
41
|
+
const { includeEnd, end } = this;
|
|
42
|
+
let current = this.start;
|
|
43
|
+
let done = false;
|
|
44
|
+
return {
|
|
45
|
+
next: () => {
|
|
46
|
+
if (done)
|
|
47
|
+
return { value: undefined, done: true };
|
|
48
|
+
const value = current;
|
|
49
|
+
current += stepper;
|
|
50
|
+
if (includeEnd ? value > end : value >= end) {
|
|
51
|
+
done = true;
|
|
52
|
+
return { value: undefined, done: true };
|
|
53
|
+
}
|
|
54
|
+
return { value, done: false };
|
|
55
|
+
},
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Returns the default iterator for the Interval.
|
|
60
|
+
*
|
|
61
|
+
* it steps by 1.
|
|
62
|
+
*/
|
|
63
|
+
[Symbol.iterator]() {
|
|
64
|
+
return this.iter();
|
|
65
|
+
}
|
|
66
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
export interface BaseArray<TItem> extends Pick<Array<TItem>, 'length' | 'forEach' | 'findIndex' | 'map' | 'at' | 'find'> {
|
|
2
|
+
}
|
|
3
|
+
/**
|
|
4
|
+
* A wrapper around an array that applies a "virtual" index to the index when accessing items.
|
|
5
|
+
* This is useful when the array is just a view of the real data and the real data has gaps in the indexes.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```ts
|
|
9
|
+
* const originalArray = [{realIndex: 2, value: 10}, {realIndex: 3, value: 20}];
|
|
10
|
+
* const offsetArray = new OffsetArray(originalArray, (item) => item.realIndex);
|
|
11
|
+
*
|
|
12
|
+
* console.log(offsetArray.at(0)); // Output: undefined
|
|
13
|
+
* console.log(offsetArray.at(2).value); // Output: 10
|
|
14
|
+
* ```
|
|
15
|
+
*/
|
|
16
|
+
export declare class VirtualIndexArray<TItem> implements BaseArray<TItem> {
|
|
17
|
+
readonly items: BaseArray<TItem>;
|
|
18
|
+
private readonly getIndex;
|
|
19
|
+
private lengthFn;
|
|
20
|
+
constructor(items: BaseArray<TItem>, getIndex: (item: TItem) => number, length: () => number);
|
|
21
|
+
get length(): number;
|
|
22
|
+
at(index: number): TItem | undefined;
|
|
23
|
+
map<U>(cb: (value: TItem, index: number, array: TItem[]) => U, thisArg?: any): U[];
|
|
24
|
+
forEach(cb: (value: TItem, index: number, array: TItem[]) => void, thisArg?: any): void;
|
|
25
|
+
findIndex(predicate: (value: TItem, index: number, obj: TItem[]) => boolean, thisArg?: any): number;
|
|
26
|
+
find(predicate: (value: TItem, index: number, obj: TItem[]) => boolean, thisArg?: any): TItem | undefined;
|
|
27
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A wrapper around an array that applies a "virtual" index to the index when accessing items.
|
|
3
|
+
* This is useful when the array is just a view of the real data and the real data has gaps in the indexes.
|
|
4
|
+
*
|
|
5
|
+
* @example
|
|
6
|
+
* ```ts
|
|
7
|
+
* const originalArray = [{realIndex: 2, value: 10}, {realIndex: 3, value: 20}];
|
|
8
|
+
* const offsetArray = new OffsetArray(originalArray, (item) => item.realIndex);
|
|
9
|
+
*
|
|
10
|
+
* console.log(offsetArray.at(0)); // Output: undefined
|
|
11
|
+
* console.log(offsetArray.at(2).value); // Output: 10
|
|
12
|
+
* ```
|
|
13
|
+
*/
|
|
14
|
+
export class VirtualIndexArray {
|
|
15
|
+
constructor(items, getIndex, length) {
|
|
16
|
+
this.getIndex = () => 0;
|
|
17
|
+
this.items = items;
|
|
18
|
+
this.getIndex = getIndex;
|
|
19
|
+
this.lengthFn = length;
|
|
20
|
+
}
|
|
21
|
+
get length() {
|
|
22
|
+
return this.lengthFn();
|
|
23
|
+
}
|
|
24
|
+
at(index) {
|
|
25
|
+
return this.items.find(item => this.getIndex(item) === index);
|
|
26
|
+
}
|
|
27
|
+
map(cb, thisArg) {
|
|
28
|
+
return this.items.map((value, _index, array) => cb.call(thisArg, value, this.getIndex(value), array));
|
|
29
|
+
}
|
|
30
|
+
forEach(cb, thisArg) {
|
|
31
|
+
this.items.forEach((value, _index, array) => {
|
|
32
|
+
cb.call(thisArg, value, this.getIndex(value), array);
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
findIndex(predicate, thisArg) {
|
|
36
|
+
const item = this.items.find((value, _index, obj) => predicate.call(thisArg, value, this.getIndex(value), obj));
|
|
37
|
+
return !item ? -1 : this.getIndex(item);
|
|
38
|
+
}
|
|
39
|
+
find(predicate, thisArg) {
|
|
40
|
+
return this.items.find((value, _index, obj) => predicate.call(thisArg, value, this.getIndex(value), obj));
|
|
41
|
+
}
|
|
42
|
+
}
|
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.122.
|
|
4
|
+
"version": "0.122.7",
|
|
5
5
|
"engines": {
|
|
6
6
|
"node": ">=20.0.0",
|
|
7
7
|
"npm": ">=8.0.0"
|
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
"@momentum-design/fonts": "*",
|
|
38
38
|
"@momentum-design/icons": "*",
|
|
39
39
|
"@momentum-design/tokens": "*",
|
|
40
|
-
"@tanstack/lit-virtual": "^3.
|
|
40
|
+
"@tanstack/lit-virtual": "^3.13.2",
|
|
41
41
|
"lit": "^3.2.0",
|
|
42
42
|
"lottie-web": "^5.12.2",
|
|
43
43
|
"uuid": "^11.0.5"
|