vira 32.4.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-drawer.element.js +1 -1
- package/dist/elements/vira-dropdown.element.d.ts +28 -12
- package/dist/elements/vira-dropdown.element.js +172 -59
- 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/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 +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
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
import { assertWrap } from '@augment-vir/assert';
|
|
2
|
+
import { classMap, css, html, listen, onResize } from 'element-vir';
|
|
3
|
+
import { ChevronUp16Icon } from '../../icons/index.js';
|
|
4
|
+
import { viraFormCssVars } from '../../styles/form-styles.js';
|
|
5
|
+
import { noUserSelect } from '../../styles/index.js';
|
|
6
|
+
import { viraShadows } from '../../styles/shadows.js';
|
|
7
|
+
import { defineViraElement } from '../../util/define-vira-element.js';
|
|
8
|
+
import { ViraIcon } from '../vira-icon.element.js';
|
|
9
|
+
/**
|
|
10
|
+
* Possible corner styles for {@link ViraMenu}.
|
|
11
|
+
*
|
|
12
|
+
* @category Internal
|
|
13
|
+
*/
|
|
14
|
+
export var ViraMenuCornerStyle;
|
|
15
|
+
(function (ViraMenuCornerStyle) {
|
|
16
|
+
/** All of the menus corners should be rounded. */
|
|
17
|
+
ViraMenuCornerStyle["Round"] = "round";
|
|
18
|
+
/** None of the menus corners should be rounded. */
|
|
19
|
+
ViraMenuCornerStyle["Square"] = "square";
|
|
20
|
+
})(ViraMenuCornerStyle || (ViraMenuCornerStyle = {}));
|
|
21
|
+
var ScrollDirection;
|
|
22
|
+
(function (ScrollDirection) {
|
|
23
|
+
ScrollDirection["Up"] = "up";
|
|
24
|
+
ScrollDirection["Down"] = "down";
|
|
25
|
+
})(ScrollDirection || (ScrollDirection = {}));
|
|
26
|
+
const defaultHoverScrollSpeed = 24;
|
|
27
|
+
function canScroll(scrollArea, direction) {
|
|
28
|
+
const remainingScroll = {
|
|
29
|
+
[ScrollDirection.Up]: scrollArea.scrollTop,
|
|
30
|
+
[ScrollDirection.Down]: scrollArea.scrollHeight - scrollArea.clientHeight - scrollArea.scrollTop,
|
|
31
|
+
};
|
|
32
|
+
/** Scroll positions can be fractional, so less than a pixel left counts as the end. */
|
|
33
|
+
return remainingScroll[direction] >= 1;
|
|
34
|
+
}
|
|
35
|
+
/** Item positions relative to the top of `scrollArea`'s content. */
|
|
36
|
+
function getItemBounds(scrollArea, items) {
|
|
37
|
+
const scrollAreaTop = scrollArea.getBoundingClientRect().top;
|
|
38
|
+
return items.map((item) => {
|
|
39
|
+
const rect = item.getBoundingClientRect();
|
|
40
|
+
const top = rect.top - scrollAreaTop + scrollArea.scrollTop;
|
|
41
|
+
return {
|
|
42
|
+
top,
|
|
43
|
+
bottom: top + rect.height,
|
|
44
|
+
};
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
/** Whether an item in `direction` is more than half scrolled out of view. */
|
|
48
|
+
function hasHalfHiddenItem(scrollArea, items, direction) {
|
|
49
|
+
const itemBounds = getItemBounds(scrollArea, items);
|
|
50
|
+
const firstItem = itemBounds[0];
|
|
51
|
+
const lastItem = itemBounds.at(-1);
|
|
52
|
+
if (!firstItem || !lastItem) {
|
|
53
|
+
return canScroll(scrollArea, direction);
|
|
54
|
+
}
|
|
55
|
+
const isHalfHidden = {
|
|
56
|
+
[ScrollDirection.Up]() {
|
|
57
|
+
return (firstItem.top + firstItem.bottom) / 2 < scrollArea.scrollTop;
|
|
58
|
+
},
|
|
59
|
+
[ScrollDirection.Down]() {
|
|
60
|
+
return ((lastItem.top + lastItem.bottom) / 2 >
|
|
61
|
+
scrollArea.scrollTop + scrollArea.clientHeight);
|
|
62
|
+
},
|
|
63
|
+
};
|
|
64
|
+
return isHalfHidden[direction]();
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Finds the `scrollTop` that fully reveals the next item that is cut off in `direction`, falling
|
|
68
|
+
* back to the end of the scroll area when no item is cut off.
|
|
69
|
+
*/
|
|
70
|
+
function findNextItemScrollTop(scrollArea, items, direction) {
|
|
71
|
+
const itemBounds = getItemBounds(scrollArea, items);
|
|
72
|
+
const maxScrollTop = scrollArea.scrollHeight - scrollArea.clientHeight;
|
|
73
|
+
const scrollTops = {
|
|
74
|
+
[ScrollDirection.Up]() {
|
|
75
|
+
const item = itemBounds.findLast((bounds) => bounds.top < scrollArea.scrollTop - 1);
|
|
76
|
+
return Math.max(0, item?.top ?? 0);
|
|
77
|
+
},
|
|
78
|
+
[ScrollDirection.Down]() {
|
|
79
|
+
const item = itemBounds.find((bounds) => bounds.bottom > scrollArea.scrollTop + scrollArea.clientHeight + 1);
|
|
80
|
+
return Math.min(maxScrollTop, (item?.bottom ?? Infinity) - scrollArea.clientHeight);
|
|
81
|
+
},
|
|
82
|
+
};
|
|
83
|
+
return scrollTops[direction]();
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Scrolls `scrollArea` one item at a time until it reaches its end or the returned callback is
|
|
87
|
+
* called.
|
|
88
|
+
*/
|
|
89
|
+
function scrollByItems({ scrollArea, items, direction, itemsPerSecond, }) {
|
|
90
|
+
function scrollOneItem() {
|
|
91
|
+
scrollArea.scrollTop = findNextItemScrollTop(scrollArea, items, direction);
|
|
92
|
+
if (!canScroll(scrollArea, direction)) {
|
|
93
|
+
clearInterval(interval);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
const interval = setInterval(scrollOneItem, 1000 / itemsPerSecond);
|
|
97
|
+
scrollOneItem();
|
|
98
|
+
return () => {
|
|
99
|
+
clearInterval(interval);
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* A simple default style wrapper for popover menus. Consider using `renderMenuItemEntries` to help
|
|
104
|
+
* rendering many menu items. Like a native `<select>` menu, a menu that scrolls shows an arrow at
|
|
105
|
+
* each end that has more items, and hovering an arrow scrolls towards that end.
|
|
106
|
+
*
|
|
107
|
+
* @category Popover
|
|
108
|
+
* @category Elements
|
|
109
|
+
*/
|
|
110
|
+
export const ViraMenu = defineViraElement()({
|
|
111
|
+
tagName: 'vira-menu',
|
|
112
|
+
state() {
|
|
113
|
+
return {
|
|
114
|
+
canScrollUp: false,
|
|
115
|
+
canScrollDown: false,
|
|
116
|
+
/** Stops the scrolling that hovering a scroll arrow started. */
|
|
117
|
+
stopScrolling: undefined,
|
|
118
|
+
};
|
|
119
|
+
},
|
|
120
|
+
cleanup({ state }) {
|
|
121
|
+
state.stopScrolling?.();
|
|
122
|
+
},
|
|
123
|
+
hostClasses: {
|
|
124
|
+
'vira-menu-square'({ inputs }) {
|
|
125
|
+
return inputs.cornerStyle === ViraMenuCornerStyle.Square;
|
|
126
|
+
},
|
|
127
|
+
},
|
|
128
|
+
styles({ hostClasses }) {
|
|
129
|
+
return css `
|
|
130
|
+
:host {
|
|
131
|
+
display: flex;
|
|
132
|
+
flex-direction: column;
|
|
133
|
+
max-width: 100%;
|
|
134
|
+
max-height: 100%;
|
|
135
|
+
overflow: hidden;
|
|
136
|
+
z-index: 99;
|
|
137
|
+
box-sizing: border-box;
|
|
138
|
+
border-radius: ${viraFormCssVars['vira-form-radius'].value};
|
|
139
|
+
background-color: ${viraFormCssVars['vira-form-background-color'].value};
|
|
140
|
+
border: 1px solid ${viraFormCssVars['vira-form-border-color'].value};
|
|
141
|
+
color: ${viraFormCssVars['vira-form-foreground-color'].value};
|
|
142
|
+
${viraShadows.menuShadow}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
${hostClasses['vira-menu-square'].selector} {
|
|
146
|
+
border-radius: 0;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
.scroll-area {
|
|
150
|
+
min-height: 0;
|
|
151
|
+
overflow-y: auto;
|
|
152
|
+
overscroll-behavior: contain;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
.scroll-area-content {
|
|
156
|
+
display: flex;
|
|
157
|
+
flex-direction: column;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
.scroll-arrow {
|
|
161
|
+
${noUserSelect};
|
|
162
|
+
display: flex;
|
|
163
|
+
flex-shrink: 0;
|
|
164
|
+
justify-content: center;
|
|
165
|
+
padding: 2px 0;
|
|
166
|
+
|
|
167
|
+
&.hidden {
|
|
168
|
+
display: none;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
&.down ${ViraIcon} {
|
|
172
|
+
transform: rotate(180deg);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
`;
|
|
176
|
+
},
|
|
177
|
+
render({ inputs, state, updateState, host }) {
|
|
178
|
+
function getScrollArea() {
|
|
179
|
+
return assertWrap.instanceOf(host.shadowRoot.querySelector('.scroll-area'), HTMLElement);
|
|
180
|
+
}
|
|
181
|
+
function getItems() {
|
|
182
|
+
return assertWrap
|
|
183
|
+
.instanceOf(host.shadowRoot.querySelector('slot'), HTMLSlotElement)
|
|
184
|
+
.assignedElements({
|
|
185
|
+
flatten: true,
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
function updateScrollArrows() {
|
|
189
|
+
updateState({
|
|
190
|
+
canScrollUp: hasHalfHiddenItem(getScrollArea(), getItems(), ScrollDirection.Up),
|
|
191
|
+
canScrollDown: hasHalfHiddenItem(getScrollArea(), getItems(), ScrollDirection.Down),
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
function renderScrollArrow(direction, isShown) {
|
|
195
|
+
return html `
|
|
196
|
+
<div
|
|
197
|
+
class=${classMap({
|
|
198
|
+
'scroll-arrow': true,
|
|
199
|
+
[direction]: true,
|
|
200
|
+
hidden: !isShown,
|
|
201
|
+
})}
|
|
202
|
+
aria-hidden="true"
|
|
203
|
+
${listen('mouseenter', () => {
|
|
204
|
+
state.stopScrolling?.();
|
|
205
|
+
updateState({
|
|
206
|
+
stopScrolling: scrollByItems({
|
|
207
|
+
scrollArea: getScrollArea(),
|
|
208
|
+
items: getItems(),
|
|
209
|
+
direction,
|
|
210
|
+
itemsPerSecond: inputs.hoverScrollSpeed ?? defaultHoverScrollSpeed,
|
|
211
|
+
}),
|
|
212
|
+
});
|
|
213
|
+
})}
|
|
214
|
+
${listen('mouseleave', () => {
|
|
215
|
+
state.stopScrolling?.();
|
|
216
|
+
updateState({
|
|
217
|
+
stopScrolling: undefined,
|
|
218
|
+
});
|
|
219
|
+
})}
|
|
220
|
+
${listen('mousedown', (event) => {
|
|
221
|
+
/** Keeps focus on the current menu item. */
|
|
222
|
+
event.preventDefault();
|
|
223
|
+
})}
|
|
224
|
+
${listen('click', (event) => {
|
|
225
|
+
/** Keeps `ViraPopoverTrigger` from closing the menu. */
|
|
226
|
+
event.stopPropagation();
|
|
227
|
+
})}
|
|
228
|
+
>
|
|
229
|
+
<${ViraIcon.assign({
|
|
230
|
+
icon: ChevronUp16Icon,
|
|
231
|
+
})}></${ViraIcon}>
|
|
232
|
+
</div>
|
|
233
|
+
`;
|
|
234
|
+
}
|
|
235
|
+
return html `
|
|
236
|
+
${renderScrollArrow(ScrollDirection.Up, state.canScrollUp)}
|
|
237
|
+
<div
|
|
238
|
+
class="scroll-area"
|
|
239
|
+
${listen('scroll', () => {
|
|
240
|
+
updateScrollArrows();
|
|
241
|
+
})}
|
|
242
|
+
${onResize(() => {
|
|
243
|
+
/**
|
|
244
|
+
* Showing or hiding an arrow resizes the scroll area, which inside a
|
|
245
|
+
* ResizeObserver callback causes a "ResizeObserver loop" error.
|
|
246
|
+
*/
|
|
247
|
+
requestAnimationFrame(updateScrollArrows);
|
|
248
|
+
})}
|
|
249
|
+
>
|
|
250
|
+
<div
|
|
251
|
+
class="scroll-area-content"
|
|
252
|
+
${onResize(() => {
|
|
253
|
+
requestAnimationFrame(updateScrollArrows);
|
|
254
|
+
})}
|
|
255
|
+
>
|
|
256
|
+
<slot> </slot>
|
|
257
|
+
</div>
|
|
258
|
+
</div>
|
|
259
|
+
${renderScrollArrow(ScrollDirection.Down, state.canScrollDown)}
|
|
260
|
+
`;
|
|
261
|
+
},
|
|
262
|
+
});
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { type PartialWithUndefined } from '@augment-vir/common';
|
|
2
|
+
import { NavController, type Coords } from 'device-navigation';
|
|
3
|
+
import { PopoverManager, PopoverTrigger } from '../../util/popover-manager.js';
|
|
4
|
+
/**
|
|
5
|
+
* Offsets applied to any menu opened by {@link ViraPopoverTrigger}.
|
|
6
|
+
*
|
|
7
|
+
* @category Internal
|
|
8
|
+
*/
|
|
9
|
+
export type PopoverOffset = PartialWithUndefined<{
|
|
10
|
+
vertical: number;
|
|
11
|
+
/** Moves the popover rightwards when it opens from the trigger's left edge. */
|
|
12
|
+
left: number;
|
|
13
|
+
/** Only shrinks the popover's minimum width (the trigger's width). This never moves the popover. */
|
|
14
|
+
right: number;
|
|
15
|
+
}>;
|
|
16
|
+
/**
|
|
17
|
+
* An element with slots for a popover trigger and popover contents.
|
|
18
|
+
*
|
|
19
|
+
* @category Popover
|
|
20
|
+
* @category Elements
|
|
21
|
+
* @see https://electrovir.github.io/vira/book/elements/vira-popover-trigger
|
|
22
|
+
*/
|
|
23
|
+
export declare const ViraPopoverTrigger: import("element-vir").DeclarativeElementDefinition<`vira-${string}`, PartialWithUndefined<{
|
|
24
|
+
isDisabled: boolean;
|
|
25
|
+
/**
|
|
26
|
+
* Defaults to {@link PopoverTrigger.Mousedown}. `true` or `false` force the popover open or
|
|
27
|
+
* closed.
|
|
28
|
+
*/
|
|
29
|
+
trigger: boolean | PopoverTrigger;
|
|
30
|
+
/** Set to `true` to keep the popover open if it is interacted with. */
|
|
31
|
+
keepOpenAfterInteraction: boolean;
|
|
32
|
+
/** All values in px. */
|
|
33
|
+
popoverOffset: PopoverOffset;
|
|
34
|
+
/** If true, the focus outline is moved inside the element. */
|
|
35
|
+
useInsideFocus: boolean;
|
|
36
|
+
/** When `true`, the trigger will focus itself when the popover closes. */
|
|
37
|
+
focusOnClose: boolean;
|
|
38
|
+
}>, {
|
|
39
|
+
isOpen: boolean;
|
|
40
|
+
navController: NavController;
|
|
41
|
+
popoverManager: PopoverManager;
|
|
42
|
+
}, {
|
|
43
|
+
navSelect: import("element-vir").DefineElementEvent<Coords>;
|
|
44
|
+
/** `true` when the popover just opened, `false` when it just closed. */
|
|
45
|
+
openChange: import("element-vir").DefineElementEvent<boolean>;
|
|
46
|
+
init: import("element-vir").DefineElementEvent<{
|
|
47
|
+
navController: NavController;
|
|
48
|
+
}>;
|
|
49
|
+
}, "vira-popover-trigger-disabled" | "vira-popover-trigger-inside-focus" | "vira-popover-trigger-outside-focus", "vira-popover-trigger-offset-vertical" | "vira-popover-trigger-offset-left" | "vira-popover-trigger-offset-right", readonly ["vira-popover-trigger-trigger", "vira-popover-trigger-popover"], readonly []>;
|
|
@@ -0,0 +1,301 @@
|
|
|
1
|
+
import { assertWrap } from '@augment-vir/assert';
|
|
2
|
+
import { NavController } from 'device-navigation';
|
|
3
|
+
import { classMap, css, defineElementEvent, html, listen, renderIf } from 'element-vir';
|
|
4
|
+
import { listenTo } from 'typed-event-target';
|
|
5
|
+
import { popover } from '../../directives/popover.directive.js';
|
|
6
|
+
import { createFocusStyles } from '../../styles/focus.js';
|
|
7
|
+
import { noNativeFormStyles, noUserSelect, viraDisabledStyles } from '../../styles/index.js';
|
|
8
|
+
import { createAnchoredPopoverStyles } from '../../util/anchored-popover.js';
|
|
9
|
+
import { defineViraElement } from '../../util/define-vira-element.js';
|
|
10
|
+
import { isMouseEventOnScrollbar, isTextInputActive, PopoverManager, PopoverTrigger, } from '../../util/popover-manager.js';
|
|
11
|
+
import { ViraMenuItem } from './vira-menu-item.element.js';
|
|
12
|
+
/**
|
|
13
|
+
* An element with slots for a popover trigger and popover contents.
|
|
14
|
+
*
|
|
15
|
+
* @category Popover
|
|
16
|
+
* @category Elements
|
|
17
|
+
* @see https://electrovir.github.io/vira/book/elements/vira-popover-trigger
|
|
18
|
+
*/
|
|
19
|
+
export const ViraPopoverTrigger = defineViraElement()({
|
|
20
|
+
tagName: 'vira-popover-trigger',
|
|
21
|
+
state({ host }) {
|
|
22
|
+
const navController = new NavController(host, {
|
|
23
|
+
activateOnMouseUp: true,
|
|
24
|
+
});
|
|
25
|
+
return {
|
|
26
|
+
isOpen: false,
|
|
27
|
+
navController,
|
|
28
|
+
popoverManager: new PopoverManager(navController),
|
|
29
|
+
};
|
|
30
|
+
},
|
|
31
|
+
slotNames: [
|
|
32
|
+
'vira-popover-trigger-trigger',
|
|
33
|
+
'vira-popover-trigger-popover',
|
|
34
|
+
],
|
|
35
|
+
hostClasses: {
|
|
36
|
+
'vira-popover-trigger-disabled'({ inputs }) {
|
|
37
|
+
return !!inputs.isDisabled;
|
|
38
|
+
},
|
|
39
|
+
'vira-popover-trigger-inside-focus'({ inputs }) {
|
|
40
|
+
return !!inputs.useInsideFocus;
|
|
41
|
+
},
|
|
42
|
+
'vira-popover-trigger-outside-focus'({ inputs }) {
|
|
43
|
+
return !inputs.useInsideFocus;
|
|
44
|
+
},
|
|
45
|
+
},
|
|
46
|
+
cssVars: {
|
|
47
|
+
'vira-popover-trigger-offset-vertical': '0px',
|
|
48
|
+
'vira-popover-trigger-offset-left': '0px',
|
|
49
|
+
'vira-popover-trigger-offset-right': '0px',
|
|
50
|
+
},
|
|
51
|
+
styles({ hostClasses, cssVars }) {
|
|
52
|
+
return css `
|
|
53
|
+
:host {
|
|
54
|
+
display: inline-flex;
|
|
55
|
+
box-sizing: border-box;
|
|
56
|
+
vertical-align: middle;
|
|
57
|
+
position: relative;
|
|
58
|
+
max-width: 100%;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
.dropdown-wrapper {
|
|
62
|
+
${noNativeFormStyles};
|
|
63
|
+
cursor: pointer;
|
|
64
|
+
max-width: 100%;
|
|
65
|
+
position: relative;
|
|
66
|
+
flex-grow: 1;
|
|
67
|
+
box-sizing: border-box;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
${hostClasses['vira-popover-trigger-inside-focus'].selector} .dropdown-wrapper {
|
|
71
|
+
${createFocusStyles({
|
|
72
|
+
renderInside: true,
|
|
73
|
+
})}
|
|
74
|
+
}
|
|
75
|
+
${hostClasses['vira-popover-trigger-outside-focus'].selector} .dropdown-wrapper {
|
|
76
|
+
${createFocusStyles()}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
.dropdown-trigger {
|
|
80
|
+
box-sizing: border-box;
|
|
81
|
+
${noUserSelect};
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
${hostClasses['vira-popover-trigger-disabled'].selector} {
|
|
85
|
+
${viraDisabledStyles}
|
|
86
|
+
pointer-events: auto;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
${hostClasses['vira-popover-trigger-disabled'].selector} .dropdown-wrapper {
|
|
90
|
+
pointer-events: none;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
${createAnchoredPopoverStyles({
|
|
94
|
+
selector: css `.popover-positioner`,
|
|
95
|
+
/**
|
|
96
|
+
* Each fallback has an upwards copy because a position-try-fallbacks entry can only
|
|
97
|
+
* reference one @position-try rule. Every downwards entry comes before any upwards
|
|
98
|
+
* entry. The last entry always fits because, when no entry fits, browsers use the
|
|
99
|
+
* first one and shift it over the trigger.
|
|
100
|
+
*/
|
|
101
|
+
fallbacks: [
|
|
102
|
+
{
|
|
103
|
+
/**
|
|
104
|
+
* Used instead of flip-inline, which would move the left offset to the
|
|
105
|
+
* right side.
|
|
106
|
+
*/
|
|
107
|
+
name: 'vira-popover-leftwards',
|
|
108
|
+
styles: css `
|
|
109
|
+
position-area: block-end span-inline-start;
|
|
110
|
+
margin-left: 0;
|
|
111
|
+
`,
|
|
112
|
+
},
|
|
113
|
+
{
|
|
114
|
+
/**
|
|
115
|
+
* For a popover that fits on neither side of the trigger. 100% instead of
|
|
116
|
+
* 100vw so that this always fits, even next to a scrollbar.
|
|
117
|
+
*/
|
|
118
|
+
name: 'vira-popover-viewport-right',
|
|
119
|
+
styles: css `
|
|
120
|
+
position-area: block-end span-all;
|
|
121
|
+
justify-self: end;
|
|
122
|
+
margin-left: 0;
|
|
123
|
+
max-width: min(500px, 100%);
|
|
124
|
+
`,
|
|
125
|
+
},
|
|
126
|
+
{
|
|
127
|
+
name: 'vira-popover-upwards',
|
|
128
|
+
styles: css `
|
|
129
|
+
min-height: 0;
|
|
130
|
+
`,
|
|
131
|
+
flipBlock: true,
|
|
132
|
+
},
|
|
133
|
+
{
|
|
134
|
+
name: 'vira-popover-leftwards-upwards',
|
|
135
|
+
styles: css `
|
|
136
|
+
position-area: block-end span-inline-start;
|
|
137
|
+
margin-left: 0;
|
|
138
|
+
min-height: 0;
|
|
139
|
+
`,
|
|
140
|
+
flipBlock: true,
|
|
141
|
+
},
|
|
142
|
+
{
|
|
143
|
+
name: 'vira-popover-viewport-right-upwards',
|
|
144
|
+
styles: css `
|
|
145
|
+
position-area: block-end span-all;
|
|
146
|
+
justify-self: end;
|
|
147
|
+
margin-left: 0;
|
|
148
|
+
max-width: min(500px, 100%);
|
|
149
|
+
min-height: 0;
|
|
150
|
+
`,
|
|
151
|
+
flipBlock: true,
|
|
152
|
+
},
|
|
153
|
+
],
|
|
154
|
+
})}
|
|
155
|
+
|
|
156
|
+
.popover-positioner {
|
|
157
|
+
pointer-events: none;
|
|
158
|
+
box-sizing: border-box;
|
|
159
|
+
flex-direction: column;
|
|
160
|
+
align-items: stretch;
|
|
161
|
+
|
|
162
|
+
margin-top: ${cssVars['vira-popover-trigger-offset-vertical'].value};
|
|
163
|
+
/*
|
|
164
|
+
The positioner is invisible outside of its content, so this minimum only makes
|
|
165
|
+
it flip upwards when there is not much room below the trigger.
|
|
166
|
+
*/
|
|
167
|
+
min-height: 200px;
|
|
168
|
+
/* Margins are not included in the percentage. */
|
|
169
|
+
max-height: calc(100% - ${cssVars['vira-popover-trigger-offset-vertical'].value});
|
|
170
|
+
width: max-content;
|
|
171
|
+
min-width: calc(
|
|
172
|
+
anchor-size(width) - ${cssVars['vira-popover-trigger-offset-left'].value} -
|
|
173
|
+
${cssVars['vira-popover-trigger-offset-right'].value}
|
|
174
|
+
);
|
|
175
|
+
/*
|
|
176
|
+
Not limited to the room beside the trigger, so a popover that does not fit there
|
|
177
|
+
overflows it and moves on to the next position-try-fallbacks entry.
|
|
178
|
+
*/
|
|
179
|
+
max-width: min(500px, 100vw);
|
|
180
|
+
position-area: block-end span-inline-end;
|
|
181
|
+
margin-left: ${cssVars['vira-popover-trigger-offset-left'].value};
|
|
182
|
+
|
|
183
|
+
&:popover-open {
|
|
184
|
+
display: flex;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
& > * {
|
|
188
|
+
pointer-events: auto;
|
|
189
|
+
max-width: 100%;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
`;
|
|
193
|
+
},
|
|
194
|
+
events: {
|
|
195
|
+
navSelect: defineElementEvent(),
|
|
196
|
+
/** `true` when the popover just opened, `false` when it just closed. */
|
|
197
|
+
openChange: defineElementEvent(),
|
|
198
|
+
init: defineElementEvent(),
|
|
199
|
+
},
|
|
200
|
+
cleanup({ state, updateState }) {
|
|
201
|
+
updateState({
|
|
202
|
+
isOpen: false,
|
|
203
|
+
});
|
|
204
|
+
state.popoverManager.destroy();
|
|
205
|
+
},
|
|
206
|
+
init({ state, inputs, dispatch, events, host }) {
|
|
207
|
+
/**
|
|
208
|
+
* Listens on the host rather than the popover because a drag from the trigger that is
|
|
209
|
+
* released in the popover fires its click on the closest element containing both.
|
|
210
|
+
*/
|
|
211
|
+
listenTo(host, 'click', (event) => {
|
|
212
|
+
const anchor = host.shadowRoot.querySelector('.dropdown-wrapper');
|
|
213
|
+
if (inputs.keepOpenAfterInteraction ||
|
|
214
|
+
(anchor && event.composedPath().includes(anchor)) ||
|
|
215
|
+
(event instanceof MouseEvent && event.detail === 0
|
|
216
|
+
? isTextInputActive()
|
|
217
|
+
: !(event instanceof MouseEvent) ||
|
|
218
|
+
event.button !== 0 ||
|
|
219
|
+
isMouseEventOnScrollbar(event))) {
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
222
|
+
/** Close the popover when something in it (like a menu item) is clicked. */
|
|
223
|
+
state.popoverManager.hide();
|
|
224
|
+
});
|
|
225
|
+
dispatch(new events.init({
|
|
226
|
+
detail: {
|
|
227
|
+
navController: state.navController,
|
|
228
|
+
},
|
|
229
|
+
}));
|
|
230
|
+
},
|
|
231
|
+
render({ dispatch, events, state, inputs, updateState, host, slotNames, cssVars }) {
|
|
232
|
+
return html `
|
|
233
|
+
<button
|
|
234
|
+
?disabled=${!!inputs.isDisabled}
|
|
235
|
+
class="dropdown-wrapper ${classMap({
|
|
236
|
+
open: state.isOpen,
|
|
237
|
+
})}"
|
|
238
|
+
role="listbox"
|
|
239
|
+
aria-expanded=${state.isOpen}
|
|
240
|
+
${popover(state.popoverManager, {
|
|
241
|
+
trigger: inputs.isDisabled
|
|
242
|
+
? false
|
|
243
|
+
: (inputs.trigger ?? PopoverTrigger.Mousedown),
|
|
244
|
+
getPopover() {
|
|
245
|
+
return assertWrap.instanceOf(host.shadowRoot.querySelector('.popover-positioner'), HTMLElement);
|
|
246
|
+
},
|
|
247
|
+
onNavSelect(coords) {
|
|
248
|
+
if (!inputs.keepOpenAfterInteraction) {
|
|
249
|
+
state.popoverManager.hide();
|
|
250
|
+
}
|
|
251
|
+
dispatch(new events.navSelect({
|
|
252
|
+
detail: coords,
|
|
253
|
+
}));
|
|
254
|
+
},
|
|
255
|
+
})}
|
|
256
|
+
>
|
|
257
|
+
<div class="dropdown-trigger">
|
|
258
|
+
<slot name=${slotNames['vira-popover-trigger-trigger']}></slot>
|
|
259
|
+
</div>
|
|
260
|
+
</button>
|
|
261
|
+
<div
|
|
262
|
+
class="popover-positioner"
|
|
263
|
+
popover="manual"
|
|
264
|
+
style=${css `
|
|
265
|
+
${cssVars['vira-popover-trigger-offset-vertical'].name}: ${inputs.popoverOffset
|
|
266
|
+
?.vertical || 0}px;
|
|
267
|
+
${cssVars['vira-popover-trigger-offset-left'].name}: ${inputs.popoverOffset
|
|
268
|
+
?.left || 0}px;
|
|
269
|
+
${cssVars['vira-popover-trigger-offset-right'].name}: ${inputs.popoverOffset
|
|
270
|
+
?.right || 0}px;
|
|
271
|
+
`}
|
|
272
|
+
${listen('toggle', () => {
|
|
273
|
+
const isOpen = state.popoverManager.isOpen();
|
|
274
|
+
if (isOpen === state.isOpen) {
|
|
275
|
+
return;
|
|
276
|
+
}
|
|
277
|
+
updateState({
|
|
278
|
+
isOpen,
|
|
279
|
+
});
|
|
280
|
+
dispatch(new events.openChange({
|
|
281
|
+
detail: isOpen,
|
|
282
|
+
}));
|
|
283
|
+
if (!isOpen && inputs.focusOnClose && !inputs.isDisabled) {
|
|
284
|
+
assertWrap
|
|
285
|
+
.instanceOf(host.shadowRoot.querySelector('.dropdown-wrapper'), HTMLButtonElement)
|
|
286
|
+
.focus();
|
|
287
|
+
}
|
|
288
|
+
})}
|
|
289
|
+
${listen(ViraMenuItem.events.activate, () => {
|
|
290
|
+
if (!inputs.keepOpenAfterInteraction) {
|
|
291
|
+
state.popoverManager.hide();
|
|
292
|
+
}
|
|
293
|
+
})}
|
|
294
|
+
>
|
|
295
|
+
${renderIf(state.isOpen, html `
|
|
296
|
+
<slot name=${slotNames['vira-popover-trigger-popover']}></slot>
|
|
297
|
+
`)}
|
|
298
|
+
</div>
|
|
299
|
+
`;
|
|
300
|
+
},
|
|
301
|
+
});
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The popover rendered by the `tooltip` directive. The element itself is the popover, centered
|
|
3
|
+
* above `anchor` when there's room. Its children are the tooltip contents.
|
|
4
|
+
*
|
|
5
|
+
* @category Popover
|
|
6
|
+
* @category Elements
|
|
7
|
+
*/
|
|
8
|
+
export declare const ViraTooltip: import("element-vir").DeclarativeElementDefinition<"vira-tooltip", {
|
|
9
|
+
anchor: HTMLElement;
|
|
10
|
+
}, {}, {}, "vira-tooltip-", "vira-tooltip-", readonly [], readonly []>;
|