vira 32.4.0 → 33.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/directives/index.d.ts +2 -0
- package/dist/directives/index.js +2 -0
- package/dist/directives/popover.directive.d.ts +29 -0
- package/dist/directives/popover.directive.js +45 -0
- package/dist/directives/tooltip.directive.d.ts +60 -0
- package/dist/directives/tooltip.directive.js +99 -0
- package/dist/elements/color/vira-color-picker.element.d.ts +1 -1
- package/dist/elements/color/vira-color-picker.element.js +17 -15
- package/dist/elements/index.d.ts +5 -5
- package/dist/elements/index.js +5 -5
- package/dist/elements/{pop-up → popover}/vira-menu-item.element.d.ts +4 -4
- package/dist/elements/{pop-up → popover}/vira-menu-item.element.js +12 -3
- package/dist/elements/popover/vira-menu-trigger.element.d.ts +56 -0
- package/dist/elements/{pop-up → popover}/vira-menu-trigger.element.js +19 -30
- package/dist/elements/popover/vira-menu.element.d.ts +35 -0
- package/dist/elements/popover/vira-menu.element.js +262 -0
- package/dist/elements/popover/vira-popover-trigger.element.d.ts +50 -0
- package/dist/elements/popover/vira-popover-trigger.element.js +307 -0
- package/dist/elements/popover/vira-tooltip.element.d.ts +10 -0
- package/dist/elements/popover/vira-tooltip.element.js +176 -0
- package/dist/elements/vira-drawer.element.js +1 -1
- package/dist/elements/vira-dropdown.element.d.ts +40 -13
- package/dist/elements/vira-dropdown.element.js +375 -69
- package/dist/elements/vira-form.element.js +10 -16
- package/dist/elements/vira-json-form.element.js +40 -36
- package/dist/elements/vira-modal.element.js +1 -1
- package/dist/elements/vira-tabs.element.d.ts +3 -9
- package/dist/elements/vira-tabs.element.js +5 -6
- package/dist/elements/vira-theme-switcher.element.js +3 -3
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/util/anchored-popover.d.ts +40 -0
- package/dist/util/anchored-popover.js +70 -0
- package/dist/util/fuzzy-match.d.ts +16 -0
- package/dist/util/fuzzy-match.js +24 -0
- package/dist/util/index.d.ts +5 -3
- package/dist/util/index.js +5 -3
- package/dist/util/{pop-up-helpers.d.ts → menu-helpers.d.ts} +8 -19
- package/dist/util/{pop-up-helpers.js → menu-helpers.js} +12 -19
- package/dist/util/popover-manager.d.ts +123 -0
- package/dist/util/popover-manager.js +414 -0
- package/dist/util/vira-dropdown-option.d.ts +30 -0
- package/dist/util/vira-dropdown-option.js +8 -0
- package/dist/util/vira-form-fields.d.ts +2 -2
- package/dist/util/vira-theme-client.d.ts +1 -1
- package/dist/util/vira-theme-client.js +3 -3
- package/package.json +2 -1
- package/dist/elements/pop-up/vira-menu-trigger.element.d.ts +0 -45
- package/dist/elements/pop-up/vira-menu.element.d.ts +0 -36
- package/dist/elements/pop-up/vira-menu.element.js +0 -88
- package/dist/elements/pop-up/vira-pop-up-trigger.element.d.ts +0 -109
- package/dist/elements/pop-up/vira-pop-up-trigger.element.js +0 -408
- package/dist/elements/vira-select.element.d.ts +0 -44
- package/dist/elements/vira-select.element.js +0 -418
- package/dist/util/pop-up-manager.d.ts +0 -200
- package/dist/util/pop-up-manager.js +0 -258
- package/dist/util/vira-select-option.d.ts +0 -31
- package/dist/util/vira-select-option.js +0 -8
|
@@ -1,18 +1,23 @@
|
|
|
1
1
|
import { check } from '@augment-vir/assert';
|
|
2
2
|
import { filterMap, randomString } from '@augment-vir/common';
|
|
3
3
|
import { extractEventTarget } from '@augment-vir/web';
|
|
4
|
-
import {
|
|
4
|
+
import { nav, NavDirection } from 'device-navigation';
|
|
5
|
+
import { attributes, classMap, css, defineElementEvent, html, ifDefined, listen, nothing, testId, } from 'element-vir';
|
|
5
6
|
import { ChevronUp16Icon } from '../icons/index.js';
|
|
7
|
+
import { createFocusStyles } from '../styles/focus.js';
|
|
6
8
|
import { viraFormCssVars } from '../styles/form-styles.js';
|
|
7
|
-
import {
|
|
9
|
+
import { ViraSize, viraSizeHeights } from '../styles/form-variants.js';
|
|
10
|
+
import { noNativeFormStyles, noUserSelect, viraAnimationDurations } from '../styles/index.js';
|
|
8
11
|
import { defineViraElement } from '../util/define-vira-element.js';
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
12
|
+
import { fuzzyMatch } from '../util/fuzzy-match.js';
|
|
13
|
+
import { renderMenuItemEntries } from '../util/menu-helpers.js';
|
|
14
|
+
import { isViraDropdownOptionGroup, } from '../util/vira-dropdown-option.js';
|
|
15
|
+
import { ViraMenuItem } from './popover/vira-menu-item.element.js';
|
|
16
|
+
import { ViraMenu, ViraMenuCornerStyle } from './popover/vira-menu.element.js';
|
|
17
|
+
import { ViraPopoverTrigger } from './popover/vira-popover-trigger.element.js';
|
|
13
18
|
import { ViraIcon } from './vira-icon.element.js';
|
|
14
19
|
/**
|
|
15
|
-
* A dropdown element that uses
|
|
20
|
+
* A dropdown element that uses popover menus.
|
|
16
21
|
*
|
|
17
22
|
* @category Dropdown
|
|
18
23
|
* @category Elements
|
|
@@ -23,18 +28,19 @@ export const ViraDropdown = defineViraElement()({
|
|
|
23
28
|
testIds: [
|
|
24
29
|
'leadingIcon',
|
|
25
30
|
'prefixText',
|
|
31
|
+
'searchInput',
|
|
26
32
|
'trigger',
|
|
27
33
|
],
|
|
28
34
|
styles: css `
|
|
29
35
|
:host {
|
|
30
36
|
display: inline-flex;
|
|
31
37
|
vertical-align: middle;
|
|
32
|
-
width:
|
|
38
|
+
width: 224px;
|
|
33
39
|
position: relative;
|
|
34
40
|
max-width: 100%;
|
|
35
41
|
}
|
|
36
42
|
|
|
37
|
-
${
|
|
43
|
+
${ViraPopoverTrigger} {
|
|
38
44
|
width: 100%;
|
|
39
45
|
}
|
|
40
46
|
|
|
@@ -44,6 +50,38 @@ export const ViraDropdown = defineViraElement()({
|
|
|
44
50
|
white-space: nowrap;
|
|
45
51
|
}
|
|
46
52
|
|
|
53
|
+
.search-input {
|
|
54
|
+
${noNativeFormStyles};
|
|
55
|
+
flex-grow: 1;
|
|
56
|
+
min-width: 0;
|
|
57
|
+
align-self: stretch;
|
|
58
|
+
text-overflow: ellipsis;
|
|
59
|
+
cursor: inherit;
|
|
60
|
+
user-select: text;
|
|
61
|
+
-webkit-user-select: text;
|
|
62
|
+
outline: none;
|
|
63
|
+
|
|
64
|
+
&::placeholder {
|
|
65
|
+
color: inherit;
|
|
66
|
+
opacity: 0.4;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
.open .search-input {
|
|
71
|
+
cursor: text;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
.dropdown-trigger:has(.search-input:focus-visible:not([disabled])) {
|
|
75
|
+
position: relative;
|
|
76
|
+
|
|
77
|
+
&::after {
|
|
78
|
+
${createFocusStyles({
|
|
79
|
+
elementBorderSize: '1px',
|
|
80
|
+
noNesting: true,
|
|
81
|
+
})}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
47
85
|
.trigger-icon {
|
|
48
86
|
width: 24px;
|
|
49
87
|
aspect-ratio: 1;
|
|
@@ -60,18 +98,8 @@ export const ViraDropdown = defineViraElement()({
|
|
|
60
98
|
justify-content: flex-end;
|
|
61
99
|
}
|
|
62
100
|
|
|
63
|
-
.open {
|
|
64
|
-
|
|
65
|
-
transform: rotate(0);
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
&:not(.open-upwards).dropdown-trigger {
|
|
69
|
-
border-bottom-left-radius: 0;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
&.open-upwards.dropdown-trigger {
|
|
73
|
-
border-top-left-radius: 0;
|
|
74
|
-
}
|
|
101
|
+
.open .trigger-icon {
|
|
102
|
+
transform: rotate(0);
|
|
75
103
|
}
|
|
76
104
|
|
|
77
105
|
.dropdown-trigger {
|
|
@@ -84,13 +112,50 @@ export const ViraDropdown = defineViraElement()({
|
|
|
84
112
|
gap: 8px;
|
|
85
113
|
text-align: left;
|
|
86
114
|
align-items: center;
|
|
87
|
-
|
|
88
|
-
padding
|
|
115
|
+
min-height: ${viraSizeHeights[ViraSize.Medium]}px;
|
|
116
|
+
padding: 0 3px 0 10px;
|
|
89
117
|
border-radius: ${viraFormCssVars['vira-form-radius'].value};
|
|
90
118
|
background-color: ${viraFormCssVars['vira-form-background-color'].value};
|
|
91
119
|
color: ${viraFormCssVars['vira-form-foreground-color'].value};
|
|
92
120
|
}
|
|
93
121
|
|
|
122
|
+
.dropdown-wrapper {
|
|
123
|
+
display: flex;
|
|
124
|
+
width: 100%;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
.has-error .dropdown-trigger {
|
|
128
|
+
border-color: ${viraFormCssVars['vira-form-error-color'].value};
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
.option-group-label,
|
|
132
|
+
.no-options {
|
|
133
|
+
${noUserSelect};
|
|
134
|
+
/* Aligns with the label text of each ViraMenuItem, past its check icon. */
|
|
135
|
+
padding: 8px 12px 4px
|
|
136
|
+
calc(${ViraMenuItem.cssVars['vira-menu-item-icon-gap'].value} * 3 + 16px);
|
|
137
|
+
opacity: 0.6;
|
|
138
|
+
text-align: left;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
.option-group-label {
|
|
142
|
+
font-weight: ${viraFormCssVars['vira-form-label-font-weight'].value};
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
.no-options {
|
|
146
|
+
cursor: default;
|
|
147
|
+
padding-bottom: 8px;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
${ViraMenuItem}.option-group-label {
|
|
151
|
+
padding: 0 0 0 calc(${ViraMenuItem.cssVars['vira-menu-item-icon-gap'].value} * 2);
|
|
152
|
+
${ViraMenuItem.cssVars['vira-menu-item-padding'].name}: 8px 12px 4px 0;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
.readonly-value {
|
|
156
|
+
overflow-wrap: anywhere;
|
|
157
|
+
}
|
|
158
|
+
|
|
94
159
|
.using-placeholder {
|
|
95
160
|
opacity: 0.4;
|
|
96
161
|
}
|
|
@@ -114,22 +179,29 @@ export const ViraDropdown = defineViraElement()({
|
|
|
114
179
|
events: {
|
|
115
180
|
/** Emits all currently selected values. */
|
|
116
181
|
selectedValuesChange: defineElementEvent(),
|
|
182
|
+
/** `true` when the popover just opened, `false` when it just closed. */
|
|
117
183
|
openChange: defineElementEvent(),
|
|
118
184
|
},
|
|
119
185
|
state() {
|
|
120
186
|
return {
|
|
121
|
-
|
|
122
|
-
showPopUpResult: undefined,
|
|
187
|
+
isOpen: false,
|
|
123
188
|
shouldSelectOnMouseUp: false,
|
|
124
189
|
/**
|
|
125
190
|
* Used to couple the label and trigger together. This is not applied if no label is
|
|
126
191
|
* provided.
|
|
127
192
|
*/
|
|
128
193
|
randomId: randomString(32),
|
|
194
|
+
/** The text typed into the search input while the dropdown is open. */
|
|
195
|
+
searchText: '',
|
|
196
|
+
navController: undefined,
|
|
197
|
+
popoverManager: undefined,
|
|
129
198
|
};
|
|
130
199
|
},
|
|
131
|
-
render({ state, inputs, dispatch, events, updateState, testIds }) {
|
|
132
|
-
const
|
|
200
|
+
render({ state, inputs, dispatch, events, updateState, testIds, host }) {
|
|
201
|
+
const flatOptions = inputs.options.flatMap((entry) => {
|
|
202
|
+
return isViraDropdownOptionGroup(entry) ? entry.options : [entry];
|
|
203
|
+
});
|
|
204
|
+
const selectedOptions = filterMap(inputs.selected, (selectedValue) => flatOptions.find((option) => option.value === selectedValue), check.isTruthy);
|
|
133
205
|
const leadingIconTemplate = inputs.icon
|
|
134
206
|
? html `
|
|
135
207
|
<${ViraIcon.assign({
|
|
@@ -165,13 +237,115 @@ export const ViraDropdown = defineViraElement()({
|
|
|
165
237
|
detail: newSelectedValues,
|
|
166
238
|
}));
|
|
167
239
|
}
|
|
240
|
+
const filteredOptions = state.searchText
|
|
241
|
+
? filterMap(inputs.options, (entry) => {
|
|
242
|
+
return isViraDropdownOptionGroup(entry)
|
|
243
|
+
? {
|
|
244
|
+
...entry,
|
|
245
|
+
options: entry.options.filter((option) => {
|
|
246
|
+
return fuzzyMatch({
|
|
247
|
+
search: state.searchText,
|
|
248
|
+
text: option.label,
|
|
249
|
+
});
|
|
250
|
+
}),
|
|
251
|
+
}
|
|
252
|
+
: entry;
|
|
253
|
+
}, (entry) => {
|
|
254
|
+
return isViraDropdownOptionGroup(entry)
|
|
255
|
+
? !!entry.options.length
|
|
256
|
+
: fuzzyMatch({
|
|
257
|
+
search: state.searchText,
|
|
258
|
+
text: entry.label,
|
|
259
|
+
});
|
|
260
|
+
})
|
|
261
|
+
: inputs.options;
|
|
262
|
+
const filteredFlatOptions = filteredOptions.flatMap((entry) => {
|
|
263
|
+
return isViraDropdownOptionGroup(entry) ? entry.options : [entry];
|
|
264
|
+
});
|
|
265
|
+
function findSearchInput() {
|
|
266
|
+
const searchInput = host.shadowRoot.querySelector('.search-input');
|
|
267
|
+
return searchInput instanceof HTMLInputElement ? searchInput : undefined;
|
|
268
|
+
}
|
|
269
|
+
function clearSearch(searchInput) {
|
|
270
|
+
/** Set directly because the `.value` binding skips values it has already committed. */
|
|
271
|
+
searchInput.value = '';
|
|
272
|
+
updateState({
|
|
273
|
+
searchText: '',
|
|
274
|
+
});
|
|
275
|
+
}
|
|
276
|
+
function toggleGroup(group) {
|
|
277
|
+
const enabledGroupOptions = group.options.filter((option) => !option.disabled);
|
|
278
|
+
const isGroupSelected = enabledGroupOptions.every((option) => {
|
|
279
|
+
return selectedOptions.includes(option);
|
|
280
|
+
});
|
|
281
|
+
dispatch(new events.selectedValuesChange({
|
|
282
|
+
detail: (isGroupSelected
|
|
283
|
+
? selectedOptions.filter((option) => !enabledGroupOptions.includes(option))
|
|
284
|
+
: [
|
|
285
|
+
...selectedOptions,
|
|
286
|
+
...enabledGroupOptions.filter((option) => {
|
|
287
|
+
return !selectedOptions.includes(option);
|
|
288
|
+
}),
|
|
289
|
+
]).map((option) => option.value),
|
|
290
|
+
}));
|
|
291
|
+
}
|
|
292
|
+
function renderGroupLabel(group) {
|
|
293
|
+
return inputs.isMultiSelect
|
|
294
|
+
? html `
|
|
295
|
+
<${ViraMenuItem.assign({
|
|
296
|
+
disablePointerStyles: true,
|
|
297
|
+
})}
|
|
298
|
+
class="option-group-label"
|
|
299
|
+
${state.navController ? nav(state.navController) : nothing}
|
|
300
|
+
${listen('click', () => {
|
|
301
|
+
toggleGroup(group);
|
|
302
|
+
})}
|
|
303
|
+
>
|
|
304
|
+
${group.groupName}
|
|
305
|
+
</${ViraMenuItem}>
|
|
306
|
+
`
|
|
307
|
+
: html `
|
|
308
|
+
<div class="option-group-label">${group.groupName}</div>
|
|
309
|
+
`;
|
|
310
|
+
}
|
|
311
|
+
/** Drag-select actions in the same order as the rendered menu items. */
|
|
312
|
+
const menuItemActions = filteredOptions.flatMap((entry) => {
|
|
313
|
+
const options = isViraDropdownOptionGroup(entry) ? entry.options : [entry];
|
|
314
|
+
const optionActions = options.map((option) => {
|
|
315
|
+
return option.disabled
|
|
316
|
+
? undefined
|
|
317
|
+
: () => {
|
|
318
|
+
selectOption(option);
|
|
319
|
+
};
|
|
320
|
+
});
|
|
321
|
+
return isViraDropdownOptionGroup(entry) && inputs.isMultiSelect
|
|
322
|
+
? [
|
|
323
|
+
() => {
|
|
324
|
+
toggleGroup(entry);
|
|
325
|
+
},
|
|
326
|
+
...optionActions,
|
|
327
|
+
]
|
|
328
|
+
: optionActions;
|
|
329
|
+
});
|
|
330
|
+
function renderOptionEntries(options) {
|
|
331
|
+
return renderMenuItemEntries(options.map((option) => {
|
|
332
|
+
return {
|
|
333
|
+
content: option.label,
|
|
334
|
+
onClick() {
|
|
335
|
+
selectOption(option);
|
|
336
|
+
},
|
|
337
|
+
disabled: option.disabled,
|
|
338
|
+
selected: selectedOptions.includes(option),
|
|
339
|
+
};
|
|
340
|
+
}), state.navController);
|
|
341
|
+
}
|
|
342
|
+
const isMenuShown = state.isOpen && (!!filteredFlatOptions.length || !!inputs.noOptionsText);
|
|
168
343
|
const menuTemplate = html `
|
|
169
344
|
<${ViraMenu.assign({
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
: ViraMenuPopUpDirection.Upwards,
|
|
345
|
+
cornerStyle: ViraMenuCornerStyle.Round,
|
|
346
|
+
hoverScrollSpeed: inputs.menuHoverScrollSpeed,
|
|
173
347
|
})}
|
|
174
|
-
slot=${
|
|
348
|
+
slot=${ViraPopoverTrigger.slotNames['vira-popover-trigger-popover']}
|
|
175
349
|
${listen('mouseup', (event) => {
|
|
176
350
|
if (state.shouldSelectOnMouseUp) {
|
|
177
351
|
const menuItem = event
|
|
@@ -179,47 +353,82 @@ export const ViraDropdown = defineViraElement()({
|
|
|
179
353
|
.find((eventTarget) => {
|
|
180
354
|
return eventTarget instanceof ViraMenuItem;
|
|
181
355
|
});
|
|
182
|
-
const
|
|
183
|
-
?
|
|
356
|
+
const menuItemAction = menuItem
|
|
357
|
+
? menuItemActions[Array.from(extractEventTarget(event, ViraMenu).querySelectorAll(ViraMenuItem.tagName)).indexOf(menuItem)]
|
|
184
358
|
: undefined;
|
|
185
|
-
|
|
186
|
-
selectOption(option);
|
|
187
|
-
}
|
|
359
|
+
menuItemAction?.();
|
|
188
360
|
}
|
|
189
361
|
})}
|
|
190
362
|
>
|
|
191
|
-
${
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
363
|
+
${filteredFlatOptions.length
|
|
364
|
+
? filteredOptions.map((entry) => {
|
|
365
|
+
return isViraDropdownOptionGroup(entry)
|
|
366
|
+
? html `
|
|
367
|
+
${renderGroupLabel(entry)} ${renderOptionEntries(entry.options)}
|
|
368
|
+
`
|
|
369
|
+
: renderOptionEntries([entry]);
|
|
370
|
+
})
|
|
371
|
+
: html `
|
|
372
|
+
<div class="no-options">${inputs.noOptionsText}</div>
|
|
373
|
+
`}
|
|
201
374
|
</${ViraMenu}>
|
|
202
375
|
`;
|
|
203
376
|
const triggerTemplate = html `
|
|
204
|
-
<${
|
|
377
|
+
<${ViraPopoverTrigger.assign({
|
|
205
378
|
...inputs,
|
|
206
379
|
keepOpenAfterInteraction: inputs.isMultiSelect,
|
|
207
380
|
focusOnClose: true,
|
|
208
|
-
|
|
381
|
+
popoverOffset: {
|
|
209
382
|
vertical: -1,
|
|
210
383
|
right: 24,
|
|
211
384
|
},
|
|
212
|
-
horizontalAnchor: inputs.horizontalAnchor || HorizontalAnchor.Both,
|
|
213
385
|
})}
|
|
214
|
-
${listen(
|
|
215
|
-
|
|
386
|
+
${listen(ViraPopoverTrigger.events.init, (event) => {
|
|
387
|
+
updateState({
|
|
388
|
+
navController: event.detail.navController,
|
|
389
|
+
popoverManager: event.detail.popoverManager,
|
|
390
|
+
});
|
|
391
|
+
})}
|
|
392
|
+
${listen(ViraPopoverTrigger.events.openChange, (event) => {
|
|
393
|
+
if (state.isOpen !== event.detail) {
|
|
216
394
|
dispatch(new events.openChange({
|
|
217
395
|
detail: event.detail,
|
|
218
396
|
}));
|
|
219
397
|
}
|
|
220
398
|
updateState({
|
|
221
|
-
|
|
399
|
+
isOpen: event.detail,
|
|
222
400
|
});
|
|
401
|
+
const searchInput = findSearchInput();
|
|
402
|
+
if (!searchInput) {
|
|
403
|
+
return;
|
|
404
|
+
}
|
|
405
|
+
else if (event.detail) {
|
|
406
|
+
searchInput.focus();
|
|
407
|
+
}
|
|
408
|
+
else {
|
|
409
|
+
clearSearch(searchInput);
|
|
410
|
+
}
|
|
411
|
+
})}
|
|
412
|
+
${listen('keydown', (event) => {
|
|
413
|
+
const searchInput = findSearchInput();
|
|
414
|
+
const isSearchKey = event.key === 'Backspace' ||
|
|
415
|
+
(event.key.length === 1 &&
|
|
416
|
+
event.key !== ' ' &&
|
|
417
|
+
!event.ctrlKey &&
|
|
418
|
+
!event.metaKey &&
|
|
419
|
+
!event.altKey);
|
|
420
|
+
if (!searchInput || !isSearchKey) {
|
|
421
|
+
return;
|
|
422
|
+
}
|
|
423
|
+
if (!state.isOpen) {
|
|
424
|
+
clearSearch(searchInput);
|
|
425
|
+
}
|
|
426
|
+
/**
|
|
427
|
+
* Focusing during keydown makes the browser type the pressed key into the
|
|
428
|
+
* search input, even when the key was pressed on the trigger or a menu item.
|
|
429
|
+
*/
|
|
430
|
+
searchInput.focus();
|
|
431
|
+
state.popoverManager?.show();
|
|
223
432
|
})}
|
|
224
433
|
${listen('mouseup', () => {
|
|
225
434
|
if (state.shouldSelectOnMouseUp) {
|
|
@@ -231,15 +440,15 @@ export const ViraDropdown = defineViraElement()({
|
|
|
231
440
|
>
|
|
232
441
|
<div
|
|
233
442
|
class="dropdown-trigger ${classMap({
|
|
234
|
-
open:
|
|
235
|
-
'open-upwards': !state.showPopUpResult?.popDown,
|
|
443
|
+
open: state.isOpen,
|
|
236
444
|
})}"
|
|
237
|
-
slot=${
|
|
445
|
+
slot=${ViraPopoverTrigger.slotNames['vira-popover-trigger-trigger']}
|
|
238
446
|
id=${ifDefined(inputs.label ? state.randomId : undefined)}
|
|
239
447
|
aria-label=${ifDefined((check.isString(inputs.label) && inputs.label) || undefined)}
|
|
448
|
+
${attributes(inputs.attributePassthrough)}
|
|
240
449
|
${testId(testIds.trigger)}
|
|
241
450
|
${listen('mousedown', () => {
|
|
242
|
-
if (!state.
|
|
451
|
+
if (!state.isOpen) {
|
|
243
452
|
updateState({
|
|
244
453
|
shouldSelectOnMouseUp: true,
|
|
245
454
|
});
|
|
@@ -247,14 +456,93 @@ export const ViraDropdown = defineViraElement()({
|
|
|
247
456
|
})}
|
|
248
457
|
>
|
|
249
458
|
${leadingIconTemplate}
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
459
|
+
${inputs.isSearchable
|
|
460
|
+
? html `
|
|
461
|
+
${state.isOpen ? nothing : prefixTemplate}
|
|
462
|
+
<input
|
|
463
|
+
class="search-input"
|
|
464
|
+
${testId(testIds.searchInput)}
|
|
465
|
+
tabindex="-1"
|
|
466
|
+
autocomplete="off"
|
|
467
|
+
spellcheck="false"
|
|
468
|
+
aria-label=${ifDefined((check.isString(inputs.label) && inputs.label) || undefined)}
|
|
469
|
+
?disabled=${!!inputs.isDisabled}
|
|
470
|
+
title=${ifDefined(shouldUsePlaceholder || state.isOpen
|
|
471
|
+
? undefined
|
|
472
|
+
: selectionDisplay)}
|
|
473
|
+
placeholder=${state.isOpen && !shouldUsePlaceholder
|
|
474
|
+
? selectionDisplay
|
|
475
|
+
: inputs.placeholder || ''}
|
|
476
|
+
.value=${state.isOpen
|
|
477
|
+
? state.searchText
|
|
478
|
+
: shouldUsePlaceholder
|
|
479
|
+
? ''
|
|
480
|
+
: selectionDisplay}
|
|
481
|
+
${listen('mousedown', (event) => {
|
|
482
|
+
/** Keeps the popover open when clicking into the input. */
|
|
483
|
+
if (state.isOpen) {
|
|
484
|
+
event.stopPropagation();
|
|
485
|
+
}
|
|
486
|
+
})}
|
|
487
|
+
${listen('input', (event) => {
|
|
488
|
+
updateState({
|
|
489
|
+
searchText: extractEventTarget(event, HTMLInputElement)
|
|
490
|
+
.value,
|
|
491
|
+
});
|
|
492
|
+
state.popoverManager?.show();
|
|
493
|
+
})}
|
|
494
|
+
${listen('keydown', (event) => {
|
|
495
|
+
if (!state.isOpen) {
|
|
496
|
+
return;
|
|
497
|
+
}
|
|
498
|
+
else if (event.key === 'ArrowDown' ||
|
|
499
|
+
event.key === 'ArrowUp') {
|
|
500
|
+
event.preventDefault();
|
|
501
|
+
state.navController?.navigate({
|
|
502
|
+
direction: event.key === 'ArrowDown'
|
|
503
|
+
? NavDirection.Down
|
|
504
|
+
: NavDirection.Up,
|
|
505
|
+
allowWrapping: false,
|
|
506
|
+
});
|
|
507
|
+
}
|
|
508
|
+
else if (event.key === 'Tab' && !event.shiftKey) {
|
|
509
|
+
/**
|
|
510
|
+
* Without this, browsers move focus from this input past
|
|
511
|
+
* the menu items (sometimes closing the menu).
|
|
512
|
+
*/
|
|
513
|
+
const firstMenuItem = Array.from(host.shadowRoot.querySelectorAll(ViraMenuItem.tagName)).find((menuItem) => menuItem.tabIndex >= 0);
|
|
514
|
+
if (firstMenuItem) {
|
|
515
|
+
event.preventDefault();
|
|
516
|
+
firstMenuItem.focus();
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
else if (event.key === 'Enter') {
|
|
520
|
+
const firstOption = filteredFlatOptions.find((option) => !option.disabled);
|
|
521
|
+
if (!firstOption) {
|
|
522
|
+
return;
|
|
523
|
+
}
|
|
524
|
+
event.preventDefault();
|
|
525
|
+
selectOption(firstOption);
|
|
526
|
+
if (inputs.isMultiSelect) {
|
|
527
|
+
clearSearch(extractEventTarget(event, HTMLInputElement));
|
|
528
|
+
}
|
|
529
|
+
else {
|
|
530
|
+
state.popoverManager?.hide();
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
})}
|
|
534
|
+
/>
|
|
535
|
+
`
|
|
536
|
+
: html `
|
|
537
|
+
<span
|
|
538
|
+
class="selection-display ${classMap({
|
|
539
|
+
'using-placeholder': shouldUsePlaceholder,
|
|
540
|
+
})}"
|
|
541
|
+
title=${ifDefined(shouldUsePlaceholder ? undefined : selectionDisplay)}
|
|
542
|
+
>
|
|
543
|
+
${prefixTemplate} ${selectionDisplay}
|
|
544
|
+
</span>
|
|
545
|
+
`}
|
|
258
546
|
|
|
259
547
|
<span class="trigger-icon-wrapper">
|
|
260
548
|
<${ViraIcon.assign({
|
|
@@ -264,19 +552,37 @@ export const ViraDropdown = defineViraElement()({
|
|
|
264
552
|
></${ViraIcon}>
|
|
265
553
|
</span>
|
|
266
554
|
</div>
|
|
267
|
-
${
|
|
268
|
-
</${
|
|
555
|
+
${isMenuShown ? menuTemplate : nothing}
|
|
556
|
+
</${ViraPopoverTrigger}>
|
|
269
557
|
`;
|
|
558
|
+
const contentTemplate = inputs.isReadonly
|
|
559
|
+
? html `
|
|
560
|
+
<span class="readonly-value">${selectionDisplay}</span>
|
|
561
|
+
`
|
|
562
|
+
: triggerTemplate;
|
|
270
563
|
if (inputs.label) {
|
|
271
564
|
return html `
|
|
272
|
-
<label
|
|
565
|
+
<label
|
|
566
|
+
for=${ifDefined(inputs.isReadonly ? undefined : state.randomId)}
|
|
567
|
+
class=${classMap({
|
|
568
|
+
'has-error': !!inputs.hasError,
|
|
569
|
+
})}
|
|
570
|
+
>
|
|
273
571
|
<span class="dropdown-label">${inputs.label}</span>
|
|
274
|
-
${
|
|
572
|
+
${contentTemplate}
|
|
275
573
|
</label>
|
|
276
574
|
`;
|
|
277
575
|
}
|
|
278
576
|
else {
|
|
279
|
-
return
|
|
577
|
+
return html `
|
|
578
|
+
<span
|
|
579
|
+
class="dropdown-wrapper ${classMap({
|
|
580
|
+
'has-error': !!inputs.hasError,
|
|
581
|
+
})}"
|
|
582
|
+
>
|
|
583
|
+
${contentTemplate}
|
|
584
|
+
</span>
|
|
585
|
+
`;
|
|
280
586
|
}
|
|
281
587
|
},
|
|
282
588
|
});
|
|
@@ -6,8 +6,8 @@ import { defineViraElement } from '../util/define-vira-element.js';
|
|
|
6
6
|
import { applyRequiredLabel, areFormFieldsValid, ViraFormFieldType, } from '../util/vira-form-fields.js';
|
|
7
7
|
import { ViraCheckbox } from './vira-checkbox.element.js';
|
|
8
8
|
import { ViraDateInput } from './vira-date-input.element.js';
|
|
9
|
+
import { ViraDropdown } from './vira-dropdown.element.js';
|
|
9
10
|
import { ViraInput, ViraInputType } from './vira-input.element.js';
|
|
10
|
-
import { ViraSelect } from './vira-select.element.js';
|
|
11
11
|
import { ViraTextArea } from './vira-text-area.element.js';
|
|
12
12
|
/**
|
|
13
13
|
* A form element.
|
|
@@ -70,9 +70,9 @@ export const ViraForm = defineViraElement()({
|
|
|
70
70
|
&
|
|
71
71
|
> ${ViraDateInput},
|
|
72
72
|
&
|
|
73
|
-
> ${
|
|
73
|
+
> ${ViraDropdown},
|
|
74
74
|
&
|
|
75
|
-
> ${
|
|
75
|
+
> ${ViraInput},
|
|
76
76
|
&
|
|
77
77
|
> ${ViraTextArea} {
|
|
78
78
|
width: 100%;
|
|
@@ -159,34 +159,28 @@ export const ViraForm = defineViraElement()({
|
|
|
159
159
|
return wrapFormField({
|
|
160
160
|
label,
|
|
161
161
|
fieldTemplate: html `
|
|
162
|
-
<${
|
|
162
|
+
<${ViraDropdown.assign({
|
|
163
163
|
options: field.options,
|
|
164
|
-
|
|
164
|
+
selected: field.value ? [field.value] : [],
|
|
165
165
|
placeholder: field.placeholder,
|
|
166
|
-
|
|
166
|
+
isDisabled,
|
|
167
167
|
isReadonly: inputs.isReadonly,
|
|
168
168
|
label: childLabel,
|
|
169
169
|
hasError: field.hasError,
|
|
170
170
|
icon: field.icon,
|
|
171
|
-
|
|
172
|
-
? {
|
|
173
|
-
attributePassthrough: {
|
|
174
|
-
select: horizontalLabelAttributes,
|
|
175
|
-
},
|
|
176
|
-
}
|
|
177
|
-
: {}),
|
|
171
|
+
attributePassthrough: horizontalLabelAttributes,
|
|
178
172
|
})}
|
|
179
173
|
${field.testId ? testId(field.testId) : nothing}
|
|
180
|
-
${listen(
|
|
174
|
+
${listen(ViraDropdown.events.selectedValuesChange, (event) => {
|
|
181
175
|
dispatch(new events.valueChange({
|
|
182
176
|
detail: {
|
|
183
177
|
key,
|
|
184
178
|
...field,
|
|
185
|
-
value: event.detail,
|
|
179
|
+
value: event.detail[0],
|
|
186
180
|
},
|
|
187
181
|
}));
|
|
188
182
|
})}
|
|
189
|
-
></${
|
|
183
|
+
></${ViraDropdown}>
|
|
190
184
|
`,
|
|
191
185
|
});
|
|
192
186
|
}
|