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.
Files changed (56) hide show
  1. package/dist/directives/index.d.ts +2 -0
  2. package/dist/directives/index.js +2 -0
  3. package/dist/directives/popover.directive.d.ts +29 -0
  4. package/dist/directives/popover.directive.js +45 -0
  5. package/dist/directives/tooltip.directive.d.ts +60 -0
  6. package/dist/directives/tooltip.directive.js +99 -0
  7. package/dist/elements/color/vira-color-picker.element.d.ts +1 -1
  8. package/dist/elements/color/vira-color-picker.element.js +17 -15
  9. package/dist/elements/index.d.ts +5 -5
  10. package/dist/elements/index.js +5 -5
  11. package/dist/elements/{pop-up → popover}/vira-menu-item.element.d.ts +4 -4
  12. package/dist/elements/{pop-up → popover}/vira-menu-item.element.js +3 -3
  13. package/dist/elements/popover/vira-menu-trigger.element.d.ts +56 -0
  14. package/dist/elements/{pop-up → popover}/vira-menu-trigger.element.js +19 -30
  15. package/dist/elements/popover/vira-menu.element.d.ts +35 -0
  16. package/dist/elements/popover/vira-menu.element.js +262 -0
  17. package/dist/elements/popover/vira-popover-trigger.element.d.ts +49 -0
  18. package/dist/elements/popover/vira-popover-trigger.element.js +301 -0
  19. package/dist/elements/popover/vira-tooltip.element.d.ts +10 -0
  20. package/dist/elements/popover/vira-tooltip.element.js +176 -0
  21. package/dist/elements/vira-drawer.element.js +1 -1
  22. package/dist/elements/vira-dropdown.element.d.ts +28 -12
  23. package/dist/elements/vira-dropdown.element.js +172 -59
  24. package/dist/elements/vira-form.element.js +10 -16
  25. package/dist/elements/vira-json-form.element.js +40 -36
  26. package/dist/elements/vira-modal.element.js +1 -1
  27. package/dist/elements/vira-tabs.element.d.ts +3 -9
  28. package/dist/elements/vira-tabs.element.js +5 -6
  29. package/dist/elements/vira-theme-switcher.element.js +3 -3
  30. package/dist/index.d.ts +1 -0
  31. package/dist/index.js +1 -0
  32. package/dist/util/anchored-popover.d.ts +40 -0
  33. package/dist/util/anchored-popover.js +70 -0
  34. package/dist/util/index.d.ts +4 -3
  35. package/dist/util/index.js +4 -3
  36. package/dist/util/{pop-up-helpers.d.ts → menu-helpers.d.ts} +4 -18
  37. package/dist/util/{pop-up-helpers.js → menu-helpers.js} +2 -17
  38. package/dist/util/popover-manager.d.ts +123 -0
  39. package/dist/util/popover-manager.js +412 -0
  40. package/dist/util/vira-dropdown-option.d.ts +30 -0
  41. package/dist/util/vira-dropdown-option.js +8 -0
  42. package/dist/util/vira-form-fields.d.ts +2 -2
  43. package/dist/util/vira-theme-client.d.ts +1 -1
  44. package/dist/util/vira-theme-client.js +3 -3
  45. package/package.json +2 -1
  46. package/dist/elements/pop-up/vira-menu-trigger.element.d.ts +0 -45
  47. package/dist/elements/pop-up/vira-menu.element.d.ts +0 -36
  48. package/dist/elements/pop-up/vira-menu.element.js +0 -88
  49. package/dist/elements/pop-up/vira-pop-up-trigger.element.d.ts +0 -109
  50. package/dist/elements/pop-up/vira-pop-up-trigger.element.js +0 -408
  51. package/dist/elements/vira-select.element.d.ts +0 -44
  52. package/dist/elements/vira-select.element.js +0 -418
  53. package/dist/util/pop-up-manager.d.ts +0 -200
  54. package/dist/util/pop-up-manager.js +0 -258
  55. package/dist/util/vira-select-option.d.ts +0 -31
  56. 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>&nbsp;</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 []>;