@matter-server/dashboard 0.2.6 → 0.2.7-alpha.0-20260118-45c7af0
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 +16 -0
- package/dist/esm/client/models/descriptions.js +1754 -1754
- package/dist/esm/components/dialogs/binding/node-binding-dialog.d.ts.map +1 -1
- package/dist/esm/components/dialogs/binding/node-binding-dialog.js +8 -4
- package/dist/esm/components/dialogs/binding/node-binding-dialog.js.map +1 -1
- package/dist/esm/components/dialogs/settings/log-level-dialog.d.ts +33 -0
- package/dist/esm/components/dialogs/settings/log-level-dialog.d.ts.map +1 -0
- package/dist/esm/components/dialogs/settings/log-level-dialog.js +185 -0
- package/dist/esm/components/dialogs/settings/log-level-dialog.js.map +6 -0
- package/dist/esm/components/dialogs/settings/show-log-level-dialog.d.ts +8 -0
- package/dist/esm/components/dialogs/settings/show-log-level-dialog.d.ts.map +1 -0
- package/dist/esm/components/dialogs/settings/show-log-level-dialog.js +15 -0
- package/dist/esm/components/dialogs/settings/show-log-level-dialog.js.map +6 -0
- package/dist/esm/entrypoint/main.d.ts +1 -1
- package/dist/esm/entrypoint/main.d.ts.map +1 -1
- package/dist/esm/entrypoint/main.js +1 -0
- package/dist/esm/entrypoint/main.js.map +1 -1
- package/dist/esm/pages/components/header.d.ts +9 -0
- package/dist/esm/pages/components/header.d.ts.map +1 -1
- package/dist/esm/pages/components/header.js +68 -6
- package/dist/esm/pages/components/header.js.map +1 -1
- package/dist/esm/pages/matter-dashboard-app.d.ts +6 -1
- package/dist/esm/pages/matter-dashboard-app.d.ts.map +1 -1
- package/dist/esm/pages/matter-dashboard-app.js +119 -24
- package/dist/esm/pages/matter-dashboard-app.js.map +1 -1
- package/dist/esm/util/theme-service.d.ts +27 -0
- package/dist/esm/util/theme-service.d.ts.map +1 -0
- package/dist/esm/util/theme-service.js +71 -0
- package/dist/esm/util/theme-service.js.map +6 -0
- package/dist/web/index.html +35 -0
- package/dist/web/js/{commission-node-dialog--19-sX9D.js → commission-node-dialog-CoaDIV2Y.js} +5 -5
- package/dist/web/js/{commission-node-existing-DY6SnsHb.js → commission-node-existing-DEU_mJjO.js} +5 -4
- package/dist/web/js/{commission-node-thread-CXquVvK5.js → commission-node-thread-DZ6DghSs.js} +5 -4
- package/dist/web/js/{commission-node-wifi-VQGVOrr7.js → commission-node-wifi-DOyin0q3.js} +5 -4
- package/dist/web/js/{dialog-box-qX-alVZJ.js → dialog-box-B5sunUPv.js} +2 -2
- package/dist/web/js/{fire_event-B13DcOc9.js → fire_event-C9Duc1j-.js} +1 -1
- package/dist/web/js/log-level-dialog-B7LsZYUL.js +3232 -0
- package/dist/web/js/main.js +163 -8
- package/dist/web/js/{matter-dashboard-app-CU3-L2nl.js → matter-dashboard-app-DlHSE_Qh.js} +13253 -13039
- package/dist/web/js/{node-binding-dialog-D4rr_G9I.js → node-binding-dialog-BifZsigR.js} +12 -7
- package/dist/web/js/outlined-text-field-D2BOt1yD.js +968 -0
- package/dist/web/js/{prevent_default-Dw7ifAL-.js → prevent_default-CuW2EnKR.js} +1 -1
- package/dist/web/js/validator-MOJiFndw.js +1122 -0
- package/package.json +4 -4
- package/src/client/models/descriptions.ts +1754 -1754
- package/src/components/dialogs/binding/node-binding-dialog.ts +8 -4
- package/src/components/dialogs/settings/log-level-dialog.ts +179 -0
- package/src/components/dialogs/settings/show-log-level-dialog.ts +14 -0
- package/src/entrypoint/main.ts +1 -0
- package/src/pages/components/header.ts +72 -8
- package/src/pages/matter-dashboard-app.ts +123 -26
- package/src/util/theme-service.ts +98 -0
- package/dist/web/js/outlined-text-field-CtlEkpbk.js +0 -2086
|
@@ -0,0 +1,3232 @@
|
|
|
1
|
+
import { e, N as NavigableKeys, _ as __decorate, a as e$1, n as n$1, o, r, i, c as createAnimationSignal, L as ListController, g as getActiveItem, b as getLastActivatableItem, d as getFirstActivatableItem, f as e$2, A, h as b, E as EASING, j as i$1, t, D, m as mixinDelegatesAria, k as mixinElementInternals, u, l as i$2 } from './matter-dashboard-app-DlHSE_Qh.js';
|
|
2
|
+
import { r as redispatchEvent, p as preventDefault } from './prevent_default-CuW2EnKR.js';
|
|
3
|
+
import { o as o$1, V as Validator, m as mixinOnReportValidity, a as mixinConstraintValidation, b as mixinFormAssociated, c as onReportValidity, g as getFormValue, d as createValidator, e as getValidityAnchor } from './validator-MOJiFndw.js';
|
|
4
|
+
import './main.js';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* @license
|
|
8
|
+
* Copyright 2017 Google LLC
|
|
9
|
+
* SPDX-License-Identifier: BSD-3-Clause
|
|
10
|
+
*/
|
|
11
|
+
function n(n) {
|
|
12
|
+
return (o, r) => {
|
|
13
|
+
const {
|
|
14
|
+
slot: e$1
|
|
15
|
+
} = n ?? {},
|
|
16
|
+
s = "slot" + (e$1 ? `[name=${e$1}]` : ":not([name])");
|
|
17
|
+
return e(o, r, {
|
|
18
|
+
get() {
|
|
19
|
+
var _this$renderRoot;
|
|
20
|
+
const t = (_this$renderRoot = this.renderRoot) === null || _this$renderRoot === void 0 ? void 0 : _this$renderRoot.querySelector(s);
|
|
21
|
+
return (t === null || t === void 0 ? void 0 : t.assignedNodes(n)) ?? [];
|
|
22
|
+
}
|
|
23
|
+
});
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* @license
|
|
29
|
+
* Copyright 2023 Google LLC
|
|
30
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
31
|
+
*/
|
|
32
|
+
/**
|
|
33
|
+
* Creates an event that closes any parent menus.
|
|
34
|
+
*/
|
|
35
|
+
function createCloseMenuEvent(initiator, reason) {
|
|
36
|
+
return new CustomEvent('close-menu', {
|
|
37
|
+
bubbles: true,
|
|
38
|
+
composed: true,
|
|
39
|
+
detail: {
|
|
40
|
+
initiator,
|
|
41
|
+
reason,
|
|
42
|
+
itemPath: [initiator]
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Creates a default close menu event used by md-menu.
|
|
48
|
+
*/
|
|
49
|
+
const createDefaultCloseMenuEvent = createCloseMenuEvent;
|
|
50
|
+
/**
|
|
51
|
+
* Keys that are used for selection in menus.
|
|
52
|
+
*/
|
|
53
|
+
// tslint:disable-next-line:enforce-name-casing We are mimicking enum style
|
|
54
|
+
const SelectionKey = {
|
|
55
|
+
SPACE: 'Space',
|
|
56
|
+
ENTER: 'Enter'
|
|
57
|
+
};
|
|
58
|
+
/**
|
|
59
|
+
* Default close `Reason` kind values.
|
|
60
|
+
*/
|
|
61
|
+
// tslint:disable-next-line:enforce-name-casing We are mimicking enum style
|
|
62
|
+
const CloseReason = {
|
|
63
|
+
CLICK_SELECTION: 'click-selection',
|
|
64
|
+
KEYDOWN: 'keydown'
|
|
65
|
+
};
|
|
66
|
+
/**
|
|
67
|
+
* Keys that can close menus.
|
|
68
|
+
*/
|
|
69
|
+
// tslint:disable-next-line:enforce-name-casing We are mimicking enum style
|
|
70
|
+
const KeydownCloseKey = {
|
|
71
|
+
ESCAPE: 'Escape',
|
|
72
|
+
SPACE: SelectionKey.SPACE,
|
|
73
|
+
ENTER: SelectionKey.ENTER
|
|
74
|
+
};
|
|
75
|
+
/**
|
|
76
|
+
* Determines whether the given key code is a key code that should close the
|
|
77
|
+
* menu.
|
|
78
|
+
*
|
|
79
|
+
* @param code The KeyboardEvent code to check.
|
|
80
|
+
* @return Whether or not the key code is in the predetermined list to close the
|
|
81
|
+
* menu.
|
|
82
|
+
*/
|
|
83
|
+
function isClosableKey(code) {
|
|
84
|
+
return Object.values(KeydownCloseKey).some(value => value === code);
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Determines whether the given key code is a key code that should select a menu
|
|
88
|
+
* item.
|
|
89
|
+
*
|
|
90
|
+
* @param code They KeyboardEvent code to check.
|
|
91
|
+
* @return Whether or not the key code is in the predetermined list to select a
|
|
92
|
+
* menu item.
|
|
93
|
+
*/
|
|
94
|
+
function isSelectableKey(code) {
|
|
95
|
+
return Object.values(SelectionKey).some(value => value === code);
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Determines whether a target element is contained inside another element's
|
|
99
|
+
* composed tree.
|
|
100
|
+
*
|
|
101
|
+
* @param target The potential contained element.
|
|
102
|
+
* @param container The potential containing element of the target.
|
|
103
|
+
* @returns Whether the target element is contained inside the container's
|
|
104
|
+
* composed subtree
|
|
105
|
+
*/
|
|
106
|
+
function isElementInSubtree(target, container) {
|
|
107
|
+
// Dispatch a composed, bubbling event to check its path to see if the
|
|
108
|
+
// newly-focused element is contained in container's subtree
|
|
109
|
+
const focusEv = new Event('md-contains', {
|
|
110
|
+
bubbles: true,
|
|
111
|
+
composed: true
|
|
112
|
+
});
|
|
113
|
+
let composedPath = [];
|
|
114
|
+
const listener = ev => {
|
|
115
|
+
composedPath = ev.composedPath();
|
|
116
|
+
};
|
|
117
|
+
container.addEventListener('md-contains', listener);
|
|
118
|
+
target.dispatchEvent(focusEv);
|
|
119
|
+
container.removeEventListener('md-contains', listener);
|
|
120
|
+
const isContained = composedPath.length > 0;
|
|
121
|
+
return isContained;
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Element to focus on when menu is first opened.
|
|
125
|
+
*/
|
|
126
|
+
// tslint:disable-next-line:enforce-name-casing We are mimicking enum style
|
|
127
|
+
const FocusState = {
|
|
128
|
+
NONE: 'none',
|
|
129
|
+
LIST_ROOT: 'list-root',
|
|
130
|
+
FIRST_ITEM: 'first-item',
|
|
131
|
+
LAST_ITEM: 'last-item'
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* @license
|
|
136
|
+
* Copyright 2023 Google LLC
|
|
137
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
138
|
+
*/
|
|
139
|
+
/**
|
|
140
|
+
* An enum of supported Menu corners
|
|
141
|
+
*/
|
|
142
|
+
// tslint:disable-next-line:enforce-name-casing We are mimicking enum style
|
|
143
|
+
const Corner = {
|
|
144
|
+
END_START: 'end-start',
|
|
145
|
+
START_START: 'start-start'};
|
|
146
|
+
/**
|
|
147
|
+
* Given a surface, an anchor, corners, and some options, this surface will
|
|
148
|
+
* calculate the position of a surface to align the two given corners and keep
|
|
149
|
+
* the surface inside the window viewport. It also provides a StyleInfo map that
|
|
150
|
+
* can be applied to the surface to handle visiblility and position.
|
|
151
|
+
*/
|
|
152
|
+
class SurfacePositionController {
|
|
153
|
+
/**
|
|
154
|
+
* @param host The host to connect the controller to.
|
|
155
|
+
* @param getProperties A function that returns the properties for the
|
|
156
|
+
* controller.
|
|
157
|
+
*/
|
|
158
|
+
constructor(host, getProperties) {
|
|
159
|
+
this.host = host;
|
|
160
|
+
this.getProperties = getProperties;
|
|
161
|
+
// The current styles to apply to the surface.
|
|
162
|
+
this.surfaceStylesInternal = {
|
|
163
|
+
'display': 'none'
|
|
164
|
+
};
|
|
165
|
+
// Previous values stored for change detection. Open change detection is
|
|
166
|
+
// calculated separately so initialize it here.
|
|
167
|
+
this.lastValues = {
|
|
168
|
+
isOpen: false
|
|
169
|
+
};
|
|
170
|
+
this.host.addController(this);
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* The StyleInfo map to apply to the surface via Lit's stylemap
|
|
174
|
+
*/
|
|
175
|
+
get surfaceStyles() {
|
|
176
|
+
return this.surfaceStylesInternal;
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* Calculates the surface's new position required so that the surface's
|
|
180
|
+
* `surfaceCorner` aligns to the anchor's `anchorCorner` while keeping the
|
|
181
|
+
* surface inside the window viewport. This positioning also respects RTL by
|
|
182
|
+
* checking `getComputedStyle()` on the surface element.
|
|
183
|
+
*/
|
|
184
|
+
async position() {
|
|
185
|
+
const {
|
|
186
|
+
surfaceEl,
|
|
187
|
+
anchorEl,
|
|
188
|
+
anchorCorner: anchorCornerRaw,
|
|
189
|
+
surfaceCorner: surfaceCornerRaw,
|
|
190
|
+
positioning,
|
|
191
|
+
xOffset,
|
|
192
|
+
yOffset,
|
|
193
|
+
disableBlockFlip,
|
|
194
|
+
disableInlineFlip,
|
|
195
|
+
repositionStrategy
|
|
196
|
+
} = this.getProperties();
|
|
197
|
+
const anchorCorner = anchorCornerRaw.toLowerCase().trim();
|
|
198
|
+
const surfaceCorner = surfaceCornerRaw.toLowerCase().trim();
|
|
199
|
+
if (!surfaceEl || !anchorEl) {
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
// Store these before we potentially resize the window with the next set of
|
|
203
|
+
// lines
|
|
204
|
+
const windowInnerWidth = window.innerWidth;
|
|
205
|
+
const windowInnerHeight = window.innerHeight;
|
|
206
|
+
const div = document.createElement('div');
|
|
207
|
+
div.style.opacity = '0';
|
|
208
|
+
div.style.position = 'fixed';
|
|
209
|
+
div.style.display = 'block';
|
|
210
|
+
div.style.inset = '0';
|
|
211
|
+
document.body.appendChild(div);
|
|
212
|
+
const scrollbarTestRect = div.getBoundingClientRect();
|
|
213
|
+
div.remove();
|
|
214
|
+
// Calculate the widths of the scrollbars in the inline and block directions
|
|
215
|
+
// to account for window-relative calculations.
|
|
216
|
+
const blockScrollbarHeight = window.innerHeight - scrollbarTestRect.bottom;
|
|
217
|
+
const inlineScrollbarWidth = window.innerWidth - scrollbarTestRect.right;
|
|
218
|
+
// Paint the surface transparently so that we can get the position and the
|
|
219
|
+
// rect info of the surface.
|
|
220
|
+
this.surfaceStylesInternal = {
|
|
221
|
+
'display': 'block',
|
|
222
|
+
'opacity': '0'
|
|
223
|
+
};
|
|
224
|
+
// Wait for it to be visible.
|
|
225
|
+
this.host.requestUpdate();
|
|
226
|
+
await this.host.updateComplete;
|
|
227
|
+
// Safari has a bug that makes popovers render incorrectly if the node is
|
|
228
|
+
// made visible + Animation Frame before calling showPopover().
|
|
229
|
+
// https://bugs.webkit.org/show_bug.cgi?id=264069
|
|
230
|
+
// also the cast is required due to differing TS types in Google and OSS.
|
|
231
|
+
if (surfaceEl.popover && surfaceEl.isConnected) {
|
|
232
|
+
surfaceEl.showPopover();
|
|
233
|
+
}
|
|
234
|
+
const surfaceRect = surfaceEl.getSurfacePositionClientRect ? surfaceEl.getSurfacePositionClientRect() : surfaceEl.getBoundingClientRect();
|
|
235
|
+
const anchorRect = anchorEl.getSurfacePositionClientRect ? anchorEl.getSurfacePositionClientRect() : anchorEl.getBoundingClientRect();
|
|
236
|
+
const [surfaceBlock, surfaceInline] = surfaceCorner.split('-');
|
|
237
|
+
const [anchorBlock, anchorInline] = anchorCorner.split('-');
|
|
238
|
+
// LTR depends on the direction of the SURFACE not the anchor.
|
|
239
|
+
const isLTR = getComputedStyle(surfaceEl).direction === 'ltr';
|
|
240
|
+
/*
|
|
241
|
+
* For more on inline and block dimensions, see MDN article:
|
|
242
|
+
* https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_logical_properties_and_values
|
|
243
|
+
*
|
|
244
|
+
* ┌───── inline/blockDocumentOffset inlineScrollbarWidth
|
|
245
|
+
* │ │ │
|
|
246
|
+
* │ ┌─▼─────┐ │Document
|
|
247
|
+
* │ ┌┼───────┴──────────────────────────────┼────────┐
|
|
248
|
+
* │ ││ │ │
|
|
249
|
+
* └──► ││ ┌───── inline/blockWindowOffset │ │
|
|
250
|
+
* ││ │ │ ▼ │
|
|
251
|
+
* ││ │ ┌─▼───┐ Window┌┐ │
|
|
252
|
+
* └┤ │ ┌┼─────┴───────────────────────┼│ │
|
|
253
|
+
* │ │ ││ ││ │
|
|
254
|
+
* │ └──► ││ ┌──inline/blockAnchorOffset││ │
|
|
255
|
+
* │ ││ │ │ ││ │
|
|
256
|
+
* │ └┤ │ ┌──▼───┐ ││ │
|
|
257
|
+
* │ │ │ ┌┼──────┤ ││ │
|
|
258
|
+
* │ │ └─►│Anchor│ ││ │
|
|
259
|
+
* │ │ └┴──────┘ ││ │
|
|
260
|
+
* │ │ ││ │
|
|
261
|
+
* │ │ ┌───────────────────────┼┼────┐ │
|
|
262
|
+
* │ │ │ Surface ││ │ │
|
|
263
|
+
* │ │ │ ││ │ │
|
|
264
|
+
* │ │ │ ││ │ │
|
|
265
|
+
* │ │ │ ││ │ │
|
|
266
|
+
* │ │ │ ││ │ │
|
|
267
|
+
* │ ┌┼─────┼───────────────────────┼│ │ │
|
|
268
|
+
* │ ┌─►┴──────┼────────────────────────┘ ├┐ │
|
|
269
|
+
* │ │ │ inline/blockOOBCorrection ││ │
|
|
270
|
+
* │ │ │ │ ││ │
|
|
271
|
+
* │ │ │ ├──►├│ │
|
|
272
|
+
* │ │ │ │ ││ │
|
|
273
|
+
* │ │ └────────────────────────┐▼───┼┘ │
|
|
274
|
+
* │ blockScrollbarHeight └────┘ │
|
|
275
|
+
* │ │
|
|
276
|
+
* └───────────────────────────────────────────────┘
|
|
277
|
+
*/
|
|
278
|
+
// Calculate the block positioning properties
|
|
279
|
+
let {
|
|
280
|
+
blockInset,
|
|
281
|
+
blockOutOfBoundsCorrection,
|
|
282
|
+
surfaceBlockProperty
|
|
283
|
+
} = this.calculateBlock({
|
|
284
|
+
surfaceRect,
|
|
285
|
+
anchorRect,
|
|
286
|
+
anchorBlock,
|
|
287
|
+
surfaceBlock,
|
|
288
|
+
yOffset,
|
|
289
|
+
positioning,
|
|
290
|
+
windowInnerHeight,
|
|
291
|
+
blockScrollbarHeight
|
|
292
|
+
});
|
|
293
|
+
// If the surface should be out of bounds in the block direction, flip the
|
|
294
|
+
// surface and anchor corner block values and recalculate
|
|
295
|
+
if (blockOutOfBoundsCorrection && !disableBlockFlip) {
|
|
296
|
+
const flippedSurfaceBlock = surfaceBlock === 'start' ? 'end' : 'start';
|
|
297
|
+
const flippedAnchorBlock = anchorBlock === 'start' ? 'end' : 'start';
|
|
298
|
+
const flippedBlock = this.calculateBlock({
|
|
299
|
+
surfaceRect,
|
|
300
|
+
anchorRect,
|
|
301
|
+
anchorBlock: flippedAnchorBlock,
|
|
302
|
+
surfaceBlock: flippedSurfaceBlock,
|
|
303
|
+
yOffset,
|
|
304
|
+
positioning,
|
|
305
|
+
windowInnerHeight,
|
|
306
|
+
blockScrollbarHeight
|
|
307
|
+
});
|
|
308
|
+
// In the case that the flipped verion would require less out of bounds
|
|
309
|
+
// correcting, use the flipped corner block values
|
|
310
|
+
if (blockOutOfBoundsCorrection > flippedBlock.blockOutOfBoundsCorrection) {
|
|
311
|
+
blockInset = flippedBlock.blockInset;
|
|
312
|
+
blockOutOfBoundsCorrection = flippedBlock.blockOutOfBoundsCorrection;
|
|
313
|
+
surfaceBlockProperty = flippedBlock.surfaceBlockProperty;
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
// Calculate the inline positioning properties
|
|
317
|
+
let {
|
|
318
|
+
inlineInset,
|
|
319
|
+
inlineOutOfBoundsCorrection,
|
|
320
|
+
surfaceInlineProperty
|
|
321
|
+
} = this.calculateInline({
|
|
322
|
+
surfaceRect,
|
|
323
|
+
anchorRect,
|
|
324
|
+
anchorInline,
|
|
325
|
+
surfaceInline,
|
|
326
|
+
xOffset,
|
|
327
|
+
positioning,
|
|
328
|
+
isLTR,
|
|
329
|
+
windowInnerWidth,
|
|
330
|
+
inlineScrollbarWidth
|
|
331
|
+
});
|
|
332
|
+
// If the surface should be out of bounds in the inline direction, flip the
|
|
333
|
+
// surface and anchor corner inline values and recalculate
|
|
334
|
+
if (inlineOutOfBoundsCorrection && !disableInlineFlip) {
|
|
335
|
+
const flippedSurfaceInline = surfaceInline === 'start' ? 'end' : 'start';
|
|
336
|
+
const flippedAnchorInline = anchorInline === 'start' ? 'end' : 'start';
|
|
337
|
+
const flippedInline = this.calculateInline({
|
|
338
|
+
surfaceRect,
|
|
339
|
+
anchorRect,
|
|
340
|
+
anchorInline: flippedAnchorInline,
|
|
341
|
+
surfaceInline: flippedSurfaceInline,
|
|
342
|
+
xOffset,
|
|
343
|
+
positioning,
|
|
344
|
+
isLTR,
|
|
345
|
+
windowInnerWidth,
|
|
346
|
+
inlineScrollbarWidth
|
|
347
|
+
});
|
|
348
|
+
// In the case that the flipped verion would require less out of bounds
|
|
349
|
+
// correcting, use the flipped corner inline values
|
|
350
|
+
if (Math.abs(inlineOutOfBoundsCorrection) > Math.abs(flippedInline.inlineOutOfBoundsCorrection)) {
|
|
351
|
+
inlineInset = flippedInline.inlineInset;
|
|
352
|
+
inlineOutOfBoundsCorrection = flippedInline.inlineOutOfBoundsCorrection;
|
|
353
|
+
surfaceInlineProperty = flippedInline.surfaceInlineProperty;
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
// If we are simply repositioning the surface back inside the viewport,
|
|
357
|
+
// subtract the out of bounds correction values from the positioning.
|
|
358
|
+
if (repositionStrategy === 'move') {
|
|
359
|
+
blockInset = blockInset - blockOutOfBoundsCorrection;
|
|
360
|
+
inlineInset = inlineInset - inlineOutOfBoundsCorrection;
|
|
361
|
+
}
|
|
362
|
+
this.surfaceStylesInternal = {
|
|
363
|
+
'display': 'block',
|
|
364
|
+
'opacity': '1',
|
|
365
|
+
[surfaceBlockProperty]: `${blockInset}px`,
|
|
366
|
+
[surfaceInlineProperty]: `${inlineInset}px`
|
|
367
|
+
};
|
|
368
|
+
// In the case that we are resizing the surface to stay inside the viewport
|
|
369
|
+
// we need to set height and width on the surface.
|
|
370
|
+
if (repositionStrategy === 'resize') {
|
|
371
|
+
// Add a height property to the styles if there is block height correction
|
|
372
|
+
if (blockOutOfBoundsCorrection) {
|
|
373
|
+
this.surfaceStylesInternal['height'] = `${surfaceRect.height - blockOutOfBoundsCorrection}px`;
|
|
374
|
+
}
|
|
375
|
+
// Add a width property to the styles if there is block height correction
|
|
376
|
+
if (inlineOutOfBoundsCorrection) {
|
|
377
|
+
this.surfaceStylesInternal['width'] = `${surfaceRect.width - inlineOutOfBoundsCorrection}px`;
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
this.host.requestUpdate();
|
|
381
|
+
}
|
|
382
|
+
/**
|
|
383
|
+
* Calculates the css property, the inset, and the out of bounds correction
|
|
384
|
+
* for the surface in the block direction.
|
|
385
|
+
*/
|
|
386
|
+
calculateBlock(config) {
|
|
387
|
+
const {
|
|
388
|
+
surfaceRect,
|
|
389
|
+
anchorRect,
|
|
390
|
+
anchorBlock,
|
|
391
|
+
surfaceBlock,
|
|
392
|
+
yOffset,
|
|
393
|
+
positioning,
|
|
394
|
+
windowInnerHeight,
|
|
395
|
+
blockScrollbarHeight
|
|
396
|
+
} = config;
|
|
397
|
+
// We use number booleans to multiply values rather than `if` / ternary
|
|
398
|
+
// statements because it _heavily_ cuts down on nesting and readability
|
|
399
|
+
const relativeToWindow = positioning === 'fixed' || positioning === 'document' ? 1 : 0;
|
|
400
|
+
const relativeToDocument = positioning === 'document' ? 1 : 0;
|
|
401
|
+
const isSurfaceBlockStart = surfaceBlock === 'start' ? 1 : 0;
|
|
402
|
+
const isSurfaceBlockEnd = surfaceBlock === 'end' ? 1 : 0;
|
|
403
|
+
const isOneBlockEnd = anchorBlock !== surfaceBlock ? 1 : 0;
|
|
404
|
+
// Whether or not to apply the height of the anchor
|
|
405
|
+
const blockAnchorOffset = isOneBlockEnd * anchorRect.height + yOffset;
|
|
406
|
+
// The absolute block position of the anchor relative to window
|
|
407
|
+
const blockTopLayerOffset = isSurfaceBlockStart * anchorRect.top + isSurfaceBlockEnd * (windowInnerHeight - anchorRect.bottom - blockScrollbarHeight);
|
|
408
|
+
const blockDocumentOffset = isSurfaceBlockStart * window.scrollY - isSurfaceBlockEnd * window.scrollY;
|
|
409
|
+
// If the surface's block would be out of bounds of the window, move it back
|
|
410
|
+
// in
|
|
411
|
+
const blockOutOfBoundsCorrection = Math.abs(Math.min(0, windowInnerHeight - blockTopLayerOffset - blockAnchorOffset - surfaceRect.height));
|
|
412
|
+
// The block logical value of the surface
|
|
413
|
+
const blockInset = relativeToWindow * blockTopLayerOffset + relativeToDocument * blockDocumentOffset + blockAnchorOffset;
|
|
414
|
+
const surfaceBlockProperty = surfaceBlock === 'start' ? 'inset-block-start' : 'inset-block-end';
|
|
415
|
+
return {
|
|
416
|
+
blockInset,
|
|
417
|
+
blockOutOfBoundsCorrection,
|
|
418
|
+
surfaceBlockProperty
|
|
419
|
+
};
|
|
420
|
+
}
|
|
421
|
+
/**
|
|
422
|
+
* Calculates the css property, the inset, and the out of bounds correction
|
|
423
|
+
* for the surface in the inline direction.
|
|
424
|
+
*/
|
|
425
|
+
calculateInline(config) {
|
|
426
|
+
const {
|
|
427
|
+
isLTR: isLTRBool,
|
|
428
|
+
surfaceInline,
|
|
429
|
+
anchorInline,
|
|
430
|
+
anchorRect,
|
|
431
|
+
surfaceRect,
|
|
432
|
+
xOffset,
|
|
433
|
+
positioning,
|
|
434
|
+
windowInnerWidth,
|
|
435
|
+
inlineScrollbarWidth
|
|
436
|
+
} = config;
|
|
437
|
+
// We use number booleans to multiply values rather than `if` / ternary
|
|
438
|
+
// statements because it _heavily_ cuts down on nesting and readability
|
|
439
|
+
const relativeToWindow = positioning === 'fixed' || positioning === 'document' ? 1 : 0;
|
|
440
|
+
const relativeToDocument = positioning === 'document' ? 1 : 0;
|
|
441
|
+
const isLTR = isLTRBool ? 1 : 0;
|
|
442
|
+
const isRTL = isLTRBool ? 0 : 1;
|
|
443
|
+
const isSurfaceInlineStart = surfaceInline === 'start' ? 1 : 0;
|
|
444
|
+
const isSurfaceInlineEnd = surfaceInline === 'end' ? 1 : 0;
|
|
445
|
+
const isOneInlineEnd = anchorInline !== surfaceInline ? 1 : 0;
|
|
446
|
+
// Whether or not to apply the width of the anchor
|
|
447
|
+
const inlineAnchorOffset = isOneInlineEnd * anchorRect.width + xOffset;
|
|
448
|
+
// The inline position of the anchor relative to window in LTR
|
|
449
|
+
const inlineTopLayerOffsetLTR = isSurfaceInlineStart * anchorRect.left + isSurfaceInlineEnd * (windowInnerWidth - anchorRect.right - inlineScrollbarWidth);
|
|
450
|
+
// The inline position of the anchor relative to window in RTL
|
|
451
|
+
const inlineTopLayerOffsetRTL = isSurfaceInlineStart * (windowInnerWidth - anchorRect.right - inlineScrollbarWidth) + isSurfaceInlineEnd * anchorRect.left;
|
|
452
|
+
// The inline position of the anchor relative to window
|
|
453
|
+
const inlineTopLayerOffset = isLTR * inlineTopLayerOffsetLTR + isRTL * inlineTopLayerOffsetRTL;
|
|
454
|
+
// The inline position of the anchor relative to window in LTR
|
|
455
|
+
const inlineDocumentOffsetLTR = isSurfaceInlineStart * window.scrollX - isSurfaceInlineEnd * window.scrollX;
|
|
456
|
+
// The inline position of the anchor relative to window in RTL
|
|
457
|
+
const inlineDocumentOffsetRTL = isSurfaceInlineEnd * window.scrollX - isSurfaceInlineStart * window.scrollX;
|
|
458
|
+
// The inline position of the anchor relative to window
|
|
459
|
+
const inlineDocumentOffset = isLTR * inlineDocumentOffsetLTR + isRTL * inlineDocumentOffsetRTL;
|
|
460
|
+
// If the surface's inline would be out of bounds of the window, move it
|
|
461
|
+
// back in
|
|
462
|
+
const inlineOutOfBoundsCorrection = Math.abs(Math.min(0, windowInnerWidth - inlineTopLayerOffset - inlineAnchorOffset - surfaceRect.width));
|
|
463
|
+
// The inline logical value of the surface
|
|
464
|
+
const inlineInset = relativeToWindow * inlineTopLayerOffset + inlineAnchorOffset + relativeToDocument * inlineDocumentOffset;
|
|
465
|
+
let surfaceInlineProperty = surfaceInline === 'start' ? 'inset-inline-start' : 'inset-inline-end';
|
|
466
|
+
// There are cases where the element is RTL but the root of the page is not.
|
|
467
|
+
// In these cases we want to not use logical properties.
|
|
468
|
+
if (positioning === 'document' || positioning === 'fixed') {
|
|
469
|
+
if (surfaceInline === 'start' && isLTRBool || surfaceInline === 'end' && !isLTRBool) {
|
|
470
|
+
surfaceInlineProperty = 'left';
|
|
471
|
+
} else {
|
|
472
|
+
surfaceInlineProperty = 'right';
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
return {
|
|
476
|
+
inlineInset,
|
|
477
|
+
inlineOutOfBoundsCorrection,
|
|
478
|
+
surfaceInlineProperty
|
|
479
|
+
};
|
|
480
|
+
}
|
|
481
|
+
hostUpdate() {
|
|
482
|
+
this.onUpdate();
|
|
483
|
+
}
|
|
484
|
+
hostUpdated() {
|
|
485
|
+
this.onUpdate();
|
|
486
|
+
}
|
|
487
|
+
/**
|
|
488
|
+
* Checks whether the properties passed into the controller have changed since
|
|
489
|
+
* the last positioning. If so, it will reposition if the surface is open or
|
|
490
|
+
* close it if the surface should close.
|
|
491
|
+
*/
|
|
492
|
+
async onUpdate() {
|
|
493
|
+
const props = this.getProperties();
|
|
494
|
+
let hasChanged = false;
|
|
495
|
+
for (const [key, value] of Object.entries(props)) {
|
|
496
|
+
// tslint:disable-next-line
|
|
497
|
+
hasChanged = hasChanged || value !== this.lastValues[key];
|
|
498
|
+
if (hasChanged) break;
|
|
499
|
+
}
|
|
500
|
+
const openChanged = this.lastValues.isOpen !== props.isOpen;
|
|
501
|
+
const hasAnchor = !!props.anchorEl;
|
|
502
|
+
const hasSurface = !!props.surfaceEl;
|
|
503
|
+
if (hasChanged && hasAnchor && hasSurface) {
|
|
504
|
+
// Only update isOpen, because if it's closed, we do not want to waste
|
|
505
|
+
// time on a useless reposition calculation. So save the other "dirty"
|
|
506
|
+
// values until next time it opens.
|
|
507
|
+
this.lastValues.isOpen = props.isOpen;
|
|
508
|
+
if (props.isOpen) {
|
|
509
|
+
// We are going to do a reposition, so save the prop values for future
|
|
510
|
+
// dirty checking.
|
|
511
|
+
this.lastValues = props;
|
|
512
|
+
await this.position();
|
|
513
|
+
props.onOpen();
|
|
514
|
+
} else if (openChanged) {
|
|
515
|
+
await props.beforeClose();
|
|
516
|
+
this.close();
|
|
517
|
+
props.onClose();
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
/**
|
|
522
|
+
* Hides the surface.
|
|
523
|
+
*/
|
|
524
|
+
close() {
|
|
525
|
+
this.surfaceStylesInternal = {
|
|
526
|
+
'display': 'none'
|
|
527
|
+
};
|
|
528
|
+
this.host.requestUpdate();
|
|
529
|
+
const surfaceEl = this.getProperties().surfaceEl;
|
|
530
|
+
// The following type casts are required due to differing TS types in Google
|
|
531
|
+
// and open source.
|
|
532
|
+
if (surfaceEl !== null && surfaceEl !== void 0 && surfaceEl.popover && surfaceEl !== null && surfaceEl !== void 0 && surfaceEl.isConnected) {
|
|
533
|
+
surfaceEl.hidePopover();
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
/**
|
|
539
|
+
* @license
|
|
540
|
+
* Copyright 2023 Google LLC
|
|
541
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
542
|
+
*/
|
|
543
|
+
/**
|
|
544
|
+
* Indicies to access the TypeaheadRecord tuple type.
|
|
545
|
+
*/
|
|
546
|
+
const TYPEAHEAD_RECORD = {
|
|
547
|
+
INDEX: 0,
|
|
548
|
+
ITEM: 1,
|
|
549
|
+
TEXT: 2
|
|
550
|
+
};
|
|
551
|
+
/**
|
|
552
|
+
* This controller listens to `keydown` events and searches the header text of
|
|
553
|
+
* an array of `MenuItem`s with the corresponding entered keys within the buffer
|
|
554
|
+
* time and activates the item.
|
|
555
|
+
*
|
|
556
|
+
* @example
|
|
557
|
+
* ```ts
|
|
558
|
+
* const typeaheadController = new TypeaheadController(() => ({
|
|
559
|
+
* typeaheadBufferTime: 50,
|
|
560
|
+
* getItems: () => Array.from(document.querySelectorAll('md-menu-item'))
|
|
561
|
+
* }));
|
|
562
|
+
* html`
|
|
563
|
+
* <div
|
|
564
|
+
* @keydown=${typeaheadController.onKeydown}
|
|
565
|
+
* tabindex="0"
|
|
566
|
+
* class="activeItemText">
|
|
567
|
+
* <!-- focusable element that will receive keydown events -->
|
|
568
|
+
* Apple
|
|
569
|
+
* </div>
|
|
570
|
+
* <div>
|
|
571
|
+
* <md-menu-item active header="Apple"></md-menu-item>
|
|
572
|
+
* <md-menu-item header="Apricot"></md-menu-item>
|
|
573
|
+
* <md-menu-item header="Banana"></md-menu-item>
|
|
574
|
+
* <md-menu-item header="Olive"></md-menu-item>
|
|
575
|
+
* <md-menu-item header="Orange"></md-menu-item>
|
|
576
|
+
* </div>
|
|
577
|
+
* `;
|
|
578
|
+
* ```
|
|
579
|
+
*/
|
|
580
|
+
class TypeaheadController {
|
|
581
|
+
/**
|
|
582
|
+
* @param getProperties A function that returns the options of the typeahead
|
|
583
|
+
* controller:
|
|
584
|
+
*
|
|
585
|
+
* {
|
|
586
|
+
* getItems: A function that returns an array of menu items to be searched.
|
|
587
|
+
* typeaheadBufferTime: The maximum time between each keystroke to keep the
|
|
588
|
+
* current type buffer alive.
|
|
589
|
+
* }
|
|
590
|
+
*/
|
|
591
|
+
constructor(getProperties) {
|
|
592
|
+
this.getProperties = getProperties;
|
|
593
|
+
/**
|
|
594
|
+
* Array of tuples that helps with indexing.
|
|
595
|
+
*/
|
|
596
|
+
this.typeaheadRecords = [];
|
|
597
|
+
/**
|
|
598
|
+
* Currently-typed text since last buffer timeout
|
|
599
|
+
*/
|
|
600
|
+
this.typaheadBuffer = '';
|
|
601
|
+
/**
|
|
602
|
+
* The timeout id from the current buffer's setTimeout
|
|
603
|
+
*/
|
|
604
|
+
this.cancelTypeaheadTimeout = 0;
|
|
605
|
+
/**
|
|
606
|
+
* If we are currently "typing"
|
|
607
|
+
*/
|
|
608
|
+
this.isTypingAhead = false;
|
|
609
|
+
/**
|
|
610
|
+
* The record of the last active item.
|
|
611
|
+
*/
|
|
612
|
+
this.lastActiveRecord = null;
|
|
613
|
+
/**
|
|
614
|
+
* Apply this listener to the element that will receive `keydown` events that
|
|
615
|
+
* should trigger this controller.
|
|
616
|
+
*
|
|
617
|
+
* @param event The native browser `KeyboardEvent` from the `keydown` event.
|
|
618
|
+
*/
|
|
619
|
+
this.onKeydown = event => {
|
|
620
|
+
if (this.isTypingAhead) {
|
|
621
|
+
this.typeahead(event);
|
|
622
|
+
} else {
|
|
623
|
+
this.beginTypeahead(event);
|
|
624
|
+
}
|
|
625
|
+
};
|
|
626
|
+
/**
|
|
627
|
+
* Ends the current typeahead and clears the buffer.
|
|
628
|
+
*/
|
|
629
|
+
this.endTypeahead = () => {
|
|
630
|
+
this.isTypingAhead = false;
|
|
631
|
+
this.typaheadBuffer = '';
|
|
632
|
+
this.typeaheadRecords = [];
|
|
633
|
+
};
|
|
634
|
+
}
|
|
635
|
+
get items() {
|
|
636
|
+
return this.getProperties().getItems();
|
|
637
|
+
}
|
|
638
|
+
get active() {
|
|
639
|
+
return this.getProperties().active;
|
|
640
|
+
}
|
|
641
|
+
/**
|
|
642
|
+
* Sets up typingahead
|
|
643
|
+
*/
|
|
644
|
+
beginTypeahead(event) {
|
|
645
|
+
if (!this.active) {
|
|
646
|
+
return;
|
|
647
|
+
}
|
|
648
|
+
// We don't want to typeahead if the _beginning_ of the typeahead is a menu
|
|
649
|
+
// navigation, or a selection. We will handle "Space" only if it's in the
|
|
650
|
+
// middle of a typeahead
|
|
651
|
+
if (event.code === 'Space' || event.code === 'Enter' || event.code.startsWith('Arrow') || event.code === 'Escape') {
|
|
652
|
+
return;
|
|
653
|
+
}
|
|
654
|
+
this.isTypingAhead = true;
|
|
655
|
+
// Generates the record array data structure which is the index, the element
|
|
656
|
+
// and a normalized header.
|
|
657
|
+
this.typeaheadRecords = this.items.map((el, index) => [index, el, el.typeaheadText.trim().toLowerCase()]);
|
|
658
|
+
this.lastActiveRecord = this.typeaheadRecords.find(record => record[TYPEAHEAD_RECORD.ITEM].tabIndex === 0) ?? null;
|
|
659
|
+
if (this.lastActiveRecord) {
|
|
660
|
+
this.lastActiveRecord[TYPEAHEAD_RECORD.ITEM].tabIndex = -1;
|
|
661
|
+
}
|
|
662
|
+
this.typeahead(event);
|
|
663
|
+
}
|
|
664
|
+
/**
|
|
665
|
+
* Performs the typeahead. Based on the normalized items and the current text
|
|
666
|
+
* buffer, finds the _next_ item with matching text and activates it.
|
|
667
|
+
*
|
|
668
|
+
* @example
|
|
669
|
+
*
|
|
670
|
+
* items: Apple, Banana, Olive, Orange, Cucumber
|
|
671
|
+
* buffer: ''
|
|
672
|
+
* user types: o
|
|
673
|
+
*
|
|
674
|
+
* activates Olive
|
|
675
|
+
*
|
|
676
|
+
* @example
|
|
677
|
+
*
|
|
678
|
+
* items: Apple, Banana, Olive (active), Orange, Cucumber
|
|
679
|
+
* buffer: 'o'
|
|
680
|
+
* user types: l
|
|
681
|
+
*
|
|
682
|
+
* activates Olive
|
|
683
|
+
*
|
|
684
|
+
* @example
|
|
685
|
+
*
|
|
686
|
+
* items: Apple, Banana, Olive (active), Orange, Cucumber
|
|
687
|
+
* buffer: ''
|
|
688
|
+
* user types: o
|
|
689
|
+
*
|
|
690
|
+
* activates Orange
|
|
691
|
+
*
|
|
692
|
+
* @example
|
|
693
|
+
*
|
|
694
|
+
* items: Apple, Banana, Olive, Orange (active), Cucumber
|
|
695
|
+
* buffer: ''
|
|
696
|
+
* user types: o
|
|
697
|
+
*
|
|
698
|
+
* activates Olive
|
|
699
|
+
*/
|
|
700
|
+
typeahead(event) {
|
|
701
|
+
if (event.defaultPrevented) return;
|
|
702
|
+
clearTimeout(this.cancelTypeaheadTimeout);
|
|
703
|
+
// Stop typingahead if one of the navigation or selection keys (except for
|
|
704
|
+
// Space) are pressed
|
|
705
|
+
if (event.code === 'Enter' || event.code.startsWith('Arrow') || event.code === 'Escape') {
|
|
706
|
+
this.endTypeahead();
|
|
707
|
+
if (this.lastActiveRecord) {
|
|
708
|
+
this.lastActiveRecord[TYPEAHEAD_RECORD.ITEM].tabIndex = -1;
|
|
709
|
+
}
|
|
710
|
+
return;
|
|
711
|
+
}
|
|
712
|
+
// If Space is pressed, prevent it from selecting and closing the menu
|
|
713
|
+
if (event.code === 'Space') {
|
|
714
|
+
event.preventDefault();
|
|
715
|
+
}
|
|
716
|
+
// Start up a new keystroke buffer timeout
|
|
717
|
+
this.cancelTypeaheadTimeout = setTimeout(this.endTypeahead, this.getProperties().typeaheadBufferTime);
|
|
718
|
+
this.typaheadBuffer += event.key.toLowerCase();
|
|
719
|
+
const lastActiveIndex = this.lastActiveRecord ? this.lastActiveRecord[TYPEAHEAD_RECORD.INDEX] : -1;
|
|
720
|
+
const numRecords = this.typeaheadRecords.length;
|
|
721
|
+
/**
|
|
722
|
+
* Sorting function that will resort the items starting with the given index
|
|
723
|
+
*
|
|
724
|
+
* @example
|
|
725
|
+
*
|
|
726
|
+
* this.typeaheadRecords =
|
|
727
|
+
* 0: [0, <reference>, 'apple']
|
|
728
|
+
* 1: [1, <reference>, 'apricot']
|
|
729
|
+
* 2: [2, <reference>, 'banana']
|
|
730
|
+
* 3: [3, <reference>, 'olive'] <-- lastActiveIndex
|
|
731
|
+
* 4: [4, <reference>, 'orange']
|
|
732
|
+
* 5: [5, <reference>, 'strawberry']
|
|
733
|
+
*
|
|
734
|
+
* this.typeaheadRecords.sort((a,b) => rebaseIndexOnActive(a)
|
|
735
|
+
* - rebaseIndexOnActive(b)) ===
|
|
736
|
+
* 0: [3, <reference>, 'olive'] <-- lastActiveIndex
|
|
737
|
+
* 1: [4, <reference>, 'orange']
|
|
738
|
+
* 2: [5, <reference>, 'strawberry']
|
|
739
|
+
* 3: [0, <reference>, 'apple']
|
|
740
|
+
* 4: [1, <reference>, 'apricot']
|
|
741
|
+
* 5: [2, <reference>, 'banana']
|
|
742
|
+
*/
|
|
743
|
+
const rebaseIndexOnActive = record => {
|
|
744
|
+
return (record[TYPEAHEAD_RECORD.INDEX] + numRecords - lastActiveIndex) % numRecords;
|
|
745
|
+
};
|
|
746
|
+
// records filtered and sorted / rebased around the last active index
|
|
747
|
+
const matchingRecords = this.typeaheadRecords.filter(record => !record[TYPEAHEAD_RECORD.ITEM].disabled && record[TYPEAHEAD_RECORD.TEXT].startsWith(this.typaheadBuffer)).sort((a, b) => rebaseIndexOnActive(a) - rebaseIndexOnActive(b));
|
|
748
|
+
// Just leave if there's nothing that matches. Native select will just
|
|
749
|
+
// choose the first thing that starts with the next letter in the alphabet
|
|
750
|
+
// but that's out of scope and hard to localize
|
|
751
|
+
if (matchingRecords.length === 0) {
|
|
752
|
+
clearTimeout(this.cancelTypeaheadTimeout);
|
|
753
|
+
if (this.lastActiveRecord) {
|
|
754
|
+
this.lastActiveRecord[TYPEAHEAD_RECORD.ITEM].tabIndex = -1;
|
|
755
|
+
}
|
|
756
|
+
this.endTypeahead();
|
|
757
|
+
return;
|
|
758
|
+
}
|
|
759
|
+
const isNewQuery = this.typaheadBuffer.length === 1;
|
|
760
|
+
let nextRecord;
|
|
761
|
+
// This is likely the case that someone is trying to "tab" through different
|
|
762
|
+
// entries that start with the same letter
|
|
763
|
+
if (this.lastActiveRecord === matchingRecords[0] && isNewQuery) {
|
|
764
|
+
nextRecord = matchingRecords[1] ?? matchingRecords[0];
|
|
765
|
+
} else {
|
|
766
|
+
nextRecord = matchingRecords[0];
|
|
767
|
+
}
|
|
768
|
+
if (this.lastActiveRecord) {
|
|
769
|
+
this.lastActiveRecord[TYPEAHEAD_RECORD.ITEM].tabIndex = -1;
|
|
770
|
+
}
|
|
771
|
+
this.lastActiveRecord = nextRecord;
|
|
772
|
+
nextRecord[TYPEAHEAD_RECORD.ITEM].tabIndex = 0;
|
|
773
|
+
nextRecord[TYPEAHEAD_RECORD.ITEM].focus();
|
|
774
|
+
return;
|
|
775
|
+
}
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
/**
|
|
779
|
+
* @license
|
|
780
|
+
* Copyright 2023 Google LLC
|
|
781
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
782
|
+
*/
|
|
783
|
+
/**
|
|
784
|
+
* The default value for the typeahead buffer time in Milliseconds.
|
|
785
|
+
*/
|
|
786
|
+
const DEFAULT_TYPEAHEAD_BUFFER_TIME = 200;
|
|
787
|
+
const submenuNavKeys = new Set([NavigableKeys.ArrowDown, NavigableKeys.ArrowUp, NavigableKeys.Home, NavigableKeys.End]);
|
|
788
|
+
const menuNavKeys = new Set([NavigableKeys.ArrowLeft, NavigableKeys.ArrowRight, ...submenuNavKeys]);
|
|
789
|
+
/**
|
|
790
|
+
* Gets the currently focused element on the page.
|
|
791
|
+
*
|
|
792
|
+
* @param activeDoc The document or shadowroot from which to start the search.
|
|
793
|
+
* Defaults to `window.document`
|
|
794
|
+
* @return Returns the currently deeply focused element or `null` if none.
|
|
795
|
+
*/
|
|
796
|
+
function getFocusedElement(activeDoc = document) {
|
|
797
|
+
let activeEl = activeDoc.activeElement;
|
|
798
|
+
// Check for activeElement in the case that an element with a shadow root host
|
|
799
|
+
// is currently focused.
|
|
800
|
+
while (activeEl && (_activeEl = activeEl) !== null && _activeEl !== void 0 && (_activeEl = _activeEl.shadowRoot) !== null && _activeEl !== void 0 && _activeEl.activeElement) {
|
|
801
|
+
var _activeEl;
|
|
802
|
+
activeEl = activeEl.shadowRoot.activeElement;
|
|
803
|
+
}
|
|
804
|
+
return activeEl;
|
|
805
|
+
}
|
|
806
|
+
/**
|
|
807
|
+
* @fires opening {Event} Fired before the opening animation begins
|
|
808
|
+
* @fires opened {Event} Fired once the menu is open, after any animations
|
|
809
|
+
* @fires closing {Event} Fired before the closing animation begins
|
|
810
|
+
* @fires closed {Event} Fired once the menu is closed, after any animations
|
|
811
|
+
*/
|
|
812
|
+
class Menu extends i {
|
|
813
|
+
/**
|
|
814
|
+
* Whether the menu is animating upwards or downwards when opening. This is
|
|
815
|
+
* helpful for calculating some animation calculations.
|
|
816
|
+
*/
|
|
817
|
+
get openDirection() {
|
|
818
|
+
const menuCornerBlock = this.menuCorner.split('-')[0];
|
|
819
|
+
return menuCornerBlock === 'start' ? 'DOWN' : 'UP';
|
|
820
|
+
}
|
|
821
|
+
/**
|
|
822
|
+
* The element which the menu should align to. If `anchor` is set to a
|
|
823
|
+
* non-empty idref string, then `anchorEl` will resolve to the element with
|
|
824
|
+
* the given id in the same root node. Otherwise, `null`.
|
|
825
|
+
*/
|
|
826
|
+
get anchorElement() {
|
|
827
|
+
if (this.anchor) {
|
|
828
|
+
return this.getRootNode().querySelector(`#${this.anchor}`);
|
|
829
|
+
}
|
|
830
|
+
return this.currentAnchorElement;
|
|
831
|
+
}
|
|
832
|
+
set anchorElement(element) {
|
|
833
|
+
this.currentAnchorElement = element;
|
|
834
|
+
this.requestUpdate('anchorElement');
|
|
835
|
+
}
|
|
836
|
+
constructor() {
|
|
837
|
+
super();
|
|
838
|
+
/**
|
|
839
|
+
* The ID of the element in the same root node in which the menu should align
|
|
840
|
+
* to. Overrides setting `anchorElement = elementReference`.
|
|
841
|
+
*
|
|
842
|
+
* __NOTE__: anchor or anchorElement must either be an HTMLElement or resolve
|
|
843
|
+
* to an HTMLElement in order for menu to open.
|
|
844
|
+
*/
|
|
845
|
+
this.anchor = '';
|
|
846
|
+
/**
|
|
847
|
+
* Whether the positioning algorithm should calculate relative to the parent
|
|
848
|
+
* of the anchor element (`absolute`), relative to the window (`fixed`), or
|
|
849
|
+
* relative to the document (`document`). `popover` will use the popover API
|
|
850
|
+
* to render the menu in the top-layer. If your browser does not support the
|
|
851
|
+
* popover API, it will fall back to `fixed`.
|
|
852
|
+
*
|
|
853
|
+
* __Examples for `position = 'fixed'`:__
|
|
854
|
+
*
|
|
855
|
+
* - If there is no `position:relative` in the given parent tree and the
|
|
856
|
+
* surface is `position:absolute`
|
|
857
|
+
* - If the surface is `position:fixed`
|
|
858
|
+
* - If the surface is in the "top layer"
|
|
859
|
+
* - The anchor and the surface do not share a common `position:relative`
|
|
860
|
+
* ancestor
|
|
861
|
+
*
|
|
862
|
+
* When using `positioning=fixed`, in most cases, the menu should position
|
|
863
|
+
* itself above most other `position:absolute` or `position:fixed` elements
|
|
864
|
+
* when placed inside of them. e.g. using a menu inside of an `md-dialog`.
|
|
865
|
+
*
|
|
866
|
+
* __NOTE__: Fixed menus will not scroll with the page and will be fixed to
|
|
867
|
+
* the window instead.
|
|
868
|
+
*
|
|
869
|
+
* __Examples for `position = 'document'`:__
|
|
870
|
+
*
|
|
871
|
+
* - There is no parent that creates a relative positioning context e.g.
|
|
872
|
+
* `position: relative`, `position: absolute`, `transform: translate(x, y)`,
|
|
873
|
+
* etc.
|
|
874
|
+
* - You put the effort into hoisting the menu to the top of the DOM like the
|
|
875
|
+
* end of the `<body>` to render over everything or in a top-layer.
|
|
876
|
+
* - You are reusing a single `md-menu` element that dynamically renders
|
|
877
|
+
* content.
|
|
878
|
+
*
|
|
879
|
+
* __Examples for `position = 'popover'`:__
|
|
880
|
+
*
|
|
881
|
+
* - Your browser supports `popover`.
|
|
882
|
+
* - Most cases. Once popover is in browsers, this will become the default.
|
|
883
|
+
*/
|
|
884
|
+
this.positioning = 'absolute';
|
|
885
|
+
/**
|
|
886
|
+
* Skips the opening and closing animations.
|
|
887
|
+
*/
|
|
888
|
+
this.quick = false;
|
|
889
|
+
/**
|
|
890
|
+
* Displays overflow content like a submenu. Not required in most cases when
|
|
891
|
+
* using `positioning="popover"`.
|
|
892
|
+
*
|
|
893
|
+
* __NOTE__: This may cause adverse effects if you set
|
|
894
|
+
* `md-menu {max-height:...}`
|
|
895
|
+
* and have items overflowing items in the "y" direction.
|
|
896
|
+
*/
|
|
897
|
+
this.hasOverflow = false;
|
|
898
|
+
/**
|
|
899
|
+
* Opens the menu and makes it visible. Alternative to the `.show()` and
|
|
900
|
+
* `.close()` methods
|
|
901
|
+
*/
|
|
902
|
+
this.open = false;
|
|
903
|
+
/**
|
|
904
|
+
* Offsets the menu's inline alignment from the anchor by the given number in
|
|
905
|
+
* pixels. This value is direction aware and will follow the LTR / RTL
|
|
906
|
+
* direction.
|
|
907
|
+
*
|
|
908
|
+
* e.g. LTR: positive -> right, negative -> left
|
|
909
|
+
* RTL: positive -> left, negative -> right
|
|
910
|
+
*/
|
|
911
|
+
this.xOffset = 0;
|
|
912
|
+
/**
|
|
913
|
+
* Offsets the menu's block alignment from the anchor by the given number in
|
|
914
|
+
* pixels.
|
|
915
|
+
*
|
|
916
|
+
* e.g. positive -> down, negative -> up
|
|
917
|
+
*/
|
|
918
|
+
this.yOffset = 0;
|
|
919
|
+
/**
|
|
920
|
+
* Disable the `flip` behavior that usually happens on the horizontal axis
|
|
921
|
+
* when the surface would render outside the viewport.
|
|
922
|
+
*/
|
|
923
|
+
this.noHorizontalFlip = false;
|
|
924
|
+
/**
|
|
925
|
+
* Disable the `flip` behavior that usually happens on the vertical axis when
|
|
926
|
+
* the surface would render outside the viewport.
|
|
927
|
+
*/
|
|
928
|
+
this.noVerticalFlip = false;
|
|
929
|
+
/**
|
|
930
|
+
* The max time between the keystrokes of the typeahead menu behavior before
|
|
931
|
+
* it clears the typeahead buffer.
|
|
932
|
+
*/
|
|
933
|
+
this.typeaheadDelay = DEFAULT_TYPEAHEAD_BUFFER_TIME;
|
|
934
|
+
/**
|
|
935
|
+
* The corner of the anchor which to align the menu in the standard logical
|
|
936
|
+
* property style of <block>-<inline> e.g. `'end-start'`.
|
|
937
|
+
*
|
|
938
|
+
* NOTE: This value may not be respected by the menu positioning algorithm
|
|
939
|
+
* if the menu would render outisde the viewport.
|
|
940
|
+
* Use `no-horizontal-flip` or `no-vertical-flip` to force the usage of the value
|
|
941
|
+
*/
|
|
942
|
+
this.anchorCorner = Corner.END_START;
|
|
943
|
+
/**
|
|
944
|
+
* The corner of the menu which to align the anchor in the standard logical
|
|
945
|
+
* property style of <block>-<inline> e.g. `'start-start'`.
|
|
946
|
+
*
|
|
947
|
+
* NOTE: This value may not be respected by the menu positioning algorithm
|
|
948
|
+
* if the menu would render outisde the viewport.
|
|
949
|
+
* Use `no-horizontal-flip` or `no-vertical-flip` to force the usage of the value
|
|
950
|
+
*/
|
|
951
|
+
this.menuCorner = Corner.START_START;
|
|
952
|
+
/**
|
|
953
|
+
* Keeps the user clicks outside the menu.
|
|
954
|
+
*
|
|
955
|
+
* NOTE: clicking outside may still cause focusout to close the menu so see
|
|
956
|
+
* `stayOpenOnFocusout`.
|
|
957
|
+
*/
|
|
958
|
+
this.stayOpenOnOutsideClick = false;
|
|
959
|
+
/**
|
|
960
|
+
* Keeps the menu open when focus leaves the menu's composed subtree.
|
|
961
|
+
*
|
|
962
|
+
* NOTE: Focusout behavior will stop propagation of the focusout event. Set
|
|
963
|
+
* this property to true to opt-out of menu's focusout handling altogether.
|
|
964
|
+
*/
|
|
965
|
+
this.stayOpenOnFocusout = false;
|
|
966
|
+
/**
|
|
967
|
+
* After closing, does not restore focus to the last focused element before
|
|
968
|
+
* the menu was opened.
|
|
969
|
+
*/
|
|
970
|
+
this.skipRestoreFocus = false;
|
|
971
|
+
/**
|
|
972
|
+
* The element that should be focused by default once opened.
|
|
973
|
+
*
|
|
974
|
+
* NOTE: When setting default focus to 'LIST_ROOT', remember to change
|
|
975
|
+
* `tabindex` to `0` and change md-menu's display to something other than
|
|
976
|
+
* `display: contents` when necessary.
|
|
977
|
+
*/
|
|
978
|
+
this.defaultFocus = FocusState.FIRST_ITEM;
|
|
979
|
+
/**
|
|
980
|
+
* Turns off navigation wrapping. By default, navigating past the end of the
|
|
981
|
+
* menu items will wrap focus back to the beginning and vice versa. Use this
|
|
982
|
+
* for ARIA patterns that do not wrap focus, like combobox.
|
|
983
|
+
*/
|
|
984
|
+
this.noNavigationWrap = false;
|
|
985
|
+
this.typeaheadActive = true;
|
|
986
|
+
/**
|
|
987
|
+
* Whether or not the current menu is a submenu and should not handle specific
|
|
988
|
+
* navigation keys.
|
|
989
|
+
*
|
|
990
|
+
* @export
|
|
991
|
+
*/
|
|
992
|
+
this.isSubmenu = false;
|
|
993
|
+
/**
|
|
994
|
+
* The event path of the last window pointerdown event.
|
|
995
|
+
*/
|
|
996
|
+
this.pointerPath = [];
|
|
997
|
+
/**
|
|
998
|
+
* Whether or not the menu is repositoining due to window / document resize
|
|
999
|
+
*/
|
|
1000
|
+
this.isRepositioning = false;
|
|
1001
|
+
this.openCloseAnimationSignal = createAnimationSignal();
|
|
1002
|
+
this.listController = new ListController({
|
|
1003
|
+
isItem: maybeItem => {
|
|
1004
|
+
return maybeItem.hasAttribute('md-menu-item');
|
|
1005
|
+
},
|
|
1006
|
+
getPossibleItems: () => this.slotItems,
|
|
1007
|
+
isRtl: () => getComputedStyle(this).direction === 'rtl',
|
|
1008
|
+
deactivateItem: item => {
|
|
1009
|
+
item.selected = false;
|
|
1010
|
+
item.tabIndex = -1;
|
|
1011
|
+
},
|
|
1012
|
+
activateItem: item => {
|
|
1013
|
+
item.selected = true;
|
|
1014
|
+
item.tabIndex = 0;
|
|
1015
|
+
},
|
|
1016
|
+
isNavigableKey: key => {
|
|
1017
|
+
if (!this.isSubmenu) {
|
|
1018
|
+
return menuNavKeys.has(key);
|
|
1019
|
+
}
|
|
1020
|
+
const isRtl = getComputedStyle(this).direction === 'rtl';
|
|
1021
|
+
// we want md-submenu to handle the submenu's left/right arrow exit
|
|
1022
|
+
// key so it can close the menu instead of navigate the list.
|
|
1023
|
+
// Therefore we need to include all keys but left/right arrow close
|
|
1024
|
+
// key
|
|
1025
|
+
const arrowOpen = isRtl ? NavigableKeys.ArrowLeft : NavigableKeys.ArrowRight;
|
|
1026
|
+
if (key === arrowOpen) {
|
|
1027
|
+
return true;
|
|
1028
|
+
}
|
|
1029
|
+
return submenuNavKeys.has(key);
|
|
1030
|
+
},
|
|
1031
|
+
wrapNavigation: () => !this.noNavigationWrap
|
|
1032
|
+
});
|
|
1033
|
+
/**
|
|
1034
|
+
* The element that was focused before the menu opened.
|
|
1035
|
+
*/
|
|
1036
|
+
this.lastFocusedElement = null;
|
|
1037
|
+
/**
|
|
1038
|
+
* Handles typeahead navigation through the menu.
|
|
1039
|
+
*/
|
|
1040
|
+
this.typeaheadController = new TypeaheadController(() => {
|
|
1041
|
+
return {
|
|
1042
|
+
getItems: () => this.items,
|
|
1043
|
+
typeaheadBufferTime: this.typeaheadDelay,
|
|
1044
|
+
active: this.typeaheadActive
|
|
1045
|
+
};
|
|
1046
|
+
});
|
|
1047
|
+
this.currentAnchorElement = null;
|
|
1048
|
+
this.internals =
|
|
1049
|
+
// Cast needed for closure
|
|
1050
|
+
this.attachInternals();
|
|
1051
|
+
/**
|
|
1052
|
+
* Handles positioning the surface and aligning it to the anchor as well as
|
|
1053
|
+
* keeping it in the viewport.
|
|
1054
|
+
*/
|
|
1055
|
+
this.menuPositionController = new SurfacePositionController(this, () => {
|
|
1056
|
+
return {
|
|
1057
|
+
anchorCorner: this.anchorCorner,
|
|
1058
|
+
surfaceCorner: this.menuCorner,
|
|
1059
|
+
surfaceEl: this.surfaceEl,
|
|
1060
|
+
anchorEl: this.anchorElement,
|
|
1061
|
+
positioning: this.positioning === 'popover' ? 'document' : this.positioning,
|
|
1062
|
+
isOpen: this.open,
|
|
1063
|
+
xOffset: this.xOffset,
|
|
1064
|
+
yOffset: this.yOffset,
|
|
1065
|
+
disableBlockFlip: this.noVerticalFlip,
|
|
1066
|
+
disableInlineFlip: this.noHorizontalFlip,
|
|
1067
|
+
onOpen: this.onOpened,
|
|
1068
|
+
beforeClose: this.beforeClose,
|
|
1069
|
+
onClose: this.onClosed,
|
|
1070
|
+
// We can't resize components that have overflow like menus with
|
|
1071
|
+
// submenus because the overflow-y will show menu items / content
|
|
1072
|
+
// outside the bounds of the menu. Popover API fixes this because each
|
|
1073
|
+
// submenu is hoisted to the top-layer and are not considered overflow
|
|
1074
|
+
// content.
|
|
1075
|
+
repositionStrategy: this.hasOverflow && this.positioning !== 'popover' ? 'move' : 'resize'
|
|
1076
|
+
};
|
|
1077
|
+
});
|
|
1078
|
+
this.onWindowResize = () => {
|
|
1079
|
+
if (this.isRepositioning || this.positioning !== 'document' && this.positioning !== 'fixed' && this.positioning !== 'popover') {
|
|
1080
|
+
return;
|
|
1081
|
+
}
|
|
1082
|
+
this.isRepositioning = true;
|
|
1083
|
+
this.reposition();
|
|
1084
|
+
this.isRepositioning = false;
|
|
1085
|
+
};
|
|
1086
|
+
this.handleFocusout = async event => {
|
|
1087
|
+
const anchorEl = this.anchorElement;
|
|
1088
|
+
// Do not close if we focused out by clicking on the anchor element. We
|
|
1089
|
+
// can't assume anchor buttons can be the related target because of iOS does
|
|
1090
|
+
// not focus buttons.
|
|
1091
|
+
if (this.stayOpenOnFocusout || !this.open || this.pointerPath.includes(anchorEl)) {
|
|
1092
|
+
return;
|
|
1093
|
+
}
|
|
1094
|
+
if (event.relatedTarget) {
|
|
1095
|
+
// Don't close the menu if we are switching focus between menu,
|
|
1096
|
+
// md-menu-item, and md-list or if the anchor was click focused, but check
|
|
1097
|
+
// if length of pointerPath is 0 because that means something was at least
|
|
1098
|
+
// clicked (shift+tab case).
|
|
1099
|
+
if (isElementInSubtree(event.relatedTarget, this) || this.pointerPath.length !== 0 && isElementInSubtree(event.relatedTarget, anchorEl)) {
|
|
1100
|
+
return;
|
|
1101
|
+
}
|
|
1102
|
+
} else if (this.pointerPath.includes(this)) {
|
|
1103
|
+
// If menu tabindex == -1 and the user clicks on the menu or a divider, we
|
|
1104
|
+
// want to keep the menu open.
|
|
1105
|
+
return;
|
|
1106
|
+
}
|
|
1107
|
+
const oldRestoreFocus = this.skipRestoreFocus;
|
|
1108
|
+
// allow focus to continue to the next focused object rather than returning
|
|
1109
|
+
this.skipRestoreFocus = true;
|
|
1110
|
+
this.close();
|
|
1111
|
+
// await for close
|
|
1112
|
+
await this.updateComplete;
|
|
1113
|
+
// return to previous behavior
|
|
1114
|
+
this.skipRestoreFocus = oldRestoreFocus;
|
|
1115
|
+
};
|
|
1116
|
+
/**
|
|
1117
|
+
* Saves the last focused element focuses the new element based on
|
|
1118
|
+
* `defaultFocus`, and animates open.
|
|
1119
|
+
*/
|
|
1120
|
+
this.onOpened = async () => {
|
|
1121
|
+
this.lastFocusedElement = getFocusedElement();
|
|
1122
|
+
const items = this.items;
|
|
1123
|
+
const activeItemRecord = getActiveItem(items);
|
|
1124
|
+
if (activeItemRecord && this.defaultFocus !== FocusState.NONE) {
|
|
1125
|
+
activeItemRecord.item.tabIndex = -1;
|
|
1126
|
+
}
|
|
1127
|
+
let animationAborted = !this.quick;
|
|
1128
|
+
if (this.quick) {
|
|
1129
|
+
this.dispatchEvent(new Event('opening'));
|
|
1130
|
+
} else {
|
|
1131
|
+
animationAborted = !!(await this.animateOpen());
|
|
1132
|
+
}
|
|
1133
|
+
// This must come after the opening animation or else it may focus one of
|
|
1134
|
+
// the items before the animation has begun and causes the list to slide
|
|
1135
|
+
// (block-padding-of-the-menu)px at the end of the animation
|
|
1136
|
+
switch (this.defaultFocus) {
|
|
1137
|
+
case FocusState.FIRST_ITEM:
|
|
1138
|
+
const first = getFirstActivatableItem(items);
|
|
1139
|
+
if (first) {
|
|
1140
|
+
first.tabIndex = 0;
|
|
1141
|
+
first.focus();
|
|
1142
|
+
await first.updateComplete;
|
|
1143
|
+
}
|
|
1144
|
+
break;
|
|
1145
|
+
case FocusState.LAST_ITEM:
|
|
1146
|
+
const last = getLastActivatableItem(items);
|
|
1147
|
+
if (last) {
|
|
1148
|
+
last.tabIndex = 0;
|
|
1149
|
+
last.focus();
|
|
1150
|
+
await last.updateComplete;
|
|
1151
|
+
}
|
|
1152
|
+
break;
|
|
1153
|
+
case FocusState.LIST_ROOT:
|
|
1154
|
+
this.focus();
|
|
1155
|
+
break;
|
|
1156
|
+
default:
|
|
1157
|
+
case FocusState.NONE:
|
|
1158
|
+
// Do nothing.
|
|
1159
|
+
break;
|
|
1160
|
+
}
|
|
1161
|
+
if (!animationAborted) {
|
|
1162
|
+
this.dispatchEvent(new Event('opened'));
|
|
1163
|
+
}
|
|
1164
|
+
};
|
|
1165
|
+
/**
|
|
1166
|
+
* Animates closed.
|
|
1167
|
+
*/
|
|
1168
|
+
this.beforeClose = async () => {
|
|
1169
|
+
this.open = false;
|
|
1170
|
+
if (!this.skipRestoreFocus) {
|
|
1171
|
+
var _this$lastFocusedElem, _this$lastFocusedElem2;
|
|
1172
|
+
(_this$lastFocusedElem = this.lastFocusedElement) === null || _this$lastFocusedElem === void 0 || (_this$lastFocusedElem2 = _this$lastFocusedElem.focus) === null || _this$lastFocusedElem2 === void 0 || _this$lastFocusedElem2.call(_this$lastFocusedElem);
|
|
1173
|
+
}
|
|
1174
|
+
if (!this.quick) {
|
|
1175
|
+
await this.animateClose();
|
|
1176
|
+
}
|
|
1177
|
+
};
|
|
1178
|
+
/**
|
|
1179
|
+
* Focuses the last focused element.
|
|
1180
|
+
*/
|
|
1181
|
+
this.onClosed = () => {
|
|
1182
|
+
if (this.quick) {
|
|
1183
|
+
this.dispatchEvent(new Event('closing'));
|
|
1184
|
+
this.dispatchEvent(new Event('closed'));
|
|
1185
|
+
}
|
|
1186
|
+
};
|
|
1187
|
+
this.onWindowPointerdown = event => {
|
|
1188
|
+
this.pointerPath = event.composedPath();
|
|
1189
|
+
};
|
|
1190
|
+
/**
|
|
1191
|
+
* We cannot listen to window click because Safari on iOS will not bubble a
|
|
1192
|
+
* click event on window if the item clicked is not a "clickable" item such as
|
|
1193
|
+
* <body>
|
|
1194
|
+
*/
|
|
1195
|
+
this.onDocumentClick = event => {
|
|
1196
|
+
if (!this.open) {
|
|
1197
|
+
return;
|
|
1198
|
+
}
|
|
1199
|
+
const path = event.composedPath();
|
|
1200
|
+
if (!this.stayOpenOnOutsideClick && !path.includes(this) && !path.includes(this.anchorElement)) {
|
|
1201
|
+
this.open = false;
|
|
1202
|
+
}
|
|
1203
|
+
};
|
|
1204
|
+
{
|
|
1205
|
+
this.internals.role = 'menu';
|
|
1206
|
+
this.addEventListener('keydown', this.handleKeydown);
|
|
1207
|
+
// Capture so that we can grab the event before it reaches the menu item
|
|
1208
|
+
// istelf. Specifically useful for the case where typeahead encounters a
|
|
1209
|
+
// space and we don't want the menu item to close the menu.
|
|
1210
|
+
this.addEventListener('keydown', this.captureKeydown, {
|
|
1211
|
+
capture: true
|
|
1212
|
+
});
|
|
1213
|
+
this.addEventListener('focusout', this.handleFocusout);
|
|
1214
|
+
}
|
|
1215
|
+
}
|
|
1216
|
+
/**
|
|
1217
|
+
* The menu items associated with this menu. The items must be `MenuItem`s and
|
|
1218
|
+
* have both the `md-menu-item` and `md-list-item` attributes.
|
|
1219
|
+
*/
|
|
1220
|
+
get items() {
|
|
1221
|
+
return this.listController.items;
|
|
1222
|
+
}
|
|
1223
|
+
willUpdate(changed) {
|
|
1224
|
+
if (!changed.has('open')) {
|
|
1225
|
+
return;
|
|
1226
|
+
}
|
|
1227
|
+
if (this.open) {
|
|
1228
|
+
this.removeAttribute('aria-hidden');
|
|
1229
|
+
return;
|
|
1230
|
+
}
|
|
1231
|
+
this.setAttribute('aria-hidden', 'true');
|
|
1232
|
+
}
|
|
1233
|
+
update(changed) {
|
|
1234
|
+
if (changed.has('open')) {
|
|
1235
|
+
if (this.open) {
|
|
1236
|
+
this.setUpGlobalEventListeners();
|
|
1237
|
+
} else {
|
|
1238
|
+
this.cleanUpGlobalEventListeners();
|
|
1239
|
+
}
|
|
1240
|
+
}
|
|
1241
|
+
// Firefox does not support popover. Fall-back to using fixed.
|
|
1242
|
+
if (changed.has('positioning') && this.positioning === 'popover' &&
|
|
1243
|
+
// type required for Google JS conformance
|
|
1244
|
+
!this.showPopover) {
|
|
1245
|
+
this.positioning = 'fixed';
|
|
1246
|
+
}
|
|
1247
|
+
super.update(changed);
|
|
1248
|
+
}
|
|
1249
|
+
connectedCallback() {
|
|
1250
|
+
super.connectedCallback();
|
|
1251
|
+
if (this.open) {
|
|
1252
|
+
this.setUpGlobalEventListeners();
|
|
1253
|
+
}
|
|
1254
|
+
}
|
|
1255
|
+
disconnectedCallback() {
|
|
1256
|
+
super.disconnectedCallback();
|
|
1257
|
+
this.cleanUpGlobalEventListeners();
|
|
1258
|
+
}
|
|
1259
|
+
getBoundingClientRect() {
|
|
1260
|
+
if (!this.surfaceEl) {
|
|
1261
|
+
return super.getBoundingClientRect();
|
|
1262
|
+
}
|
|
1263
|
+
return this.surfaceEl.getBoundingClientRect();
|
|
1264
|
+
}
|
|
1265
|
+
getClientRects() {
|
|
1266
|
+
if (!this.surfaceEl) {
|
|
1267
|
+
return super.getClientRects();
|
|
1268
|
+
}
|
|
1269
|
+
return this.surfaceEl.getClientRects();
|
|
1270
|
+
}
|
|
1271
|
+
render() {
|
|
1272
|
+
return this.renderSurface();
|
|
1273
|
+
}
|
|
1274
|
+
/**
|
|
1275
|
+
* Renders the positionable surface element and its contents.
|
|
1276
|
+
*/
|
|
1277
|
+
renderSurface() {
|
|
1278
|
+
return b`
|
|
1279
|
+
<div
|
|
1280
|
+
class="menu ${e$2(this.getSurfaceClasses())}"
|
|
1281
|
+
style=${o$1(this.menuPositionController.surfaceStyles)}
|
|
1282
|
+
popover=${this.positioning === 'popover' ? 'manual' : A}>
|
|
1283
|
+
${this.renderElevation()}
|
|
1284
|
+
<div class="items">
|
|
1285
|
+
<div class="item-padding"> ${this.renderMenuItems()} </div>
|
|
1286
|
+
</div>
|
|
1287
|
+
</div>
|
|
1288
|
+
`;
|
|
1289
|
+
}
|
|
1290
|
+
/**
|
|
1291
|
+
* Renders the menu items' slot
|
|
1292
|
+
*/
|
|
1293
|
+
renderMenuItems() {
|
|
1294
|
+
return b`<slot
|
|
1295
|
+
@close-menu=${this.onCloseMenu}
|
|
1296
|
+
@deactivate-items=${this.onDeactivateItems}
|
|
1297
|
+
@request-activation=${this.onRequestActivation}
|
|
1298
|
+
@deactivate-typeahead=${this.handleDeactivateTypeahead}
|
|
1299
|
+
@activate-typeahead=${this.handleActivateTypeahead}
|
|
1300
|
+
@stay-open-on-focusout=${this.handleStayOpenOnFocusout}
|
|
1301
|
+
@close-on-focusout=${this.handleCloseOnFocusout}
|
|
1302
|
+
@slotchange=${this.listController.onSlotchange}></slot>`;
|
|
1303
|
+
}
|
|
1304
|
+
/**
|
|
1305
|
+
* Renders the elevation component.
|
|
1306
|
+
*/
|
|
1307
|
+
renderElevation() {
|
|
1308
|
+
return b`<md-elevation part="elevation"></md-elevation>`;
|
|
1309
|
+
}
|
|
1310
|
+
getSurfaceClasses() {
|
|
1311
|
+
return {
|
|
1312
|
+
open: this.open,
|
|
1313
|
+
fixed: this.positioning === 'fixed',
|
|
1314
|
+
'has-overflow': this.hasOverflow
|
|
1315
|
+
};
|
|
1316
|
+
}
|
|
1317
|
+
captureKeydown(event) {
|
|
1318
|
+
if (event.target === this && !event.defaultPrevented && isClosableKey(event.code)) {
|
|
1319
|
+
event.preventDefault();
|
|
1320
|
+
this.close();
|
|
1321
|
+
}
|
|
1322
|
+
this.typeaheadController.onKeydown(event);
|
|
1323
|
+
}
|
|
1324
|
+
/**
|
|
1325
|
+
* Performs the opening animation:
|
|
1326
|
+
*
|
|
1327
|
+
* https://direct.googleplex.com/#/spec/295000003+271060003
|
|
1328
|
+
*
|
|
1329
|
+
* @return A promise that resolve to `true` if the animation was aborted,
|
|
1330
|
+
* `false` if it was not aborted.
|
|
1331
|
+
*/
|
|
1332
|
+
async animateOpen() {
|
|
1333
|
+
const surfaceEl = this.surfaceEl;
|
|
1334
|
+
const slotEl = this.slotEl;
|
|
1335
|
+
if (!surfaceEl || !slotEl) return true;
|
|
1336
|
+
const openDirection = this.openDirection;
|
|
1337
|
+
this.dispatchEvent(new Event('opening'));
|
|
1338
|
+
// needs to be imperative because we don't want to mix animation and Lit
|
|
1339
|
+
// render timing
|
|
1340
|
+
surfaceEl.classList.toggle('animating', true);
|
|
1341
|
+
const signal = this.openCloseAnimationSignal.start();
|
|
1342
|
+
const height = surfaceEl.offsetHeight;
|
|
1343
|
+
const openingUpwards = openDirection === 'UP';
|
|
1344
|
+
const children = this.items;
|
|
1345
|
+
const FULL_DURATION = 500;
|
|
1346
|
+
const SURFACE_OPACITY_DURATION = 50;
|
|
1347
|
+
const ITEM_OPACITY_DURATION = 250;
|
|
1348
|
+
// We want to fit every child fade-in animation within the full duration of
|
|
1349
|
+
// the animation.
|
|
1350
|
+
const DELAY_BETWEEN_ITEMS = (FULL_DURATION - ITEM_OPACITY_DURATION) / children.length;
|
|
1351
|
+
const surfaceHeightAnimation = surfaceEl.animate([{
|
|
1352
|
+
height: '0px'
|
|
1353
|
+
}, {
|
|
1354
|
+
height: `${height}px`
|
|
1355
|
+
}], {
|
|
1356
|
+
duration: FULL_DURATION,
|
|
1357
|
+
easing: EASING.EMPHASIZED
|
|
1358
|
+
});
|
|
1359
|
+
// When we are opening upwards, we want to make sure the last item is always
|
|
1360
|
+
// in view, so we need to translate it upwards the opposite direction of the
|
|
1361
|
+
// height animation
|
|
1362
|
+
const upPositionCorrectionAnimation = slotEl.animate([{
|
|
1363
|
+
transform: openingUpwards ? `translateY(-${height}px)` : ''
|
|
1364
|
+
}, {
|
|
1365
|
+
transform: ''
|
|
1366
|
+
}], {
|
|
1367
|
+
duration: FULL_DURATION,
|
|
1368
|
+
easing: EASING.EMPHASIZED
|
|
1369
|
+
});
|
|
1370
|
+
const surfaceOpacityAnimation = surfaceEl.animate([{
|
|
1371
|
+
opacity: 0
|
|
1372
|
+
}, {
|
|
1373
|
+
opacity: 1
|
|
1374
|
+
}], SURFACE_OPACITY_DURATION);
|
|
1375
|
+
const childrenAnimations = [];
|
|
1376
|
+
for (let i = 0; i < children.length; i++) {
|
|
1377
|
+
// If we are animating upwards, then reverse the children list.
|
|
1378
|
+
const directionalIndex = openingUpwards ? children.length - 1 - i : i;
|
|
1379
|
+
const child = children[directionalIndex];
|
|
1380
|
+
const animation = child.animate([{
|
|
1381
|
+
opacity: 0
|
|
1382
|
+
}, {
|
|
1383
|
+
opacity: 1
|
|
1384
|
+
}], {
|
|
1385
|
+
duration: ITEM_OPACITY_DURATION,
|
|
1386
|
+
delay: DELAY_BETWEEN_ITEMS * i
|
|
1387
|
+
});
|
|
1388
|
+
// Make them all initially hidden and then clean up at the end of each
|
|
1389
|
+
// animation.
|
|
1390
|
+
child.classList.toggle('md-menu-hidden', true);
|
|
1391
|
+
animation.addEventListener('finish', () => {
|
|
1392
|
+
child.classList.toggle('md-menu-hidden', false);
|
|
1393
|
+
});
|
|
1394
|
+
childrenAnimations.push([child, animation]);
|
|
1395
|
+
}
|
|
1396
|
+
let resolveAnimation = value => {};
|
|
1397
|
+
const animationFinished = new Promise(resolve => {
|
|
1398
|
+
resolveAnimation = resolve;
|
|
1399
|
+
});
|
|
1400
|
+
signal.addEventListener('abort', () => {
|
|
1401
|
+
surfaceHeightAnimation.cancel();
|
|
1402
|
+
upPositionCorrectionAnimation.cancel();
|
|
1403
|
+
surfaceOpacityAnimation.cancel();
|
|
1404
|
+
childrenAnimations.forEach(([child, animation]) => {
|
|
1405
|
+
child.classList.toggle('md-menu-hidden', false);
|
|
1406
|
+
animation.cancel();
|
|
1407
|
+
});
|
|
1408
|
+
resolveAnimation(true);
|
|
1409
|
+
});
|
|
1410
|
+
surfaceHeightAnimation.addEventListener('finish', () => {
|
|
1411
|
+
surfaceEl.classList.toggle('animating', false);
|
|
1412
|
+
this.openCloseAnimationSignal.finish();
|
|
1413
|
+
resolveAnimation(false);
|
|
1414
|
+
});
|
|
1415
|
+
return await animationFinished;
|
|
1416
|
+
}
|
|
1417
|
+
/**
|
|
1418
|
+
* Performs the closing animation:
|
|
1419
|
+
*
|
|
1420
|
+
* https://direct.googleplex.com/#/spec/295000003+271060003
|
|
1421
|
+
*/
|
|
1422
|
+
animateClose() {
|
|
1423
|
+
let resolve;
|
|
1424
|
+
// This promise blocks the surface position controller from setting
|
|
1425
|
+
// display: none on the surface which will interfere with this animation.
|
|
1426
|
+
const animationEnded = new Promise(res => {
|
|
1427
|
+
resolve = res;
|
|
1428
|
+
});
|
|
1429
|
+
const surfaceEl = this.surfaceEl;
|
|
1430
|
+
const slotEl = this.slotEl;
|
|
1431
|
+
if (!surfaceEl || !slotEl) {
|
|
1432
|
+
resolve(false);
|
|
1433
|
+
return animationEnded;
|
|
1434
|
+
}
|
|
1435
|
+
const openDirection = this.openDirection;
|
|
1436
|
+
const closingDownwards = openDirection === 'UP';
|
|
1437
|
+
this.dispatchEvent(new Event('closing'));
|
|
1438
|
+
// needs to be imperative because we don't want to mix animation and Lit
|
|
1439
|
+
// render timing
|
|
1440
|
+
surfaceEl.classList.toggle('animating', true);
|
|
1441
|
+
const signal = this.openCloseAnimationSignal.start();
|
|
1442
|
+
const height = surfaceEl.offsetHeight;
|
|
1443
|
+
const children = this.items;
|
|
1444
|
+
const FULL_DURATION = 150;
|
|
1445
|
+
const SURFACE_OPACITY_DURATION = 50;
|
|
1446
|
+
// The surface fades away at the very end
|
|
1447
|
+
const SURFACE_OPACITY_DELAY = FULL_DURATION - SURFACE_OPACITY_DURATION;
|
|
1448
|
+
const ITEM_OPACITY_DURATION = 50;
|
|
1449
|
+
const ITEM_OPACITY_INITIAL_DELAY = 50;
|
|
1450
|
+
const END_HEIGHT_PERCENTAGE = 0.35;
|
|
1451
|
+
// We want to fit every child fade-out animation within the full duration of
|
|
1452
|
+
// the animation.
|
|
1453
|
+
const DELAY_BETWEEN_ITEMS = (FULL_DURATION - ITEM_OPACITY_INITIAL_DELAY - ITEM_OPACITY_DURATION) / children.length;
|
|
1454
|
+
// The mock has the animation shrink to 35%
|
|
1455
|
+
const surfaceHeightAnimation = surfaceEl.animate([{
|
|
1456
|
+
height: `${height}px`
|
|
1457
|
+
}, {
|
|
1458
|
+
height: `${height * END_HEIGHT_PERCENTAGE}px`
|
|
1459
|
+
}], {
|
|
1460
|
+
duration: FULL_DURATION,
|
|
1461
|
+
easing: EASING.EMPHASIZED_ACCELERATE
|
|
1462
|
+
});
|
|
1463
|
+
// When we are closing downwards, we want to make sure the last item is
|
|
1464
|
+
// always in view, so we need to translate it upwards the opposite direction
|
|
1465
|
+
// of the height animation
|
|
1466
|
+
const downPositionCorrectionAnimation = slotEl.animate([{
|
|
1467
|
+
transform: ''
|
|
1468
|
+
}, {
|
|
1469
|
+
transform: closingDownwards ? `translateY(-${height * (1 - END_HEIGHT_PERCENTAGE)}px)` : ''
|
|
1470
|
+
}], {
|
|
1471
|
+
duration: FULL_DURATION,
|
|
1472
|
+
easing: EASING.EMPHASIZED_ACCELERATE
|
|
1473
|
+
});
|
|
1474
|
+
const surfaceOpacityAnimation = surfaceEl.animate([{
|
|
1475
|
+
opacity: 1
|
|
1476
|
+
}, {
|
|
1477
|
+
opacity: 0
|
|
1478
|
+
}], {
|
|
1479
|
+
duration: SURFACE_OPACITY_DURATION,
|
|
1480
|
+
delay: SURFACE_OPACITY_DELAY
|
|
1481
|
+
});
|
|
1482
|
+
const childrenAnimations = [];
|
|
1483
|
+
for (let i = 0; i < children.length; i++) {
|
|
1484
|
+
// If the animation is closing upwards, then reverse the list of
|
|
1485
|
+
// children so that we animate in the opposite direction.
|
|
1486
|
+
const directionalIndex = closingDownwards ? i : children.length - 1 - i;
|
|
1487
|
+
const child = children[directionalIndex];
|
|
1488
|
+
const animation = child.animate([{
|
|
1489
|
+
opacity: 1
|
|
1490
|
+
}, {
|
|
1491
|
+
opacity: 0
|
|
1492
|
+
}], {
|
|
1493
|
+
duration: ITEM_OPACITY_DURATION,
|
|
1494
|
+
delay: ITEM_OPACITY_INITIAL_DELAY + DELAY_BETWEEN_ITEMS * i
|
|
1495
|
+
});
|
|
1496
|
+
// Make sure the items stay hidden at the end of each child animation.
|
|
1497
|
+
// We clean this up at the end of the overall animation.
|
|
1498
|
+
animation.addEventListener('finish', () => {
|
|
1499
|
+
child.classList.toggle('md-menu-hidden', true);
|
|
1500
|
+
});
|
|
1501
|
+
childrenAnimations.push([child, animation]);
|
|
1502
|
+
}
|
|
1503
|
+
signal.addEventListener('abort', () => {
|
|
1504
|
+
surfaceHeightAnimation.cancel();
|
|
1505
|
+
downPositionCorrectionAnimation.cancel();
|
|
1506
|
+
surfaceOpacityAnimation.cancel();
|
|
1507
|
+
childrenAnimations.forEach(([child, animation]) => {
|
|
1508
|
+
animation.cancel();
|
|
1509
|
+
child.classList.toggle('md-menu-hidden', false);
|
|
1510
|
+
});
|
|
1511
|
+
resolve(false);
|
|
1512
|
+
});
|
|
1513
|
+
surfaceHeightAnimation.addEventListener('finish', () => {
|
|
1514
|
+
surfaceEl.classList.toggle('animating', false);
|
|
1515
|
+
childrenAnimations.forEach(([child]) => {
|
|
1516
|
+
child.classList.toggle('md-menu-hidden', false);
|
|
1517
|
+
});
|
|
1518
|
+
this.openCloseAnimationSignal.finish();
|
|
1519
|
+
this.dispatchEvent(new Event('closed'));
|
|
1520
|
+
resolve(true);
|
|
1521
|
+
});
|
|
1522
|
+
return animationEnded;
|
|
1523
|
+
}
|
|
1524
|
+
handleKeydown(event) {
|
|
1525
|
+
// At any key event, the pointer interaction is done so we need to clear our
|
|
1526
|
+
// cached pointerpath. This handles the case where the user clicks on the
|
|
1527
|
+
// anchor, and then hits shift+tab
|
|
1528
|
+
this.pointerPath = [];
|
|
1529
|
+
this.listController.handleKeydown(event);
|
|
1530
|
+
}
|
|
1531
|
+
setUpGlobalEventListeners() {
|
|
1532
|
+
document.addEventListener('click', this.onDocumentClick, {
|
|
1533
|
+
capture: true
|
|
1534
|
+
});
|
|
1535
|
+
window.addEventListener('pointerdown', this.onWindowPointerdown);
|
|
1536
|
+
document.addEventListener('resize', this.onWindowResize, {
|
|
1537
|
+
passive: true
|
|
1538
|
+
});
|
|
1539
|
+
window.addEventListener('resize', this.onWindowResize, {
|
|
1540
|
+
passive: true
|
|
1541
|
+
});
|
|
1542
|
+
}
|
|
1543
|
+
cleanUpGlobalEventListeners() {
|
|
1544
|
+
document.removeEventListener('click', this.onDocumentClick, {
|
|
1545
|
+
capture: true
|
|
1546
|
+
});
|
|
1547
|
+
window.removeEventListener('pointerdown', this.onWindowPointerdown);
|
|
1548
|
+
document.removeEventListener('resize', this.onWindowResize);
|
|
1549
|
+
window.removeEventListener('resize', this.onWindowResize);
|
|
1550
|
+
}
|
|
1551
|
+
onCloseMenu() {
|
|
1552
|
+
this.close();
|
|
1553
|
+
}
|
|
1554
|
+
onDeactivateItems(event) {
|
|
1555
|
+
event.stopPropagation();
|
|
1556
|
+
this.listController.onDeactivateItems();
|
|
1557
|
+
}
|
|
1558
|
+
onRequestActivation(event) {
|
|
1559
|
+
event.stopPropagation();
|
|
1560
|
+
this.listController.onRequestActivation(event);
|
|
1561
|
+
}
|
|
1562
|
+
handleDeactivateTypeahead(event) {
|
|
1563
|
+
// stopPropagation so that this does not deactivate any typeaheads in menus
|
|
1564
|
+
// nested above it e.g. md-sub-menu
|
|
1565
|
+
event.stopPropagation();
|
|
1566
|
+
this.typeaheadActive = false;
|
|
1567
|
+
}
|
|
1568
|
+
handleActivateTypeahead(event) {
|
|
1569
|
+
// stopPropagation so that this does not activate any typeaheads in menus
|
|
1570
|
+
// nested above it e.g. md-sub-menu
|
|
1571
|
+
event.stopPropagation();
|
|
1572
|
+
this.typeaheadActive = true;
|
|
1573
|
+
}
|
|
1574
|
+
handleStayOpenOnFocusout(event) {
|
|
1575
|
+
event.stopPropagation();
|
|
1576
|
+
this.stayOpenOnFocusout = true;
|
|
1577
|
+
}
|
|
1578
|
+
handleCloseOnFocusout(event) {
|
|
1579
|
+
event.stopPropagation();
|
|
1580
|
+
this.stayOpenOnFocusout = false;
|
|
1581
|
+
}
|
|
1582
|
+
close() {
|
|
1583
|
+
this.open = false;
|
|
1584
|
+
const maybeSubmenu = this.slotItems;
|
|
1585
|
+
maybeSubmenu.forEach(item => {
|
|
1586
|
+
var _item$close;
|
|
1587
|
+
(_item$close = item.close) === null || _item$close === void 0 || _item$close.call(item);
|
|
1588
|
+
});
|
|
1589
|
+
}
|
|
1590
|
+
show() {
|
|
1591
|
+
this.open = true;
|
|
1592
|
+
}
|
|
1593
|
+
/**
|
|
1594
|
+
* Activates the next item in the menu. If at the end of the menu, the first
|
|
1595
|
+
* item will be activated.
|
|
1596
|
+
*
|
|
1597
|
+
* @return The activated menu item or `null` if there are no items.
|
|
1598
|
+
*/
|
|
1599
|
+
activateNextItem() {
|
|
1600
|
+
return this.listController.activateNextItem() ?? null;
|
|
1601
|
+
}
|
|
1602
|
+
/**
|
|
1603
|
+
* Activates the previous item in the menu. If at the start of the menu, the
|
|
1604
|
+
* last item will be activated.
|
|
1605
|
+
*
|
|
1606
|
+
* @return The activated menu item or `null` if there are no items.
|
|
1607
|
+
*/
|
|
1608
|
+
activatePreviousItem() {
|
|
1609
|
+
return this.listController.activatePreviousItem() ?? null;
|
|
1610
|
+
}
|
|
1611
|
+
/**
|
|
1612
|
+
* Repositions the menu if it is open.
|
|
1613
|
+
*
|
|
1614
|
+
* Useful for the case where document or window-positioned menus have their
|
|
1615
|
+
* anchors moved while open.
|
|
1616
|
+
*/
|
|
1617
|
+
reposition() {
|
|
1618
|
+
if (this.open) {
|
|
1619
|
+
this.menuPositionController.position();
|
|
1620
|
+
}
|
|
1621
|
+
}
|
|
1622
|
+
}
|
|
1623
|
+
__decorate([e$1('.menu')], Menu.prototype, "surfaceEl", void 0);
|
|
1624
|
+
__decorate([e$1('slot')], Menu.prototype, "slotEl", void 0);
|
|
1625
|
+
__decorate([n$1()], Menu.prototype, "anchor", void 0);
|
|
1626
|
+
__decorate([n$1()], Menu.prototype, "positioning", void 0);
|
|
1627
|
+
__decorate([n$1({
|
|
1628
|
+
type: Boolean
|
|
1629
|
+
})], Menu.prototype, "quick", void 0);
|
|
1630
|
+
__decorate([n$1({
|
|
1631
|
+
type: Boolean,
|
|
1632
|
+
attribute: 'has-overflow'
|
|
1633
|
+
})], Menu.prototype, "hasOverflow", void 0);
|
|
1634
|
+
__decorate([n$1({
|
|
1635
|
+
type: Boolean,
|
|
1636
|
+
reflect: true
|
|
1637
|
+
})], Menu.prototype, "open", void 0);
|
|
1638
|
+
__decorate([n$1({
|
|
1639
|
+
type: Number,
|
|
1640
|
+
attribute: 'x-offset'
|
|
1641
|
+
})], Menu.prototype, "xOffset", void 0);
|
|
1642
|
+
__decorate([n$1({
|
|
1643
|
+
type: Number,
|
|
1644
|
+
attribute: 'y-offset'
|
|
1645
|
+
})], Menu.prototype, "yOffset", void 0);
|
|
1646
|
+
__decorate([n$1({
|
|
1647
|
+
type: Boolean,
|
|
1648
|
+
attribute: 'no-horizontal-flip'
|
|
1649
|
+
})], Menu.prototype, "noHorizontalFlip", void 0);
|
|
1650
|
+
__decorate([n$1({
|
|
1651
|
+
type: Boolean,
|
|
1652
|
+
attribute: 'no-vertical-flip'
|
|
1653
|
+
})], Menu.prototype, "noVerticalFlip", void 0);
|
|
1654
|
+
__decorate([n$1({
|
|
1655
|
+
type: Number,
|
|
1656
|
+
attribute: 'typeahead-delay'
|
|
1657
|
+
})], Menu.prototype, "typeaheadDelay", void 0);
|
|
1658
|
+
__decorate([n$1({
|
|
1659
|
+
attribute: 'anchor-corner'
|
|
1660
|
+
})], Menu.prototype, "anchorCorner", void 0);
|
|
1661
|
+
__decorate([n$1({
|
|
1662
|
+
attribute: 'menu-corner'
|
|
1663
|
+
})], Menu.prototype, "menuCorner", void 0);
|
|
1664
|
+
__decorate([n$1({
|
|
1665
|
+
type: Boolean,
|
|
1666
|
+
attribute: 'stay-open-on-outside-click'
|
|
1667
|
+
})], Menu.prototype, "stayOpenOnOutsideClick", void 0);
|
|
1668
|
+
__decorate([n$1({
|
|
1669
|
+
type: Boolean,
|
|
1670
|
+
attribute: 'stay-open-on-focusout'
|
|
1671
|
+
})], Menu.prototype, "stayOpenOnFocusout", void 0);
|
|
1672
|
+
__decorate([n$1({
|
|
1673
|
+
type: Boolean,
|
|
1674
|
+
attribute: 'skip-restore-focus'
|
|
1675
|
+
})], Menu.prototype, "skipRestoreFocus", void 0);
|
|
1676
|
+
__decorate([n$1({
|
|
1677
|
+
attribute: 'default-focus'
|
|
1678
|
+
})], Menu.prototype, "defaultFocus", void 0);
|
|
1679
|
+
__decorate([n$1({
|
|
1680
|
+
type: Boolean,
|
|
1681
|
+
attribute: 'no-navigation-wrap'
|
|
1682
|
+
})], Menu.prototype, "noNavigationWrap", void 0);
|
|
1683
|
+
__decorate([o({
|
|
1684
|
+
flatten: true
|
|
1685
|
+
})], Menu.prototype, "slotItems", void 0);
|
|
1686
|
+
__decorate([r()], Menu.prototype, "typeaheadActive", void 0);
|
|
1687
|
+
|
|
1688
|
+
/**
|
|
1689
|
+
* @license
|
|
1690
|
+
* Copyright 2024 Google LLC
|
|
1691
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
1692
|
+
*/
|
|
1693
|
+
// Generated stylesheet for ./menu/internal/menu-styles.css.
|
|
1694
|
+
const styles$3 = i$1`:host{--md-elevation-level: var(--md-menu-container-elevation, 2);--md-elevation-shadow-color: var(--md-menu-container-shadow-color, var(--md-sys-color-shadow, #000));min-width:112px;color:unset;display:contents}md-focus-ring{--md-focus-ring-shape: var(--md-menu-container-shape, var(--md-sys-shape-corner-extra-small, 4px))}.menu{border-radius:var(--md-menu-container-shape, var(--md-sys-shape-corner-extra-small, 4px));display:none;inset:auto;border:none;padding:0px;overflow:visible;background-color:rgba(0,0,0,0);color:inherit;opacity:0;z-index:20;position:absolute;user-select:none;max-height:inherit;height:inherit;min-width:inherit;max-width:inherit;scrollbar-width:inherit}.menu::backdrop{display:none}.fixed{position:fixed}.items{display:block;list-style-type:none;margin:0;outline:none;box-sizing:border-box;background-color:var(--md-menu-container-color, var(--md-sys-color-surface-container, #f3edf7));height:inherit;max-height:inherit;overflow:auto;min-width:inherit;max-width:inherit;border-radius:inherit;scrollbar-width:inherit}.item-padding{padding-block:var(--md-menu-top-space, 8px) var(--md-menu-bottom-space, 8px)}.has-overflow:not([popover]) .items{overflow:visible}.has-overflow.animating .items,.animating .items{overflow:hidden}.has-overflow.animating .items{pointer-events:none}.animating ::slotted(.md-menu-hidden){opacity:0}slot{display:block;height:inherit;max-height:inherit}::slotted(:is(md-divider,[role=separator])){margin:8px 0}@media(forced-colors: active){.menu{border-style:solid;border-color:CanvasText;border-width:1px}}
|
|
1695
|
+
`;
|
|
1696
|
+
|
|
1697
|
+
/**
|
|
1698
|
+
* @license
|
|
1699
|
+
* Copyright 2022 Google LLC
|
|
1700
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
1701
|
+
*/
|
|
1702
|
+
/**
|
|
1703
|
+
* @summary Menus display a list of choices on a temporary surface.
|
|
1704
|
+
*
|
|
1705
|
+
* @description
|
|
1706
|
+
* Menus appear when users interact with a button, action, or other control.
|
|
1707
|
+
*
|
|
1708
|
+
* They can be opened from a variety of elements, most commonly icon buttons,
|
|
1709
|
+
* buttons, and text fields.
|
|
1710
|
+
*
|
|
1711
|
+
* md-menu listens for the `close-menu` and `deselect-items` events.
|
|
1712
|
+
*
|
|
1713
|
+
* - `close-menu` closes the menu when dispatched from a child element.
|
|
1714
|
+
* - `deselect-items` deselects all of its immediate menu-item children.
|
|
1715
|
+
*
|
|
1716
|
+
* @example
|
|
1717
|
+
* ```html
|
|
1718
|
+
* <div style="position:relative;">
|
|
1719
|
+
* <button
|
|
1720
|
+
* id="anchor"
|
|
1721
|
+
* @click=${() => this.menuRef.value.show()}>
|
|
1722
|
+
* Click to open menu
|
|
1723
|
+
* </button>
|
|
1724
|
+
* <!--
|
|
1725
|
+
* `has-overflow` is required when using a submenu which overflows the
|
|
1726
|
+
* menu's contents.
|
|
1727
|
+
*
|
|
1728
|
+
* Additionally, `anchor` ingests an idref which do not pass through shadow
|
|
1729
|
+
* roots. You can also set `.anchorElement` to an element reference if
|
|
1730
|
+
* necessary.
|
|
1731
|
+
* -->
|
|
1732
|
+
* <md-menu anchor="anchor" has-overflow ${ref(menuRef)}>
|
|
1733
|
+
* <md-menu-item headline="This is a headline"></md-menu-item>
|
|
1734
|
+
* <md-sub-menu>
|
|
1735
|
+
* <md-menu-item
|
|
1736
|
+
* slot="item"
|
|
1737
|
+
* headline="this is a submenu item">
|
|
1738
|
+
* </md-menu-item>
|
|
1739
|
+
* <md-menu slot="menu">
|
|
1740
|
+
* <md-menu-item headline="This is an item inside a submenu">
|
|
1741
|
+
* </md-menu-item>
|
|
1742
|
+
* </md-menu>
|
|
1743
|
+
* </md-sub-menu>
|
|
1744
|
+
* </md-menu>
|
|
1745
|
+
* </div>
|
|
1746
|
+
* ```
|
|
1747
|
+
*
|
|
1748
|
+
* @final
|
|
1749
|
+
* @suppress {visibility}
|
|
1750
|
+
*/
|
|
1751
|
+
let MdMenu = class MdMenu extends Menu {};
|
|
1752
|
+
MdMenu.styles = [styles$3];
|
|
1753
|
+
MdMenu = __decorate([t('md-menu')], MdMenu);
|
|
1754
|
+
|
|
1755
|
+
/**
|
|
1756
|
+
* @license
|
|
1757
|
+
* Copyright 2023 Google LLC
|
|
1758
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
1759
|
+
*/
|
|
1760
|
+
/**
|
|
1761
|
+
* A validator that provides constraint validation that emulates `<select>`
|
|
1762
|
+
* validation.
|
|
1763
|
+
*/
|
|
1764
|
+
class SelectValidator extends Validator {
|
|
1765
|
+
computeValidity(state) {
|
|
1766
|
+
if (!this.selectControl) {
|
|
1767
|
+
// Lazily create the platform select
|
|
1768
|
+
this.selectControl = document.createElement('select');
|
|
1769
|
+
}
|
|
1770
|
+
D(b`<option value=${state.value}></option>`, this.selectControl);
|
|
1771
|
+
this.selectControl.value = state.value;
|
|
1772
|
+
this.selectControl.required = state.required;
|
|
1773
|
+
return {
|
|
1774
|
+
validity: this.selectControl.validity,
|
|
1775
|
+
validationMessage: this.selectControl.validationMessage
|
|
1776
|
+
};
|
|
1777
|
+
}
|
|
1778
|
+
equals(prev, next) {
|
|
1779
|
+
return prev.value === next.value && prev.required === next.required;
|
|
1780
|
+
}
|
|
1781
|
+
copy({
|
|
1782
|
+
value,
|
|
1783
|
+
required
|
|
1784
|
+
}) {
|
|
1785
|
+
return {
|
|
1786
|
+
value,
|
|
1787
|
+
required
|
|
1788
|
+
};
|
|
1789
|
+
}
|
|
1790
|
+
}
|
|
1791
|
+
|
|
1792
|
+
/**
|
|
1793
|
+
* @license
|
|
1794
|
+
* Copyright 2023 Google LLC
|
|
1795
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
1796
|
+
*/
|
|
1797
|
+
/**
|
|
1798
|
+
* Given a list of select options, this function will return an array of
|
|
1799
|
+
* SelectOptionRecords that are selected.
|
|
1800
|
+
*
|
|
1801
|
+
* @return An array of SelectOptionRecords describing the options that are
|
|
1802
|
+
* selected.
|
|
1803
|
+
*/
|
|
1804
|
+
function getSelectedItems(items) {
|
|
1805
|
+
const selectedItemRecords = [];
|
|
1806
|
+
for (let i = 0; i < items.length; i++) {
|
|
1807
|
+
const item = items[i];
|
|
1808
|
+
if (item.selected) {
|
|
1809
|
+
selectedItemRecords.push([item, i]);
|
|
1810
|
+
}
|
|
1811
|
+
}
|
|
1812
|
+
return selectedItemRecords;
|
|
1813
|
+
}
|
|
1814
|
+
|
|
1815
|
+
/**
|
|
1816
|
+
* @license
|
|
1817
|
+
* Copyright 2023 Google LLC
|
|
1818
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
1819
|
+
*/
|
|
1820
|
+
var _a;
|
|
1821
|
+
const VALUE = Symbol('value');
|
|
1822
|
+
// Separate variable needed for closure.
|
|
1823
|
+
const selectBaseClass = mixinDelegatesAria(mixinOnReportValidity(mixinConstraintValidation(mixinFormAssociated(mixinElementInternals(i)))));
|
|
1824
|
+
/**
|
|
1825
|
+
* @fires change {Event} The native `change` event on
|
|
1826
|
+
* [`<input>`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/change_event)
|
|
1827
|
+
* --bubbles
|
|
1828
|
+
* @fires input {InputEvent} The native `input` event on
|
|
1829
|
+
* [`<input>`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/input_event)
|
|
1830
|
+
* --bubbles --composed
|
|
1831
|
+
* @fires opening {Event} Fired when the select's menu is about to open.
|
|
1832
|
+
* @fires opened {Event} Fired when the select's menu has finished animations
|
|
1833
|
+
* and opened.
|
|
1834
|
+
* @fires closing {Event} Fired when the select's menu is about to close.
|
|
1835
|
+
* @fires closed {Event} Fired when the select's menu has finished animations
|
|
1836
|
+
* and closed.
|
|
1837
|
+
*/
|
|
1838
|
+
class Select extends selectBaseClass {
|
|
1839
|
+
/**
|
|
1840
|
+
* The value of the currently selected option.
|
|
1841
|
+
*
|
|
1842
|
+
* Note: For SSR, set `[selected]` on the requested option and `displayText`
|
|
1843
|
+
* rather than setting `value` setting `value` will incur a DOM query.
|
|
1844
|
+
*/
|
|
1845
|
+
get value() {
|
|
1846
|
+
return this[VALUE];
|
|
1847
|
+
}
|
|
1848
|
+
set value(value) {
|
|
1849
|
+
this.lastUserSetValue = value;
|
|
1850
|
+
this.select(value);
|
|
1851
|
+
}
|
|
1852
|
+
get options() {
|
|
1853
|
+
var _this$menu;
|
|
1854
|
+
// NOTE: this does a DOM query.
|
|
1855
|
+
return ((_this$menu = this.menu) === null || _this$menu === void 0 ? void 0 : _this$menu.items) ?? [];
|
|
1856
|
+
}
|
|
1857
|
+
/**
|
|
1858
|
+
* The index of the currently selected option.
|
|
1859
|
+
*
|
|
1860
|
+
* Note: For SSR, set `[selected]` on the requested option and `displayText`
|
|
1861
|
+
* rather than setting `selectedIndex` setting `selectedIndex` will incur a
|
|
1862
|
+
* DOM query.
|
|
1863
|
+
*/
|
|
1864
|
+
get selectedIndex() {
|
|
1865
|
+
// tslint:disable-next-line:enforce-name-casing
|
|
1866
|
+
const [_option, index] = (this.getSelectedOptions() ?? [])[0] ?? [];
|
|
1867
|
+
return index ?? -1;
|
|
1868
|
+
}
|
|
1869
|
+
set selectedIndex(index) {
|
|
1870
|
+
this.lastUserSetSelectedIndex = index;
|
|
1871
|
+
this.selectIndex(index);
|
|
1872
|
+
}
|
|
1873
|
+
/**
|
|
1874
|
+
* Returns an array of selected options.
|
|
1875
|
+
*
|
|
1876
|
+
* NOTE: md-select only supports single selection.
|
|
1877
|
+
*/
|
|
1878
|
+
get selectedOptions() {
|
|
1879
|
+
return (this.getSelectedOptions() ?? []).map(([option]) => option);
|
|
1880
|
+
}
|
|
1881
|
+
get hasError() {
|
|
1882
|
+
return this.error || this.nativeError;
|
|
1883
|
+
}
|
|
1884
|
+
constructor() {
|
|
1885
|
+
super();
|
|
1886
|
+
/**
|
|
1887
|
+
* Opens the menu synchronously with no animation.
|
|
1888
|
+
*/
|
|
1889
|
+
this.quick = false;
|
|
1890
|
+
/**
|
|
1891
|
+
* Whether or not the select is required.
|
|
1892
|
+
*/
|
|
1893
|
+
this.required = false;
|
|
1894
|
+
/**
|
|
1895
|
+
* The error message that replaces supporting text when `error` is true. If
|
|
1896
|
+
* `errorText` is an empty string, then the supporting text will continue to
|
|
1897
|
+
* show.
|
|
1898
|
+
*
|
|
1899
|
+
* This error message overrides the error message displayed by
|
|
1900
|
+
* `reportValidity()`.
|
|
1901
|
+
*/
|
|
1902
|
+
this.errorText = '';
|
|
1903
|
+
/**
|
|
1904
|
+
* The floating label for the field.
|
|
1905
|
+
*/
|
|
1906
|
+
this.label = '';
|
|
1907
|
+
/**
|
|
1908
|
+
* Disables the asterisk on the floating label, when the select is
|
|
1909
|
+
* required.
|
|
1910
|
+
*/
|
|
1911
|
+
this.noAsterisk = false;
|
|
1912
|
+
/**
|
|
1913
|
+
* Conveys additional information below the select, such as how it should
|
|
1914
|
+
* be used.
|
|
1915
|
+
*/
|
|
1916
|
+
this.supportingText = '';
|
|
1917
|
+
/**
|
|
1918
|
+
* Gets or sets whether or not the select is in a visually invalid state.
|
|
1919
|
+
*
|
|
1920
|
+
* This error state overrides the error state controlled by
|
|
1921
|
+
* `reportValidity()`.
|
|
1922
|
+
*/
|
|
1923
|
+
this.error = false;
|
|
1924
|
+
/**
|
|
1925
|
+
* Whether or not the underlying md-menu should be position: fixed to display
|
|
1926
|
+
* in a top-level manner, or position: absolute.
|
|
1927
|
+
*
|
|
1928
|
+
* position:fixed is useful for cases where select is inside of another
|
|
1929
|
+
* element with stacking context and hidden overflows such as `md-dialog`.
|
|
1930
|
+
*/
|
|
1931
|
+
this.menuPositioning = 'popover';
|
|
1932
|
+
/**
|
|
1933
|
+
* Clamps the menu-width to the width of the select.
|
|
1934
|
+
*/
|
|
1935
|
+
this.clampMenuWidth = false;
|
|
1936
|
+
/**
|
|
1937
|
+
* The max time between the keystrokes of the typeahead select / menu behavior
|
|
1938
|
+
* before it clears the typeahead buffer.
|
|
1939
|
+
*/
|
|
1940
|
+
this.typeaheadDelay = DEFAULT_TYPEAHEAD_BUFFER_TIME;
|
|
1941
|
+
/**
|
|
1942
|
+
* Whether or not the text field has a leading icon. Used for SSR.
|
|
1943
|
+
*/
|
|
1944
|
+
this.hasLeadingIcon = false;
|
|
1945
|
+
/**
|
|
1946
|
+
* Text to display in the field. Only set for SSR.
|
|
1947
|
+
*/
|
|
1948
|
+
this.displayText = '';
|
|
1949
|
+
/**
|
|
1950
|
+
* Whether the menu should be aligned to the start or the end of the select's
|
|
1951
|
+
* textbox.
|
|
1952
|
+
*/
|
|
1953
|
+
this.menuAlign = 'start';
|
|
1954
|
+
this[_a] = '';
|
|
1955
|
+
/**
|
|
1956
|
+
* Used for initializing select when the user sets the `value` directly.
|
|
1957
|
+
*/
|
|
1958
|
+
this.lastUserSetValue = null;
|
|
1959
|
+
/**
|
|
1960
|
+
* Used for initializing select when the user sets the `selectedIndex`
|
|
1961
|
+
* directly.
|
|
1962
|
+
*/
|
|
1963
|
+
this.lastUserSetSelectedIndex = null;
|
|
1964
|
+
/**
|
|
1965
|
+
* Used for `input` and `change` event change detection.
|
|
1966
|
+
*/
|
|
1967
|
+
this.lastSelectedOption = null;
|
|
1968
|
+
// tslint:disable-next-line:enforce-name-casing
|
|
1969
|
+
this.lastSelectedOptionRecords = [];
|
|
1970
|
+
/**
|
|
1971
|
+
* Whether or not a native error has been reported via `reportValidity()`.
|
|
1972
|
+
*/
|
|
1973
|
+
this.nativeError = false;
|
|
1974
|
+
/**
|
|
1975
|
+
* The validation message displayed from a native error via
|
|
1976
|
+
* `reportValidity()`.
|
|
1977
|
+
*/
|
|
1978
|
+
this.nativeErrorText = '';
|
|
1979
|
+
this.focused = false;
|
|
1980
|
+
this.open = false;
|
|
1981
|
+
this.defaultFocus = FocusState.NONE;
|
|
1982
|
+
// Have to keep track of previous open because it's state and private and thus
|
|
1983
|
+
// cannot be tracked in PropertyValues<this> map.
|
|
1984
|
+
this.prevOpen = this.open;
|
|
1985
|
+
this.selectWidth = 0;
|
|
1986
|
+
this.addEventListener('focus', this.handleFocus.bind(this));
|
|
1987
|
+
this.addEventListener('blur', this.handleBlur.bind(this));
|
|
1988
|
+
}
|
|
1989
|
+
/**
|
|
1990
|
+
* Selects an option given the value of the option, and updates MdSelect's
|
|
1991
|
+
* value.
|
|
1992
|
+
*/
|
|
1993
|
+
select(value) {
|
|
1994
|
+
const optionToSelect = this.options.find(option => option.value === value);
|
|
1995
|
+
if (optionToSelect) {
|
|
1996
|
+
this.selectItem(optionToSelect);
|
|
1997
|
+
}
|
|
1998
|
+
}
|
|
1999
|
+
/**
|
|
2000
|
+
* Selects an option given the index of the option, and updates MdSelect's
|
|
2001
|
+
* value.
|
|
2002
|
+
*/
|
|
2003
|
+
selectIndex(index) {
|
|
2004
|
+
const optionToSelect = this.options[index];
|
|
2005
|
+
if (optionToSelect) {
|
|
2006
|
+
this.selectItem(optionToSelect);
|
|
2007
|
+
}
|
|
2008
|
+
}
|
|
2009
|
+
/**
|
|
2010
|
+
* Reset the select to its default value.
|
|
2011
|
+
*/
|
|
2012
|
+
reset() {
|
|
2013
|
+
for (const option of this.options) {
|
|
2014
|
+
option.selected = option.hasAttribute('selected');
|
|
2015
|
+
}
|
|
2016
|
+
this.updateValueAndDisplayText();
|
|
2017
|
+
this.nativeError = false;
|
|
2018
|
+
this.nativeErrorText = '';
|
|
2019
|
+
}
|
|
2020
|
+
/** Shows the picker. If it's already open, this is a no-op. */
|
|
2021
|
+
showPicker() {
|
|
2022
|
+
this.open = true;
|
|
2023
|
+
}
|
|
2024
|
+
[(_a = VALUE, onReportValidity)](invalidEvent) {
|
|
2025
|
+
// Prevent default pop-up behavior.
|
|
2026
|
+
invalidEvent === null || invalidEvent === void 0 || invalidEvent.preventDefault();
|
|
2027
|
+
const prevMessage = this.getErrorText();
|
|
2028
|
+
this.nativeError = !!invalidEvent;
|
|
2029
|
+
this.nativeErrorText = this.validationMessage;
|
|
2030
|
+
if (prevMessage === this.getErrorText()) {
|
|
2031
|
+
var _this$field;
|
|
2032
|
+
(_this$field = this.field) === null || _this$field === void 0 || _this$field.reannounceError();
|
|
2033
|
+
}
|
|
2034
|
+
}
|
|
2035
|
+
update(changed) {
|
|
2036
|
+
// In SSR the options will be ready to query, so try to figure out what
|
|
2037
|
+
// the value and display text should be.
|
|
2038
|
+
if (!this.hasUpdated) {
|
|
2039
|
+
this.initUserSelection();
|
|
2040
|
+
}
|
|
2041
|
+
// We have just opened the menu.
|
|
2042
|
+
// We are only able to check for the select's rect in `update()` instead of
|
|
2043
|
+
// having to wait for `updated()` because the menu can never be open on
|
|
2044
|
+
// first render since it is not settable and Lit SSR does not support click
|
|
2045
|
+
// events which would open the menu.
|
|
2046
|
+
if (this.prevOpen !== this.open && this.open) {
|
|
2047
|
+
const selectRect = this.getBoundingClientRect();
|
|
2048
|
+
this.selectWidth = selectRect.width;
|
|
2049
|
+
}
|
|
2050
|
+
this.prevOpen = this.open;
|
|
2051
|
+
super.update(changed);
|
|
2052
|
+
}
|
|
2053
|
+
render() {
|
|
2054
|
+
return b`
|
|
2055
|
+
<span
|
|
2056
|
+
class="select ${e$2(this.getRenderClasses())}"
|
|
2057
|
+
@focusout=${this.handleFocusout}>
|
|
2058
|
+
${this.renderField()} ${this.renderMenu()}
|
|
2059
|
+
</span>
|
|
2060
|
+
`;
|
|
2061
|
+
}
|
|
2062
|
+
async firstUpdated(changed) {
|
|
2063
|
+
var _this$menu2;
|
|
2064
|
+
await ((_this$menu2 = this.menu) === null || _this$menu2 === void 0 ? void 0 : _this$menu2.updateComplete);
|
|
2065
|
+
// If this has been handled on update already due to SSR, try again.
|
|
2066
|
+
if (!this.lastSelectedOptionRecords.length) {
|
|
2067
|
+
this.initUserSelection();
|
|
2068
|
+
}
|
|
2069
|
+
// Case for when the DOM is streaming, there are no children, and a child
|
|
2070
|
+
// has [selected] set on it, we need to wait for DOM to render something.
|
|
2071
|
+
if (!this.lastSelectedOptionRecords.length && true && !this.options.length) {
|
|
2072
|
+
setTimeout(() => {
|
|
2073
|
+
this.updateValueAndDisplayText();
|
|
2074
|
+
});
|
|
2075
|
+
}
|
|
2076
|
+
super.firstUpdated(changed);
|
|
2077
|
+
}
|
|
2078
|
+
getRenderClasses() {
|
|
2079
|
+
return {
|
|
2080
|
+
'disabled': this.disabled,
|
|
2081
|
+
'error': this.error,
|
|
2082
|
+
'open': this.open
|
|
2083
|
+
};
|
|
2084
|
+
}
|
|
2085
|
+
renderField() {
|
|
2086
|
+
const ariaLabel = this.ariaLabel || this.label;
|
|
2087
|
+
return u`
|
|
2088
|
+
<${this.fieldTag}
|
|
2089
|
+
aria-haspopup="listbox"
|
|
2090
|
+
role="combobox"
|
|
2091
|
+
part="field"
|
|
2092
|
+
id="field"
|
|
2093
|
+
tabindex=${this.disabled ? '-1' : '0'}
|
|
2094
|
+
aria-label=${ariaLabel || A}
|
|
2095
|
+
aria-describedby="description"
|
|
2096
|
+
aria-expanded=${this.open ? 'true' : 'false'}
|
|
2097
|
+
aria-controls="listbox"
|
|
2098
|
+
class="field"
|
|
2099
|
+
label=${this.label}
|
|
2100
|
+
?no-asterisk=${this.noAsterisk}
|
|
2101
|
+
.focused=${this.focused || this.open}
|
|
2102
|
+
.populated=${!!this.displayText}
|
|
2103
|
+
.disabled=${this.disabled}
|
|
2104
|
+
.required=${this.required}
|
|
2105
|
+
.error=${this.hasError}
|
|
2106
|
+
?has-start=${this.hasLeadingIcon}
|
|
2107
|
+
has-end
|
|
2108
|
+
supporting-text=${this.supportingText}
|
|
2109
|
+
error-text=${this.getErrorText()}
|
|
2110
|
+
@keydown=${this.handleKeydown}
|
|
2111
|
+
@click=${this.handleClick}>
|
|
2112
|
+
${this.renderFieldContent()}
|
|
2113
|
+
<div id="description" slot="aria-describedby"></div>
|
|
2114
|
+
</${this.fieldTag}>`;
|
|
2115
|
+
}
|
|
2116
|
+
renderFieldContent() {
|
|
2117
|
+
return [this.renderLeadingIcon(), this.renderLabel(), this.renderTrailingIcon()];
|
|
2118
|
+
}
|
|
2119
|
+
renderLeadingIcon() {
|
|
2120
|
+
return b`
|
|
2121
|
+
<span class="icon leading" slot="start">
|
|
2122
|
+
<slot name="leading-icon" @slotchange=${this.handleIconChange}></slot>
|
|
2123
|
+
</span>
|
|
2124
|
+
`;
|
|
2125
|
+
}
|
|
2126
|
+
renderTrailingIcon() {
|
|
2127
|
+
return b`
|
|
2128
|
+
<span class="icon trailing" slot="end">
|
|
2129
|
+
<slot name="trailing-icon" @slotchange=${this.handleIconChange}>
|
|
2130
|
+
<svg height="5" viewBox="7 10 10 5" focusable="false">
|
|
2131
|
+
<polygon
|
|
2132
|
+
class="down"
|
|
2133
|
+
stroke="none"
|
|
2134
|
+
fill-rule="evenodd"
|
|
2135
|
+
points="7 10 12 15 17 10"></polygon>
|
|
2136
|
+
<polygon
|
|
2137
|
+
class="up"
|
|
2138
|
+
stroke="none"
|
|
2139
|
+
fill-rule="evenodd"
|
|
2140
|
+
points="7 15 12 10 17 15"></polygon>
|
|
2141
|
+
</svg>
|
|
2142
|
+
</slot>
|
|
2143
|
+
</span>
|
|
2144
|
+
`;
|
|
2145
|
+
}
|
|
2146
|
+
renderLabel() {
|
|
2147
|
+
// need to render so that line-height can apply and give it a
|
|
2148
|
+
// non-zero height
|
|
2149
|
+
return b`<div id="label">${this.displayText || b` `}</div>`;
|
|
2150
|
+
}
|
|
2151
|
+
renderMenu() {
|
|
2152
|
+
const ariaLabel = this.label || this.ariaLabel;
|
|
2153
|
+
return b`<div class="menu-wrapper">
|
|
2154
|
+
<md-menu
|
|
2155
|
+
id="listbox"
|
|
2156
|
+
.defaultFocus=${this.defaultFocus}
|
|
2157
|
+
role="listbox"
|
|
2158
|
+
tabindex="-1"
|
|
2159
|
+
aria-label=${ariaLabel || A}
|
|
2160
|
+
stay-open-on-focusout
|
|
2161
|
+
part="menu"
|
|
2162
|
+
exportparts="focus-ring: menu-focus-ring"
|
|
2163
|
+
anchor="field"
|
|
2164
|
+
style=${o$1({
|
|
2165
|
+
'--__menu-min-width': `${this.selectWidth}px`,
|
|
2166
|
+
'--__menu-max-width': this.clampMenuWidth ? `${this.selectWidth}px` : undefined
|
|
2167
|
+
})}
|
|
2168
|
+
no-navigation-wrap
|
|
2169
|
+
.open=${this.open}
|
|
2170
|
+
.quick=${this.quick}
|
|
2171
|
+
.positioning=${this.menuPositioning}
|
|
2172
|
+
.typeaheadDelay=${this.typeaheadDelay}
|
|
2173
|
+
.anchorCorner=${this.menuAlign === 'start' ? 'end-start' : 'end-end'}
|
|
2174
|
+
.menuCorner=${this.menuAlign === 'start' ? 'start-start' : 'start-end'}
|
|
2175
|
+
@opening=${this.handleOpening}
|
|
2176
|
+
@opened=${this.redispatchEvent}
|
|
2177
|
+
@closing=${this.redispatchEvent}
|
|
2178
|
+
@closed=${this.handleClosed}
|
|
2179
|
+
@close-menu=${this.handleCloseMenu}
|
|
2180
|
+
@request-selection=${this.handleRequestSelection}
|
|
2181
|
+
@request-deselection=${this.handleRequestDeselection}>
|
|
2182
|
+
${this.renderMenuContent()}
|
|
2183
|
+
</md-menu>
|
|
2184
|
+
</div>`;
|
|
2185
|
+
}
|
|
2186
|
+
renderMenuContent() {
|
|
2187
|
+
return b`<slot></slot>`;
|
|
2188
|
+
}
|
|
2189
|
+
/**
|
|
2190
|
+
* Handles opening the select on keydown and typahead selection when the menu
|
|
2191
|
+
* is closed.
|
|
2192
|
+
*/
|
|
2193
|
+
handleKeydown(event) {
|
|
2194
|
+
if (this.open || this.disabled || !this.menu) {
|
|
2195
|
+
return;
|
|
2196
|
+
}
|
|
2197
|
+
const typeaheadController = this.menu.typeaheadController;
|
|
2198
|
+
const isOpenKey = event.code === 'Space' || event.code === 'ArrowDown' || event.code === 'ArrowUp' || event.code === 'End' || event.code === 'Home' || event.code === 'Enter';
|
|
2199
|
+
// Do not open if currently typing ahead because the user may be typing the
|
|
2200
|
+
// spacebar to match a word with a space
|
|
2201
|
+
if (!typeaheadController.isTypingAhead && isOpenKey) {
|
|
2202
|
+
event.preventDefault();
|
|
2203
|
+
this.open = true;
|
|
2204
|
+
// https://www.w3.org/WAI/ARIA/apg/patterns/combobox/examples/combobox-select-only/#kbd_label
|
|
2205
|
+
switch (event.code) {
|
|
2206
|
+
case 'Space':
|
|
2207
|
+
case 'ArrowDown':
|
|
2208
|
+
case 'Enter':
|
|
2209
|
+
// We will handle focusing last selected item in this.handleOpening()
|
|
2210
|
+
this.defaultFocus = FocusState.NONE;
|
|
2211
|
+
break;
|
|
2212
|
+
case 'End':
|
|
2213
|
+
this.defaultFocus = FocusState.LAST_ITEM;
|
|
2214
|
+
break;
|
|
2215
|
+
case 'ArrowUp':
|
|
2216
|
+
case 'Home':
|
|
2217
|
+
this.defaultFocus = FocusState.FIRST_ITEM;
|
|
2218
|
+
break;
|
|
2219
|
+
}
|
|
2220
|
+
return;
|
|
2221
|
+
}
|
|
2222
|
+
const isPrintableKey = event.key.length === 1;
|
|
2223
|
+
// Handles typing ahead when the menu is closed by delegating the event to
|
|
2224
|
+
// the underlying menu's typeaheadController
|
|
2225
|
+
if (isPrintableKey) {
|
|
2226
|
+
var _this$labelEl, _this$labelEl$setAttr;
|
|
2227
|
+
typeaheadController.onKeydown(event);
|
|
2228
|
+
event.preventDefault();
|
|
2229
|
+
const {
|
|
2230
|
+
lastActiveRecord
|
|
2231
|
+
} = typeaheadController;
|
|
2232
|
+
if (!lastActiveRecord) {
|
|
2233
|
+
return;
|
|
2234
|
+
}
|
|
2235
|
+
(_this$labelEl = this.labelEl) === null || _this$labelEl === void 0 || (_this$labelEl$setAttr = _this$labelEl.setAttribute) === null || _this$labelEl$setAttr === void 0 || _this$labelEl$setAttr.call(_this$labelEl, 'aria-live', 'polite');
|
|
2236
|
+
const hasChanged = this.selectItem(lastActiveRecord[TYPEAHEAD_RECORD.ITEM]);
|
|
2237
|
+
if (hasChanged) {
|
|
2238
|
+
this.dispatchInteractionEvents();
|
|
2239
|
+
}
|
|
2240
|
+
}
|
|
2241
|
+
}
|
|
2242
|
+
handleClick() {
|
|
2243
|
+
this.open = !this.open;
|
|
2244
|
+
}
|
|
2245
|
+
handleFocus() {
|
|
2246
|
+
this.focused = true;
|
|
2247
|
+
}
|
|
2248
|
+
handleBlur() {
|
|
2249
|
+
this.focused = false;
|
|
2250
|
+
}
|
|
2251
|
+
/**
|
|
2252
|
+
* Handles closing the menu when the focus leaves the select's subtree.
|
|
2253
|
+
*/
|
|
2254
|
+
handleFocusout(event) {
|
|
2255
|
+
// Don't close the menu if we are switching focus between menu,
|
|
2256
|
+
// select-option, and field
|
|
2257
|
+
if (event.relatedTarget && isElementInSubtree(event.relatedTarget, this)) {
|
|
2258
|
+
return;
|
|
2259
|
+
}
|
|
2260
|
+
this.open = false;
|
|
2261
|
+
}
|
|
2262
|
+
/**
|
|
2263
|
+
* Gets a list of all selected select options as a list item record array.
|
|
2264
|
+
*
|
|
2265
|
+
* @return An array of selected list option records.
|
|
2266
|
+
*/
|
|
2267
|
+
getSelectedOptions() {
|
|
2268
|
+
if (!this.menu) {
|
|
2269
|
+
this.lastSelectedOptionRecords = [];
|
|
2270
|
+
return null;
|
|
2271
|
+
}
|
|
2272
|
+
const items = this.menu.items;
|
|
2273
|
+
this.lastSelectedOptionRecords = getSelectedItems(items);
|
|
2274
|
+
return this.lastSelectedOptionRecords;
|
|
2275
|
+
}
|
|
2276
|
+
async getUpdateComplete() {
|
|
2277
|
+
var _this$menu3;
|
|
2278
|
+
await ((_this$menu3 = this.menu) === null || _this$menu3 === void 0 ? void 0 : _this$menu3.updateComplete);
|
|
2279
|
+
return super.getUpdateComplete();
|
|
2280
|
+
}
|
|
2281
|
+
/**
|
|
2282
|
+
* Gets the selected options from the DOM, and updates the value and display
|
|
2283
|
+
* text to the first selected option's value and headline respectively.
|
|
2284
|
+
*
|
|
2285
|
+
* @return Whether or not the selected option has changed since last update.
|
|
2286
|
+
*/
|
|
2287
|
+
updateValueAndDisplayText() {
|
|
2288
|
+
const selectedOptions = this.getSelectedOptions() ?? [];
|
|
2289
|
+
// Used to determine whether or not we need to fire an input / change event
|
|
2290
|
+
// which fire whenever the option element changes (value or selectedIndex)
|
|
2291
|
+
// on user interaction.
|
|
2292
|
+
let hasSelectedOptionChanged = false;
|
|
2293
|
+
if (selectedOptions.length) {
|
|
2294
|
+
const [firstSelectedOption] = selectedOptions[0];
|
|
2295
|
+
hasSelectedOptionChanged = this.lastSelectedOption !== firstSelectedOption;
|
|
2296
|
+
this.lastSelectedOption = firstSelectedOption;
|
|
2297
|
+
this[VALUE] = firstSelectedOption.value;
|
|
2298
|
+
this.displayText = firstSelectedOption.displayText;
|
|
2299
|
+
} else {
|
|
2300
|
+
hasSelectedOptionChanged = this.lastSelectedOption !== null;
|
|
2301
|
+
this.lastSelectedOption = null;
|
|
2302
|
+
this[VALUE] = '';
|
|
2303
|
+
this.displayText = '';
|
|
2304
|
+
}
|
|
2305
|
+
return hasSelectedOptionChanged;
|
|
2306
|
+
}
|
|
2307
|
+
/**
|
|
2308
|
+
* Focuses and activates the last selected item upon opening, and resets other
|
|
2309
|
+
* active items.
|
|
2310
|
+
*/
|
|
2311
|
+
async handleOpening(e) {
|
|
2312
|
+
var _this$labelEl2, _this$labelEl2$remove, _getActiveItem;
|
|
2313
|
+
(_this$labelEl2 = this.labelEl) === null || _this$labelEl2 === void 0 || (_this$labelEl2$remove = _this$labelEl2.removeAttribute) === null || _this$labelEl2$remove === void 0 || _this$labelEl2$remove.call(_this$labelEl2, 'aria-live');
|
|
2314
|
+
this.redispatchEvent(e);
|
|
2315
|
+
// FocusState.NONE means we want to handle focus ourselves and focus the
|
|
2316
|
+
// last selected item.
|
|
2317
|
+
if (this.defaultFocus !== FocusState.NONE) {
|
|
2318
|
+
return;
|
|
2319
|
+
}
|
|
2320
|
+
const items = this.menu.items;
|
|
2321
|
+
const activeItem = (_getActiveItem = getActiveItem(items)) === null || _getActiveItem === void 0 ? void 0 : _getActiveItem.item;
|
|
2322
|
+
let [selectedItem] = this.lastSelectedOptionRecords[0] ?? [null];
|
|
2323
|
+
// This is true if the user keys through the list but clicks out of the menu
|
|
2324
|
+
// thus no close-menu event is fired by an item and we can't clean up in
|
|
2325
|
+
// handleCloseMenu.
|
|
2326
|
+
if (activeItem && activeItem !== selectedItem) {
|
|
2327
|
+
activeItem.tabIndex = -1;
|
|
2328
|
+
}
|
|
2329
|
+
// in the case that nothing is selected, focus the first item
|
|
2330
|
+
selectedItem = selectedItem ?? items[0];
|
|
2331
|
+
if (selectedItem) {
|
|
2332
|
+
selectedItem.tabIndex = 0;
|
|
2333
|
+
selectedItem.focus();
|
|
2334
|
+
}
|
|
2335
|
+
}
|
|
2336
|
+
redispatchEvent(e) {
|
|
2337
|
+
redispatchEvent(this, e);
|
|
2338
|
+
}
|
|
2339
|
+
handleClosed(e) {
|
|
2340
|
+
this.open = false;
|
|
2341
|
+
this.redispatchEvent(e);
|
|
2342
|
+
}
|
|
2343
|
+
/**
|
|
2344
|
+
* Determines the reason for closing, and updates the UI accordingly.
|
|
2345
|
+
*/
|
|
2346
|
+
handleCloseMenu(event) {
|
|
2347
|
+
const reason = event.detail.reason;
|
|
2348
|
+
const item = event.detail.itemPath[0];
|
|
2349
|
+
this.open = false;
|
|
2350
|
+
let hasChanged = false;
|
|
2351
|
+
if (reason.kind === 'click-selection') {
|
|
2352
|
+
hasChanged = this.selectItem(item);
|
|
2353
|
+
} else if (reason.kind === 'keydown' && isSelectableKey(reason.key)) {
|
|
2354
|
+
hasChanged = this.selectItem(item);
|
|
2355
|
+
} else {
|
|
2356
|
+
// This can happen on ESC being pressed
|
|
2357
|
+
item.tabIndex = -1;
|
|
2358
|
+
item.blur();
|
|
2359
|
+
}
|
|
2360
|
+
// Dispatch interaction events since selection has been made via keyboard
|
|
2361
|
+
// or mouse.
|
|
2362
|
+
if (hasChanged) {
|
|
2363
|
+
this.dispatchInteractionEvents();
|
|
2364
|
+
}
|
|
2365
|
+
}
|
|
2366
|
+
/**
|
|
2367
|
+
* Selects a given option, deselects other options, and updates the UI.
|
|
2368
|
+
*
|
|
2369
|
+
* @return Whether the last selected option has changed.
|
|
2370
|
+
*/
|
|
2371
|
+
selectItem(item) {
|
|
2372
|
+
const selectedOptions = this.getSelectedOptions() ?? [];
|
|
2373
|
+
selectedOptions.forEach(([option]) => {
|
|
2374
|
+
if (item !== option) {
|
|
2375
|
+
option.selected = false;
|
|
2376
|
+
}
|
|
2377
|
+
});
|
|
2378
|
+
item.selected = true;
|
|
2379
|
+
return this.updateValueAndDisplayText();
|
|
2380
|
+
}
|
|
2381
|
+
/**
|
|
2382
|
+
* Handles updating selection when an option element requests selection via
|
|
2383
|
+
* property / attribute change.
|
|
2384
|
+
*/
|
|
2385
|
+
handleRequestSelection(event) {
|
|
2386
|
+
const requestingOptionEl = event.target;
|
|
2387
|
+
// No-op if this item is already selected.
|
|
2388
|
+
if (this.lastSelectedOptionRecords.some(([option]) => option === requestingOptionEl)) {
|
|
2389
|
+
return;
|
|
2390
|
+
}
|
|
2391
|
+
this.selectItem(requestingOptionEl);
|
|
2392
|
+
}
|
|
2393
|
+
/**
|
|
2394
|
+
* Handles updating selection when an option element requests deselection via
|
|
2395
|
+
* property / attribute change.
|
|
2396
|
+
*/
|
|
2397
|
+
handleRequestDeselection(event) {
|
|
2398
|
+
const requestingOptionEl = event.target;
|
|
2399
|
+
// No-op if this item is not even in the list of tracked selected items.
|
|
2400
|
+
if (!this.lastSelectedOptionRecords.some(([option]) => option === requestingOptionEl)) {
|
|
2401
|
+
return;
|
|
2402
|
+
}
|
|
2403
|
+
this.updateValueAndDisplayText();
|
|
2404
|
+
}
|
|
2405
|
+
/**
|
|
2406
|
+
* Attempts to initialize the selected option from user-settable values like
|
|
2407
|
+
* SSR, setting `value`, or `selectedIndex` at startup.
|
|
2408
|
+
*/
|
|
2409
|
+
initUserSelection() {
|
|
2410
|
+
// User has set `.value` directly, but internals have not yet booted up.
|
|
2411
|
+
if (this.lastUserSetValue && !this.lastSelectedOptionRecords.length) {
|
|
2412
|
+
this.select(this.lastUserSetValue);
|
|
2413
|
+
// User has set `.selectedIndex` directly, but internals have not yet
|
|
2414
|
+
// booted up.
|
|
2415
|
+
} else if (this.lastUserSetSelectedIndex !== null && !this.lastSelectedOptionRecords.length) {
|
|
2416
|
+
this.selectIndex(this.lastUserSetSelectedIndex);
|
|
2417
|
+
// Regular boot up!
|
|
2418
|
+
} else {
|
|
2419
|
+
this.updateValueAndDisplayText();
|
|
2420
|
+
}
|
|
2421
|
+
}
|
|
2422
|
+
handleIconChange() {
|
|
2423
|
+
this.hasLeadingIcon = this.leadingIcons.length > 0;
|
|
2424
|
+
}
|
|
2425
|
+
/**
|
|
2426
|
+
* Dispatches the `input` and `change` events.
|
|
2427
|
+
*/
|
|
2428
|
+
dispatchInteractionEvents() {
|
|
2429
|
+
this.dispatchEvent(new Event('input', {
|
|
2430
|
+
bubbles: true,
|
|
2431
|
+
composed: true
|
|
2432
|
+
}));
|
|
2433
|
+
this.dispatchEvent(new Event('change', {
|
|
2434
|
+
bubbles: true
|
|
2435
|
+
}));
|
|
2436
|
+
}
|
|
2437
|
+
getErrorText() {
|
|
2438
|
+
return this.error ? this.errorText : this.nativeErrorText;
|
|
2439
|
+
}
|
|
2440
|
+
[getFormValue]() {
|
|
2441
|
+
return this.value;
|
|
2442
|
+
}
|
|
2443
|
+
formResetCallback() {
|
|
2444
|
+
this.reset();
|
|
2445
|
+
}
|
|
2446
|
+
formStateRestoreCallback(state) {
|
|
2447
|
+
this.value = state;
|
|
2448
|
+
}
|
|
2449
|
+
click() {
|
|
2450
|
+
var _this$field2;
|
|
2451
|
+
(_this$field2 = this.field) === null || _this$field2 === void 0 || _this$field2.click();
|
|
2452
|
+
}
|
|
2453
|
+
[createValidator]() {
|
|
2454
|
+
return new SelectValidator(() => this);
|
|
2455
|
+
}
|
|
2456
|
+
[getValidityAnchor]() {
|
|
2457
|
+
return this.field;
|
|
2458
|
+
}
|
|
2459
|
+
}
|
|
2460
|
+
/** @nocollapse */
|
|
2461
|
+
Select.shadowRootOptions = {
|
|
2462
|
+
...i.shadowRootOptions,
|
|
2463
|
+
delegatesFocus: true
|
|
2464
|
+
};
|
|
2465
|
+
__decorate([n$1({
|
|
2466
|
+
type: Boolean
|
|
2467
|
+
})], Select.prototype, "quick", void 0);
|
|
2468
|
+
__decorate([n$1({
|
|
2469
|
+
type: Boolean
|
|
2470
|
+
})], Select.prototype, "required", void 0);
|
|
2471
|
+
__decorate([n$1({
|
|
2472
|
+
type: String,
|
|
2473
|
+
attribute: 'error-text'
|
|
2474
|
+
})], Select.prototype, "errorText", void 0);
|
|
2475
|
+
__decorate([n$1()], Select.prototype, "label", void 0);
|
|
2476
|
+
__decorate([n$1({
|
|
2477
|
+
type: Boolean,
|
|
2478
|
+
attribute: 'no-asterisk'
|
|
2479
|
+
})], Select.prototype, "noAsterisk", void 0);
|
|
2480
|
+
__decorate([n$1({
|
|
2481
|
+
type: String,
|
|
2482
|
+
attribute: 'supporting-text'
|
|
2483
|
+
})], Select.prototype, "supportingText", void 0);
|
|
2484
|
+
__decorate([n$1({
|
|
2485
|
+
type: Boolean,
|
|
2486
|
+
reflect: true
|
|
2487
|
+
})], Select.prototype, "error", void 0);
|
|
2488
|
+
__decorate([n$1({
|
|
2489
|
+
attribute: 'menu-positioning'
|
|
2490
|
+
})], Select.prototype, "menuPositioning", void 0);
|
|
2491
|
+
__decorate([n$1({
|
|
2492
|
+
type: Boolean,
|
|
2493
|
+
attribute: 'clamp-menu-width'
|
|
2494
|
+
})], Select.prototype, "clampMenuWidth", void 0);
|
|
2495
|
+
__decorate([n$1({
|
|
2496
|
+
type: Number,
|
|
2497
|
+
attribute: 'typeahead-delay'
|
|
2498
|
+
})], Select.prototype, "typeaheadDelay", void 0);
|
|
2499
|
+
__decorate([n$1({
|
|
2500
|
+
type: Boolean,
|
|
2501
|
+
attribute: 'has-leading-icon'
|
|
2502
|
+
})], Select.prototype, "hasLeadingIcon", void 0);
|
|
2503
|
+
__decorate([n$1({
|
|
2504
|
+
attribute: 'display-text'
|
|
2505
|
+
})], Select.prototype, "displayText", void 0);
|
|
2506
|
+
__decorate([n$1({
|
|
2507
|
+
attribute: 'menu-align'
|
|
2508
|
+
})], Select.prototype, "menuAlign", void 0);
|
|
2509
|
+
__decorate([n$1()], Select.prototype, "value", null);
|
|
2510
|
+
__decorate([n$1({
|
|
2511
|
+
type: Number,
|
|
2512
|
+
attribute: 'selected-index'
|
|
2513
|
+
})], Select.prototype, "selectedIndex", null);
|
|
2514
|
+
__decorate([r()], Select.prototype, "nativeError", void 0);
|
|
2515
|
+
__decorate([r()], Select.prototype, "nativeErrorText", void 0);
|
|
2516
|
+
__decorate([r()], Select.prototype, "focused", void 0);
|
|
2517
|
+
__decorate([r()], Select.prototype, "open", void 0);
|
|
2518
|
+
__decorate([r()], Select.prototype, "defaultFocus", void 0);
|
|
2519
|
+
__decorate([e$1('.field')], Select.prototype, "field", void 0);
|
|
2520
|
+
__decorate([e$1('md-menu')], Select.prototype, "menu", void 0);
|
|
2521
|
+
__decorate([e$1('#label')], Select.prototype, "labelEl", void 0);
|
|
2522
|
+
__decorate([o({
|
|
2523
|
+
slot: 'leading-icon',
|
|
2524
|
+
flatten: true
|
|
2525
|
+
})], Select.prototype, "leadingIcons", void 0);
|
|
2526
|
+
|
|
2527
|
+
/**
|
|
2528
|
+
* @license
|
|
2529
|
+
* Copyright 2023 Google LLC
|
|
2530
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
2531
|
+
*/
|
|
2532
|
+
// tslint:disable-next-line:enforce-comments-on-exported-symbols
|
|
2533
|
+
class OutlinedSelect extends Select {
|
|
2534
|
+
constructor() {
|
|
2535
|
+
super(...arguments);
|
|
2536
|
+
this.fieldTag = i$2`md-outlined-field`;
|
|
2537
|
+
}
|
|
2538
|
+
}
|
|
2539
|
+
|
|
2540
|
+
/**
|
|
2541
|
+
* @license
|
|
2542
|
+
* Copyright 2024 Google LLC
|
|
2543
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
2544
|
+
*/
|
|
2545
|
+
// Generated stylesheet for ./select/internal/outlined-select-styles.css.
|
|
2546
|
+
const styles$2 = i$1`:host{--_text-field-disabled-input-text-color: var(--md-outlined-select-text-field-disabled-input-text-color, var(--md-sys-color-on-surface, #1d1b20));--_text-field-disabled-input-text-opacity: var(--md-outlined-select-text-field-disabled-input-text-opacity, 0.38);--_text-field-disabled-label-text-color: var(--md-outlined-select-text-field-disabled-label-text-color, var(--md-sys-color-on-surface, #1d1b20));--_text-field-disabled-label-text-opacity: var(--md-outlined-select-text-field-disabled-label-text-opacity, 0.38);--_text-field-disabled-leading-icon-color: var(--md-outlined-select-text-field-disabled-leading-icon-color, var(--md-sys-color-on-surface, #1d1b20));--_text-field-disabled-leading-icon-opacity: var(--md-outlined-select-text-field-disabled-leading-icon-opacity, 0.38);--_text-field-disabled-outline-color: var(--md-outlined-select-text-field-disabled-outline-color, var(--md-sys-color-on-surface, #1d1b20));--_text-field-disabled-outline-opacity: var(--md-outlined-select-text-field-disabled-outline-opacity, 0.12);--_text-field-disabled-outline-width: var(--md-outlined-select-text-field-disabled-outline-width, 1px);--_text-field-disabled-supporting-text-color: var(--md-outlined-select-text-field-disabled-supporting-text-color, var(--md-sys-color-on-surface, #1d1b20));--_text-field-disabled-supporting-text-opacity: var(--md-outlined-select-text-field-disabled-supporting-text-opacity, 0.38);--_text-field-disabled-trailing-icon-color: var(--md-outlined-select-text-field-disabled-trailing-icon-color, var(--md-sys-color-on-surface, #1d1b20));--_text-field-disabled-trailing-icon-opacity: var(--md-outlined-select-text-field-disabled-trailing-icon-opacity, 0.38);--_text-field-error-focus-input-text-color: var(--md-outlined-select-text-field-error-focus-input-text-color, var(--md-sys-color-on-surface, #1d1b20));--_text-field-error-focus-label-text-color: var(--md-outlined-select-text-field-error-focus-label-text-color, var(--md-sys-color-error, #b3261e));--_text-field-error-focus-leading-icon-color: var(--md-outlined-select-text-field-error-focus-leading-icon-color, var(--md-sys-color-on-surface-variant, #49454f));--_text-field-error-focus-outline-color: var(--md-outlined-select-text-field-error-focus-outline-color, var(--md-sys-color-error, #b3261e));--_text-field-error-focus-supporting-text-color: var(--md-outlined-select-text-field-error-focus-supporting-text-color, var(--md-sys-color-error, #b3261e));--_text-field-error-focus-trailing-icon-color: var(--md-outlined-select-text-field-error-focus-trailing-icon-color, var(--md-sys-color-error, #b3261e));--_text-field-error-hover-input-text-color: var(--md-outlined-select-text-field-error-hover-input-text-color, var(--md-sys-color-on-surface, #1d1b20));--_text-field-error-hover-label-text-color: var(--md-outlined-select-text-field-error-hover-label-text-color, var(--md-sys-color-on-error-container, #410e0b));--_text-field-error-hover-leading-icon-color: var(--md-outlined-select-text-field-error-hover-leading-icon-color, var(--md-sys-color-on-surface-variant, #49454f));--_text-field-error-hover-outline-color: var(--md-outlined-select-text-field-error-hover-outline-color, var(--md-sys-color-on-error-container, #410e0b));--_text-field-error-hover-supporting-text-color: var(--md-outlined-select-text-field-error-hover-supporting-text-color, var(--md-sys-color-error, #b3261e));--_text-field-error-hover-trailing-icon-color: var(--md-outlined-select-text-field-error-hover-trailing-icon-color, var(--md-sys-color-on-error-container, #410e0b));--_text-field-error-input-text-color: var(--md-outlined-select-text-field-error-input-text-color, var(--md-sys-color-on-surface, #1d1b20));--_text-field-error-label-text-color: var(--md-outlined-select-text-field-error-label-text-color, var(--md-sys-color-error, #b3261e));--_text-field-error-leading-icon-color: var(--md-outlined-select-text-field-error-leading-icon-color, var(--md-sys-color-on-surface-variant, #49454f));--_text-field-error-outline-color: var(--md-outlined-select-text-field-error-outline-color, var(--md-sys-color-error, #b3261e));--_text-field-error-supporting-text-color: var(--md-outlined-select-text-field-error-supporting-text-color, var(--md-sys-color-error, #b3261e));--_text-field-error-trailing-icon-color: var(--md-outlined-select-text-field-error-trailing-icon-color, var(--md-sys-color-error, #b3261e));--_text-field-focus-input-text-color: var(--md-outlined-select-text-field-focus-input-text-color, var(--md-sys-color-on-surface, #1d1b20));--_text-field-focus-label-text-color: var(--md-outlined-select-text-field-focus-label-text-color, var(--md-sys-color-primary, #6750a4));--_text-field-focus-leading-icon-color: var(--md-outlined-select-text-field-focus-leading-icon-color, var(--md-sys-color-on-surface-variant, #49454f));--_text-field-focus-outline-color: var(--md-outlined-select-text-field-focus-outline-color, var(--md-sys-color-primary, #6750a4));--_text-field-focus-outline-width: var(--md-outlined-select-text-field-focus-outline-width, 3px);--_text-field-focus-supporting-text-color: var(--md-outlined-select-text-field-focus-supporting-text-color, var(--md-sys-color-on-surface-variant, #49454f));--_text-field-focus-trailing-icon-color: var(--md-outlined-select-text-field-focus-trailing-icon-color, var(--md-sys-color-primary, #6750a4));--_text-field-hover-input-text-color: var(--md-outlined-select-text-field-hover-input-text-color, var(--md-sys-color-on-surface, #1d1b20));--_text-field-hover-label-text-color: var(--md-outlined-select-text-field-hover-label-text-color, var(--md-sys-color-on-surface, #1d1b20));--_text-field-hover-leading-icon-color: var(--md-outlined-select-text-field-hover-leading-icon-color, var(--md-sys-color-on-surface-variant, #49454f));--_text-field-hover-outline-color: var(--md-outlined-select-text-field-hover-outline-color, var(--md-sys-color-on-surface, #1d1b20));--_text-field-hover-outline-width: var(--md-outlined-select-text-field-hover-outline-width, 1px);--_text-field-hover-supporting-text-color: var(--md-outlined-select-text-field-hover-supporting-text-color, var(--md-sys-color-on-surface-variant, #49454f));--_text-field-hover-trailing-icon-color: var(--md-outlined-select-text-field-hover-trailing-icon-color, var(--md-sys-color-on-surface-variant, #49454f));--_text-field-input-text-color: var(--md-outlined-select-text-field-input-text-color, var(--md-sys-color-on-surface, #1d1b20));--_text-field-input-text-font: var(--md-outlined-select-text-field-input-text-font, var(--md-sys-typescale-body-large-font, var(--md-ref-typeface-plain, Roboto)));--_text-field-input-text-line-height: var(--md-outlined-select-text-field-input-text-line-height, var(--md-sys-typescale-body-large-line-height, 1.5rem));--_text-field-input-text-size: var(--md-outlined-select-text-field-input-text-size, var(--md-sys-typescale-body-large-size, 1rem));--_text-field-input-text-weight: var(--md-outlined-select-text-field-input-text-weight, var(--md-sys-typescale-body-large-weight, var(--md-ref-typeface-weight-regular, 400)));--_text-field-label-text-color: var(--md-outlined-select-text-field-label-text-color, var(--md-sys-color-on-surface-variant, #49454f));--_text-field-label-text-font: var(--md-outlined-select-text-field-label-text-font, var(--md-sys-typescale-body-large-font, var(--md-ref-typeface-plain, Roboto)));--_text-field-label-text-line-height: var(--md-outlined-select-text-field-label-text-line-height, var(--md-sys-typescale-body-large-line-height, 1.5rem));--_text-field-label-text-populated-line-height: var(--md-outlined-select-text-field-label-text-populated-line-height, var(--md-sys-typescale-body-small-line-height, 1rem));--_text-field-label-text-populated-size: var(--md-outlined-select-text-field-label-text-populated-size, var(--md-sys-typescale-body-small-size, 0.75rem));--_text-field-label-text-size: var(--md-outlined-select-text-field-label-text-size, var(--md-sys-typescale-body-large-size, 1rem));--_text-field-label-text-weight: var(--md-outlined-select-text-field-label-text-weight, var(--md-sys-typescale-body-large-weight, var(--md-ref-typeface-weight-regular, 400)));--_text-field-leading-icon-color: var(--md-outlined-select-text-field-leading-icon-color, var(--md-sys-color-on-surface-variant, #49454f));--_text-field-leading-icon-size: var(--md-outlined-select-text-field-leading-icon-size, 24px);--_text-field-outline-color: var(--md-outlined-select-text-field-outline-color, var(--md-sys-color-outline, #79747e));--_text-field-outline-width: var(--md-outlined-select-text-field-outline-width, 1px);--_text-field-supporting-text-color: var(--md-outlined-select-text-field-supporting-text-color, var(--md-sys-color-on-surface-variant, #49454f));--_text-field-supporting-text-font: var(--md-outlined-select-text-field-supporting-text-font, var(--md-sys-typescale-body-small-font, var(--md-ref-typeface-plain, Roboto)));--_text-field-supporting-text-line-height: var(--md-outlined-select-text-field-supporting-text-line-height, var(--md-sys-typescale-body-small-line-height, 1rem));--_text-field-supporting-text-size: var(--md-outlined-select-text-field-supporting-text-size, var(--md-sys-typescale-body-small-size, 0.75rem));--_text-field-supporting-text-weight: var(--md-outlined-select-text-field-supporting-text-weight, var(--md-sys-typescale-body-small-weight, var(--md-ref-typeface-weight-regular, 400)));--_text-field-trailing-icon-color: var(--md-outlined-select-text-field-trailing-icon-color, var(--md-sys-color-on-surface-variant, #49454f));--_text-field-trailing-icon-size: var(--md-outlined-select-text-field-trailing-icon-size, 24px);--_text-field-container-shape-start-start: var(--md-outlined-select-text-field-container-shape-start-start, var(--md-outlined-select-text-field-container-shape, var(--md-sys-shape-corner-extra-small, 4px)));--_text-field-container-shape-start-end: var(--md-outlined-select-text-field-container-shape-start-end, var(--md-outlined-select-text-field-container-shape, var(--md-sys-shape-corner-extra-small, 4px)));--_text-field-container-shape-end-end: var(--md-outlined-select-text-field-container-shape-end-end, var(--md-outlined-select-text-field-container-shape, var(--md-sys-shape-corner-extra-small, 4px)));--_text-field-container-shape-end-start: var(--md-outlined-select-text-field-container-shape-end-start, var(--md-outlined-select-text-field-container-shape, var(--md-sys-shape-corner-extra-small, 4px)));--md-outlined-field-container-shape-end-end: var(--_text-field-container-shape-end-end);--md-outlined-field-container-shape-end-start: var(--_text-field-container-shape-end-start);--md-outlined-field-container-shape-start-end: var(--_text-field-container-shape-start-end);--md-outlined-field-container-shape-start-start: var(--_text-field-container-shape-start-start);--md-outlined-field-content-color: var(--_text-field-input-text-color);--md-outlined-field-content-font: var(--_text-field-input-text-font);--md-outlined-field-content-line-height: var(--_text-field-input-text-line-height);--md-outlined-field-content-size: var(--_text-field-input-text-size);--md-outlined-field-content-weight: var(--_text-field-input-text-weight);--md-outlined-field-disabled-content-color: var(--_text-field-disabled-input-text-color);--md-outlined-field-disabled-content-opacity: var(--_text-field-disabled-input-text-opacity);--md-outlined-field-disabled-label-text-color: var(--_text-field-disabled-label-text-color);--md-outlined-field-disabled-label-text-opacity: var(--_text-field-disabled-label-text-opacity);--md-outlined-field-disabled-leading-content-color: var(--_text-field-disabled-leading-icon-color);--md-outlined-field-disabled-leading-content-opacity: var(--_text-field-disabled-leading-icon-opacity);--md-outlined-field-disabled-outline-color: var(--_text-field-disabled-outline-color);--md-outlined-field-disabled-outline-opacity: var(--_text-field-disabled-outline-opacity);--md-outlined-field-disabled-outline-width: var(--_text-field-disabled-outline-width);--md-outlined-field-disabled-supporting-text-color: var(--_text-field-disabled-supporting-text-color);--md-outlined-field-disabled-supporting-text-opacity: var(--_text-field-disabled-supporting-text-opacity);--md-outlined-field-disabled-trailing-content-color: var(--_text-field-disabled-trailing-icon-color);--md-outlined-field-disabled-trailing-content-opacity: var(--_text-field-disabled-trailing-icon-opacity);--md-outlined-field-error-content-color: var(--_text-field-error-input-text-color);--md-outlined-field-error-focus-content-color: var(--_text-field-error-focus-input-text-color);--md-outlined-field-error-focus-label-text-color: var(--_text-field-error-focus-label-text-color);--md-outlined-field-error-focus-leading-content-color: var(--_text-field-error-focus-leading-icon-color);--md-outlined-field-error-focus-outline-color: var(--_text-field-error-focus-outline-color);--md-outlined-field-error-focus-supporting-text-color: var(--_text-field-error-focus-supporting-text-color);--md-outlined-field-error-focus-trailing-content-color: var(--_text-field-error-focus-trailing-icon-color);--md-outlined-field-error-hover-content-color: var(--_text-field-error-hover-input-text-color);--md-outlined-field-error-hover-label-text-color: var(--_text-field-error-hover-label-text-color);--md-outlined-field-error-hover-leading-content-color: var(--_text-field-error-hover-leading-icon-color);--md-outlined-field-error-hover-outline-color: var(--_text-field-error-hover-outline-color);--md-outlined-field-error-hover-supporting-text-color: var(--_text-field-error-hover-supporting-text-color);--md-outlined-field-error-hover-trailing-content-color: var(--_text-field-error-hover-trailing-icon-color);--md-outlined-field-error-label-text-color: var(--_text-field-error-label-text-color);--md-outlined-field-error-leading-content-color: var(--_text-field-error-leading-icon-color);--md-outlined-field-error-outline-color: var(--_text-field-error-outline-color);--md-outlined-field-error-supporting-text-color: var(--_text-field-error-supporting-text-color);--md-outlined-field-error-trailing-content-color: var(--_text-field-error-trailing-icon-color);--md-outlined-field-focus-content-color: var(--_text-field-focus-input-text-color);--md-outlined-field-focus-label-text-color: var(--_text-field-focus-label-text-color);--md-outlined-field-focus-leading-content-color: var(--_text-field-focus-leading-icon-color);--md-outlined-field-focus-outline-color: var(--_text-field-focus-outline-color);--md-outlined-field-focus-outline-width: var(--_text-field-focus-outline-width);--md-outlined-field-focus-supporting-text-color: var(--_text-field-focus-supporting-text-color);--md-outlined-field-focus-trailing-content-color: var(--_text-field-focus-trailing-icon-color);--md-outlined-field-hover-content-color: var(--_text-field-hover-input-text-color);--md-outlined-field-hover-label-text-color: var(--_text-field-hover-label-text-color);--md-outlined-field-hover-leading-content-color: var(--_text-field-hover-leading-icon-color);--md-outlined-field-hover-outline-color: var(--_text-field-hover-outline-color);--md-outlined-field-hover-outline-width: var(--_text-field-hover-outline-width);--md-outlined-field-hover-supporting-text-color: var(--_text-field-hover-supporting-text-color);--md-outlined-field-hover-trailing-content-color: var(--_text-field-hover-trailing-icon-color);--md-outlined-field-label-text-color: var(--_text-field-label-text-color);--md-outlined-field-label-text-font: var(--_text-field-label-text-font);--md-outlined-field-label-text-line-height: var(--_text-field-label-text-line-height);--md-outlined-field-label-text-populated-line-height: var(--_text-field-label-text-populated-line-height);--md-outlined-field-label-text-populated-size: var(--_text-field-label-text-populated-size);--md-outlined-field-label-text-size: var(--_text-field-label-text-size);--md-outlined-field-label-text-weight: var(--_text-field-label-text-weight);--md-outlined-field-leading-content-color: var(--_text-field-leading-icon-color);--md-outlined-field-outline-color: var(--_text-field-outline-color);--md-outlined-field-outline-width: var(--_text-field-outline-width);--md-outlined-field-supporting-text-color: var(--_text-field-supporting-text-color);--md-outlined-field-supporting-text-font: var(--_text-field-supporting-text-font);--md-outlined-field-supporting-text-line-height: var(--_text-field-supporting-text-line-height);--md-outlined-field-supporting-text-size: var(--_text-field-supporting-text-size);--md-outlined-field-supporting-text-weight: var(--_text-field-supporting-text-weight);--md-outlined-field-trailing-content-color: var(--_text-field-trailing-icon-color)}[has-start] .icon.leading{font-size:var(--_text-field-leading-icon-size);height:var(--_text-field-leading-icon-size);width:var(--_text-field-leading-icon-size)}.icon.trailing{font-size:var(--_text-field-trailing-icon-size);height:var(--_text-field-trailing-icon-size);width:var(--_text-field-trailing-icon-size)}
|
|
2547
|
+
`;
|
|
2548
|
+
|
|
2549
|
+
/**
|
|
2550
|
+
* @license
|
|
2551
|
+
* Copyright 2024 Google LLC
|
|
2552
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
2553
|
+
*/
|
|
2554
|
+
// Generated stylesheet for ./select/internal/shared-styles.css.
|
|
2555
|
+
const styles$1 = i$1`:host{color:unset;min-width:210px;display:flex}.field{cursor:default;outline:none}.select{position:relative;flex-direction:column}.icon.trailing svg,.icon ::slotted(*){fill:currentColor}.icon ::slotted(*){width:inherit;height:inherit;font-size:inherit}.icon slot{display:flex;height:100%;width:100%;align-items:center;justify-content:center}.icon.trailing :is(.up,.down){opacity:0;transition:opacity 75ms linear 75ms}.select:not(.open) .down,.select.open .up{opacity:1}.field,.select,md-menu{min-width:inherit;width:inherit;max-width:inherit;display:flex}md-menu{min-width:var(--__menu-min-width);max-width:var(--__menu-max-width, inherit)}.menu-wrapper{width:0px;height:0px;max-width:inherit}md-menu ::slotted(:not[disabled]){cursor:pointer}.field,.select{width:100%}:host{display:inline-flex}:host([disabled]){pointer-events:none}
|
|
2556
|
+
`;
|
|
2557
|
+
|
|
2558
|
+
/**
|
|
2559
|
+
* @license
|
|
2560
|
+
* Copyright 2023 Google LLC
|
|
2561
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
2562
|
+
*/
|
|
2563
|
+
/**
|
|
2564
|
+
* @summary
|
|
2565
|
+
* Select menus display a list of choices on temporary surfaces and display the
|
|
2566
|
+
* currently selected menu item above the menu.
|
|
2567
|
+
*
|
|
2568
|
+
* @description
|
|
2569
|
+
* The select component allows users to choose a value from a fixed list of
|
|
2570
|
+
* available options. Composed of an interactive anchor button and a menu, it is
|
|
2571
|
+
* analogous to the native HTML `<select>` element. This is the "outlined"
|
|
2572
|
+
* variant.
|
|
2573
|
+
*
|
|
2574
|
+
* @example
|
|
2575
|
+
* ```html
|
|
2576
|
+
* <md-outlined-select label="fruits">
|
|
2577
|
+
* <!-- An empty selected option will give select an "un-filled" state -->
|
|
2578
|
+
* <md-select-option selected></md-select-option>
|
|
2579
|
+
* <md-select-option value="apple" headline="Apple"></md-select-option>
|
|
2580
|
+
* <md-select-option value="banana" headline="Banana"></md-select-option>
|
|
2581
|
+
* <md-select-option value="kiwi" headline="Kiwi"></md-select-option>
|
|
2582
|
+
* <md-select-option value="orange" headline="Orange"></md-select-option>
|
|
2583
|
+
* <md-select-option value="tomato" headline="Tomato"></md-select-option>
|
|
2584
|
+
* </md-outlined-select>
|
|
2585
|
+
* ```
|
|
2586
|
+
*
|
|
2587
|
+
* @final
|
|
2588
|
+
* @suppress {visibility}
|
|
2589
|
+
*/
|
|
2590
|
+
let MdOutlinedSelect = class MdOutlinedSelect extends OutlinedSelect {};
|
|
2591
|
+
MdOutlinedSelect.styles = [styles$1, styles$2];
|
|
2592
|
+
MdOutlinedSelect = __decorate([t('md-outlined-select')], MdOutlinedSelect);
|
|
2593
|
+
|
|
2594
|
+
/**
|
|
2595
|
+
* @license
|
|
2596
|
+
* Copyright 2024 Google LLC
|
|
2597
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
2598
|
+
*/
|
|
2599
|
+
// Generated stylesheet for ./menu/internal/menuitem/menu-item-styles.css.
|
|
2600
|
+
const styles = i$1`:host{display:flex;--md-ripple-hover-color: var(--md-menu-item-hover-state-layer-color, var(--md-sys-color-on-surface, #1d1b20));--md-ripple-hover-opacity: var(--md-menu-item-hover-state-layer-opacity, 0.08);--md-ripple-pressed-color: var(--md-menu-item-pressed-state-layer-color, var(--md-sys-color-on-surface, #1d1b20));--md-ripple-pressed-opacity: var(--md-menu-item-pressed-state-layer-opacity, 0.12)}:host([disabled]){opacity:var(--md-menu-item-disabled-opacity, 0.3);pointer-events:none}md-focus-ring{z-index:1;--md-focus-ring-shape: 8px}a,button,li{background:none;border:none;padding:0;margin:0;text-align:unset;text-decoration:none}.list-item{border-radius:inherit;display:flex;flex:1;max-width:inherit;min-width:inherit;outline:none;-webkit-tap-highlight-color:rgba(0,0,0,0)}.list-item:not(.disabled){cursor:pointer}[slot=container]{pointer-events:none}md-ripple{border-radius:inherit}md-item{border-radius:inherit;flex:1;color:var(--md-menu-item-label-text-color, var(--md-sys-color-on-surface, #1d1b20));font-family:var(--md-menu-item-label-text-font, var(--md-sys-typescale-body-large-font, var(--md-ref-typeface-plain, Roboto)));font-size:var(--md-menu-item-label-text-size, var(--md-sys-typescale-body-large-size, 1rem));line-height:var(--md-menu-item-label-text-line-height, var(--md-sys-typescale-body-large-line-height, 1.5rem));font-weight:var(--md-menu-item-label-text-weight, var(--md-sys-typescale-body-large-weight, var(--md-ref-typeface-weight-regular, 400)));min-height:var(--md-menu-item-one-line-container-height, 56px);padding-top:var(--md-menu-item-top-space, 12px);padding-bottom:var(--md-menu-item-bottom-space, 12px);padding-inline-start:var(--md-menu-item-leading-space, 16px);padding-inline-end:var(--md-menu-item-trailing-space, 16px)}md-item[multiline]{min-height:var(--md-menu-item-two-line-container-height, 72px)}[slot=supporting-text]{color:var(--md-menu-item-supporting-text-color, var(--md-sys-color-on-surface-variant, #49454f));font-family:var(--md-menu-item-supporting-text-font, var(--md-sys-typescale-body-medium-font, var(--md-ref-typeface-plain, Roboto)));font-size:var(--md-menu-item-supporting-text-size, var(--md-sys-typescale-body-medium-size, 0.875rem));line-height:var(--md-menu-item-supporting-text-line-height, var(--md-sys-typescale-body-medium-line-height, 1.25rem));font-weight:var(--md-menu-item-supporting-text-weight, var(--md-sys-typescale-body-medium-weight, var(--md-ref-typeface-weight-regular, 400)))}[slot=trailing-supporting-text]{color:var(--md-menu-item-trailing-supporting-text-color, var(--md-sys-color-on-surface-variant, #49454f));font-family:var(--md-menu-item-trailing-supporting-text-font, var(--md-sys-typescale-label-small-font, var(--md-ref-typeface-plain, Roboto)));font-size:var(--md-menu-item-trailing-supporting-text-size, var(--md-sys-typescale-label-small-size, 0.6875rem));line-height:var(--md-menu-item-trailing-supporting-text-line-height, var(--md-sys-typescale-label-small-line-height, 1rem));font-weight:var(--md-menu-item-trailing-supporting-text-weight, var(--md-sys-typescale-label-small-weight, var(--md-ref-typeface-weight-medium, 500)))}:is([slot=start],[slot=end])::slotted(*){fill:currentColor}[slot=start]{color:var(--md-menu-item-leading-icon-color, var(--md-sys-color-on-surface-variant, #49454f))}[slot=end]{color:var(--md-menu-item-trailing-icon-color, var(--md-sys-color-on-surface-variant, #49454f))}.list-item{background-color:var(--md-menu-item-container-color, transparent)}.list-item.selected{background-color:var(--md-menu-item-selected-container-color, var(--md-sys-color-secondary-container, #e8def8))}.selected:not(.disabled) ::slotted(*){color:var(--md-menu-item-selected-label-text-color, var(--md-sys-color-on-secondary-container, #1d192b))}@media(forced-colors: active){:host([disabled]),:host([disabled]) slot{color:GrayText;opacity:1}.list-item{position:relative}.list-item.selected::before{content:"";position:absolute;inset:0;box-sizing:border-box;border-radius:inherit;pointer-events:none;border:3px double CanvasText}}
|
|
2601
|
+
`;
|
|
2602
|
+
|
|
2603
|
+
/**
|
|
2604
|
+
* @license
|
|
2605
|
+
* Copyright 2023 Google LLC
|
|
2606
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
2607
|
+
*/
|
|
2608
|
+
/**
|
|
2609
|
+
* A controller that provides most functionality of an element that implements
|
|
2610
|
+
* the MenuItem interface.
|
|
2611
|
+
*/
|
|
2612
|
+
class MenuItemController {
|
|
2613
|
+
/**
|
|
2614
|
+
* @param host The MenuItem in which to attach this controller to.
|
|
2615
|
+
* @param config The object that configures this controller's behavior.
|
|
2616
|
+
*/
|
|
2617
|
+
constructor(host, config) {
|
|
2618
|
+
this.host = host;
|
|
2619
|
+
this.internalTypeaheadText = null;
|
|
2620
|
+
/**
|
|
2621
|
+
* Bind this click listener to the interactive element. Handles closing the
|
|
2622
|
+
* menu.
|
|
2623
|
+
*/
|
|
2624
|
+
this.onClick = () => {
|
|
2625
|
+
if (this.host.keepOpen) return;
|
|
2626
|
+
this.host.dispatchEvent(createDefaultCloseMenuEvent(this.host, {
|
|
2627
|
+
kind: CloseReason.CLICK_SELECTION
|
|
2628
|
+
}));
|
|
2629
|
+
};
|
|
2630
|
+
/**
|
|
2631
|
+
* Bind this click listener to the interactive element. Handles closing the
|
|
2632
|
+
* menu.
|
|
2633
|
+
*/
|
|
2634
|
+
this.onKeydown = event => {
|
|
2635
|
+
// Check if the interactive element is an anchor tag. If so, click it.
|
|
2636
|
+
if (this.host.href && event.code === 'Enter') {
|
|
2637
|
+
const interactiveElement = this.getInteractiveElement();
|
|
2638
|
+
if (interactiveElement instanceof HTMLAnchorElement) {
|
|
2639
|
+
interactiveElement.click();
|
|
2640
|
+
}
|
|
2641
|
+
}
|
|
2642
|
+
if (event.defaultPrevented) return;
|
|
2643
|
+
// If the host has keepOpen = true we should ignore clicks & Space/Enter,
|
|
2644
|
+
// however we always maintain the ability to close a menu with a explicit
|
|
2645
|
+
// `escape` keypress.
|
|
2646
|
+
const keyCode = event.code;
|
|
2647
|
+
if (this.host.keepOpen && keyCode !== 'Escape') return;
|
|
2648
|
+
if (isClosableKey(keyCode)) {
|
|
2649
|
+
event.preventDefault();
|
|
2650
|
+
this.host.dispatchEvent(createDefaultCloseMenuEvent(this.host, {
|
|
2651
|
+
kind: CloseReason.KEYDOWN,
|
|
2652
|
+
key: keyCode
|
|
2653
|
+
}));
|
|
2654
|
+
}
|
|
2655
|
+
};
|
|
2656
|
+
this.getHeadlineElements = config.getHeadlineElements;
|
|
2657
|
+
this.getSupportingTextElements = config.getSupportingTextElements;
|
|
2658
|
+
this.getDefaultElements = config.getDefaultElements;
|
|
2659
|
+
this.getInteractiveElement = config.getInteractiveElement;
|
|
2660
|
+
this.host.addController(this);
|
|
2661
|
+
}
|
|
2662
|
+
/**
|
|
2663
|
+
* The text that is selectable via typeahead. If not set, defaults to the
|
|
2664
|
+
* innerText of the item slotted into the `"headline"` slot, and if there are
|
|
2665
|
+
* no slotted elements into headline, then it checks the _default_ slot, and
|
|
2666
|
+
* then the `"supporting-text"` slot if nothing is in _default_.
|
|
2667
|
+
*/
|
|
2668
|
+
get typeaheadText() {
|
|
2669
|
+
if (this.internalTypeaheadText !== null) {
|
|
2670
|
+
return this.internalTypeaheadText;
|
|
2671
|
+
}
|
|
2672
|
+
const headlineElements = this.getHeadlineElements();
|
|
2673
|
+
const textParts = [];
|
|
2674
|
+
headlineElements.forEach(headlineElement => {
|
|
2675
|
+
if (headlineElement.textContent && headlineElement.textContent.trim()) {
|
|
2676
|
+
textParts.push(headlineElement.textContent.trim());
|
|
2677
|
+
}
|
|
2678
|
+
});
|
|
2679
|
+
// If there are no headline elements, check the default slot's text content
|
|
2680
|
+
if (textParts.length === 0) {
|
|
2681
|
+
this.getDefaultElements().forEach(defaultElement => {
|
|
2682
|
+
if (defaultElement.textContent && defaultElement.textContent.trim()) {
|
|
2683
|
+
textParts.push(defaultElement.textContent.trim());
|
|
2684
|
+
}
|
|
2685
|
+
});
|
|
2686
|
+
}
|
|
2687
|
+
// If there are no headline nor default slot elements, check the
|
|
2688
|
+
//supporting-text slot's text content
|
|
2689
|
+
if (textParts.length === 0) {
|
|
2690
|
+
this.getSupportingTextElements().forEach(supportingTextElement => {
|
|
2691
|
+
if (supportingTextElement.textContent && supportingTextElement.textContent.trim()) {
|
|
2692
|
+
textParts.push(supportingTextElement.textContent.trim());
|
|
2693
|
+
}
|
|
2694
|
+
});
|
|
2695
|
+
}
|
|
2696
|
+
return textParts.join(' ');
|
|
2697
|
+
}
|
|
2698
|
+
/**
|
|
2699
|
+
* The recommended tag name to render as the list item.
|
|
2700
|
+
*/
|
|
2701
|
+
get tagName() {
|
|
2702
|
+
const type = this.host.type;
|
|
2703
|
+
switch (type) {
|
|
2704
|
+
case 'link':
|
|
2705
|
+
return 'a';
|
|
2706
|
+
case 'button':
|
|
2707
|
+
return 'button';
|
|
2708
|
+
default:
|
|
2709
|
+
case 'menuitem':
|
|
2710
|
+
case 'option':
|
|
2711
|
+
return 'li';
|
|
2712
|
+
}
|
|
2713
|
+
}
|
|
2714
|
+
/**
|
|
2715
|
+
* The recommended role of the menu item.
|
|
2716
|
+
*/
|
|
2717
|
+
get role() {
|
|
2718
|
+
return this.host.type === 'option' ? 'option' : 'menuitem';
|
|
2719
|
+
}
|
|
2720
|
+
hostConnected() {
|
|
2721
|
+
this.host.toggleAttribute('md-menu-item', true);
|
|
2722
|
+
}
|
|
2723
|
+
hostUpdate() {
|
|
2724
|
+
if (this.host.href) {
|
|
2725
|
+
this.host.type = 'link';
|
|
2726
|
+
}
|
|
2727
|
+
}
|
|
2728
|
+
/**
|
|
2729
|
+
* Use to set the typeaheadText when it changes.
|
|
2730
|
+
*/
|
|
2731
|
+
setTypeaheadText(text) {
|
|
2732
|
+
this.internalTypeaheadText = text;
|
|
2733
|
+
}
|
|
2734
|
+
}
|
|
2735
|
+
|
|
2736
|
+
/**
|
|
2737
|
+
* @license
|
|
2738
|
+
* Copyright 2023 Google LLC
|
|
2739
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
2740
|
+
*/
|
|
2741
|
+
/**
|
|
2742
|
+
* Creates an event fired by a SelectOption to request selection from md-select.
|
|
2743
|
+
* Typically fired after `selected` changes from `false` to `true`.
|
|
2744
|
+
*/
|
|
2745
|
+
function createRequestSelectionEvent() {
|
|
2746
|
+
return new Event('request-selection', {
|
|
2747
|
+
bubbles: true,
|
|
2748
|
+
composed: true
|
|
2749
|
+
});
|
|
2750
|
+
}
|
|
2751
|
+
/**
|
|
2752
|
+
* Creates an event fired by a SelectOption to request deselection from
|
|
2753
|
+
* md-select. Typically fired after `selected` changes from `true` to `false`.
|
|
2754
|
+
*/
|
|
2755
|
+
function createRequestDeselectionEvent() {
|
|
2756
|
+
return new Event('request-deselection', {
|
|
2757
|
+
bubbles: true,
|
|
2758
|
+
composed: true
|
|
2759
|
+
});
|
|
2760
|
+
}
|
|
2761
|
+
/**
|
|
2762
|
+
* A controller that provides most functionality and md-select compatibility for
|
|
2763
|
+
* an element that implements the SelectOption interface.
|
|
2764
|
+
*/
|
|
2765
|
+
class SelectOptionController {
|
|
2766
|
+
/**
|
|
2767
|
+
* The recommended role of the select option.
|
|
2768
|
+
*/
|
|
2769
|
+
get role() {
|
|
2770
|
+
return this.menuItemController.role;
|
|
2771
|
+
}
|
|
2772
|
+
/**
|
|
2773
|
+
* The text that is selectable via typeahead. If not set, defaults to the
|
|
2774
|
+
* innerText of the item slotted into the `"headline"` slot, and if there are
|
|
2775
|
+
* no slotted elements into headline, then it checks the _default_ slot, and
|
|
2776
|
+
* then the `"supporting-text"` slot if nothing is in _default_.
|
|
2777
|
+
*/
|
|
2778
|
+
get typeaheadText() {
|
|
2779
|
+
return this.menuItemController.typeaheadText;
|
|
2780
|
+
}
|
|
2781
|
+
setTypeaheadText(text) {
|
|
2782
|
+
this.menuItemController.setTypeaheadText(text);
|
|
2783
|
+
}
|
|
2784
|
+
/**
|
|
2785
|
+
* The text that is displayed in the select field when selected. If not set,
|
|
2786
|
+
* defaults to the textContent of the item slotted into the `"headline"` slot,
|
|
2787
|
+
* and if there are no slotted elements into headline, then it checks the
|
|
2788
|
+
* _default_ slot, and then the `"supporting-text"` slot if nothing is in
|
|
2789
|
+
* _default_.
|
|
2790
|
+
*/
|
|
2791
|
+
get displayText() {
|
|
2792
|
+
if (this.internalDisplayText !== null) {
|
|
2793
|
+
return this.internalDisplayText;
|
|
2794
|
+
}
|
|
2795
|
+
return this.menuItemController.typeaheadText;
|
|
2796
|
+
}
|
|
2797
|
+
setDisplayText(text) {
|
|
2798
|
+
this.internalDisplayText = text;
|
|
2799
|
+
}
|
|
2800
|
+
/**
|
|
2801
|
+
* @param host The SelectOption in which to attach this controller to.
|
|
2802
|
+
* @param config The object that configures this controller's behavior.
|
|
2803
|
+
*/
|
|
2804
|
+
constructor(host, config) {
|
|
2805
|
+
this.host = host;
|
|
2806
|
+
this.internalDisplayText = null;
|
|
2807
|
+
this.firstUpdate = true;
|
|
2808
|
+
/**
|
|
2809
|
+
* Bind this click listener to the interactive element. Handles closing the
|
|
2810
|
+
* menu.
|
|
2811
|
+
*/
|
|
2812
|
+
this.onClick = () => {
|
|
2813
|
+
this.menuItemController.onClick();
|
|
2814
|
+
};
|
|
2815
|
+
/**
|
|
2816
|
+
* Bind this click listener to the interactive element. Handles closing the
|
|
2817
|
+
* menu.
|
|
2818
|
+
*/
|
|
2819
|
+
this.onKeydown = e => {
|
|
2820
|
+
this.menuItemController.onKeydown(e);
|
|
2821
|
+
};
|
|
2822
|
+
this.lastSelected = this.host.selected;
|
|
2823
|
+
this.menuItemController = new MenuItemController(host, config);
|
|
2824
|
+
host.addController(this);
|
|
2825
|
+
}
|
|
2826
|
+
hostUpdate() {
|
|
2827
|
+
if (this.lastSelected !== this.host.selected) {
|
|
2828
|
+
this.host.ariaSelected = this.host.selected ? 'true' : 'false';
|
|
2829
|
+
}
|
|
2830
|
+
}
|
|
2831
|
+
hostUpdated() {
|
|
2832
|
+
// Do not dispatch event on first update / boot-up.
|
|
2833
|
+
if (this.lastSelected !== this.host.selected && !this.firstUpdate) {
|
|
2834
|
+
// This section is really useful for when the user sets selected on the
|
|
2835
|
+
// option programmatically. Most other cases (click and keyboard) are
|
|
2836
|
+
// handled by md-select because it needs to coordinate the
|
|
2837
|
+
// single-selection behavior.
|
|
2838
|
+
if (this.host.selected) {
|
|
2839
|
+
this.host.dispatchEvent(createRequestSelectionEvent());
|
|
2840
|
+
} else {
|
|
2841
|
+
this.host.dispatchEvent(createRequestDeselectionEvent());
|
|
2842
|
+
}
|
|
2843
|
+
}
|
|
2844
|
+
this.lastSelected = this.host.selected;
|
|
2845
|
+
this.firstUpdate = false;
|
|
2846
|
+
}
|
|
2847
|
+
}
|
|
2848
|
+
|
|
2849
|
+
/**
|
|
2850
|
+
* @license
|
|
2851
|
+
* Copyright 2023 Google LLC
|
|
2852
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
2853
|
+
*/
|
|
2854
|
+
// Separate variable needed for closure.
|
|
2855
|
+
const selectOptionBaseClass = mixinDelegatesAria(i);
|
|
2856
|
+
/**
|
|
2857
|
+
* @fires close-menu {CustomEvent<{initiator: SelectOption, reason: Reason, itemPath: SelectOption[]}>}
|
|
2858
|
+
* Closes the encapsulating menu on closable interaction. --bubbles --composed
|
|
2859
|
+
* @fires request-selection {Event} Requests the parent md-select to select this
|
|
2860
|
+
* element (and deselect others if single-selection) when `selected` changed to
|
|
2861
|
+
* `true`. --bubbles --composed
|
|
2862
|
+
* @fires request-deselection {Event} Requests the parent md-select to deselect
|
|
2863
|
+
* this element when `selected` changed to `false`. --bubbles --composed
|
|
2864
|
+
*/
|
|
2865
|
+
class SelectOptionEl extends selectOptionBaseClass {
|
|
2866
|
+
constructor() {
|
|
2867
|
+
super(...arguments);
|
|
2868
|
+
/**
|
|
2869
|
+
* Disables the item and makes it non-selectable and non-interactive.
|
|
2870
|
+
*/
|
|
2871
|
+
this.disabled = false;
|
|
2872
|
+
/**
|
|
2873
|
+
* READONLY: self-identifies as a menu item and sets its identifying attribute
|
|
2874
|
+
*/
|
|
2875
|
+
this.isMenuItem = true;
|
|
2876
|
+
/**
|
|
2877
|
+
* Sets the item in the selected visual state when a submenu is opened.
|
|
2878
|
+
*/
|
|
2879
|
+
this.selected = false;
|
|
2880
|
+
/**
|
|
2881
|
+
* Form value of the option.
|
|
2882
|
+
*/
|
|
2883
|
+
this.value = '';
|
|
2884
|
+
this.type = 'option';
|
|
2885
|
+
this.selectOptionController = new SelectOptionController(this, {
|
|
2886
|
+
getHeadlineElements: () => {
|
|
2887
|
+
return this.headlineElements;
|
|
2888
|
+
},
|
|
2889
|
+
getSupportingTextElements: () => {
|
|
2890
|
+
return this.supportingTextElements;
|
|
2891
|
+
},
|
|
2892
|
+
getDefaultElements: () => {
|
|
2893
|
+
return this.defaultElements;
|
|
2894
|
+
},
|
|
2895
|
+
getInteractiveElement: () => this.listItemRoot
|
|
2896
|
+
});
|
|
2897
|
+
}
|
|
2898
|
+
/**
|
|
2899
|
+
* The text that is selectable via typeahead. If not set, defaults to the
|
|
2900
|
+
* innerText of the item slotted into the `"headline"` slot.
|
|
2901
|
+
*/
|
|
2902
|
+
get typeaheadText() {
|
|
2903
|
+
return this.selectOptionController.typeaheadText;
|
|
2904
|
+
}
|
|
2905
|
+
set typeaheadText(text) {
|
|
2906
|
+
this.selectOptionController.setTypeaheadText(text);
|
|
2907
|
+
}
|
|
2908
|
+
/**
|
|
2909
|
+
* The text that is displayed in the select field when selected. If not set,
|
|
2910
|
+
* defaults to the textContent of the item slotted into the `"headline"` slot.
|
|
2911
|
+
*/
|
|
2912
|
+
get displayText() {
|
|
2913
|
+
return this.selectOptionController.displayText;
|
|
2914
|
+
}
|
|
2915
|
+
set displayText(text) {
|
|
2916
|
+
this.selectOptionController.setDisplayText(text);
|
|
2917
|
+
}
|
|
2918
|
+
render() {
|
|
2919
|
+
return this.renderListItem(b`
|
|
2920
|
+
<md-item>
|
|
2921
|
+
<div slot="container">
|
|
2922
|
+
${this.renderRipple()} ${this.renderFocusRing()}
|
|
2923
|
+
</div>
|
|
2924
|
+
<slot name="start" slot="start"></slot>
|
|
2925
|
+
<slot name="end" slot="end"></slot>
|
|
2926
|
+
${this.renderBody()}
|
|
2927
|
+
</md-item>
|
|
2928
|
+
`);
|
|
2929
|
+
}
|
|
2930
|
+
/**
|
|
2931
|
+
* Renders the root list item.
|
|
2932
|
+
*
|
|
2933
|
+
* @param content the child content of the list item.
|
|
2934
|
+
*/
|
|
2935
|
+
renderListItem(content) {
|
|
2936
|
+
return b`
|
|
2937
|
+
<li
|
|
2938
|
+
id="item"
|
|
2939
|
+
tabindex=${this.disabled ? -1 : 0}
|
|
2940
|
+
role=${this.selectOptionController.role}
|
|
2941
|
+
aria-label=${this.ariaLabel || A}
|
|
2942
|
+
aria-selected=${this.ariaSelected || A}
|
|
2943
|
+
aria-checked=${this.ariaChecked || A}
|
|
2944
|
+
aria-expanded=${this.ariaExpanded || A}
|
|
2945
|
+
aria-haspopup=${this.ariaHasPopup || A}
|
|
2946
|
+
class="list-item ${e$2(this.getRenderClasses())}"
|
|
2947
|
+
@click=${this.selectOptionController.onClick}
|
|
2948
|
+
@keydown=${this.selectOptionController.onKeydown}
|
|
2949
|
+
>${content}</li
|
|
2950
|
+
>
|
|
2951
|
+
`;
|
|
2952
|
+
}
|
|
2953
|
+
/**
|
|
2954
|
+
* Handles rendering of the ripple element.
|
|
2955
|
+
*/
|
|
2956
|
+
renderRipple() {
|
|
2957
|
+
return b` <md-ripple
|
|
2958
|
+
part="ripple"
|
|
2959
|
+
for="item"
|
|
2960
|
+
?disabled=${this.disabled}></md-ripple>`;
|
|
2961
|
+
}
|
|
2962
|
+
/**
|
|
2963
|
+
* Handles rendering of the focus ring.
|
|
2964
|
+
*/
|
|
2965
|
+
renderFocusRing() {
|
|
2966
|
+
return b` <md-focus-ring
|
|
2967
|
+
part="focus-ring"
|
|
2968
|
+
for="item"
|
|
2969
|
+
inward></md-focus-ring>`;
|
|
2970
|
+
}
|
|
2971
|
+
/**
|
|
2972
|
+
* Classes applied to the list item root.
|
|
2973
|
+
*/
|
|
2974
|
+
getRenderClasses() {
|
|
2975
|
+
return {
|
|
2976
|
+
'disabled': this.disabled,
|
|
2977
|
+
'selected': this.selected
|
|
2978
|
+
};
|
|
2979
|
+
}
|
|
2980
|
+
/**
|
|
2981
|
+
* Handles rendering the headline and supporting text.
|
|
2982
|
+
*/
|
|
2983
|
+
renderBody() {
|
|
2984
|
+
return b`
|
|
2985
|
+
<slot></slot>
|
|
2986
|
+
<slot name="overline" slot="overline"></slot>
|
|
2987
|
+
<slot name="headline" slot="headline"></slot>
|
|
2988
|
+
<slot name="supporting-text" slot="supporting-text"></slot>
|
|
2989
|
+
<slot
|
|
2990
|
+
name="trailing-supporting-text"
|
|
2991
|
+
slot="trailing-supporting-text"></slot>
|
|
2992
|
+
`;
|
|
2993
|
+
}
|
|
2994
|
+
focus() {
|
|
2995
|
+
var _this$listItemRoot;
|
|
2996
|
+
// TODO(b/300334509): needed for some cases where delegatesFocus doesn't
|
|
2997
|
+
// work programmatically like in FF and select-option
|
|
2998
|
+
(_this$listItemRoot = this.listItemRoot) === null || _this$listItemRoot === void 0 || _this$listItemRoot.focus();
|
|
2999
|
+
}
|
|
3000
|
+
}
|
|
3001
|
+
/** @nocollapse */
|
|
3002
|
+
SelectOptionEl.shadowRootOptions = {
|
|
3003
|
+
...i.shadowRootOptions,
|
|
3004
|
+
delegatesFocus: true
|
|
3005
|
+
};
|
|
3006
|
+
__decorate([n$1({
|
|
3007
|
+
type: Boolean,
|
|
3008
|
+
reflect: true
|
|
3009
|
+
})], SelectOptionEl.prototype, "disabled", void 0);
|
|
3010
|
+
__decorate([n$1({
|
|
3011
|
+
type: Boolean,
|
|
3012
|
+
attribute: 'md-menu-item',
|
|
3013
|
+
reflect: true
|
|
3014
|
+
})], SelectOptionEl.prototype, "isMenuItem", void 0);
|
|
3015
|
+
__decorate([n$1({
|
|
3016
|
+
type: Boolean
|
|
3017
|
+
})], SelectOptionEl.prototype, "selected", void 0);
|
|
3018
|
+
__decorate([n$1()], SelectOptionEl.prototype, "value", void 0);
|
|
3019
|
+
__decorate([e$1('.list-item')], SelectOptionEl.prototype, "listItemRoot", void 0);
|
|
3020
|
+
__decorate([o({
|
|
3021
|
+
slot: 'headline'
|
|
3022
|
+
})], SelectOptionEl.prototype, "headlineElements", void 0);
|
|
3023
|
+
__decorate([o({
|
|
3024
|
+
slot: 'supporting-text'
|
|
3025
|
+
})], SelectOptionEl.prototype, "supportingTextElements", void 0);
|
|
3026
|
+
__decorate([n({
|
|
3027
|
+
slot: ''
|
|
3028
|
+
})], SelectOptionEl.prototype, "defaultElements", void 0);
|
|
3029
|
+
__decorate([n$1({
|
|
3030
|
+
attribute: 'typeahead-text'
|
|
3031
|
+
})], SelectOptionEl.prototype, "typeaheadText", null);
|
|
3032
|
+
__decorate([n$1({
|
|
3033
|
+
attribute: 'display-text'
|
|
3034
|
+
})], SelectOptionEl.prototype, "displayText", null);
|
|
3035
|
+
|
|
3036
|
+
/**
|
|
3037
|
+
* @license
|
|
3038
|
+
* Copyright 2023 Google LLC
|
|
3039
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
3040
|
+
*/
|
|
3041
|
+
/**
|
|
3042
|
+
* @summary
|
|
3043
|
+
* Select menus display a list of choices on temporary surfaces and display the
|
|
3044
|
+
* currently selected menu item above the menu.
|
|
3045
|
+
*
|
|
3046
|
+
* @description
|
|
3047
|
+
* The select component allows users to choose a value from a fixed list of
|
|
3048
|
+
* available options. Composed of an interactive anchor button and a menu, it is
|
|
3049
|
+
* analogous to the native HTML `<select>` element. This is the option that
|
|
3050
|
+
* can be placed inside of an md-select.
|
|
3051
|
+
*
|
|
3052
|
+
* This component is a subclass of `md-menu-item` and can accept the same slots,
|
|
3053
|
+
* properties, and events as `md-menu-item`.
|
|
3054
|
+
*
|
|
3055
|
+
* @example
|
|
3056
|
+
* ```html
|
|
3057
|
+
* <md-outlined-select label="fruits">
|
|
3058
|
+
* <!-- An empty selected option will give select an "un-filled" state -->
|
|
3059
|
+
* <md-select-option selected></md-select-option>
|
|
3060
|
+
* <md-select-option value="apple" headline="Apple"></md-select-option>
|
|
3061
|
+
* <md-select-option value="banana" headline="Banana"></md-select-option>
|
|
3062
|
+
* <md-select-option value="kiwi" headline="Kiwi"></md-select-option>
|
|
3063
|
+
* <md-select-option value="orange" headline="Orange"></md-select-option>
|
|
3064
|
+
* <md-select-option value="tomato" headline="Tomato"></md-select-option>
|
|
3065
|
+
* </md-outlined-select>
|
|
3066
|
+
* ```
|
|
3067
|
+
*
|
|
3068
|
+
* @final
|
|
3069
|
+
* @suppress {visibility}
|
|
3070
|
+
*/
|
|
3071
|
+
let MdSelectOption = class MdSelectOption extends SelectOptionEl {};
|
|
3072
|
+
MdSelectOption.styles = [styles];
|
|
3073
|
+
MdSelectOption = __decorate([t('md-select-option')], MdSelectOption);
|
|
3074
|
+
|
|
3075
|
+
var __defProp = Object.defineProperty;
|
|
3076
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
3077
|
+
var __decorateClass = (decorators, target, key, kind) => {
|
|
3078
|
+
var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc(target, key) : target;
|
|
3079
|
+
for (var i = decorators.length - 1, decorator; i >= 0; i--) if (decorator = decorators[i]) result = (kind ? decorator(target, key, result) : decorator(result)) || result;
|
|
3080
|
+
if (kind && result) __defProp(target, key, result);
|
|
3081
|
+
return result;
|
|
3082
|
+
};
|
|
3083
|
+
const LOG_LEVELS = [{
|
|
3084
|
+
value: "critical",
|
|
3085
|
+
label: "Critical"
|
|
3086
|
+
}, {
|
|
3087
|
+
value: "error",
|
|
3088
|
+
label: "Error"
|
|
3089
|
+
}, {
|
|
3090
|
+
value: "warning",
|
|
3091
|
+
label: "Warning"
|
|
3092
|
+
}, {
|
|
3093
|
+
value: "info",
|
|
3094
|
+
label: "Info"
|
|
3095
|
+
}, {
|
|
3096
|
+
value: "debug",
|
|
3097
|
+
label: "Debug"
|
|
3098
|
+
}];
|
|
3099
|
+
let LogLevelDialog = class extends i {
|
|
3100
|
+
constructor() {
|
|
3101
|
+
super(...arguments);
|
|
3102
|
+
this._consoleLevel = "info";
|
|
3103
|
+
this._fileLevel = null;
|
|
3104
|
+
this._loading = true;
|
|
3105
|
+
this._applying = false;
|
|
3106
|
+
}
|
|
3107
|
+
connectedCallback() {
|
|
3108
|
+
super.connectedCallback();
|
|
3109
|
+
void this._loadLogLevels();
|
|
3110
|
+
}
|
|
3111
|
+
async _loadLogLevels() {
|
|
3112
|
+
try {
|
|
3113
|
+
const result = await this.client.getLogLevel();
|
|
3114
|
+
this._consoleLevel = result.console_loglevel;
|
|
3115
|
+
this._fileLevel = result.file_loglevel;
|
|
3116
|
+
} catch (err) {
|
|
3117
|
+
console.error("Failed to load log levels:", err);
|
|
3118
|
+
} finally {
|
|
3119
|
+
this._loading = false;
|
|
3120
|
+
}
|
|
3121
|
+
}
|
|
3122
|
+
async _apply() {
|
|
3123
|
+
this._applying = true;
|
|
3124
|
+
try {
|
|
3125
|
+
var _this$_fileSelect;
|
|
3126
|
+
const consoleLevel = this._consoleSelect.value;
|
|
3127
|
+
const fileLevel = (_this$_fileSelect = this._fileSelect) === null || _this$_fileSelect === void 0 ? void 0 : _this$_fileSelect.value;
|
|
3128
|
+
const result = await this.client.setLogLevel(consoleLevel, this._fileLevel !== null ? fileLevel : void 0);
|
|
3129
|
+
this._consoleLevel = result.console_loglevel;
|
|
3130
|
+
this._fileLevel = result.file_loglevel;
|
|
3131
|
+
this._close();
|
|
3132
|
+
} catch (err) {
|
|
3133
|
+
console.error("Failed to apply log levels:", err);
|
|
3134
|
+
alert("Failed to apply log levels");
|
|
3135
|
+
} finally {
|
|
3136
|
+
this._applying = false;
|
|
3137
|
+
}
|
|
3138
|
+
}
|
|
3139
|
+
_close() {
|
|
3140
|
+
this.shadowRoot.querySelector("md-dialog").close();
|
|
3141
|
+
}
|
|
3142
|
+
_handleClosed() {
|
|
3143
|
+
this.parentNode.removeChild(this);
|
|
3144
|
+
}
|
|
3145
|
+
render() {
|
|
3146
|
+
return b`
|
|
3147
|
+
<md-dialog open @cancel=${preventDefault} @closed=${this._handleClosed}>
|
|
3148
|
+
<div slot="headline">Server Log Settings</div>
|
|
3149
|
+
<div slot="content">
|
|
3150
|
+
${this._loading ? b`<p class="loading">Loading...</p>` : b`
|
|
3151
|
+
<p class="hint">Changes are temporary and will be reset on the next server restart.</p>
|
|
3152
|
+
<div class="form-field">
|
|
3153
|
+
<label>Console Log Level</label>
|
|
3154
|
+
<md-outlined-select name="console" .value=${this._consoleLevel}>
|
|
3155
|
+
${LOG_LEVELS.map(level => b`
|
|
3156
|
+
<md-select-option
|
|
3157
|
+
value=${level.value}
|
|
3158
|
+
?selected=${level.value === this._consoleLevel}
|
|
3159
|
+
>
|
|
3160
|
+
<div slot="headline">${level.label}</div>
|
|
3161
|
+
</md-select-option>
|
|
3162
|
+
`)}
|
|
3163
|
+
</md-outlined-select>
|
|
3164
|
+
</div>
|
|
3165
|
+
${this._fileLevel !== null ? b`
|
|
3166
|
+
<div class="form-field">
|
|
3167
|
+
<label>File Log Level</label>
|
|
3168
|
+
<md-outlined-select name="file" .value=${this._fileLevel}>
|
|
3169
|
+
${LOG_LEVELS.map(level => b`
|
|
3170
|
+
<md-select-option
|
|
3171
|
+
value=${level.value}
|
|
3172
|
+
?selected=${level.value === this._fileLevel}
|
|
3173
|
+
>
|
|
3174
|
+
<div slot="headline">${level.label}</div>
|
|
3175
|
+
</md-select-option>
|
|
3176
|
+
`)}
|
|
3177
|
+
</md-outlined-select>
|
|
3178
|
+
</div>
|
|
3179
|
+
` : A}
|
|
3180
|
+
`}
|
|
3181
|
+
</div>
|
|
3182
|
+
<div slot="actions">
|
|
3183
|
+
<md-text-button @click=${this._close}>Cancel</md-text-button>
|
|
3184
|
+
<md-text-button @click=${this._apply} ?disabled=${this._loading || this._applying}>
|
|
3185
|
+
${this._applying ? "Applying..." : "Apply"}
|
|
3186
|
+
</md-text-button>
|
|
3187
|
+
</div>
|
|
3188
|
+
</md-dialog>
|
|
3189
|
+
`;
|
|
3190
|
+
}
|
|
3191
|
+
};
|
|
3192
|
+
LogLevelDialog.styles = i$1`
|
|
3193
|
+
.loading {
|
|
3194
|
+
text-align: center;
|
|
3195
|
+
padding: 24px;
|
|
3196
|
+
color: var(--md-sys-color-on-surface-variant);
|
|
3197
|
+
}
|
|
3198
|
+
|
|
3199
|
+
.hint {
|
|
3200
|
+
font-size: 0.875rem;
|
|
3201
|
+
color: var(--md-sys-color-on-surface-variant);
|
|
3202
|
+
margin: 0 0 16px 0;
|
|
3203
|
+
font-style: italic;
|
|
3204
|
+
}
|
|
3205
|
+
|
|
3206
|
+
.form-field {
|
|
3207
|
+
margin-bottom: 16px;
|
|
3208
|
+
}
|
|
3209
|
+
|
|
3210
|
+
.form-field label {
|
|
3211
|
+
display: block;
|
|
3212
|
+
margin-bottom: 8px;
|
|
3213
|
+
font-weight: 500;
|
|
3214
|
+
color: var(--md-sys-color-on-surface);
|
|
3215
|
+
}
|
|
3216
|
+
|
|
3217
|
+
md-outlined-select {
|
|
3218
|
+
width: 100%;
|
|
3219
|
+
}
|
|
3220
|
+
`;
|
|
3221
|
+
__decorateClass([n$1({
|
|
3222
|
+
attribute: false
|
|
3223
|
+
})], LogLevelDialog.prototype, "client", 2);
|
|
3224
|
+
__decorateClass([r()], LogLevelDialog.prototype, "_consoleLevel", 2);
|
|
3225
|
+
__decorateClass([r()], LogLevelDialog.prototype, "_fileLevel", 2);
|
|
3226
|
+
__decorateClass([r()], LogLevelDialog.prototype, "_loading", 2);
|
|
3227
|
+
__decorateClass([r()], LogLevelDialog.prototype, "_applying", 2);
|
|
3228
|
+
__decorateClass([e$1("md-outlined-select[name='console']")], LogLevelDialog.prototype, "_consoleSelect", 2);
|
|
3229
|
+
__decorateClass([e$1("md-outlined-select[name='file']")], LogLevelDialog.prototype, "_fileSelect", 2);
|
|
3230
|
+
LogLevelDialog = __decorateClass([t("log-level-dialog")], LogLevelDialog);
|
|
3231
|
+
|
|
3232
|
+
export { LogLevelDialog };
|