@vollowx/seele 0.13.10 → 0.14.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.
Files changed (80) hide show
  1. package/README.md +3 -3
  2. package/custom-elements.json +604 -359
  3. package/package.json +1 -1
  4. package/src/all.d.ts +2 -2
  5. package/src/all.js +2 -2
  6. package/src/base/autocomplete.d.ts +9 -33
  7. package/src/base/autocomplete.js +42 -65
  8. package/src/base/button.d.ts +0 -1
  9. package/src/base/button.js +7 -11
  10. package/src/base/checkbox.d.ts +0 -2
  11. package/src/base/checkbox.js +6 -13
  12. package/src/base/controllers/list-controller.d.ts +1 -0
  13. package/src/base/controllers/list-controller.js +6 -1
  14. package/src/base/dialog.d.ts +2 -2
  15. package/src/base/dialog.js +11 -2
  16. package/src/base/list-item.d.ts +1 -1
  17. package/src/base/list-item.js +11 -12
  18. package/src/base/list.d.ts +16 -33
  19. package/src/base/list.js +91 -173
  20. package/src/base/menu.d.ts +41 -61
  21. package/src/base/menu.js +184 -306
  22. package/src/base/{experimental/popup.d.ts → popup.d.ts} +7 -11
  23. package/src/base/{experimental/popup.js → popup.js} +45 -90
  24. package/src/base/radio.js +5 -5
  25. package/src/base/select.d.ts +7 -53
  26. package/src/base/select.js +45 -79
  27. package/src/base/tooltip.js +2 -2
  28. package/src/core/focus.d.ts +11 -0
  29. package/src/core/focus.js +53 -0
  30. package/src/m3/autocomplete/autocomplete-styles.css.js +1 -1
  31. package/src/m3/autocomplete/autocomplete.d.ts +1 -0
  32. package/src/m3/autocomplete/autocomplete.js +2 -1
  33. package/src/m3/button/common-button-styles.css.js +1 -1
  34. package/src/m3/button/icon-button-styles.css.js +1 -1
  35. package/src/m3/button/icon-button-toggle.js +1 -1
  36. package/src/m3/button/shared-button-styles.css.js +1 -1
  37. package/src/m3/button-group/standard-button-group.js +1 -1
  38. package/src/m3/checkbox/checkbox.js +1 -1
  39. package/src/m3/dialog/dialog.d.ts +2 -2
  40. package/src/m3/dialog/dialog.js +2 -2
  41. package/src/m3/field/field-styles.css.js +1 -1
  42. package/src/m3/field/field.js +2 -2
  43. package/src/m3/field/filled-field-styles.css.js +1 -1
  44. package/src/m3/field/filled-field.js +1 -1
  45. package/src/m3/field/outlined-field-styles.css.js +1 -1
  46. package/src/m3/focus-ring/focus-ring.js +1 -1
  47. package/src/m3/list/list-item.d.ts +2 -2
  48. package/src/m3/list/list-item.js +6 -6
  49. package/src/m3/menu/composed-menu.d.ts +19 -0
  50. package/src/m3/menu/composed-menu.js +42 -0
  51. package/src/m3/menu/menu-styles.css.js +1 -1
  52. package/src/m3/menu/menu.d.ts +1 -7
  53. package/src/m3/menu/menu.js +1 -13
  54. package/src/m3/navigation/navigation-rail-item-styles.css.js +1 -1
  55. package/src/m3/popup/popup-styles.css.js +2 -0
  56. package/src/m3/{experimental → popup}/popup.d.ts +2 -1
  57. package/src/m3/popup/popup.js +18 -0
  58. package/src/m3/radio/radio.js +1 -1
  59. package/src/m3/ripple/ripple.js +3 -3
  60. package/src/m3/select/filled-select.d.ts +2 -2
  61. package/src/m3/select/filled-select.js +5 -5
  62. package/src/m3/select/outlined-select.d.ts +2 -2
  63. package/src/m3/select/outlined-select.js +5 -5
  64. package/src/m3/select/select-styles.css.js +1 -1
  65. package/src/m3/select/select.d.ts +2 -5
  66. package/src/m3/select/select.js +3 -6
  67. package/src/m3/styles/color-default-dark.css +51 -0
  68. package/src/m3/styles/color-default-light.css +51 -0
  69. package/src/m3/switch/switch-styles.css.js +1 -1
  70. package/src/m3/switch/switch.js +1 -1
  71. package/src/m3/tab/tabs-styles.css.js +1 -1
  72. package/src/m3/tab/tabs.js +1 -1
  73. package/src/m3/text-field/filled-text-field.js +2 -2
  74. package/src/m3/text-field/outlined-text-field.js +2 -2
  75. package/src/m3/tooltip/tooltip-styles.css.js +1 -1
  76. package/src/core/focus-visible.d.ts +0 -2
  77. package/src/core/focus-visible.js +0 -13
  78. package/src/m3/experimental/popup-styles.css.js +0 -2
  79. package/src/m3/experimental/popup.js +0 -11
  80. /package/src/base/{experimental/popup-styles.css.js → popup-styles.css.js} +0 -0
