@vollowx/seele 0.13.10 → 0.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (77) hide show
  1. package/custom-elements.json +604 -359
  2. package/package.json +1 -1
  3. package/src/all.d.ts +2 -2
  4. package/src/all.js +2 -2
  5. package/src/base/autocomplete.d.ts +9 -33
  6. package/src/base/autocomplete.js +42 -65
  7. package/src/base/button.d.ts +0 -1
  8. package/src/base/button.js +7 -11
  9. package/src/base/checkbox.d.ts +0 -2
  10. package/src/base/checkbox.js +6 -13
  11. package/src/base/controllers/list-controller.d.ts +1 -0
  12. package/src/base/controllers/list-controller.js +6 -1
  13. package/src/base/dialog.d.ts +2 -2
  14. package/src/base/dialog.js +11 -2
  15. package/src/base/list-item.d.ts +1 -1
  16. package/src/base/list-item.js +11 -12
  17. package/src/base/list.d.ts +16 -33
  18. package/src/base/list.js +91 -173
  19. package/src/base/menu.d.ts +41 -61
  20. package/src/base/menu.js +184 -306
  21. package/src/base/{experimental/popup.d.ts → popup.d.ts} +7 -11
  22. package/src/base/{experimental/popup.js → popup.js} +45 -90
  23. package/src/base/radio.js +5 -5
  24. package/src/base/select.d.ts +7 -53
  25. package/src/base/select.js +45 -79
  26. package/src/base/tooltip.js +2 -2
  27. package/src/core/focus.d.ts +11 -0
  28. package/src/core/focus.js +53 -0
  29. package/src/m3/autocomplete/autocomplete-styles.css.js +1 -1
  30. package/src/m3/autocomplete/autocomplete.d.ts +1 -0
  31. package/src/m3/autocomplete/autocomplete.js +2 -1
  32. package/src/m3/button/common-button-styles.css.js +1 -1
  33. package/src/m3/button/icon-button-styles.css.js +1 -1
  34. package/src/m3/button/icon-button-toggle.js +1 -1
  35. package/src/m3/button/shared-button-styles.css.js +1 -1
  36. package/src/m3/button-group/standard-button-group.js +1 -1
  37. package/src/m3/checkbox/checkbox.js +1 -1
  38. package/src/m3/dialog/dialog.d.ts +2 -2
  39. package/src/m3/dialog/dialog.js +2 -2
  40. package/src/m3/field/field-styles.css.js +1 -1
  41. package/src/m3/field/field.js +2 -2
  42. package/src/m3/field/filled-field-styles.css.js +1 -1
  43. package/src/m3/field/filled-field.js +1 -1
  44. package/src/m3/field/outlined-field-styles.css.js +1 -1
  45. package/src/m3/focus-ring/focus-ring.js +1 -1
  46. package/src/m3/list/list-item.d.ts +2 -2
  47. package/src/m3/list/list-item.js +6 -6
  48. package/src/m3/menu/composed-menu.d.ts +19 -0
  49. package/src/m3/menu/composed-menu.js +42 -0
  50. package/src/m3/menu/menu-styles.css.js +1 -1
  51. package/src/m3/menu/menu.d.ts +1 -7
  52. package/src/m3/menu/menu.js +1 -13
  53. package/src/m3/navigation/navigation-rail-item-styles.css.js +1 -1
  54. package/src/m3/popup/popup-styles.css.js +2 -0
  55. package/src/m3/{experimental → popup}/popup.d.ts +2 -1
  56. package/src/m3/popup/popup.js +18 -0
  57. package/src/m3/radio/radio.js +1 -1
  58. package/src/m3/ripple/ripple.js +3 -3
  59. package/src/m3/select/filled-select.d.ts +2 -2
  60. package/src/m3/select/filled-select.js +5 -5
  61. package/src/m3/select/outlined-select.d.ts +2 -2
  62. package/src/m3/select/outlined-select.js +5 -5
  63. package/src/m3/select/select-styles.css.js +1 -1
  64. package/src/m3/select/select.d.ts +2 -5
  65. package/src/m3/select/select.js +3 -6
  66. package/src/m3/switch/switch-styles.css.js +1 -1
  67. package/src/m3/switch/switch.js +1 -1
  68. package/src/m3/tab/tabs-styles.css.js +1 -1
  69. package/src/m3/tab/tabs.js +1 -1
  70. package/src/m3/text-field/filled-text-field.js +2 -2
  71. package/src/m3/text-field/outlined-text-field.js +2 -2
  72. package/src/m3/tooltip/tooltip-styles.css.js +1 -1
  73. package/src/core/focus-visible.d.ts +0 -2
  74. package/src/core/focus-visible.js +0 -13
  75. package/src/m3/experimental/popup-styles.css.js +0 -2
  76. package/src/m3/experimental/popup.js +0 -11
  77. /package/src/base/{experimental/popup-styles.css.js → popup-styles.css.js} +0 -0
