vira 32.3.0 → 33.0.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 +3 -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 +49 -0
- package/dist/elements/popover/vira-popover-trigger.element.js +301 -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-checkbox.element.d.ts +3 -3
- package/dist/elements/vira-checkbox.element.js +4 -3
- package/dist/elements/vira-date-input.element.d.ts +2 -1
- package/dist/elements/vira-date-input.element.js +3 -2
- package/dist/elements/vira-drawer.element.js +1 -1
- package/dist/elements/vira-dropdown.element.d.ts +29 -12
- package/dist/elements/vira-dropdown.element.js +173 -60
- package/dist/elements/vira-form.element.js +13 -22
- package/dist/elements/vira-input.element.d.ts +2 -1
- package/dist/elements/vira-input.element.js +2 -2
- 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-text-area.element.d.ts +2 -1
- package/dist/elements/vira-text-area.element.js +3 -2
- 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/index.d.ts +4 -3
- package/dist/util/index.js +4 -3
- package/dist/util/{pop-up-helpers.d.ts → menu-helpers.d.ts} +4 -18
- package/dist/util/{pop-up-helpers.js → menu-helpers.js} +2 -17
- package/dist/util/popover-manager.d.ts +123 -0
- package/dist/util/popover-manager.js +412 -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 +5 -10
- package/dist/util/vira-form-fields.js +15 -11
- 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
|
@@ -0,0 +1,412 @@
|
|
|
1
|
+
import { check } from '@augment-vir/assert';
|
|
2
|
+
import { walkActiveElement } from '@augment-vir/web';
|
|
3
|
+
import { convertDuration } from 'date-vir';
|
|
4
|
+
import { NavActivateEvent, NavDirection } from 'device-navigation';
|
|
5
|
+
import { listenToPageActivation } from 'page-active';
|
|
6
|
+
import { listenTo, listenToGlobal } from 'typed-event-target';
|
|
7
|
+
import { anchoredPopoverRetryClass } from './anchored-popover.js';
|
|
8
|
+
/**
|
|
9
|
+
* Used to prevent popovers from closing when a text input is active.
|
|
10
|
+
*
|
|
11
|
+
* @category Internal
|
|
12
|
+
*/
|
|
13
|
+
export function isInputLikeElement(element) {
|
|
14
|
+
return ((element instanceof HTMLInputElement &&
|
|
15
|
+
(element.type === 'text' ||
|
|
16
|
+
element.type === 'search' ||
|
|
17
|
+
element.type === 'email' ||
|
|
18
|
+
element.type === 'url' ||
|
|
19
|
+
element.type === 'tel' ||
|
|
20
|
+
element.type === 'password' ||
|
|
21
|
+
element.type === 'number')) ||
|
|
22
|
+
element instanceof HTMLTextAreaElement ||
|
|
23
|
+
(element instanceof HTMLElement && element.isContentEditable));
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Whether a keyboard click (a click with `detail` of `0`) came from pressing Enter in a text input,
|
|
27
|
+
* which browsers turn into a click on the form's first button.
|
|
28
|
+
*
|
|
29
|
+
* @category Internal
|
|
30
|
+
*/
|
|
31
|
+
export function isTextInputActive() {
|
|
32
|
+
let isActive = false;
|
|
33
|
+
walkActiveElement(({ element }) => {
|
|
34
|
+
isActive = isInputLikeElement(element);
|
|
35
|
+
return isActive;
|
|
36
|
+
});
|
|
37
|
+
return isActive;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Determines if a mouse event landed on a scrollbar rather than on element content. Scrollbar
|
|
41
|
+
* interactions fire mouse events on the scrolling element itself (or on `<html>` for the page
|
|
42
|
+
* scrollbar), so an outside-click check would otherwise treat dragging a scrollbar as a click away
|
|
43
|
+
* from the popover and dismiss it mid-drag.
|
|
44
|
+
*
|
|
45
|
+
* @category Internal
|
|
46
|
+
*/
|
|
47
|
+
export function isMouseEventOnScrollbar(event) {
|
|
48
|
+
const target = event.composedPath()[0];
|
|
49
|
+
if (!(target instanceof Element)) {
|
|
50
|
+
return false;
|
|
51
|
+
}
|
|
52
|
+
const rect = target.getBoundingClientRect();
|
|
53
|
+
/**
|
|
54
|
+
* `clientLeft` and `clientTop` are the border widths, which also include the scrollbar itself
|
|
55
|
+
* when it's rendered on the leading edge (RTL layouts).
|
|
56
|
+
*/
|
|
57
|
+
const clientLeft = rect.left + target.clientLeft;
|
|
58
|
+
const clientTop = rect.top + target.clientTop;
|
|
59
|
+
const outsideHorizontally = event.clientX < clientLeft || event.clientX >= clientLeft + target.clientWidth;
|
|
60
|
+
const outsideVertically = event.clientY < clientTop || event.clientY >= clientTop + target.clientHeight;
|
|
61
|
+
return ((outsideHorizontally && target.scrollHeight > target.clientHeight) ||
|
|
62
|
+
(outsideVertically && target.scrollWidth > target.clientWidth));
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* The interaction on the anchor element that shows and hides a popover.
|
|
66
|
+
*
|
|
67
|
+
* @category Popover
|
|
68
|
+
*/
|
|
69
|
+
export var PopoverTrigger;
|
|
70
|
+
(function (PopoverTrigger) {
|
|
71
|
+
/**
|
|
72
|
+
* Shows the popover shortly after the anchor is hovered or focused and hides it when the anchor
|
|
73
|
+
* is no longer hovered or focused.
|
|
74
|
+
*/
|
|
75
|
+
PopoverTrigger["Hover"] = "hover";
|
|
76
|
+
/** Toggles the popover when the anchor is clicked. An arrow key opens it. */
|
|
77
|
+
PopoverTrigger["Click"] = "click";
|
|
78
|
+
/**
|
|
79
|
+
* Toggles the popover as soon as the mouse is pressed on the anchor, so it can be dragged over
|
|
80
|
+
* the popover's contents and released on one of them. Keyboard clicks and arrow keys also
|
|
81
|
+
* work.
|
|
82
|
+
*/
|
|
83
|
+
PopoverTrigger["Mousedown"] = "mousedown";
|
|
84
|
+
})(PopoverTrigger || (PopoverTrigger = {}));
|
|
85
|
+
const defaultDelays = {
|
|
86
|
+
[PopoverTrigger.Hover]: {
|
|
87
|
+
milliseconds: 300,
|
|
88
|
+
},
|
|
89
|
+
};
|
|
90
|
+
function listenToAnchor(anchor, eventName, listener) {
|
|
91
|
+
return listenTo(anchor, eventName, (event) => {
|
|
92
|
+
listener(event);
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
const anchorListeners = {
|
|
96
|
+
[PopoverTrigger.Hover](popoverManager, anchor) {
|
|
97
|
+
return [
|
|
98
|
+
listenToAnchor(anchor, 'mouseenter', () => {
|
|
99
|
+
popoverManager.scheduleShow();
|
|
100
|
+
}),
|
|
101
|
+
listenToAnchor(anchor, 'focusin', () => {
|
|
102
|
+
popoverManager.scheduleShow();
|
|
103
|
+
}),
|
|
104
|
+
listenToAnchor(anchor, 'mouseleave', () => {
|
|
105
|
+
popoverManager.hide();
|
|
106
|
+
}),
|
|
107
|
+
listenToAnchor(anchor, 'focusout', () => {
|
|
108
|
+
popoverManager.hide();
|
|
109
|
+
}),
|
|
110
|
+
];
|
|
111
|
+
},
|
|
112
|
+
[PopoverTrigger.Click](popoverManager, anchor) {
|
|
113
|
+
return [
|
|
114
|
+
listenToAnchor(anchor, 'click', (event) => {
|
|
115
|
+
if (event.detail !== 0 || !isTextInputActive()) {
|
|
116
|
+
popoverManager.toggle();
|
|
117
|
+
}
|
|
118
|
+
}),
|
|
119
|
+
listenToAnchor(anchor, 'keydown', (event) => {
|
|
120
|
+
popoverManager.openOnArrow(event);
|
|
121
|
+
}),
|
|
122
|
+
];
|
|
123
|
+
},
|
|
124
|
+
[PopoverTrigger.Mousedown](popoverManager, anchor) {
|
|
125
|
+
return [
|
|
126
|
+
listenToAnchor(anchor, 'mousedown', (event) => {
|
|
127
|
+
if (event.button === 0 && !isMouseEventOnScrollbar(event)) {
|
|
128
|
+
const target = event.composedPath()[0];
|
|
129
|
+
if (!(target instanceof Element && isInputLikeElement(target))) {
|
|
130
|
+
/**
|
|
131
|
+
* Without this, WebKit scrolls the anchor's scroll containers when the
|
|
132
|
+
* pointer is dragged past their edges on the way to a popover item.
|
|
133
|
+
*/
|
|
134
|
+
event.preventDefault();
|
|
135
|
+
/** Preventing the default also prevents the focus that mousedown gives. */
|
|
136
|
+
event
|
|
137
|
+
.composedPath()
|
|
138
|
+
.find((eventTarget) => {
|
|
139
|
+
return (eventTarget instanceof HTMLElement && eventTarget.tabIndex >= 0);
|
|
140
|
+
})
|
|
141
|
+
?.focus({
|
|
142
|
+
preventScroll: true,
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
popoverManager.toggle();
|
|
146
|
+
}
|
|
147
|
+
}),
|
|
148
|
+
listenToAnchor(anchor, 'click', (event) => {
|
|
149
|
+
/** Mouse clicks were already handled by the mousedown. */
|
|
150
|
+
if (event.detail === 0 && !isTextInputActive()) {
|
|
151
|
+
popoverManager.toggle();
|
|
152
|
+
}
|
|
153
|
+
}),
|
|
154
|
+
listenToAnchor(anchor, 'keydown', (event) => {
|
|
155
|
+
popoverManager.openOnArrow(event);
|
|
156
|
+
}),
|
|
157
|
+
];
|
|
158
|
+
},
|
|
159
|
+
};
|
|
160
|
+
/**
|
|
161
|
+
* Opens and closes a single `popover` element that is anchored to another element: it opens the
|
|
162
|
+
* popover in response to the anchor's interactions (see {@link PopoverTrigger}) and closes it on
|
|
163
|
+
* Escape, an outside mousedown, or the page going inactive. Style the popover with
|
|
164
|
+
* `createAnchoredPopoverStyles`.
|
|
165
|
+
*
|
|
166
|
+
* @category Popover
|
|
167
|
+
*/
|
|
168
|
+
export class PopoverManager {
|
|
169
|
+
navController;
|
|
170
|
+
anchor;
|
|
171
|
+
options;
|
|
172
|
+
/** The most recently opened popover. */
|
|
173
|
+
popover;
|
|
174
|
+
showTimeout;
|
|
175
|
+
/** Closes the popover after `options.timeout`. */
|
|
176
|
+
hideTimeout;
|
|
177
|
+
/** Removes the listeners for the current anchor and `trigger`. */
|
|
178
|
+
removeAnchorListeners;
|
|
179
|
+
/** Removes the global listeners attached while the popover is open. */
|
|
180
|
+
removeOpenListeners;
|
|
181
|
+
constructor(
|
|
182
|
+
/**
|
|
183
|
+
* Enables arrow key navigation within the open popover. Omit this for popovers that have
|
|
184
|
+
* nothing to navigate, like tooltips.
|
|
185
|
+
*/
|
|
186
|
+
navController) {
|
|
187
|
+
this.navController = navController;
|
|
188
|
+
}
|
|
189
|
+
/** Whether the popover is open. */
|
|
190
|
+
isOpen() {
|
|
191
|
+
return !!this.popover?.matches(':popover-open');
|
|
192
|
+
}
|
|
193
|
+
/** Call this each time the anchor or options might have changed. The `popover` directive does. */
|
|
194
|
+
update(anchor, options) {
|
|
195
|
+
const previousTrigger = this.options?.trigger;
|
|
196
|
+
this.options = options;
|
|
197
|
+
if (anchor === this.anchor && options.trigger === previousTrigger) {
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
this.removeAnchorListeners?.();
|
|
201
|
+
this.anchor = anchor;
|
|
202
|
+
const removeListeners = check.isBoolean(options.trigger)
|
|
203
|
+
? []
|
|
204
|
+
: anchorListeners[options.trigger](this, anchor);
|
|
205
|
+
this.removeAnchorListeners = () => {
|
|
206
|
+
removeListeners.forEach((removeListener) => removeListener());
|
|
207
|
+
};
|
|
208
|
+
this.applyForcedState();
|
|
209
|
+
}
|
|
210
|
+
/** Opens or closes the popover when `trigger` is `true` or `false`. */
|
|
211
|
+
applyForcedState() {
|
|
212
|
+
if (this.options?.trigger === true) {
|
|
213
|
+
/**
|
|
214
|
+
* Deferred because on first render lit updates directives before inserting the template
|
|
215
|
+
* into the document, and a disconnected popover cannot open.
|
|
216
|
+
*/
|
|
217
|
+
queueMicrotask(() => {
|
|
218
|
+
if (this.options?.trigger === true) {
|
|
219
|
+
this.scheduleShow();
|
|
220
|
+
}
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
else if (this.options?.trigger === false) {
|
|
224
|
+
this.close();
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
/** Opens the popover right away. */
|
|
228
|
+
show() {
|
|
229
|
+
clearTimeout(this.showTimeout);
|
|
230
|
+
this.showTimeout = undefined;
|
|
231
|
+
if (!this.anchor || !this.options || this.isOpen()) {
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
234
|
+
this.popover = this.options.getPopover();
|
|
235
|
+
this.popover.showPopover({
|
|
236
|
+
source: this.anchor,
|
|
237
|
+
});
|
|
238
|
+
this.removeOpenListeners = this.attachOpenListeners();
|
|
239
|
+
if (this.options.timeout) {
|
|
240
|
+
this.hideTimeout = setTimeout(() => {
|
|
241
|
+
this.hide();
|
|
242
|
+
}, convertDuration(this.options.timeout, {
|
|
243
|
+
milliseconds: true,
|
|
244
|
+
}).milliseconds);
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
/** Closes the popover, unless `trigger` forces it open, and cancels a scheduled show. */
|
|
248
|
+
hide() {
|
|
249
|
+
if (this.options?.trigger !== true) {
|
|
250
|
+
this.close();
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
/** Opens the popover after `options.delay`. */
|
|
254
|
+
scheduleShow() {
|
|
255
|
+
if (this.showTimeout || this.isOpen() || !this.options) {
|
|
256
|
+
return;
|
|
257
|
+
}
|
|
258
|
+
const rawDelay = this.options.delay ||
|
|
259
|
+
(check.isBoolean(this.options.trigger) ? {} : defaultDelays[this.options.trigger]);
|
|
260
|
+
const delay = rawDelay &&
|
|
261
|
+
convertDuration(rawDelay, {
|
|
262
|
+
milliseconds: true,
|
|
263
|
+
});
|
|
264
|
+
if (delay && delay.milliseconds > 0) {
|
|
265
|
+
this.showTimeout = setTimeout(() => {
|
|
266
|
+
this.show();
|
|
267
|
+
}, delay.milliseconds);
|
|
268
|
+
}
|
|
269
|
+
else {
|
|
270
|
+
this.show();
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
/** Opens the popover, or closes it (and cancels a delayed open). */
|
|
274
|
+
toggle() {
|
|
275
|
+
if (this.isOpen() || this.showTimeout) {
|
|
276
|
+
this.hide();
|
|
277
|
+
}
|
|
278
|
+
else {
|
|
279
|
+
this.scheduleShow();
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
/** Opens a closed popover when an arrow key is pressed. */
|
|
283
|
+
openOnArrow(event) {
|
|
284
|
+
if (event.code.startsWith('Arrow')) {
|
|
285
|
+
this.scheduleShow();
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
/** Closes the popover and removes all listeners. */
|
|
289
|
+
destroy() {
|
|
290
|
+
this.removeAnchorListeners?.();
|
|
291
|
+
this.removeAnchorListeners = undefined;
|
|
292
|
+
this.anchor = undefined;
|
|
293
|
+
this.close();
|
|
294
|
+
}
|
|
295
|
+
/** Closes the popover even when `trigger` forces it open. */
|
|
296
|
+
close() {
|
|
297
|
+
clearTimeout(this.showTimeout);
|
|
298
|
+
this.showTimeout = undefined;
|
|
299
|
+
clearTimeout(this.hideTimeout);
|
|
300
|
+
this.hideTimeout = undefined;
|
|
301
|
+
this.removeOpenListeners?.();
|
|
302
|
+
this.removeOpenListeners = undefined;
|
|
303
|
+
if (this.isOpen()) {
|
|
304
|
+
this.popover?.hidePopover();
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
/** Attaches the global listeners that only matter while the popover is open. */
|
|
308
|
+
attachOpenListeners() {
|
|
309
|
+
const navController = this.navController;
|
|
310
|
+
const removeListeners = [
|
|
311
|
+
listenToGlobal('resize', () => {
|
|
312
|
+
this.popover?.classList.toggle(anchoredPopoverRetryClass);
|
|
313
|
+
/** `nearest` does not scroll at all when the anchor is already fully visible. */
|
|
314
|
+
this.anchor?.scrollIntoView({
|
|
315
|
+
block: 'nearest',
|
|
316
|
+
inline: 'nearest',
|
|
317
|
+
});
|
|
318
|
+
}, {
|
|
319
|
+
passive: true,
|
|
320
|
+
}),
|
|
321
|
+
listenToGlobal('scroll', () => {
|
|
322
|
+
this.popover?.classList.toggle(anchoredPopoverRetryClass);
|
|
323
|
+
}, {
|
|
324
|
+
/** Scroll events do not bubble, so this catches scrolling in any element. */
|
|
325
|
+
capture: true,
|
|
326
|
+
passive: true,
|
|
327
|
+
}),
|
|
328
|
+
listenToPageActivation(false, (isPageActive) => {
|
|
329
|
+
if (!isPageActive) {
|
|
330
|
+
this.hide();
|
|
331
|
+
}
|
|
332
|
+
}),
|
|
333
|
+
navController?.listen(NavActivateEvent, (event) => {
|
|
334
|
+
const target = event.composedPath()[0];
|
|
335
|
+
if (target instanceof Element && isInputLikeElement(target)) {
|
|
336
|
+
return;
|
|
337
|
+
}
|
|
338
|
+
if (event.detail.success) {
|
|
339
|
+
this.options?.onNavSelect?.(event.detail.coords);
|
|
340
|
+
navController.currentNavEntry?.entry.focus(true);
|
|
341
|
+
event.stopImmediatePropagation();
|
|
342
|
+
event.preventDefault();
|
|
343
|
+
}
|
|
344
|
+
}),
|
|
345
|
+
listenToGlobal('mousedown', (event) => {
|
|
346
|
+
const path = event.composedPath();
|
|
347
|
+
if ((this.anchor && path.includes(this.anchor)) ||
|
|
348
|
+
(this.popover && path.includes(this.popover)) ||
|
|
349
|
+
isMouseEventOnScrollbar(event)) {
|
|
350
|
+
return;
|
|
351
|
+
}
|
|
352
|
+
this.hide();
|
|
353
|
+
}, {
|
|
354
|
+
passive: true,
|
|
355
|
+
}),
|
|
356
|
+
listenToGlobal('keydown', (event) => {
|
|
357
|
+
const keyCode = event.code;
|
|
358
|
+
if (keyCode === 'Escape') {
|
|
359
|
+
this.hide();
|
|
360
|
+
}
|
|
361
|
+
else if (navController) {
|
|
362
|
+
const target = event.composedPath()[0];
|
|
363
|
+
if (target instanceof Element && isInputLikeElement(target)) {
|
|
364
|
+
return;
|
|
365
|
+
}
|
|
366
|
+
if (keyCode === 'ArrowDown') {
|
|
367
|
+
event.stopImmediatePropagation();
|
|
368
|
+
event.preventDefault();
|
|
369
|
+
navController.navigate({
|
|
370
|
+
direction: NavDirection.Down,
|
|
371
|
+
allowWrapping: false,
|
|
372
|
+
});
|
|
373
|
+
}
|
|
374
|
+
else if (keyCode === 'ArrowUp') {
|
|
375
|
+
event.stopImmediatePropagation();
|
|
376
|
+
event.preventDefault();
|
|
377
|
+
navController.navigate({
|
|
378
|
+
direction: NavDirection.Up,
|
|
379
|
+
allowWrapping: false,
|
|
380
|
+
});
|
|
381
|
+
}
|
|
382
|
+
else if (keyCode === 'ArrowLeft') {
|
|
383
|
+
event.stopImmediatePropagation();
|
|
384
|
+
event.preventDefault();
|
|
385
|
+
navController.navigate({
|
|
386
|
+
direction: NavDirection.Left,
|
|
387
|
+
allowWrapping: false,
|
|
388
|
+
});
|
|
389
|
+
}
|
|
390
|
+
else if (keyCode === 'ArrowRight') {
|
|
391
|
+
event.stopImmediatePropagation();
|
|
392
|
+
event.preventDefault();
|
|
393
|
+
navController.navigate({
|
|
394
|
+
direction: NavDirection.Right,
|
|
395
|
+
allowWrapping: false,
|
|
396
|
+
});
|
|
397
|
+
}
|
|
398
|
+
else if ((keyCode === 'Enter' || keyCode === 'Return' || keyCode === 'Space') &&
|
|
399
|
+
navController.enterInto({
|
|
400
|
+
fallbackToActivate: true,
|
|
401
|
+
}).success) {
|
|
402
|
+
event.stopImmediatePropagation();
|
|
403
|
+
event.preventDefault();
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
}),
|
|
407
|
+
].filter(check.isDefined);
|
|
408
|
+
return () => {
|
|
409
|
+
removeListeners.forEach((removeListener) => removeListener());
|
|
410
|
+
};
|
|
411
|
+
}
|
|
412
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { type PartialWithUndefined } from '@augment-vir/common';
|
|
2
|
+
/**
|
|
3
|
+
* Options for `ViraDropdown`.
|
|
4
|
+
*
|
|
5
|
+
* @category Dropdown
|
|
6
|
+
* @category Elements
|
|
7
|
+
*/
|
|
8
|
+
export type ViraDropdownOption = {
|
|
9
|
+
/** A value or id, used to keep track of which option is selected. */
|
|
10
|
+
value: string;
|
|
11
|
+
label: string;
|
|
12
|
+
} & PartialWithUndefined<{
|
|
13
|
+
disabled: boolean;
|
|
14
|
+
}>;
|
|
15
|
+
/**
|
|
16
|
+
* A group of options for `ViraDropdown`, rendered under a group label.
|
|
17
|
+
*
|
|
18
|
+
* @category Dropdown
|
|
19
|
+
* @category Elements
|
|
20
|
+
*/
|
|
21
|
+
export type ViraDropdownOptionGroup = {
|
|
22
|
+
groupName: string;
|
|
23
|
+
options: ReadonlyArray<Readonly<ViraDropdownOption>>;
|
|
24
|
+
};
|
|
25
|
+
/**
|
|
26
|
+
* Type guard to determine if a `ViraDropdown` options entry is a group.
|
|
27
|
+
*
|
|
28
|
+
* @category Internal
|
|
29
|
+
*/
|
|
30
|
+
export declare function isViraDropdownOptionGroup(entry: Readonly<ViraDropdownOption> | Readonly<ViraDropdownOptionGroup>): entry is Readonly<ViraDropdownOptionGroup>;
|
|
@@ -2,7 +2,7 @@ import { type PartialWithUndefined } from '@augment-vir/common';
|
|
|
2
2
|
import { type FullDate, type Timezone } from 'date-vir';
|
|
3
3
|
import { type HtmlInterpolation } from 'element-vir';
|
|
4
4
|
import { type ViraIconSvg } from '../icons/icon-svg.js';
|
|
5
|
-
import { type
|
|
5
|
+
import { type ViraDropdownOption } from './vira-dropdown-option.js';
|
|
6
6
|
/**
|
|
7
7
|
* Form field types for {@link ViraFormField}.
|
|
8
8
|
*
|
|
@@ -29,7 +29,7 @@ export declare enum ViraFormFieldType {
|
|
|
29
29
|
* @category Internal
|
|
30
30
|
*/
|
|
31
31
|
export type CommonViraFormFields = {
|
|
32
|
-
label: string;
|
|
32
|
+
label: string | HtmlInterpolation;
|
|
33
33
|
} & PartialWithUndefined<{
|
|
34
34
|
/** Applies a test id to the form field element. */
|
|
35
35
|
testId: string;
|
|
@@ -73,24 +73,19 @@ export type ViraFormField = ({
|
|
|
73
73
|
}> & CommonViraFormFields) | ({
|
|
74
74
|
type: ViraFormFieldType.Select;
|
|
75
75
|
value: string | undefined;
|
|
76
|
-
options: ReadonlyArray<Readonly<
|
|
76
|
+
options: ReadonlyArray<Readonly<ViraDropdownOption>>;
|
|
77
77
|
} & PartialWithUndefined<{
|
|
78
78
|
placeholder: string;
|
|
79
79
|
icon: ViraIconSvg;
|
|
80
80
|
}> & CommonViraFormFields) | ({
|
|
81
81
|
type: ViraFormFieldType.Checkbox;
|
|
82
82
|
value: boolean | undefined;
|
|
83
|
-
/**
|
|
84
|
-
* Label for the checkbox. Unlike other field types, this accepts arbitrary HTML so the
|
|
85
|
-
* checkbox's label slot can be filled with rich content.
|
|
86
|
-
*/
|
|
87
|
-
label: HtmlInterpolation;
|
|
88
83
|
} & PartialWithUndefined<{
|
|
89
84
|
/** The checkbox will be filled with a form selection color when it is checked. */
|
|
90
85
|
fillWhenChecked: boolean;
|
|
91
86
|
/** The checkbox will be filled with a form error color when it is unchecked. */
|
|
92
87
|
fillWhenUnchecked: boolean;
|
|
93
|
-
}> &
|
|
88
|
+
}> & CommonViraFormFields) | ({
|
|
94
89
|
type: ViraFormFieldType.Number;
|
|
95
90
|
value: number | undefined;
|
|
96
91
|
} & PartialWithUndefined<{
|
|
@@ -132,7 +127,7 @@ export type ViraFormFields = Record<string, ViraFormField>;
|
|
|
132
127
|
*
|
|
133
128
|
* @category Internal
|
|
134
129
|
*/
|
|
135
|
-
export declare function applyRequiredLabel(label: string | undefined, isRequired: boolean): string | undefined;
|
|
130
|
+
export declare function applyRequiredLabel(label: string | HtmlInterpolation | undefined, isRequired: boolean): string | HtmlInterpolation | undefined;
|
|
136
131
|
/**
|
|
137
132
|
* Checks if all the {@link ViraFormField} entries in a given {@link ViraFormFields} are valid through
|
|
138
133
|
* the following checks:
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { check } from '@augment-vir/assert';
|
|
2
2
|
import { addSuffix, getObjectTypedValues } from '@augment-vir/common';
|
|
3
|
+
import { html } from 'element-vir';
|
|
3
4
|
/**
|
|
4
5
|
* Form field types for {@link ViraFormField}.
|
|
5
6
|
*
|
|
@@ -27,19 +28,22 @@ export var ViraFormFieldType;
|
|
|
27
28
|
* @category Internal
|
|
28
29
|
*/
|
|
29
30
|
export function applyRequiredLabel(label, isRequired) {
|
|
30
|
-
if (label) {
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
31
|
+
if (!label) {
|
|
32
|
+
return undefined;
|
|
33
|
+
}
|
|
34
|
+
else if (!isRequired) {
|
|
35
|
+
return label;
|
|
36
|
+
}
|
|
37
|
+
else if (check.isString(label)) {
|
|
38
|
+
return addSuffix({
|
|
39
|
+
value: label,
|
|
40
|
+
suffix: '*',
|
|
41
|
+
});
|
|
40
42
|
}
|
|
41
43
|
else {
|
|
42
|
-
return
|
|
44
|
+
return html `
|
|
45
|
+
${label}*
|
|
46
|
+
`;
|
|
43
47
|
}
|
|
44
48
|
}
|
|
45
49
|
/**
|
|
@@ -72,7 +72,7 @@ export declare class ViraThemeClient {
|
|
|
72
72
|
* The currently selected theme. If you use multiple clients to set the same theme, this might
|
|
73
73
|
* get out of sync.
|
|
74
74
|
*/
|
|
75
|
-
|
|
75
|
+
getCurrentTheme(): ViraThemeSelection;
|
|
76
76
|
/** Set the selected theme. */
|
|
77
77
|
setSelectedTheme(selection: ViraThemeSelection): void;
|
|
78
78
|
/** Cleanup internal state and listeners. */
|
|
@@ -47,7 +47,7 @@ export class ViraThemeClient {
|
|
|
47
47
|
/** A callback to remove the global theme preference listener. */
|
|
48
48
|
removeThemePreferenceListener = listenTo(globalThis.matchMedia(darkSchemeMediaQuery), 'change', (event) => {
|
|
49
49
|
assert.instanceOf(event, MediaQueryListEvent);
|
|
50
|
-
if (this.
|
|
50
|
+
if (this.getCurrentTheme() === ViraThemeSelection.Auto) {
|
|
51
51
|
void this.applyThemeCallback({
|
|
52
52
|
useDarkTheme: event.matches,
|
|
53
53
|
});
|
|
@@ -60,13 +60,13 @@ export class ViraThemeClient {
|
|
|
60
60
|
this.localStorageClient = new LocalStorageClient(themeStorageShapes, {
|
|
61
61
|
storeName: params.storeName || 'vira-theme',
|
|
62
62
|
});
|
|
63
|
-
this.applySelection(this.
|
|
63
|
+
this.applySelection(this.getCurrentTheme());
|
|
64
64
|
}
|
|
65
65
|
/**
|
|
66
66
|
* The currently selected theme. If you use multiple clients to set the same theme, this might
|
|
67
67
|
* get out of sync.
|
|
68
68
|
*/
|
|
69
|
-
|
|
69
|
+
getCurrentTheme() {
|
|
70
70
|
return this.localStorageClient.get.selectedTheme()?.theme || ViraThemeSelection.Auto;
|
|
71
71
|
}
|
|
72
72
|
/** Set the selected theme. */
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "vira",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "33.0.0",
|
|
4
4
|
"description": "A simple and highly versatile design system using element-vir.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"design",
|
|
@@ -46,6 +46,7 @@
|
|
|
46
46
|
"date-vir": "^9.1.1",
|
|
47
47
|
"device-navigation": "^5.0.0",
|
|
48
48
|
"json-schema-to-ts": "^3.1.1",
|
|
49
|
+
"lit": "^3.3.3",
|
|
49
50
|
"lit-css-vars": "^3.6.3",
|
|
50
51
|
"object-shape-tester": "^6.15.0",
|
|
51
52
|
"observavir": "^2.3.3",
|
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
import { type PartialWithUndefined } from '@augment-vir/common';
|
|
2
|
-
import { type NavController } from 'device-navigation';
|
|
3
|
-
import { type PopUpManager, type ShowPopUpResult } from '../../util/pop-up-manager.js';
|
|
4
|
-
import { type ViraMenuCornerStyle } from './vira-menu.element.js';
|
|
5
|
-
import { type PopUpOffset, type PopUpTriggerPosition } from './vira-pop-up-trigger.element.js';
|
|
6
|
-
/**
|
|
7
|
-
* Test ids for {@link ViraMenuTrigger}.
|
|
8
|
-
*
|
|
9
|
-
* @category Internal
|
|
10
|
-
*/
|
|
11
|
-
export declare const viraMenuTriggerTestIds: {
|
|
12
|
-
menu: string;
|
|
13
|
-
};
|
|
14
|
-
/**
|
|
15
|
-
* A more specific wrapper of `ViraPopUpTrigger` that always opens a menu.
|
|
16
|
-
*
|
|
17
|
-
* @category PopUp
|
|
18
|
-
* @category Elements
|
|
19
|
-
*/
|
|
20
|
-
export declare const ViraMenuTrigger: import("element-vir").DeclarativeElementDefinition<`vira-${string}`, PartialWithUndefined<{
|
|
21
|
-
isDisabled: boolean;
|
|
22
|
-
z_debug_forceOpenState: boolean;
|
|
23
|
-
popUpOffset: PopUpOffset;
|
|
24
|
-
keepOpenAfterInteraction: boolean;
|
|
25
|
-
menuCornerStyle: ViraMenuCornerStyle;
|
|
26
|
-
/**
|
|
27
|
-
* If true, the focus outline is moved inside the element.
|
|
28
|
-
*
|
|
29
|
-
* @default false
|
|
30
|
-
*/
|
|
31
|
-
useInsideFocus: boolean;
|
|
32
|
-
/**
|
|
33
|
-
* When `true`, the trigger will focus itself when the pop-up closes.
|
|
34
|
-
*
|
|
35
|
-
* @default false
|
|
36
|
-
*/
|
|
37
|
-
focusOnClose: boolean;
|
|
38
|
-
} & PopUpTriggerPosition>, {
|
|
39
|
-
navController: undefined | NavController;
|
|
40
|
-
popUpManager: undefined | PopUpManager;
|
|
41
|
-
/** `undefined` means the pop up is not currently showing. */
|
|
42
|
-
showPopUpResult: ShowPopUpResult | undefined;
|
|
43
|
-
}, {
|
|
44
|
-
openChange: import("element-vir").DefineElementEvent<ShowPopUpResult | undefined>;
|
|
45
|
-
}, `vira-${string}-`, `vira-${string}-`, readonly ["vira-menu-trigger-trigger"], readonly []>;
|
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
import { type PartialWithUndefined } from '@augment-vir/common';
|
|
2
|
-
/**
|
|
3
|
-
* Possible corner styles for {@link ViraMenu}.
|
|
4
|
-
*
|
|
5
|
-
* @category Internal
|
|
6
|
-
*/
|
|
7
|
-
export declare enum ViraMenuCornerStyle {
|
|
8
|
-
/** Rounding of corners depends on the open direction of the menu. */
|
|
9
|
-
Directional = "directional",
|
|
10
|
-
/** All of the menus corners should be rounded. */
|
|
11
|
-
AllRounded = "all-rounded",
|
|
12
|
-
/** None of the menus corners should be rounded. */
|
|
13
|
-
AllSquare = "all-square"
|
|
14
|
-
}
|
|
15
|
-
/**
|
|
16
|
-
* Menu pop-up directions available for {@link ViraMenu}.
|
|
17
|
-
*
|
|
18
|
-
* @category Internal
|
|
19
|
-
*/
|
|
20
|
-
export declare enum ViraMenuPopUpDirection {
|
|
21
|
-
Downwards = "downwards",
|
|
22
|
-
Upwards = "upwards"
|
|
23
|
-
}
|
|
24
|
-
/**
|
|
25
|
-
* A simple default style wrapper for pop-up menus. Consider using `renderMenuItemEntries` to help
|
|
26
|
-
* rendering many menu items.
|
|
27
|
-
*
|
|
28
|
-
* @category PopUp
|
|
29
|
-
* @category Elements
|
|
30
|
-
*/
|
|
31
|
-
export declare const ViraMenu: import("element-vir").DeclarativeElementDefinition<"vira-menu", PartialWithUndefined<{
|
|
32
|
-
/** @default PopUpMenuDirection.Downwards */
|
|
33
|
-
direction: ViraMenuPopUpDirection;
|
|
34
|
-
/** @default PopUpMenuCornerStyle.Directional */
|
|
35
|
-
cornerStyle: ViraMenuCornerStyle;
|
|
36
|
-
}>, {}, {}, "vira-menu-open-upwards" | "vira-menu-rounded" | "vira-menu-square", "vira-menu-", readonly [], readonly []>;
|