@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.
- package/README.md +3 -3
- package/custom-elements.json +604 -359
- package/package.json +1 -1
- package/src/all.d.ts +2 -2
- package/src/all.js +2 -2
- package/src/base/autocomplete.d.ts +9 -33
- package/src/base/autocomplete.js +42 -65
- package/src/base/button.d.ts +0 -1
- package/src/base/button.js +7 -11
- package/src/base/checkbox.d.ts +0 -2
- package/src/base/checkbox.js +6 -13
- package/src/base/controllers/list-controller.d.ts +1 -0
- package/src/base/controllers/list-controller.js +6 -1
- package/src/base/dialog.d.ts +2 -2
- package/src/base/dialog.js +11 -2
- package/src/base/list-item.d.ts +1 -1
- package/src/base/list-item.js +11 -12
- package/src/base/list.d.ts +16 -33
- package/src/base/list.js +91 -173
- package/src/base/menu.d.ts +41 -61
- package/src/base/menu.js +184 -306
- package/src/base/{experimental/popup.d.ts → popup.d.ts} +7 -11
- package/src/base/{experimental/popup.js → popup.js} +45 -90
- package/src/base/radio.js +5 -5
- package/src/base/select.d.ts +7 -53
- package/src/base/select.js +45 -79
- package/src/base/tooltip.js +2 -2
- package/src/core/focus.d.ts +11 -0
- package/src/core/focus.js +53 -0
- package/src/m3/autocomplete/autocomplete-styles.css.js +1 -1
- package/src/m3/autocomplete/autocomplete.d.ts +1 -0
- package/src/m3/autocomplete/autocomplete.js +2 -1
- package/src/m3/button/common-button-styles.css.js +1 -1
- package/src/m3/button/icon-button-styles.css.js +1 -1
- package/src/m3/button/icon-button-toggle.js +1 -1
- package/src/m3/button/shared-button-styles.css.js +1 -1
- package/src/m3/button-group/standard-button-group.js +1 -1
- package/src/m3/checkbox/checkbox.js +1 -1
- package/src/m3/dialog/dialog.d.ts +2 -2
- package/src/m3/dialog/dialog.js +2 -2
- package/src/m3/field/field-styles.css.js +1 -1
- package/src/m3/field/field.js +2 -2
- package/src/m3/field/filled-field-styles.css.js +1 -1
- package/src/m3/field/filled-field.js +1 -1
- package/src/m3/field/outlined-field-styles.css.js +1 -1
- package/src/m3/focus-ring/focus-ring.js +1 -1
- package/src/m3/list/list-item.d.ts +2 -2
- package/src/m3/list/list-item.js +6 -6
- package/src/m3/menu/composed-menu.d.ts +19 -0
- package/src/m3/menu/composed-menu.js +42 -0
- package/src/m3/menu/menu-styles.css.js +1 -1
- package/src/m3/menu/menu.d.ts +1 -7
- package/src/m3/menu/menu.js +1 -13
- package/src/m3/navigation/navigation-rail-item-styles.css.js +1 -1
- package/src/m3/popup/popup-styles.css.js +2 -0
- package/src/m3/{experimental → popup}/popup.d.ts +2 -1
- package/src/m3/popup/popup.js +18 -0
- package/src/m3/radio/radio.js +1 -1
- package/src/m3/ripple/ripple.js +3 -3
- package/src/m3/select/filled-select.d.ts +2 -2
- package/src/m3/select/filled-select.js +5 -5
- package/src/m3/select/outlined-select.d.ts +2 -2
- package/src/m3/select/outlined-select.js +5 -5
- package/src/m3/select/select-styles.css.js +1 -1
- package/src/m3/select/select.d.ts +2 -5
- package/src/m3/select/select.js +3 -6
- package/src/m3/styles/color-default-dark.css +51 -0
- package/src/m3/styles/color-default-light.css +51 -0
- package/src/m3/switch/switch-styles.css.js +1 -1
- package/src/m3/switch/switch.js +1 -1
- package/src/m3/tab/tabs-styles.css.js +1 -1
- package/src/m3/tab/tabs.js +1 -1
- package/src/m3/text-field/filled-text-field.js +2 -2
- package/src/m3/text-field/outlined-text-field.js +2 -2
- package/src/m3/tooltip/tooltip-styles.css.js +1 -1
- package/src/core/focus-visible.d.ts +0 -2
- package/src/core/focus-visible.js +0 -13
- package/src/m3/experimental/popup-styles.css.js +0 -2
- package/src/m3/experimental/popup.js +0 -11
- /package/src/base/{experimental/popup-styles.css.js → popup-styles.css.js} +0 -0
|
@@ -1,68 +1,20 @@
|
|
|
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
|
var _a;
|
|
13
6
|
import { __decorate } from "tslib";
|
|
14
7
|
import { LitElement, html, isServer } from 'lit';
|
|
15
8
|
import { customElement, property } from 'lit/decorators.js';
|
|
16
9
|
import { autoUpdate, computePosition, arrow, flip, offset, shift, } from '@floating-ui/dom';
|
|
17
|
-
import {
|
|
18
|
-
import {
|
|
19
|
-
import {
|
|
10
|
+
import { getFirstTabbable } from '../core/focus.js';
|
|
11
|
+
import { InternalsAttached } from './mixins/internals-attached.js';
|
|
12
|
+
import { Attachable, autoAttachToParent, handleControlChange, } from './mixins/attachable.js';
|
|
13
|
+
import { transformOriginFromArrow } from './controllers/popover-controller.js';
|
|
20
14
|
import { popupStyles } from './popup-styles.css.js';
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
export function lookForTabbable(root) {
|
|
25
|
-
const FOCUSABLE_SELECTOR = [
|
|
26
|
-
'a[href]',
|
|
27
|
-
'button',
|
|
28
|
-
'input',
|
|
29
|
-
'textarea',
|
|
30
|
-
'select',
|
|
31
|
-
'summary',
|
|
32
|
-
'[tabindex]',
|
|
33
|
-
'[contenteditable=true]',
|
|
34
|
-
].join(',');
|
|
35
|
-
const isTabbable = (el) => el.matches(FOCUSABLE_SELECTOR) &&
|
|
36
|
-
!el.hasAttribute('disabled') &&
|
|
37
|
-
el.getAttribute('tabindex') !== '-1';
|
|
38
|
-
if ('shadowRoot' in root && root.shadowRoot) {
|
|
39
|
-
const shadowResult = lookForTabbable(root.shadowRoot);
|
|
40
|
-
if (shadowResult)
|
|
41
|
-
return shadowResult;
|
|
42
|
-
}
|
|
43
|
-
const walker = document.createTreeWalker(root, NodeFilter.SHOW_ELEMENT, {
|
|
44
|
-
acceptNode: (node) => {
|
|
45
|
-
const el = node;
|
|
46
|
-
return isTabbable(el) || el.shadowRoot
|
|
47
|
-
? NodeFilter.FILTER_ACCEPT
|
|
48
|
-
: NodeFilter.FILTER_SKIP;
|
|
49
|
-
},
|
|
50
|
-
});
|
|
51
|
-
let currentNode = walker.nextNode();
|
|
52
|
-
while (currentNode) {
|
|
53
|
-
if (isTabbable(currentNode)) {
|
|
54
|
-
return currentNode;
|
|
55
|
-
}
|
|
56
|
-
if (currentNode.shadowRoot) {
|
|
57
|
-
const shadowFocusable = lookForTabbable(currentNode.shadowRoot);
|
|
58
|
-
if (shadowFocusable) {
|
|
59
|
-
return shadowFocusable;
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
currentNode = walker.nextNode();
|
|
63
|
-
}
|
|
64
|
-
return null;
|
|
65
|
-
}
|
|
15
|
+
/**
|
|
16
|
+
* When using popup with menu, you need to manually bind them using `aria-controls`
|
|
17
|
+
*/
|
|
66
18
|
let Popup = class Popup extends Attachable(InternalsAttached(LitElement)) {
|
|
67
19
|
static { this.styles = [popupStyles]; }
|
|
68
20
|
render() {
|
|
@@ -72,16 +24,16 @@ let Popup = class Popup extends Attachable(InternalsAttached(LitElement)) {
|
|
|
72
24
|
super();
|
|
73
25
|
this.open = false;
|
|
74
26
|
this.noFocusControl = false;
|
|
75
|
-
this.
|
|
27
|
+
this.align = 'bottom-start';
|
|
76
28
|
this.strategy = 'absolute';
|
|
77
29
|
this.offset = 0;
|
|
78
30
|
this.windowPadding = 8;
|
|
79
31
|
this[_a] = false;
|
|
80
|
-
this.#
|
|
32
|
+
this.#handleRequestHide = () => {
|
|
81
33
|
this.hide();
|
|
82
34
|
};
|
|
83
35
|
// #allowHidePopover = false;
|
|
84
|
-
// #
|
|
36
|
+
// #handleBeforeToggle = (e: ToggleEvent) => {
|
|
85
37
|
// // Intercept browser light-dismiss (ESC, click outside) or external hidePopover() calls
|
|
86
38
|
// if (e.newState === 'closed' && !this.#allowHidePopover) {
|
|
87
39
|
// e.preventDefault();
|
|
@@ -90,12 +42,12 @@ let Popup = class Popup extends Attachable(InternalsAttached(LitElement)) {
|
|
|
90
42
|
// }
|
|
91
43
|
// }
|
|
92
44
|
// };
|
|
93
|
-
this.#
|
|
45
|
+
this.#handleToggle = (e) => {
|
|
94
46
|
e.preventDefault();
|
|
95
47
|
if (e.newState === 'closed' && this.open)
|
|
96
48
|
this.hide();
|
|
97
49
|
};
|
|
98
|
-
this.#
|
|
50
|
+
this.#handleFocusOut = (e) => {
|
|
99
51
|
// for `this` and `this.$control`
|
|
100
52
|
if (this.noFocusControl || !this.open)
|
|
101
53
|
return;
|
|
@@ -112,20 +64,20 @@ let Popup = class Popup extends Attachable(InternalsAttached(LitElement)) {
|
|
|
112
64
|
this.hide();
|
|
113
65
|
});
|
|
114
66
|
};
|
|
115
|
-
this.#
|
|
67
|
+
this.#handleTriggerClick = (e) => {
|
|
116
68
|
e.preventDefault();
|
|
117
69
|
this.toggle();
|
|
118
70
|
};
|
|
119
71
|
this.#dummyArrow = isServer ? null : document.createElement('div');
|
|
72
|
+
this.setAttribute('notransition', '');
|
|
120
73
|
if (!this.hasAttribute('popover'))
|
|
121
74
|
this.setAttribute('popover', 'auto');
|
|
122
75
|
if (!isServer) {
|
|
123
|
-
this.addEventListener('request-popup-hide', this.#
|
|
124
|
-
// this.addEventListener('beforetoggle', this.#
|
|
125
|
-
this.addEventListener('toggle', this.#
|
|
126
|
-
this.addEventListener('focusout', this.#
|
|
76
|
+
this.addEventListener('request-popup-hide', this.#handleRequestHide);
|
|
77
|
+
// this.addEventListener('beforetoggle', this.#handleBeforeToggle);
|
|
78
|
+
this.addEventListener('toggle', this.#handleToggle);
|
|
79
|
+
this.addEventListener('focusout', this.#handleFocusOut);
|
|
127
80
|
}
|
|
128
|
-
this.setAttribute('notransition', '');
|
|
129
81
|
}
|
|
130
82
|
connectedCallback() {
|
|
131
83
|
super.connectedCallback();
|
|
@@ -137,29 +89,30 @@ let Popup = class Popup extends Attachable(InternalsAttached(LitElement)) {
|
|
|
137
89
|
}
|
|
138
90
|
updated(changedProperties) {
|
|
139
91
|
if (changedProperties.has('open')) {
|
|
92
|
+
this.#syncTriggerAria();
|
|
140
93
|
if (this.open)
|
|
141
94
|
this._show();
|
|
142
95
|
else
|
|
143
96
|
this._hide();
|
|
144
|
-
this.#syncTriggerAria();
|
|
145
97
|
}
|
|
146
98
|
}
|
|
147
99
|
[(_a = autoAttachToParent, handleControlChange)](prev, next) {
|
|
148
100
|
if (prev) {
|
|
149
|
-
prev.removeEventListener('click', this.#
|
|
150
|
-
prev.removeEventListener('focusout', this.#
|
|
101
|
+
prev.removeEventListener('click', this.#handleTriggerClick);
|
|
102
|
+
prev.removeEventListener('focusout', this.#handleFocusOut);
|
|
151
103
|
}
|
|
152
104
|
if (next) {
|
|
153
|
-
next.addEventListener('click', this.#
|
|
154
|
-
next.addEventListener('focusout', this.#
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
105
|
+
next.addEventListener('click', this.#handleTriggerClick);
|
|
106
|
+
next.addEventListener('focusout', this.#handleFocusOut);
|
|
107
|
+
const ariaNext = this.$ariaControl ? this.$ariaControl : next;
|
|
108
|
+
if (!next.ariaHasPopup)
|
|
109
|
+
ariaNext.ariaHasPopup = 'true';
|
|
110
|
+
ariaNext.ariaExpanded = String(this.open);
|
|
158
111
|
}
|
|
159
112
|
}
|
|
160
|
-
#
|
|
113
|
+
#handleRequestHide;
|
|
161
114
|
// #allowHidePopover = false;
|
|
162
|
-
// #
|
|
115
|
+
// #handleBeforeToggle = (e: ToggleEvent) => {
|
|
163
116
|
// // Intercept browser light-dismiss (ESC, click outside) or external hidePopover() calls
|
|
164
117
|
// if (e.newState === 'closed' && !this.#allowHidePopover) {
|
|
165
118
|
// e.preventDefault();
|
|
@@ -168,23 +121,25 @@ let Popup = class Popup extends Attachable(InternalsAttached(LitElement)) {
|
|
|
168
121
|
// }
|
|
169
122
|
// }
|
|
170
123
|
// };
|
|
171
|
-
#
|
|
172
|
-
#
|
|
173
|
-
#
|
|
124
|
+
#handleToggle;
|
|
125
|
+
#handleFocusOut;
|
|
126
|
+
#handleTriggerClick;
|
|
174
127
|
#syncTriggerAria() {
|
|
175
|
-
|
|
128
|
+
if (this.$ariaControl)
|
|
129
|
+
this.$ariaControl.ariaExpanded = String(this.open);
|
|
130
|
+
else if (this.$control)
|
|
131
|
+
this.$control.ariaExpanded = String(this.open);
|
|
176
132
|
}
|
|
177
133
|
#focusFirstInteractiveElement() {
|
|
178
134
|
if (this.noFocusControl)
|
|
179
135
|
return;
|
|
180
|
-
const
|
|
181
|
-
if (
|
|
182
|
-
|
|
183
|
-
return;
|
|
136
|
+
const autoFocus = this.querySelector('[autofocus]');
|
|
137
|
+
if (autoFocus) {
|
|
138
|
+
autoFocus.focus();
|
|
184
139
|
}
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
140
|
+
else {
|
|
141
|
+
const firstTabbable = getFirstTabbable(this);
|
|
142
|
+
firstTabbable?.focus();
|
|
188
143
|
}
|
|
189
144
|
}
|
|
190
145
|
async toggle() {
|
|
@@ -234,7 +189,7 @@ let Popup = class Popup extends Attachable(InternalsAttached(LitElement)) {
|
|
|
234
189
|
if (!trigger)
|
|
235
190
|
return;
|
|
236
191
|
const { x, y, placement, middlewareData } = await computePosition(trigger, this, {
|
|
237
|
-
placement: this.
|
|
192
|
+
placement: this.align,
|
|
238
193
|
strategy: this.strategy,
|
|
239
194
|
middleware: [
|
|
240
195
|
offset(this.offset),
|
|
@@ -262,7 +217,7 @@ __decorate([
|
|
|
262
217
|
], Popup.prototype, "noFocusControl", void 0);
|
|
263
218
|
__decorate([
|
|
264
219
|
property({ type: String })
|
|
265
|
-
], Popup.prototype, "
|
|
220
|
+
], Popup.prototype, "align", void 0);
|
|
266
221
|
__decorate([
|
|
267
222
|
property({ type: String })
|
|
268
223
|
], Popup.prototype, "strategy", void 0);
|
|
@@ -270,7 +225,7 @@ __decorate([
|
|
|
270
225
|
property({ type: Number })
|
|
271
226
|
], Popup.prototype, "offset", void 0);
|
|
272
227
|
__decorate([
|
|
273
|
-
property({ type: Number })
|
|
228
|
+
property({ type: Number, attribute: 'window-padding' })
|
|
274
229
|
], Popup.prototype, "windowPadding", void 0);
|
|
275
230
|
Popup = __decorate([
|
|
276
231
|
customElement('complementary-popup')
|
package/src/base/radio.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
var _a;
|
|
2
2
|
import { __decorate } from "tslib";
|
|
3
|
-
import { LitElement } from 'lit';
|
|
3
|
+
import { isServer, LitElement } from 'lit';
|
|
4
4
|
import { property } from 'lit/decorators.js';
|
|
5
5
|
import { FormAssociated } from './mixins/form-associated.js';
|
|
6
6
|
import { InternalsAttached, internals, replaceStates, updateInternals, } from './mixins/internals-attached.js';
|
|
@@ -15,17 +15,17 @@ export class Radio extends FormAssociated(InternalsAttached(LitElement)) {
|
|
|
15
15
|
this.#currentName = '';
|
|
16
16
|
this.checked = false;
|
|
17
17
|
this[internals].role = 'radio';
|
|
18
|
+
if (!isServer) {
|
|
19
|
+
this.addEventListener('click', this.#handleClick);
|
|
20
|
+
this.addEventListener('keydown', this.#handleKeyDown);
|
|
21
|
+
}
|
|
18
22
|
}
|
|
19
23
|
connectedCallback() {
|
|
20
24
|
super.connectedCallback();
|
|
21
25
|
this.#registerToGroup();
|
|
22
26
|
this[updateInternals]();
|
|
23
|
-
this.addEventListener('click', this.#handleClick);
|
|
24
|
-
this.addEventListener('keydown', this.#handleKeyDown);
|
|
25
27
|
}
|
|
26
28
|
disconnectedCallback() {
|
|
27
|
-
this.removeEventListener('click', this.#handleClick);
|
|
28
|
-
this.removeEventListener('keydown', this.#handleKeyDown);
|
|
29
29
|
this.#unregisterFromGroup();
|
|
30
30
|
super.disconnectedCallback();
|
|
31
31
|
}
|
package/src/base/select.d.ts
CHANGED
|
@@ -1,29 +1,24 @@
|
|
|
1
1
|
import { LitElement, PropertyValues } from 'lit';
|
|
2
|
-
import type { Placement, Strategy } from '@floating-ui/dom';
|
|
3
2
|
import type { Field } from './field.js';
|
|
4
|
-
import type { Menu, MenuItemFocusEvent, MenuSelectEvent } from './menu.js';
|
|
5
3
|
import type { Option } from './option.js';
|
|
4
|
+
import type { Popup } from './popup.js';
|
|
5
|
+
import type { Menu, MenuItemFocusEvent, MenuActionEvent } from './menu.js';
|
|
6
6
|
declare const VALUE: unique symbol;
|
|
7
7
|
declare const Base: Constructor<import("./mixins/form-associated.js").FormAssociatedInterface> & Constructor<import("./mixins/focus-delegated.js").FocusDelegatedInterface> & Constructor<import("./mixins/internals-attached.js").InternalsAttachedInterface> & typeof LitElement;
|
|
8
8
|
/**
|
|
9
9
|
* @csspart field
|
|
10
|
+
* @csspart popup
|
|
10
11
|
* @csspart menu
|
|
11
|
-
* @csspart items
|
|
12
12
|
*
|
|
13
13
|
* @fires {Event} change - Fires when the value is changed.
|
|
14
14
|
* @fires {Event} input - Fires when the value is changed.
|
|
15
15
|
*
|
|
16
16
|
* TODO: Render multiple values
|
|
17
|
-
* FIXME: `aria-expanded` set twice by both Lit and Menu
|
|
18
17
|
*/
|
|
19
18
|
export declare class Select extends Base {
|
|
20
19
|
#private;
|
|
21
20
|
required: boolean;
|
|
22
21
|
error: boolean;
|
|
23
|
-
quick: boolean;
|
|
24
|
-
offset: number;
|
|
25
|
-
align: Placement;
|
|
26
|
-
alignStrategy: Strategy;
|
|
27
22
|
open: boolean;
|
|
28
23
|
/**
|
|
29
24
|
* Text to display in the field. Only set for SSR.
|
|
@@ -37,6 +32,7 @@ export declare class Select extends Base {
|
|
|
37
32
|
set selectedIndex(index: number);
|
|
38
33
|
get selectedOptions(): Option[];
|
|
39
34
|
$field: Field;
|
|
35
|
+
$popup: Popup;
|
|
40
36
|
$menu: Menu;
|
|
41
37
|
private lastUserSetValue;
|
|
42
38
|
private lastUserSetSelectedIndex;
|
|
@@ -44,49 +40,11 @@ export declare class Select extends Base {
|
|
|
44
40
|
private lastSelectedOptionRecords;
|
|
45
41
|
render(): import("lit-html").TemplateResult<1>;
|
|
46
42
|
/**
|
|
47
|
-
*
|
|
48
|
-
*
|
|
49
|
-
* ```html
|
|
50
|
-
* <your-field
|
|
51
|
-
* id="field"
|
|
52
|
-
* part="field"
|
|
53
|
-
* @click=${this.toggle}
|
|
54
|
-
* @keydown=${this.handleFieldKeydown}
|
|
55
|
-
* tabindex=${this.disabled ? '-1' : '0'}
|
|
56
|
-
* role="combobox"
|
|
57
|
-
* aria-haspopup="listbox"
|
|
58
|
-
* aria-expanded=${this.open}
|
|
59
|
-
* aria-controls="menu"
|
|
60
|
-
* aria-disabled=${this.disabled}
|
|
61
|
-
* aria-required=${this.required}
|
|
62
|
-
* >
|
|
63
|
-
* ${this.renderFieldContent()}
|
|
64
|
-
* </your-field>
|
|
65
|
-
* ```
|
|
43
|
+
* Check example content from the m3 implementation
|
|
66
44
|
*/
|
|
67
45
|
protected renderField(): import("lit-html").TemplateResult<1>;
|
|
68
46
|
/**
|
|
69
|
-
*
|
|
70
|
-
*
|
|
71
|
-
* ```html
|
|
72
|
-
* <your-menu
|
|
73
|
-
* id="menu"
|
|
74
|
-
* for="field"
|
|
75
|
-
* type="listbox"
|
|
76
|
-
* data-tabindex="-1"
|
|
77
|
-
* ?quick="${this.quick}"
|
|
78
|
-
* offset="${this.offset}"
|
|
79
|
-
* align="${this.align}"
|
|
80
|
-
* align-strategy="${this.alignStrategy}"
|
|
81
|
-
* keep-open-blur
|
|
82
|
-
* no-focus-control
|
|
83
|
-
* ?open="${this.open}"
|
|
84
|
-
* @item-focus="${this.handleMenuItemFocus}"
|
|
85
|
-
* @select="${this.handleMenuSelect}"
|
|
86
|
-
* >
|
|
87
|
-
* <slot part="items" @slotchange=${this.handleSlotChange}></slot>
|
|
88
|
-
* </your-menu>
|
|
89
|
-
* ```
|
|
47
|
+
* Check example content from the m3 implementation
|
|
90
48
|
*/
|
|
91
49
|
protected renderMenu(): import("lit-html").TemplateResult<1>;
|
|
92
50
|
protected renderFieldContent(): import("lit-html").TemplateResult<1>;
|
|
@@ -94,13 +52,9 @@ export declare class Select extends Base {
|
|
|
94
52
|
protected firstUpdated(changed: PropertyValues<Select>): Promise<void>;
|
|
95
53
|
protected update(changed: PropertyValues<Select>): void;
|
|
96
54
|
protected updated(changed: PropertyValues): void;
|
|
97
|
-
/**
|
|
98
|
-
* TODO: Handle clear action
|
|
99
|
-
*/
|
|
100
55
|
protected _handleFieldKeydown(event: KeyboardEvent): void;
|
|
101
|
-
protected
|
|
56
|
+
protected _handleMenuAction(event: MenuActionEvent): void;
|
|
102
57
|
protected _handleMenuItemFocus(event: MenuItemFocusEvent): void;
|
|
103
|
-
protected _handleFocusOut(event: FocusEvent): void;
|
|
104
58
|
protected _handleSlotChange(): void;
|
|
105
59
|
private selectItem;
|
|
106
60
|
private initUserSelection;
|
package/src/base/select.js
CHANGED
|
@@ -5,18 +5,18 @@ import { property, query } from 'lit/decorators.js';
|
|
|
5
5
|
import { InternalsAttached } from './mixins/internals-attached.js';
|
|
6
6
|
import { FocusDelegated } from './mixins/focus-delegated.js';
|
|
7
7
|
import { FormAssociated } from './mixins/form-associated.js';
|
|
8
|
+
import { getActionFromKey, MenuAction } from './menu.js';
|
|
8
9
|
const VALUE = Symbol('value');
|
|
9
10
|
const Base = FormAssociated(FocusDelegated(InternalsAttached(LitElement)));
|
|
10
11
|
/**
|
|
11
12
|
* @csspart field
|
|
13
|
+
* @csspart popup
|
|
12
14
|
* @csspart menu
|
|
13
|
-
* @csspart items
|
|
14
15
|
*
|
|
15
16
|
* @fires {Event} change - Fires when the value is changed.
|
|
16
17
|
* @fires {Event} input - Fires when the value is changed.
|
|
17
18
|
*
|
|
18
19
|
* TODO: Render multiple values
|
|
19
|
-
* FIXME: `aria-expanded` set twice by both Lit and Menu
|
|
20
20
|
*/
|
|
21
21
|
export class Select extends Base {
|
|
22
22
|
static { _a = VALUE; }
|
|
@@ -50,51 +50,13 @@ export class Select extends Base {
|
|
|
50
50
|
return html `${this.renderField()}${this.renderMenu()}`;
|
|
51
51
|
}
|
|
52
52
|
/**
|
|
53
|
-
*
|
|
54
|
-
*
|
|
55
|
-
* ```html
|
|
56
|
-
* <your-field
|
|
57
|
-
* id="field"
|
|
58
|
-
* part="field"
|
|
59
|
-
* @click=${this.toggle}
|
|
60
|
-
* @keydown=${this.handleFieldKeydown}
|
|
61
|
-
* tabindex=${this.disabled ? '-1' : '0'}
|
|
62
|
-
* role="combobox"
|
|
63
|
-
* aria-haspopup="listbox"
|
|
64
|
-
* aria-expanded=${this.open}
|
|
65
|
-
* aria-controls="menu"
|
|
66
|
-
* aria-disabled=${this.disabled}
|
|
67
|
-
* aria-required=${this.required}
|
|
68
|
-
* >
|
|
69
|
-
* ${this.renderFieldContent()}
|
|
70
|
-
* </your-field>
|
|
71
|
-
* ```
|
|
53
|
+
* Check example content from the m3 implementation
|
|
72
54
|
*/
|
|
73
55
|
renderField() {
|
|
74
56
|
return html ``;
|
|
75
57
|
}
|
|
76
58
|
/**
|
|
77
|
-
*
|
|
78
|
-
*
|
|
79
|
-
* ```html
|
|
80
|
-
* <your-menu
|
|
81
|
-
* id="menu"
|
|
82
|
-
* for="field"
|
|
83
|
-
* type="listbox"
|
|
84
|
-
* data-tabindex="-1"
|
|
85
|
-
* ?quick="${this.quick}"
|
|
86
|
-
* offset="${this.offset}"
|
|
87
|
-
* align="${this.align}"
|
|
88
|
-
* align-strategy="${this.alignStrategy}"
|
|
89
|
-
* keep-open-blur
|
|
90
|
-
* no-focus-control
|
|
91
|
-
* ?open="${this.open}"
|
|
92
|
-
* @item-focus="${this.handleMenuItemFocus}"
|
|
93
|
-
* @select="${this.handleMenuSelect}"
|
|
94
|
-
* >
|
|
95
|
-
* <slot part="items" @slotchange=${this.handleSlotChange}></slot>
|
|
96
|
-
* </your-menu>
|
|
97
|
-
* ```
|
|
59
|
+
* Check example content from the m3 implementation
|
|
98
60
|
*/
|
|
99
61
|
renderMenu() {
|
|
100
62
|
return html ``;
|
|
@@ -104,15 +66,8 @@ export class Select extends Base {
|
|
|
104
66
|
}
|
|
105
67
|
constructor() {
|
|
106
68
|
super();
|
|
107
|
-
// Form-related
|
|
108
69
|
this.required = false;
|
|
109
|
-
// Passed to field
|
|
110
70
|
this.error = false;
|
|
111
|
-
// Passed to menu
|
|
112
|
-
this.quick = false;
|
|
113
|
-
this.offset = 0;
|
|
114
|
-
this.align = 'bottom-start';
|
|
115
|
-
this.alignStrategy = 'absolute';
|
|
116
71
|
this.open = false;
|
|
117
72
|
/**
|
|
118
73
|
* Text to display in the field. Only set for SSR.
|
|
@@ -128,7 +83,7 @@ export class Select extends Base {
|
|
|
128
83
|
this.dispatchEvent(new Event('change', { bubbles: true }));
|
|
129
84
|
};
|
|
130
85
|
if (!isServer) {
|
|
131
|
-
this.addEventListener('focusout', this.
|
|
86
|
+
this.addEventListener('focusout', this.#handleFocusOut.bind(this));
|
|
132
87
|
}
|
|
133
88
|
}
|
|
134
89
|
async firstUpdated(changed) {
|
|
@@ -160,35 +115,55 @@ export class Select extends Base {
|
|
|
160
115
|
this.#focusSelectedItemOrFirst();
|
|
161
116
|
}
|
|
162
117
|
else {
|
|
163
|
-
this.$field.
|
|
118
|
+
this.$field.ariaActiveDescendantElement = null;
|
|
164
119
|
}
|
|
165
120
|
}
|
|
166
121
|
}
|
|
167
|
-
/**
|
|
168
|
-
* TODO: Handle clear action
|
|
169
|
-
*/
|
|
170
122
|
_handleFieldKeydown(event) {
|
|
171
123
|
if (this.disabled)
|
|
172
124
|
return;
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
125
|
+
if (!this.open) {
|
|
126
|
+
if (['Enter', ' ', 'ArrowDown', 'ArrowUp'].includes(event.key)) {
|
|
127
|
+
event.preventDefault();
|
|
128
|
+
this.open = true;
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
const action = getActionFromKey(event);
|
|
132
|
+
if (action === MenuAction.Type) {
|
|
133
|
+
this.open = true;
|
|
134
|
+
}
|
|
135
|
+
else {
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
if (this.open)
|
|
140
|
+
this.$menu.handleKeyDown(event);
|
|
177
141
|
}
|
|
178
|
-
|
|
142
|
+
_handleMenuAction(event) {
|
|
179
143
|
if (this.selectItem(event.detail.item)) {
|
|
180
144
|
this.#dispatchChangeEvent();
|
|
145
|
+
this.$field.focus();
|
|
146
|
+
// For Narrator, activating an option moves focus the document then
|
|
181
147
|
}
|
|
182
|
-
this.open = false;
|
|
183
148
|
}
|
|
184
149
|
_handleMenuItemFocus(event) {
|
|
185
|
-
this.$field.
|
|
150
|
+
this.$field.ariaActiveDescendantElement = event.detail.item;
|
|
186
151
|
}
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
152
|
+
#handleFocusOut(event) {
|
|
153
|
+
if (!this.open)
|
|
154
|
+
return;
|
|
155
|
+
const target = event.relatedTarget;
|
|
156
|
+
if (target && this.contains(target)) {
|
|
157
|
+
return;
|
|
191
158
|
}
|
|
159
|
+
if (target) {
|
|
160
|
+
this.$popup.hide();
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
requestAnimationFrame(() => {
|
|
164
|
+
if (!document.hasFocus())
|
|
165
|
+
this.$popup.hide();
|
|
166
|
+
});
|
|
192
167
|
}
|
|
193
168
|
_handleSlotChange() {
|
|
194
169
|
// When slots change, check for initially selected items if value is not set
|
|
@@ -298,18 +273,6 @@ __decorate([
|
|
|
298
273
|
__decorate([
|
|
299
274
|
property({ type: Boolean })
|
|
300
275
|
], Select.prototype, "error", void 0);
|
|
301
|
-
__decorate([
|
|
302
|
-
property({ type: Boolean })
|
|
303
|
-
], Select.prototype, "quick", void 0);
|
|
304
|
-
__decorate([
|
|
305
|
-
property({ type: Number })
|
|
306
|
-
], Select.prototype, "offset", void 0);
|
|
307
|
-
__decorate([
|
|
308
|
-
property({ reflect: true })
|
|
309
|
-
], Select.prototype, "align", void 0);
|
|
310
|
-
__decorate([
|
|
311
|
-
property({ type: String, reflect: true, attribute: 'align-strategy' })
|
|
312
|
-
], Select.prototype, "alignStrategy", void 0);
|
|
313
276
|
__decorate([
|
|
314
277
|
property({ type: Boolean })
|
|
315
278
|
], Select.prototype, "open", void 0);
|
|
@@ -323,8 +286,11 @@ __decorate([
|
|
|
323
286
|
property({ type: Number, attribute: 'selected-index' })
|
|
324
287
|
], Select.prototype, "selectedIndex", null);
|
|
325
288
|
__decorate([
|
|
326
|
-
query('[part=
|
|
289
|
+
query('[part=field]')
|
|
327
290
|
], Select.prototype, "$field", void 0);
|
|
328
291
|
__decorate([
|
|
329
|
-
query('[part=
|
|
292
|
+
query('[part=popup]')
|
|
293
|
+
], Select.prototype, "$popup", void 0);
|
|
294
|
+
__decorate([
|
|
295
|
+
query('[part=menu]')
|
|
330
296
|
], Select.prototype, "$menu", void 0);
|
package/src/base/tooltip.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { __decorate } from "tslib";
|
|
2
2
|
import { LitElement, html } from 'lit';
|
|
3
3
|
import { property, query } from 'lit/decorators.js';
|
|
4
|
-
import { focusVisible } from '../core/focus
|
|
4
|
+
import { focusVisible } from '../core/focus.js';
|
|
5
5
|
import { Attachable, handleControlChange } from './mixins/attachable.js';
|
|
6
6
|
import { InternalsAttached, internals } from './mixins/internals-attached.js';
|
|
7
7
|
import { PopoverController } from './controllers/popover-controller.js';
|
|
@@ -139,7 +139,7 @@ __decorate([
|
|
|
139
139
|
property({ type: Number, reflect: true })
|
|
140
140
|
], Tooltip.prototype, "offset", void 0);
|
|
141
141
|
__decorate([
|
|
142
|
-
property({ type: Boolean, reflect: true })
|
|
142
|
+
property({ type: Boolean, reflect: true, attribute: 'force-invisible' })
|
|
143
143
|
], Tooltip.prototype, "forceInvisible", void 0);
|
|
144
144
|
__decorate([
|
|
145
145
|
query('slot')
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export declare let focusVisible: boolean;
|
|
2
|
+
export declare function setFocusVisible(value: boolean): void;
|
|
3
|
+
/**
|
|
4
|
+
* Recursively walks the DOM tree, piercing Shadow DOM boundaries and
|
|
5
|
+
* resolving slots, to find the first tabbable element.
|
|
6
|
+
*
|
|
7
|
+
* TODO: consider use document.createTreeWalker which might be faster, yet more
|
|
8
|
+
* complex in code; may find references in the core/experimental/popup
|
|
9
|
+
* before the commit that added this function
|
|
10
|
+
*/
|
|
11
|
+
export declare function getFirstTabbable(root: Element | DocumentFragment): HTMLElement | null;
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { isServer } from 'lit';
|
|
2
|
+
export let focusVisible = false;
|
|
3
|
+
export function setFocusVisible(value) {
|
|
4
|
+
focusVisible = value;
|
|
5
|
+
}
|
|
6
|
+
if (!isServer) {
|
|
7
|
+
window.addEventListener('keydown', () => (focusVisible = true), {
|
|
8
|
+
capture: true,
|
|
9
|
+
});
|
|
10
|
+
window.addEventListener('mousedown', () => (focusVisible = false), {
|
|
11
|
+
capture: true,
|
|
12
|
+
});
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Recursively walks the DOM tree, piercing Shadow DOM boundaries and
|
|
16
|
+
* resolving slots, to find the first tabbable element.
|
|
17
|
+
*
|
|
18
|
+
* TODO: consider use document.createTreeWalker which might be faster, yet more
|
|
19
|
+
* complex in code; may find references in the core/experimental/popup
|
|
20
|
+
* before the commit that added this function
|
|
21
|
+
*/
|
|
22
|
+
export function getFirstTabbable(root) {
|
|
23
|
+
if (root.nodeName === 'SLOT') {
|
|
24
|
+
const assigned = root.assignedElements({
|
|
25
|
+
flatten: true,
|
|
26
|
+
});
|
|
27
|
+
for (const el of assigned) {
|
|
28
|
+
const found = getFirstTabbable(el);
|
|
29
|
+
if (found)
|
|
30
|
+
return found;
|
|
31
|
+
}
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
if (root instanceof HTMLElement) {
|
|
35
|
+
const isTabbable = root.tabIndex >= 0 &&
|
|
36
|
+
!root.hasAttribute('disabled') &&
|
|
37
|
+
!root.hidden &&
|
|
38
|
+
root.getAttribute('tabindex') !== '-1';
|
|
39
|
+
if (isTabbable)
|
|
40
|
+
return root;
|
|
41
|
+
}
|
|
42
|
+
if ('shadowRoot' in root && root.shadowRoot) {
|
|
43
|
+
const found = getFirstTabbable(root.shadowRoot);
|
|
44
|
+
if (found)
|
|
45
|
+
return found;
|
|
46
|
+
}
|
|
47
|
+
for (const child of root.children) {
|
|
48
|
+
const found = getFirstTabbable(child);
|
|
49
|
+
if (found)
|
|
50
|
+
return found;
|
|
51
|
+
}
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
import { css } from 'lit';
|
|
2
|
-
export const autocompleteStyles = css`:host{-webkit-user-select:none;user-select:none;min-width:210px;display:inline-block;position:relative}::slotted([slot=input]){cursor:pointer;outline:none;width:100%}[part=
|
|
2
|
+
export const autocompleteStyles = css`:host{-webkit-user-select:none;user-select:none;min-width:210px;display:inline-block;position:relative}::slotted([slot=input]){cursor:pointer;outline:none;width:100%}[part=popup]{--md-menu-max-height:300px;min-width:inherit}`;
|