package/src/base/menu.js CHANGED
@@ -1,59 +1,46 @@
1
1
  import { __decorate } from "tslib";
2
- import { LitElement, html } from 'lit';
3
- import { property, query, queryAssignedElements } from 'lit/decorators.js';
4
- import { setFocusVisible } from '../core/focus-visible.js';
5
- import { Attachable } from './mixins/attachable.js';
6
- import { InternalsAttached } from './mixins/internals-attached.js';
7
- import { FocusDelegated } from './mixins/focus-delegated.js';
8
- import { PopoverController } from './controllers/popover-controller.js';
2
+ import { LitElement, html, isServer } from 'lit';
3
+ import { property, queryAssignedElements } from 'lit/decorators.js';
4
+ import { focusVisible, setFocusVisible } from '../core/focus.js';
5
+ import { internals, InternalsAttached } from './mixins/internals-attached.js';
9
6
  import { ListController } from './controllers/list-controller.js';
10
- const Base = FocusDelegated(InternalsAttached(Attachable(LitElement)));
11
7
  /**
12
- * @csspart menu
13
8
  * @csspart items
14
- *
15
- * @fires {Event} open - Fires when the menu is opened.
16
- * @fires {Event} close - Fires when the menu is closed.
17
- * @fires {MenuSelectEvent} select - Fires when an item is selected.
18
- * @fires {MenuItemFocusEvent} item-focus - Fires when an item is focused
19
- *
20
- * TODO: handle slotchange
9
+ * @slot - menu items
10
+ * @fires {MenuActionEvent} action - Fires when an item is activated.
11
+ * @fires {MenuItemFocusEvent} item-focus - Fires when an item is focused.
12
+ * @fires {Event} request-popup-hide - Fires when menu should be hidden.
21
13
  */