package/src/base/list.js CHANGED
@@ -1,31 +1,40 @@
1
+ /**
2
+ * TODO: currently list and menu has too much in common, should be split into
3
+ * menu, listbox and a LinearFocusController for tabs/toolbars
4
+ */
1
5
  import { __decorate } from "tslib";
2
6
  import { LitElement, html, isServer } from 'lit';
3
7
  import { property, queryAssignedElements } from 'lit/decorators.js';
4
- import { setFocusVisible } from '../core/focus-visible.js';
5
- import { InternalsAttached, internals } from './mixins/internals-attached.js';
8
+ import { focusVisible, setFocusVisible } from '../core/focus.js';
9
+ import { internals, InternalsAttached } from './mixins/internals-attached.js';
6
10
  import { ListController } from './controllers/list-controller.js';
7
- const Base = InternalsAttached(LitElement);
11
+ import { MenuAction, getActionFromKey, getVisibleItems } from './menu.js';
8
12
  /**
9
13
  * @csspart items
10
- *
11
- * @fires {Event} open - Fires when the menu is opened.
12
- * @fires {Event} close - Fires when the menu is closed.
13
- * @fires {ListSelectEvent} select - Fires when an item is selected.
14
+ * @fires {ListboxSelectEvent} select - Fires when an item is selected.
14
15
  * @fires {ListItemFocusEvent} item-focus - Fires when an item is focused
15
16
  */
