@momentum-design/components 0.110.2 → 0.111.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.
@@ -1,5 +1,6 @@
1
1
  import type { CSSResult } from 'lit';
2
2
  import { Component } from '../../models';
3
+ declare const List_base: import("../../utils/mixins/index.types").Constructor<Component & import("../../utils/mixins/ListNavigationMixin").ListNavigationMixinInterface> & import("../../utils/mixins/index.types").Constructor<import("../../utils/mixins/lifecycle/CaptureDestroyEventForChildElement").CaptureDestroyEventForChildElementInterface> & typeof Component;
3
4
  /**
4
5
  * mdc-list component is used to display a group of list items. It is used as a container to wrap other list items.
5
6
  *
@@ -13,55 +14,48 @@ import { Component } from '../../models';
13
14
  *
14
15
  * @csspart container - The container slot around the list items
15
16
  */
16
- declare class List extends Component {
17
+ declare class List extends List_base {
17
18
  /**
18
19
  * @internal
19
- * Get all listitem elements which are not disabled in the list.
20
20
  */
21
- private listItems;
22
- constructor();
23
- connectedCallback(): void;
21
+ private itemsStore;
24
22
  /**
25
- * Handles the keydown event on the list element.
26
- * If the key is 'ArrowUp' or 'ArrowDown', it focuses to the previous or next list item
27
- * and sets the active tabindex of the list item.
28
- * Prevents the default event behavior.
29
- * @param event - The keyboard event.
23
+ * Whether to loop navigation when reaching the end of the list.
24
+ * If 'true', pressing the down arrow on the last item will focus the first item,
25
+ * and pressing the up arrow on the first item will focus the last item.
26
+ * If 'false', navigation will stop at the first or last item.
27
+ *
28
+ * @default ''
30
29
  */
31
- private handleKeyDown;
30
+ loop: 'true' | 'false';
32
31
  /**
33
- * Returns the index of the given target in the listItems array.
34
- * If the target is not a list item, but a child element of a list item,
35
- * it returns the index of the parent list item.
36
- * @param target - The target element to find the index of.
37
- * @returns The index of the target element in the listItems array.
32
+ * The index of the item that should receive focus when the list is first rendered.
33
+ * If the index is out of bounds, the first item (index 0) will receive focus.
34
+ *
35
+ * @default 0
38
36
  */
39
- private getCurrentIndex;
37
+ initialFocus: number;
38
+ constructor();
39
+ connectedCallback(): void;
40
40
  /**
41
- * Calculates a new index based on the pressed keyboard key.
42
- * Supports navigation keys for moving focus within a list.
43
- * @param key - The key that was pressed.
44
- * @param currentIndex - The current index of the focused list item.
45
- * @param wrappedDivsCount - The total number of list items.
46
- * @returns The new index to focus on, or undefined if the key is not supported.
41
+ * @internal
47
42
  */
48
- private getNewIndexBasedOnKey;
43
+ get navItems(): HTMLElement[];
49
44
  /**
50
- * Handles the mouse click event on the list element.
51
- * Finds the index of the target element in the list items array and calls
52
- * `resetTabIndexAndSetActiveTabIndex` with that index.
53
- * @param event - The mouse event.
45
+ * Update the tabIndex of the list items when a new item is added.
46
+ *
47
+ * @internal
54
48
  */
55
- protected handleMouseClick(event: MouseEvent): void;
49
+ private handleCreatedEvent;
56
50
  /**
57
- * Resets all list items tabindex to -1 and sets the tabindex of the
58
- * element at the given index to 0, effectively setting the active
59
- * element. This is used when navigating the list via keyboard.
51
+ * Update the focus when an item is removed.
52
+ * If there is a next item, focus it. If not, focus the previous item.
60
53
  *
61
- * @param newIndex - The index of the new active element in the list.
54
+ * @internal
62
55
  */
63
- private resetTabIndexAndSetActiveTabIndex;
64
- firstUpdated(): void;
56
+ private handleDestroyEvent;
57
+ /** @internal */
58
+ private isValidItem;
65
59
  render(): import("lit-html").TemplateResult<1>;
66
60
  static styles: Array<CSSResult>;
67
61
  }
@@ -8,12 +8,16 @@ 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
10
  import { html } from 'lit';
11
- import { queryAssignedElements } from 'lit/decorators.js';
11
+ import { property } from 'lit/decorators.js';
12
12
  import { Component } from '../../models';
13
- import { KEYS } from '../../utils/keys';
14
13
  import { ROLE } from '../../utils/roles';
14
+ import { ListNavigationMixin } from '../../utils/mixins/ListNavigationMixin';
15
15
  import { TAG_NAME as LISTITEM_TAGNAME } from '../listitem/listitem.constants';
16
+ import { ElementStore } from '../../utils/controllers/ElementStore';
17
+ import { CaptureDestroyEventForChildElement } from '../../utils/mixins/lifecycle/CaptureDestroyEventForChildElement';
18
+ import { LIFE_CYCLE_EVENTS } from '../../utils/mixins/lifecycle/lifecycle.contants';
16
19
  import styles from './list.styles';
20
+ import { DEFAULTS } from './list.constants';
17
21
  /**
18
22
  * mdc-list component is used to display a group of list items. It is used as a container to wrap other list items.
19
23
  *
@@ -27,10 +31,65 @@ import styles from './list.styles';
27
31
  *
28
32
  * @csspart container - The container slot around the list items
29
33
  */
30
- class List extends Component {
34
+ class List extends ListNavigationMixin(CaptureDestroyEventForChildElement(Component)) {
31
35
  constructor() {
32
36
  super();
33
- this.addEventListener('keydown', this.handleKeyDown);
37
+ /**
38
+ * Whether to loop navigation when reaching the end of the list.
39
+ * If 'true', pressing the down arrow on the last item will focus the first item,
40
+ * and pressing the up arrow on the first item will focus the last item.
41
+ * If 'false', navigation will stop at the first or last item.
42
+ *
43
+ * @default ''
44
+ */
45
+ this.loop = DEFAULTS.LOOP;
46
+ /**
47
+ * The index of the item that should receive focus when the list is first rendered.
48
+ * If the index is out of bounds, the first item (index 0) will receive focus.
49
+ *
50
+ * @default 0
51
+ */
52
+ this.initialFocus = DEFAULTS.INITIAL_FOCUS;
53
+ /**
54
+ * Update the tabIndex of the list items when a new item is added.
55
+ *
56
+ * @internal
57
+ */
58
+ this.handleCreatedEvent = (event) => {
59
+ const createdElement = event.target;
60
+ if (!this.isValidItem(createdElement)) {
61
+ return;
62
+ }
63
+ createdElement.tabIndex = -1;
64
+ };
65
+ /**
66
+ * Update the focus when an item is removed.
67
+ * If there is a next item, focus it. If not, focus the previous item.
68
+ *
69
+ * @internal
70
+ */
71
+ this.handleDestroyEvent = (event) => {
72
+ const destroyedElement = event.target;
73
+ if (!this.isValidItem(destroyedElement) || destroyedElement.tabIndex !== 0) {
74
+ return;
75
+ }
76
+ const destroyedItemIndex = this.navItems.findIndex(node => node === destroyedElement);
77
+ if (destroyedItemIndex === -1) {
78
+ return;
79
+ }
80
+ let newIndex = destroyedItemIndex + 1;
81
+ if (newIndex >= this.navItems.length) {
82
+ newIndex = destroyedItemIndex - 1;
83
+ }
84
+ this.resetTabIndexes(newIndex);
85
+ };
86
+ this.addEventListener(LIFE_CYCLE_EVENTS.CREATED, this.handleCreatedEvent);
87
+ this.addEventListener(LIFE_CYCLE_EVENTS.DESTROYED, this.handleDestroyEvent);
88
+ // This must be initialized after the destroyed event listener
89
+ // to keep the element in the itemStore in order to move the focus correctly
90
+ this.itemsStore = new ElementStore(this, {
91
+ isValidItem: this.isValidItem,
92
+ });
34
93
  }
35
94
  connectedCallback() {
36
95
  super.connectedCallback();
@@ -38,91 +97,30 @@ class List extends Component {
38
97
  this.setAttribute('role', ROLE.LIST);
39
98
  }
40
99
  /**
41
- * Handles the keydown event on the list element.
42
- * If the key is 'ArrowUp' or 'ArrowDown', it focuses to the previous or next list item
43
- * and sets the active tabindex of the list item.
44
- * Prevents the default event behavior.
45
- * @param event - The keyboard event.
46
- */
47
- handleKeyDown(event) {
48
- var _a;
49
- const currentIndex = this.getCurrentIndex(event.target);
50
- const newIndex = this.getNewIndexBasedOnKey(event.key, currentIndex, this.listItems.length);
51
- if (newIndex !== undefined) {
52
- (_a = this.listItems[newIndex]) === null || _a === void 0 ? void 0 : _a.focus();
53
- this.resetTabIndexAndSetActiveTabIndex(newIndex);
54
- }
55
- }
56
- /**
57
- * Returns the index of the given target in the listItems array.
58
- * If the target is not a list item, but a child element of a list item,
59
- * it returns the index of the parent list item.
60
- * @param target - The target element to find the index of.
61
- * @returns The index of the target element in the listItems array.
100
+ * @internal
62
101
  */
63
- getCurrentIndex(target) {
64
- return this.listItems.findIndex(node => node === target || node === target.parentElement);
102
+ get navItems() {
103
+ return this.itemsStore.items;
65
104
  }
66
- /**
67
- * Calculates a new index based on the pressed keyboard key.
68
- * Supports navigation keys for moving focus within a list.
69
- * @param key - The key that was pressed.
70
- * @param currentIndex - The current index of the focused list item.
71
- * @param wrappedDivsCount - The total number of list items.
72
- * @returns The new index to focus on, or undefined if the key is not supported.
73
- */
74
- getNewIndexBasedOnKey(key, currentIndex, wrappedDivsCount) {
75
- switch (key) {
76
- case KEYS.ARROW_DOWN:
77
- return (currentIndex + 1) % wrappedDivsCount;
78
- case KEYS.ARROW_UP:
79
- return (currentIndex - 1 + wrappedDivsCount) % wrappedDivsCount;
80
- case KEYS.HOME:
81
- return 0;
82
- case KEYS.END:
83
- return wrappedDivsCount - 1;
84
- default:
85
- return undefined;
86
- }
87
- }
88
- /**
89
- * Handles the mouse click event on the list element.
90
- * Finds the index of the target element in the list items array and calls
91
- * `resetTabIndexAndSetActiveTabIndex` with that index.
92
- * @param event - The mouse event.
93
- */
94
- handleMouseClick(event) {
95
- const newIndex = this.getCurrentIndex(event.target);
96
- this.resetTabIndexAndSetActiveTabIndex(newIndex);
97
- }
98
- /**
99
- * Resets all list items tabindex to -1 and sets the tabindex of the
100
- * element at the given index to 0, effectively setting the active
101
- * element. This is used when navigating the list via keyboard.
102
- *
103
- * @param newIndex - The index of the new active element in the list.
104
- */
105
- resetTabIndexAndSetActiveTabIndex(newIndex) {
106
- this.listItems.forEach((node, index) => {
107
- const newTabindex = newIndex === index ? '0' : '-1';
108
- node === null || node === void 0 ? void 0 : node.setAttribute('tabindex', newTabindex);
109
- });
110
- }
111
- firstUpdated() {
112
- // For the first, we set the first element only as active.
113
- this.resetTabIndexAndSetActiveTabIndex(0);
105
+ /** @internal */
106
+ isValidItem(item) {
107
+ return item.matches(`${LISTITEM_TAGNAME}:not([disabled])`);
114
108
  }
115
109
  render() {
116
110
  return html `
117
111
  <slot name="list-header"></slot>
118
112
  <!-- make the container slot role presentation to keep it ignored in a11y tree -->
119
- <slot part="container" @click="${this.handleMouseClick}" role="presentation"></slot>
113
+ <slot part="container" role="presentation"></slot>
120
114
  `;
121
115
  }
122
116
  }
123
117
  List.styles = [...Component.styles, ...styles];
124
118
  __decorate([
125
- queryAssignedElements({ selector: `${LISTITEM_TAGNAME}:not([disabled])` }),
126
- __metadata("design:type", Array)
127
- ], List.prototype, "listItems", void 0);
119
+ property({ type: String, reflect: true }),
120
+ __metadata("design:type", String)
121
+ ], List.prototype, "loop", void 0);
122
+ __decorate([
123
+ property({ type: Number, reflect: true, attribute: 'initial-focus' }),
124
+ __metadata("design:type", Number)
125
+ ], List.prototype, "initialFocus", void 0);
128
126
  export default List;
@@ -1,3 +1,7 @@
1
1
  declare const TAG_NAME: "mdc-list";
2
2
  declare const HEADER_ID = "header-id";
3
- export { TAG_NAME, HEADER_ID };
3
+ declare const DEFAULTS: {
4
+ readonly LOOP: "true";
5
+ readonly INITIAL_FOCUS: 0;
6
+ };
7
+ export { TAG_NAME, HEADER_ID, DEFAULTS };
@@ -1,4 +1,8 @@
1
1
  import utils from '../../utils/tag-name';
2
2
  const TAG_NAME = utils.constructTagName('list');
3
3
  const HEADER_ID = 'header-id';
4
- export { TAG_NAME, HEADER_ID };
4
+ const DEFAULTS = {
5
+ LOOP: 'true',
6
+ INITIAL_FOCUS: 0,
7
+ };
8
+ export { TAG_NAME, HEADER_ID, DEFAULTS };
@@ -3,12 +3,16 @@ const styles = css `
3
3
  :host {
4
4
  display: flex;
5
5
  flex-direction: column;
6
+ scroll-padding-top: 0.25rem;
7
+ scroll-padding-bottom: 0.25rem;
6
8
  }
7
9
 
8
10
  :host::part(container) {
9
11
  display: flex;
10
12
  flex-direction: column;
11
13
  gap: 0rem;
14
+ scroll-padding-top: 0.25rem;
15
+ scroll-padding-bottom: 0.25rem;
12
16
  }
13
17
  `;
14
18
  export default [styles];
@@ -32,7 +32,7 @@ declare class ListBox extends ListBox_base {
32
32
  * https://www.w3.org/WAI/ARIA/apg/practices/listbox
33
33
  * @internal
34
34
  */
35
- protected loop: boolean;
35
+ protected loop: 'true' | 'false';
36
36
  /**
37
37
  * The name attribute is used to identify the listbox
38
38
  */
@@ -49,7 +49,7 @@ class ListBox extends ListNavigationMixin(CaptureDestroyEventForChildElement(Com
49
49
  * https://www.w3.org/WAI/ARIA/apg/practices/listbox
50
50
  * @internal
51
51
  */
52
- this.loop = false;
52
+ this.loop = 'false';
53
53
  /**
54
54
  * The name attribute is used to identify the listbox
55
55
  */