22
- export class Menu extends Base {
14
+ export class Menu extends InternalsAttached(LitElement) {
15
+ get $items() {
16
+ return this.listController.items || [];
17
+ }
18
+ get currentIndex() {
19
+ return this.listController?.currentIndex;
20
+ }
21
+ focusFirstItem() {
22
+ this.listController.focusFirstItem();
23
+ }
24
+ focusLastItem() {
25
+ this.listController.focusLastItem();
26
+ }
27
+ focusItem(item) {
28
+ this.listController._focusItem(item);
29
+ }
30
+ render() {
31
+ return html `<slot part="items"></slot>`;
32
+ }
23
33
  constructor() {
24
- super(...arguments);
25
- this._durations = { show: 0, hide: 0 };
26
- this._scrollPadding = 0;
27
- this.type = 'menu';
28
- this.open = false;
29
- this.quick = false;
30
- this.offset = 0;
31
- this.align = 'bottom-start';
32
- this.alignStrategy = 'absolute';
33
- this.keepOpenBlur = false;
34
- this.keepOpenSelect = false;
35
- this.keepOpenClickAway = false;
36
- this.noFocusControl = false;
37
- this.tabIndex = 0;
38
- this.$lastFocused = null;
39
- this.popoverController = new PopoverController(this, {
40
- popover: () => this.$menu,
41
- trigger: () => this.$control,
42
- positioning: {
43
- placement: () => this.align,
44
- strategy: () => this.alignStrategy,
45
- offset: () => this.offset,
46
- windowPadding: () => 16,
47
- },
48
- durations: {
49
- open: () => (this.quick ? 0 : this._durations.show),
50
- close: () => (this.quick ? 0 : this._durations.hide),
51
- },
52
- onClickAway: () => {
53
- if (!this.keepOpenClickAway)
54
- this.open = false;
55
- },
56
- });
34
+ super();
35
+ /**
36
+ * When true, emits `request-popup-hide` on `action`
37
+ */
38
+ this.keepOpenAction = false;
39
+ /**
40
+ * When true, removes the default focus management and `aria-activedescendant`
41
+ * on the host.
42
+ */
43
+ this.bare = false;
57
44
  this.listController = new ListController(this, {
58
45
  isItem: (item) => item.getAttribute('seele-base') === 'option' &&
59
46
  !item.hasAttribute('disabled') &&
@@ -64,313 +51,204 @@ export class Menu extends Base {
64
51
  },
65
52
  focusItem: (item) => {
66
53
  item.focused = true;
67
- if (!this.noFocusControl) {
68
- this.$menu.ariaActiveDescendantElement = item;
69
- }
70
- scrollItemIntoView(this.$menu, item, this._scrollPadding);
54
+ if (!this.bare)
55
+ this[internals].ariaActiveDescendantElement = item;
56
+ if (focusVisible)
57
+ item.scrollIntoView({ block: 'nearest' });
71
58
  this.dispatchEvent(new CustomEvent('item-focus', {
72
- detail: { item: item },
59
+ detail: { item, index: this.$items.indexOf(item) },
73
60
  bubbles: true,
74
61
  composed: true,
75
62
  }));
76
63
  },
77
64
  wrapNavigation: () => false,
78
65
  });
79
- }
80
- get $items() {
81
- return this.listController.items || [];
82
- }
83
- render() {
84
- return html `<div part="menu" role="${this.type}" tabindex="${this.tabIndex}" @keydown="${this.#handleKeyDown.bind(this)}" @focusout="${this.#handleFocusOut.bind(this)}">${this.renderItemSlot()}</div>`;
85
- }
86
- renderItemSlot() {
87
- return html `<slot part="items"></slot>`;
88
- }
89
- connectedCallback() {
90
- super.connectedCallback();
91
- if (this.$control) {
92
- // TODO: Manage $control ARIA attributes
93
- // TODO: Handle $control change
94
- this.$control.addEventListener('focusout', this.#handleFocusOut.bind(this));
66
+ this[internals].role = 'menu';
67
+ if (!this.hasAttribute('tabindex')) {
68
+ this.setAttribute('tabindex', '0');
95
69
  }
96
- this.updateComplete.then(() => {
97
- this.$items.forEach((item) => {
98
- item.addEventListener('mouseover', this.#handleItemMouseOver.bind(this));
99
- item.addEventListener('click', this.#handleItemClick.bind(this));
100
- });
101
- });
102
- }
103
- disconnectedCallback() {
104
- super.disconnectedCallback();
105
- if (this.$control) {
106
- this.$control.removeEventListener('focusout', this.#handleFocusOut.bind(this));
70
+ if (!isServer) {
71
+ this.addEventListener('keydown', this.handleKeyDown.bind(this));
72
+ this.addEventListener('focusin', this.#handleFocusIn.bind(this));
73
+ this.addEventListener('focusout', this.#handleFocusOut.bind(this));
74
+ this.addEventListener('mouseover', this.#handleMouseOver.bind(this));
75
+ this.addEventListener('click', this.#handleClick.bind(this));
107
76
  }
108
77
  }
109
- updated(changed) {
110
- if (changed.has('open')) {
111
- if (this.open) {
112
- this.dispatchEvent(new Event('open', { bubbles: true, composed: true }));
113
- this.$lastFocused = document.activeElement;
114
- if (this.$control) {
115
- this.$control.ariaExpanded = 'true';
116
- }
117
- this.popoverController.animateOpen().then(() => {
118
- if (!this.noFocusControl) {
119
- this.$menu.focus();
120
- this.listController.focusFirstItem();
121
- }
122
- });
123
- }
124
- else {
125
- if (!this.noFocusControl) {
126
- this.$menu.ariaActiveDescendantElement = null;
127
- }
128
- this.dispatchEvent(new Event('close', { bubbles: true, composed: true }));
129
- this.listController.clearSearch();
130
- if (this.$control) {
131
- this.$control.ariaExpanded = 'false';
132
- }
133
- this.popoverController.animateClose().then(() => {
134
- if (this.$lastFocused) {
135
- if (!this.noFocusControl)
136
- this.$lastFocused.focus();
137
- this.$lastFocused = null;
138
- }
139
- });
140
- }
141
- }
78
+ #handleFocusIn() {
79
+ if (this.bare)
80
+ return;
81
+ // TODO: support focusing first selected
82
+ this.listController.focusFirstItem();
83
+ }
84
+ #handleFocusOut() {
85
+ this.listController._blurItem(this.listController._focusedItem);
142
86
  }
143
- #handleKeyDown(event) {
87
+ handleKeyDown(event) {
144
88
  if (event.defaultPrevented)
145
89
  return;
146
- const action = getActionFromKey(event, this.open);
90
+ const action = getActionFromKey(event);
147
91
  const items = this.$items;
148
92
  const currentIndex = this.listController.currentIndex;
149
93
  const maxIndex = items.length - 1;
150
94
  switch (action) {
151
- case MenuActions.Last:
152
- case MenuActions.First:
153
- this.open = true;
154
- // intentional fallthrough
155
- case MenuActions.Next:
156
- case MenuActions.Previous:
157
- case MenuActions.PageUp:
158
- 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:
159
118
  event.preventDefault();
160
- const nextIndex = getUpdatedIndex(currentIndex, maxIndex, action);
161
- this.listController._focusItem(items[nextIndex]);
119
+ this.listController.focusNextItem();
162
120
  return;
163
- case MenuActions.CloseSelect:
121
+ case MenuAction.Previous:
122
+ event.preventDefault();
123
+ this.listController.focusPreviousItem();
124
+ return;
125
+ case MenuAction.CloseSelect:
164
126
  event.preventDefault();
165
127
  if (currentIndex >= 0) {
166
128
  items[currentIndex].focused = false;
167
- this.dispatchEvent(new CustomEvent('select', {
168
- detail: {
169
- item: items[currentIndex],
170
- index: currentIndex,
171
- },
172
- bubbles: true,
173
- composed: true,
174
- }));
175
- if (this.keepOpenSelect)
129
+ this.#dispatchAction({
130
+ item: items[currentIndex],
131
+ index: currentIndex,
132
+ });
133
+ if (this.keepOpenAction)
176
134
  return;
177
- this.open = false;
135
+ this.#dispatchHide();
178
136
  }
179
137
  return;
180
- case MenuActions.Close:
181
- event.preventDefault();
182
- this.open = false;
183
- return;
184
- case MenuActions.Type:
185
- this.open = true;
138
+ case MenuAction.Type:
186
139
  this.listController.handleType(event.key);
187
140
  return;
188
- case MenuActions.Open:
189
- event.preventDefault();
190
- this.open = true;
191
- return;
192
141
  }
193
142
  }
194
- #handleFocusOut(event) {
195
- if (this.keepOpenBlur)
196
- return;
197
- const newFocus = event.relatedTarget;
198
- const isInside = this.contains(newFocus) ||
199
- this.shadowRoot?.contains(newFocus) ||
200
- this.$control?.contains(newFocus);
201
- if (!isInside) {
202
- this.open = false;
203
- }
204
- }
205
- #handleItemMouseOver(event) {
143
+ #handleMouseOver(event) {
206
144
  setFocusVisible(false);
207
- const hoveredItem = event.currentTarget;
208
- this.listController._focusItem(hoveredItem);
145
+ const item = this.#getItemFromEvent(event);
146
+ if (item && this.currentIndex !== item.index)
147
+ this.listController._focusItem(item.item);
209
148
  }
210
- #handleItemClick(event) {
211
- const clickedItem = event.currentTarget;
212
- const index = this.listController.items.indexOf(clickedItem);
213
- this.listController.items[index].focused = false;
214
- this.dispatchEvent(new CustomEvent('select', {
215
- detail: {
216
- item: clickedItem,
217
- index: index,
218
- },
149
+ #handleClick(event) {
150
+ const item = this.#getItemFromEvent(event);
151
+ if (!item)
152
+ return;
153
+ item.item.focused = false;
154
+ this.#dispatchAction({ ...item });
155
+ if (!this.keepOpenAction)
156
+ this.#dispatchHide();
157
+ }
158
+ #getItemFromEvent(event) {
159
+ const item = event.target.closest('[seele-base=option]');
160
+ if (!item || !this.$items.includes(item))
161
+ return null;
162
+ return { item, index: this.$items.indexOf(item) };
163
+ }
164
+ #dispatchAction(detail) {
165
+ this.dispatchEvent(new CustomEvent('action', {
166
+ detail: detail,
219
167
  bubbles: true,
220
168
  composed: true,
221
169
  }));
222
- if (!this.keepOpenSelect)
223
- this.open = false;
224
- }
225
- get currentIndex() {
226
- return this.listController?.currentIndex;
227
- }
228
- focusFirstItem() {
229
- this.listController.focusFirstItem();
230
- }
231
- focusLastItem() {
232
- this.listController.focusLastItem();
233
- }
234
- focusItem(item) {
235
- this.listController._focusItem(item);
236
- }
237
- show() {
238
- this.open = true;
239
170
  }
240
- close() {
241
- this.open = false;
171
+ #dispatchHide() {
172
+ this.dispatchEvent(new Event('request-popup-hide', { bubbles: true, composed: true }));
242
173
  }
243
174
  }
244
175
  __decorate([
245
- property()
246
- ], Menu.prototype, "type", void 0);
176
+ property({ type: Boolean, attribute: 'keep-open-action' })
177
+ ], Menu.prototype, "keepOpenAction", void 0);
247
178
  __decorate([
248
179
  property({ type: Boolean, reflect: true })
249
- ], Menu.prototype, "open", void 0);
250
- __decorate([
251
- property({ type: Boolean })
252
- ], Menu.prototype, "quick", void 0);
253
- __decorate([
254
- property({ type: Number })
255
- ], Menu.prototype, "offset", void 0);
256
- __decorate([
257
- property({ reflect: true })
258
- ], Menu.prototype, "align", void 0);
259
- __decorate([
260
- property({ type: String, reflect: true, attribute: 'align-strategy' })
261
- ], Menu.prototype, "alignStrategy", void 0);
262
- __decorate([
263
- property({ type: Boolean, attribute: 'keep-open-blur' })
264
- ], Menu.prototype, "keepOpenBlur", void 0);
265
- __decorate([
266
- property({ type: Boolean, attribute: 'keep-open-select' })
267
- ], Menu.prototype, "keepOpenSelect", void 0);
268
- __decorate([
269
- property({ type: Boolean, attribute: 'keep-open-click-away' })
270
- ], Menu.prototype, "keepOpenClickAway", void 0);
271
- __decorate([
272
- property({ type: Boolean, attribute: 'no-focus-control' })
273
- ], Menu.prototype, "noFocusControl", void 0);
274
- __decorate([
275
- property({ type: Number, attribute: 'data-tabindex' })
276
- ], Menu.prototype, "tabIndex", void 0);
277
- __decorate([
278
- query('[part="menu"]')
279
- ], Menu.prototype, "$menu", void 0);
180
+ ], Menu.prototype, "bare", void 0);
280
181
  __decorate([
281
182
  queryAssignedElements({ flatten: true })
282
183
  ], Menu.prototype, "slotItems", void 0);
184
+ // Might as well be used in listbox, so let's keep it for now
283
185
  // Reference: https://www.w3.org/WAI/ARIA/apg/patterns/combobox/examples/combobox-select-only/
284
- export const MenuActions = {
285
- Close: 0,
286
- CloseSelect: 1,
287
- First: 2,
288
- Last: 3,
289
- Next: 4,
290
- Open: 5,
291
- PageDown: 6,
292
- PageUp: 7,
293
- Previous: 8,
294
- Select: 9,
295
- Type: 10,
296
- };
297
- export function filterOptions(options = [], filter, exclude = []) {
298
- return options.filter((option) => {
299
- const matches = option.toLowerCase().indexOf(filter.toLowerCase()) === 0;
300
- return matches && exclude.indexOf(option) < 0;
301
- });
302
- }
303
- export function getActionFromKey(event, menuOpen) {
186
+ export var MenuAction;
187
+ (function (MenuAction) {
188
+ MenuAction[MenuAction["Close"] = -11] = "Close";
189
+ MenuAction[MenuAction["CloseSelect"] = -10] = "CloseSelect";
190
+ MenuAction[MenuAction["First"] = 1] = "First";
191
+ MenuAction[MenuAction["Last"] = 2] = "Last";
192
+ MenuAction[MenuAction["Next"] = 3] = "Next";
193
+ MenuAction[MenuAction["Open"] = 4] = "Open";
194
+ MenuAction[MenuAction["PageDown"] = 5] = "PageDown";
195
+ MenuAction[MenuAction["PageUp"] = 6] = "PageUp";
196
+ MenuAction[MenuAction["Previous"] = 7] = "Previous";
197
+ MenuAction[MenuAction["Select"] = 8] = "Select";
198
+ MenuAction[MenuAction["Type"] = 9] = "Type";
199
+ })(MenuAction || (MenuAction = {}));
200
+ export function getActionFromKey(event) {
304
201
  const { key, altKey, ctrlKey, metaKey } = event;
305
- const openKeys = ['ArrowDown', 'ArrowUp', 'Enter', ' '];
306
- if (!menuOpen && openKeys.includes(key)) {
307
- return MenuActions.Open;
308
- }
309
- if (key === 'Home') {
310
- return MenuActions.First;
311
- }
312
- if (key === 'End') {
313
- return MenuActions.Last;
314
- }
202
+ if (key === 'Escape')
203
+ return MenuAction.Close;
204
+ if (key === 'Enter' || key === ' ')
205
+ return MenuAction.CloseSelect;
206
+ if (key === 'Home')
207
+ return MenuAction.First;
208
+ if (key === 'End')
209
+ return MenuAction.Last;
210
+ if (key === 'PageUp')
211
+ return MenuAction.PageUp;
212
+ if (key === 'PageDown')
213
+ return MenuAction.PageDown;
214
+ if (key === 'ArrowUp' && altKey)
215
+ return MenuAction.CloseSelect;
216
+ if (key === 'ArrowDown' && !altKey)
217
+ return MenuAction.Next;
218
+ if (key === 'ArrowUp' && !altKey)
219
+ return MenuAction.Previous;
315
220
  if (key === 'Backspace' ||
316
221
  key === 'Clear' ||
317
222
  (key.length === 1 && key !== ' ' && !altKey && !ctrlKey && !metaKey)) {
318
- return MenuActions.Type;
223
+ return MenuAction.Type;
319
224
  }
320
- if (menuOpen) {
321
- if (key === 'ArrowUp' && altKey) {
322
- return MenuActions.CloseSelect;
323
- }
324
- else if (key === 'ArrowDown' && !altKey) {
325
- return MenuActions.Next;
326
- }
327
- else if (key === 'ArrowUp') {
328
- return MenuActions.Previous;
329
- }
330
- else if (key === 'PageUp') {
331
- return MenuActions.PageUp;
332
- }
333
- else if (key === 'PageDown') {
334
- return MenuActions.PageDown;
335
- }
336
- else if (key === 'Escape') {
337
- return MenuActions.Close;
225
+ return null;
226
+ }
227
+ export function getVisibleItems(container, items) {
228
+ if (!items.length)
229
+ return { first: 0, last: 0, pageSize: 1 };
230
+ const containerRect = container.getBoundingClientRect();
231
+ const style = window.getComputedStyle(container);
232
+ const paddingTop = parseFloat(style.scrollPaddingTop) || 0;
233
+ const paddingBottom = parseFloat(style.scrollPaddingBottom) || 0;
234
+ const top = containerRect.top + container.clientTop + paddingTop;
235
+ const bottom = top + container.clientHeight - paddingTop - paddingBottom;
236
+ let first = -1;
237
+ let last = -1;
238
+ for (let i = 0; i < items.length; i++) {
239
+ const itemRect = items[i].getBoundingClientRect();
240
+ if (itemRect.top < bottom && itemRect.bottom > top) {
241
+ if (first === -1)
242
+ first = i;
243
+ last = i;
338
244
  }
339
- else if (key === 'Enter' || key === ' ') {
340
- return MenuActions.CloseSelect;
245
+ else if (first !== -1) {
246
+ break;
341
247
  }
342
248
  }
343
- return undefined;
344
- }
345
- export function getUpdatedIndex(currentIndex, maxIndex, action) {
346
- const pageSize = 10;
347
- switch (action) {
348
- case MenuActions.First:
349
- return 0;
350
- case MenuActions.Last:
351
- return maxIndex;
352
- case MenuActions.Previous:
353
- return Math.max(0, currentIndex - 1);
354
- case MenuActions.Next:
355
- return Math.min(maxIndex, currentIndex + 1);
356
- case MenuActions.PageUp:
357
- return Math.max(0, currentIndex - pageSize);
358
- case MenuActions.PageDown:
359
- return Math.min(maxIndex, currentIndex + pageSize);
360
- default:
361
- return currentIndex;
362
- }
363
- }
364
- export function scrollItemIntoView(menu, item, paddingY = 0) {
365
- if (!menu)
366
- return;
367
- // Basic scroll into view logic
368
- const menuRect = menu.getBoundingClientRect();
369
- const itemRect = item.getBoundingClientRect();
370
- if (itemRect.bottom + paddingY > menuRect.bottom) {
371
- menu.scrollTop += itemRect.bottom - menuRect.bottom + paddingY;
372
- }
373
- else if (itemRect.top - paddingY < menuRect.top) {
374
- menu.scrollTop -= menuRect.top - itemRect.top + paddingY;
249
+ if (first === -1) {
250
+ return { first: 0, last: 0, pageSize: 1 };
375
251
  }
252
+ // pageSize is the span of visible items
253
+ return { first, last, pageSize: Math.max(1, last - first + 1) };
376
254
  }
@@ -1,27 +1,23 @@
1
1
  /**
2
2
  * NOTE: demos at dev/pending.html
3
- * NOTE: should apply writing style on event listeners to other files
4
- * TODO: consider removing popoverController, but in case of tooltip, reserve
5
- * for now
6
3
  * TODO: hijack all `hidePopover()`s to allow more complex JS animations
7
- * TODO: simpler Menu that just inherits List - do we really need a separated
8
- * Menu? Why not just `<md-list role="presentation">`?
9
- *
10
- * Design: TO BE ADDED
11
4
  */
12
5
  import { LitElement, PropertyValues } from 'lit';
13
6
  import { Placement, Strategy } from '@floating-ui/dom';
14
- import { autoAttachToParent, handleControlChange } from '../mixins/attachable.js';
15
- export declare function lookForTabbable(root: Node): HTMLElement | null;
16
- declare const Popup_base: Constructor<import("../mixins/attachable.js").AttachableInterface> & Constructor<import("../mixins/internals-attached.js").InternalsAttachedInterface> & typeof LitElement;
7
+ import { autoAttachToParent, handleControlChange } from './mixins/attachable.js';
8
+ declare const Popup_base: Constructor<import("./mixins/attachable.js").AttachableInterface> & Constructor<import("./mixins/internals-attached.js").InternalsAttachedInterface> & typeof LitElement;
9
+ /**
10
+ * When using popup with menu, you need to manually bind them using `aria-controls`
11
+ */
17
12
  export declare class Popup extends Popup_base {
18
13
  #private;
19
14
  open: boolean;
20
15
  noFocusControl: boolean;
21
- placement: Placement;
16
+ align: Placement;
22
17
  strategy: Strategy;
23
18
  offset: number;
24
19
  windowPadding: number;
20
+ $ariaControl: HTMLElement | null;
25
21
  static styles: any[];
26
22
  render(): import("lit-html").TemplateResult<1>;
27
23
  constructor();