16
- export class List extends Base {
17
+ export class List extends InternalsAttached(LitElement) {
17
18
  get $items() {
18
19
  return this.listController.items || [];
19
20
  }
20
- render() {
21
- return html `${this.renderItemSlot()}`;
21
+ get currentIndex() {
22
+ return this.listController?.currentIndex;
23
+ }
24
+ focusFirstItem() {
25
+ this.listController.focusFirstItem();
26
+ }
27
+ focusLastItem() {
28
+ this.listController.focusLastItem();
29
+ }
30
+ focusItem(item) {
31
+ this.listController._focusItem(item);
22
32
  }
23
- renderItemSlot() {
33
+ render() {
24
34
  return html `<slot part="items"></slot>`;
25
35
  }
26
36
  constructor() {
27
37
  super();
28
- this._scrollPadding = 0;
29
38
  this.noFocusControl = false;
30
39
  this.listController = new ListController(this, {
31
40
  isItem: (item) => item.getAttribute('seele-base') === 'option' &&
@@ -40,9 +49,10 @@ export class List extends Base {
40
49
  if (!this.noFocusControl) {
41
50
  this[internals].ariaActiveDescendantElement = item;
42
51
  }
43
- scrollItemIntoView(this, item, this._scrollPadding);
52
+ if (focusVisible)
53
+ item.scrollIntoView({ block: 'nearest' });
44
54
  this.dispatchEvent(new CustomEvent('item-focus', {
45
- detail: { item: item },
55
+ detail: { item, index: this.$items.indexOf(item) },
46
56
  bubbles: true,
47
57
  composed: true,
48
58
  }));
@@ -54,19 +64,27 @@ export class List extends Base {
54
64
  this.setAttribute('tabindex', '0');
55
65
  }
56
66
  if (!isServer) {
57
- this.addEventListener('keydown', (e) => this.#handleKeyDown(e));
58
- this.addEventListener('focusin', () => this.#handleFocusIn());
59
- this.addEventListener('focusout', () => this.#handleFocusOut());
60
- this.addEventListener('pointerdown', (e) => this.#handlePointerDown(e));
61
- this.addEventListener('click', (e) => this.#handleClick(e));
62
- this.addEventListener('mouseover', (e) => this.#handleMouseOver(e));
67
+ this.addEventListener('keydown', this.handleKeyDown.bind(this));
68
+ this.addEventListener('focusin', this.#handleFocusIn.bind(this));
69
+ this.addEventListener('focusout', this.#handleFocusOut.bind(this));
70
+ this.addEventListener('mouseover', this.#handleMouseOver.bind(this));
71
+ this.addEventListener('click', this.#handleClick.bind(this));
72
+ }
73
+ }
74
+ #handleFocusIn() {
75
+ if (this.currentIndex === -1) {
76
+ this.listController.focusFirstItem();
77
+ }
78
+ else {
79
+ this.listController._focusItem(this.$items[this.currentIndex]);
63
80
  }
64
81
  }
65
- updated(changed) {
66
- // TODO: Find somewhere to put this
67
- // this.listController.clearSearch();
82
+ #handleFocusOut() {
83
+ if (this.listController._focusedItem) {
84
+ this.listController._blurItem(this.listController._focusedItem);
85
+ }
68
86
  }
69
- #handleKeyDown(event) {
87
+ handleKeyDown(event) {
70
88
  if (event.defaultPrevented)
71
89
  return;
72
90
  const action = getActionFromKey(event);
@@ -74,88 +92,76 @@ export class List extends Base {
74
92
  const currentIndex = this.listController.currentIndex;
75
93
  const maxIndex = items.length - 1;
76
94
  switch (action) {
77
- case MenuActions.Last:
78
- case MenuActions.First:
79
- case MenuActions.Next:
80
- case MenuActions.Previous:
81
- case MenuActions.PageUp:
82
- case MenuActions.PageDown:
95
+ case MenuAction.First:
96
+ event.preventDefault();
97
+ this.listController.focusFirstItem();
98
+ return;
99
+ case MenuAction.Last:
100
+ event.preventDefault();
101
+ this.listController.focusLastItem();
102
+ return;
103
+ case MenuAction.PageUp:
104
+ case MenuAction.PageDown: {
105
+ event.preventDefault();
106
+ const { first, last, pageSize } = getVisibleItems(this, items);
107
+ const isDown = action === MenuAction.PageDown;
108
+ const boundary = isDown ? last : first;
109
+ const atBoundary = isDown
110
+ ? currentIndex >= last
111
+ : currentIndex <= first;
112
+ const step = isDown ? pageSize : -pageSize;
113
+ const nextIndex = atBoundary ? currentIndex + step : boundary;
114
+ this.listController._focusItem(items[Math.max(0, Math.min(maxIndex, nextIndex))]);
115
+ return;
116
+ }
117
+ case MenuAction.Next:
118
+ event.preventDefault();
119
+ this.listController.focusNextItem();
120
+ return;
121
+ case MenuAction.Previous:
83
122
  event.preventDefault();
84
- const nextIndex = getUpdatedIndex(currentIndex, maxIndex, action);
85
- this.listController._focusItem(items[nextIndex]);
123
+ this.listController.focusPreviousItem();
86
124
  return;
87
- case MenuActions.CloseSelect:
125
+ case MenuAction.CloseSelect:
88
126
  event.preventDefault();
89
127
  if (currentIndex >= 0) {
90
- items[currentIndex].focused = false;
91
- this.dispatchEvent(new CustomEvent('select', {
92
- detail: {
93
- item: items[currentIndex],
94
- index: currentIndex,
95
- },
96
- bubbles: true,
97
- composed: true,
98
- }));
128
+ this.#dispatchSelect({
129
+ item: items[currentIndex],
130
+ index: currentIndex,
131
+ });
99
132
  }
100
133
  return;
101
- case MenuActions.Type:
134
+ case MenuAction.Type:
102
135
  this.listController.handleType(event.key);
103
136
  return;
104
137
  }
105
138
  }
106
- #handleFocusIn() {
107
- if (this.currentIndex == -1)
108
- this.listController.focusFirstItem();
109
- else
110
- this.$items[this.currentIndex].focused = true;
111
- }
112
- #handleFocusOut() {
113
- this.$items[this.currentIndex].focused = false;
114
- }
115
139
  #handleMouseOver(event) {
116
140
  setFocusVisible(false);
117
- const item = event.target.closest('[seele-base="option"]');
118
- if (item && this.listController.items.includes(item)) {
119
- this.listController._focusItem(item);
141
+ const item = this.#getItemFromEvent(event);
142
+ if (item && this.currentIndex !== item.index) {
143
+ this.listController._focusItem(item.item);
120
144
  }
121
145
  }
122
- #handlePointerDown(event) {
123
- event.preventDefault(); // This makes sure that the container is focused
124
- this.focus();
125
- const item = this.#getEventItem(event);
126
- if (!item || !this.listController.items.includes(item))
127
- return;
128
- this.listController._focusItem(item);
129
- }
130
146
  #handleClick(event) {
131
- this.focus();
132
- const item = this.#getEventItem(event);
133
- if (!item || !this.listController.items.includes(item))
147
+ const item = this.#getItemFromEvent(event);
148
+ if (!item)
134
149
  return;
150
+ this.#dispatchSelect({ ...item });
151
+ }
152
+ #getItemFromEvent(event) {
153
+ const item = event.target.closest('[seele-base=option]');
154
+ if (!item || !this.$items.includes(item))
155
+ return null;
156
+ return { item, index: this.$items.indexOf(item) };
157
+ }
158
+ #dispatchSelect(detail) {
135
159
  this.dispatchEvent(new CustomEvent('select', {
136
- detail: {
137
- item: item,
138
- index: this.listController.items.indexOf(item),
139
- },
160
+ detail: detail,
140
161
  bubbles: true,
141
162
  composed: true,
142
163
  }));
143
164
  }
144
- #getEventItem(event) {
145
- return event.target.closest('[seele-base="option"]');
146
- }
147
- get currentIndex() {
148
- return this.listController?.currentIndex;
149
- }
150
- focusFirstItem() {
151
- this.listController.focusFirstItem();
152
- }
153
- focusLastItem() {
154
- this.listController.focusLastItem();
155
- }
156
- focusItem(item) {
157
- this.listController._focusItem(item);
158
- }
159
165
  }
160
166
  __decorate([
161
167
  property({ type: Boolean, attribute: 'no-focus-control' })
@@ -163,91 +169,3 @@ __decorate([
163
169
  __decorate([
164
170
  queryAssignedElements({ flatten: true })
165
171
  ], List.prototype, "slotItems", void 0);
166
- // Reference: https://www.w3.org/WAI/ARIA/apg/patterns/combobox/examples/combobox-select-only/
167
- export const MenuActions = {
168
- Close: 0,
169
- CloseSelect: 1,
170
- First: 2,
171
- Last: 3,
172
- Next: 4,
173
- Open: 5,
174
- PageDown: 6,
175
- PageUp: 7,
176
- Previous: 8,
177
- Select: 9,
178
- Type: 10,
179
- };
180
- export function filterOptions(options = [], filter, exclude = []) {
181
- return options.filter((option) => {
182
- const matches = option.toLowerCase().indexOf(filter.toLowerCase()) === 0;
183
- return matches && exclude.indexOf(option) < 0;
184
- });
185
- }
186
- export function getActionFromKey(event) {
187
- const { key, altKey, ctrlKey, metaKey } = event;
188
- if (key === 'Home') {
189
- return MenuActions.First;
190
- }
191
- if (key === 'End') {
192
- return MenuActions.Last;
193
- }
194
- if (key === 'Backspace' ||
195
- key === 'Clear' ||
196
- (key.length === 1 && key !== ' ' && !altKey && !ctrlKey && !metaKey)) {
197
- return MenuActions.Type;
198
- }
199
- if (key === 'ArrowUp' && altKey) {
200
- return MenuActions.CloseSelect;
201
- }
202
- else if (key === 'ArrowDown' && !altKey) {
203
- return MenuActions.Next;
204
- }
205
- else if (key === 'ArrowUp') {
206
- return MenuActions.Previous;
207
- }
208
- else if (key === 'PageUp') {
209
- return MenuActions.PageUp;
210
- }
211
- else if (key === 'PageDown') {
212
- return MenuActions.PageDown;
213
- }
214
- else if (key === 'Escape') {
215
- return MenuActions.Close;
216
- }
217
- else if (key === 'Enter' || key === ' ') {
218
- return MenuActions.CloseSelect;
219
- }
220
- return undefined;
221
- }
222
- export function getUpdatedIndex(currentIndex, maxIndex, action) {
223
- const pageSize = 10;
224
- switch (action) {
225
- case MenuActions.First:
226
- return 0;
227
- case MenuActions.Last:
228
- return maxIndex;
229
- case MenuActions.Previous:
230
- return Math.max(0, currentIndex - 1);
231
- case MenuActions.Next:
232
- return Math.min(maxIndex, currentIndex + 1);
233
- case MenuActions.PageUp:
234
- return Math.max(0, currentIndex - pageSize);
235
- case MenuActions.PageDown:
236
- return Math.min(maxIndex, currentIndex + pageSize);
237
- default:
238
- return currentIndex;
239
- }
240
- }
241
- export function scrollItemIntoView(list, item, paddingY = 0) {
242
- if (!list)
243
- return;
244
- // Basic scroll into view logic
245
- const menuRect = list.getBoundingClientRect();
246
- const itemRect = item.getBoundingClientRect();
247
- if (itemRect.bottom + paddingY > menuRect.bottom) {
248
- list.scrollTop += itemRect.bottom - menuRect.bottom + paddingY;
249
- }
250
- else if (itemRect.top - paddingY < menuRect.top) {
251
- list.scrollTop -= menuRect.top - itemRect.top + paddingY;
252
- }
253
- }
@@ -1,78 +1,58 @@
1
1
  import { LitElement } from 'lit';
2
- import type { Placement, Strategy } from '@floating-ui/dom';
3
2
  import type { MenuItem } from './menu-item.js';
4
- declare const Base: Constructor<import("./mixins/focus-delegated.js").FocusDelegatedInterface> & Constructor<import("./mixins/internals-attached.js").InternalsAttachedInterface> & Constructor<import("./mixins/attachable.js").AttachableInterface> & typeof LitElement;
5
- interface MenuSelectDetail {
3
+ export interface MenuEventDetail {
6
4
  item: MenuItem;
7
5
  index: number;
8
6
  }
9
- export type MenuSelectEvent = CustomEvent<MenuSelectDetail>;
10
- interface ItemFocusDetail {
11
- item: MenuItem;
12
- }
13
- export type MenuItemFocusEvent = CustomEvent<ItemFocusDetail>;
7
+ export type MenuActionEvent = CustomEvent<MenuEventDetail>;
8
+ export type MenuItemFocusEvent = CustomEvent<MenuEventDetail>;
9
+ declare const Menu_base: Constructor<import("./mixins/internals-attached.js").InternalsAttachedInterface> & typeof LitElement;
14
10
  /**
15
- * @csspart menu
16
11
  * @csspart items
17
- *
18
- * @fires {Event} open - Fires when the menu is opened.
19
- * @fires {Event} close - Fires when the menu is closed.
20
- * @fires {MenuSelectEvent} select - Fires when an item is selected.
21
- * @fires {MenuItemFocusEvent} item-focus - Fires when an item is focused
22
- *
23
- * TODO: handle slotchange
12
+ * @slot - menu items
13
+ * @fires {MenuActionEvent} action - Fires when an item is activated.
14
+ * @fires {MenuItemFocusEvent} item-focus - Fires when an item is focused.
15
+ * @fires {Event} request-popup-hide - Fires when menu should be hidden.
24
16
  */
25
- export declare class Menu extends Base {
17
+ export declare class Menu extends Menu_base {
26
18
  #private;
27
- readonly _durations: {
28
- show: number;
29
- hide: number;
30
- };
31
- readonly _scrollPadding: number;
32
- type: string;
33
- open: boolean;
34
- quick: boolean;
35
- offset: number;
36
- align: Placement;
37
- alignStrategy: Strategy;
38
- keepOpenBlur: boolean;
39
- keepOpenSelect: boolean;
40
- keepOpenClickAway: boolean;
41
- noFocusControl: boolean;
42
- tabIndex: number;
43
- $menu: HTMLElement;
44
- slotItems: Array<MenuItem | HTMLElement>;
19
+ /**
20
+ * When true, emits `request-popup-hide` on `action`
21
+ */
22
+ keepOpenAction: boolean;
23
+ /**
24
+ * When true, removes the default focus management and `aria-activedescendant`
25
+ * on the host.
26
+ */
27
+ bare: boolean;
28
+ slotItems: Array<MenuItem>;
45
29
  get $items(): MenuItem[];
46
- private $lastFocused;
47
- render(): import("lit-html").TemplateResult<1>;
48
- renderItemSlot(): import("lit-html").TemplateResult<1>;
49
- private readonly popoverController;
50
- private readonly listController;
51
- connectedCallback(): void;
52
- disconnectedCallback(): void;
53
- protected updated(changed: Map<string, any>): void;
54
30
  get currentIndex(): number;
55
31
  focusFirstItem(): void;
56
32
  focusLastItem(): void;
57
33
  focusItem(item: MenuItem): void;
58
- show(): void;
59
- close(): void;
34
+ render(): import("lit-html").TemplateResult<1>;
35
+ constructor();
36
+ private readonly listController;
37
+ handleKeyDown(event: KeyboardEvent): void;
38
+ }
39
+ export declare enum MenuAction {
40
+ Close = -11,
41
+ CloseSelect = -10,
42
+ First = 1,
43
+ Last = 2,
44
+ Next = 3,
45
+ Open = 4,
46
+ PageDown = 5,
47
+ PageUp = 6,
48
+ Previous = 7,
49
+ Select = 8,
50
+ Type = 9
60
51
  }
61
- export declare const MenuActions: {
62
- Close: number;
63
- CloseSelect: number;
64
- First: number;
65
- Last: number;
66
- Next: number;
67
- Open: number;
68
- PageDown: number;
69
- PageUp: number;
70
- Previous: number;
71
- Select: number;
72
- Type: number;
52
+ export declare function getActionFromKey(event: KeyboardEvent): MenuAction | null;
53
+ export declare function getVisibleItems(container: HTMLElement, items: HTMLElement[]): {
54
+ first: number;
55
+ last: number;
56
+ pageSize: number;
73
57
  };
74
- export declare function filterOptions(options: string[], filter: string, exclude?: string[]): string[];
75
- export declare function getActionFromKey(event: KeyboardEvent, menuOpen: boolean): number;
76
- export declare function getUpdatedIndex(currentIndex: number, maxIndex: number, action: number): number;
77
- export declare function scrollItemIntoView(menu: HTMLElement, item: HTMLElement, paddingY?: number): void;
78
58
  export {};