vira 25.7.3 → 25.9.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/elements/index.d.ts +8 -3
- package/dist/elements/index.js +8 -3
- package/dist/elements/pop-up/pop-up-helpers.d.ts +30 -0
- package/dist/elements/pop-up/pop-up-helpers.js +58 -0
- package/dist/elements/pop-up/pop-up-menu-item.d.ts +24 -0
- package/dist/elements/pop-up/pop-up-menu-item.js +1 -0
- package/dist/elements/pop-up/vira-menu-item.element.d.ts +19 -0
- package/dist/elements/pop-up/vira-menu-item.element.js +58 -0
- package/dist/elements/pop-up/vira-menu-trigger.element.d.ts +39 -0
- package/dist/elements/pop-up/vira-menu-trigger.element.js +108 -0
- package/dist/elements/pop-up/vira-menu.element.d.ts +38 -0
- package/dist/elements/pop-up/vira-menu.element.js +130 -0
- package/dist/elements/pop-up/vira-pop-up-menu.element.d.ts +10 -0
- package/dist/elements/pop-up/vira-pop-up-menu.element.js +48 -0
- package/dist/elements/pop-up/vira-pop-up-trigger.element.d.ts +44 -0
- package/dist/elements/pop-up/vira-pop-up-trigger.element.js +235 -0
- package/dist/elements/vira-bold-text.element.js +3 -2
- package/dist/elements/{dropdown/vira-dropdown.element.d.ts → vira-dropdown.element.d.ts} +12 -9
- package/dist/elements/vira-dropdown.element.js +185 -0
- package/dist/styles/focus.d.ts +0 -1
- package/dist/util/pop-up-manager.d.ts +1 -1
- package/dist/util/pop-up-manager.js +20 -9
- package/package.json +11 -10
- package/dist/elements/dropdown/dropdown-helpers.d.ts +0 -45
- package/dist/elements/dropdown/dropdown-helpers.js +0 -78
- package/dist/elements/dropdown/vira-dropdown-item.element.d.ts +0 -27
- package/dist/elements/dropdown/vira-dropdown-item.element.js +0 -69
- package/dist/elements/dropdown/vira-dropdown-options.element.d.ts +0 -29
- package/dist/elements/dropdown/vira-dropdown-options.element.js +0 -116
- package/dist/elements/dropdown/vira-dropdown.element.js +0 -324
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
import { assert } from '@augment-vir/assert';
|
|
2
|
+
import { NavController } from 'device-navigation';
|
|
3
|
+
import { classMap, css, defineElementEvent, html, listen, renderIf } from 'element-vir';
|
|
4
|
+
import { createFocusStyles, viraFocusCssVars } from '../../styles/focus.js';
|
|
5
|
+
import { viraFormCssVars } from '../../styles/form-themes.js';
|
|
6
|
+
import { noNativeFormStyles, noUserSelect, viraDisabledStyles } from '../../styles/index.js';
|
|
7
|
+
import { HidePopUpEvent, NavSelectEvent, PopUpManager, } from '../../util/pop-up-manager.js';
|
|
8
|
+
import { defineViraElement } from '../define-vira-element.js';
|
|
9
|
+
import { triggerPopUpState } from './pop-up-helpers.js';
|
|
10
|
+
/**
|
|
11
|
+
* An element with slots for a pop-up trigger and pop-up contents.
|
|
12
|
+
*
|
|
13
|
+
* @category PopUp
|
|
14
|
+
* @category Elements
|
|
15
|
+
* @see https://electrovir.github.io/element-vir/vira/book/elements/vira-pop-up-trigger
|
|
16
|
+
*/
|
|
17
|
+
export const ViraPopUpTrigger = defineViraElement()({
|
|
18
|
+
tagName: 'vira-pop-up-trigger',
|
|
19
|
+
state({ host }) {
|
|
20
|
+
return {
|
|
21
|
+
/** `undefined` means the pop up is not currently showing. */
|
|
22
|
+
showPopUpResult: undefined,
|
|
23
|
+
popUpManager: new PopUpManager(new NavController(host, { activateOnMouseUp: true })),
|
|
24
|
+
};
|
|
25
|
+
},
|
|
26
|
+
slotNames: [
|
|
27
|
+
'trigger',
|
|
28
|
+
'popUp',
|
|
29
|
+
],
|
|
30
|
+
hostClasses: {
|
|
31
|
+
'vira-pop-up-trigger-disabled': ({ inputs }) => !!inputs.isDisabled,
|
|
32
|
+
},
|
|
33
|
+
styles: ({ hostClasses }) => css `
|
|
34
|
+
:host {
|
|
35
|
+
display: inline-flex;
|
|
36
|
+
box-sizing: border-box;
|
|
37
|
+
vertical-align: middle;
|
|
38
|
+
${viraFocusCssVars['vira-focus-outline-color'].name}: ${viraFormCssVars['vira-form-focus-color'].value};
|
|
39
|
+
position: relative;
|
|
40
|
+
max-width: 100%;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
.dropdown-wrapper {
|
|
44
|
+
${noNativeFormStyles};
|
|
45
|
+
cursor: pointer;
|
|
46
|
+
max-width: 100%;
|
|
47
|
+
position: relative;
|
|
48
|
+
outline: none;
|
|
49
|
+
flex-grow: 1;
|
|
50
|
+
box-sizing: border-box;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
${createFocusStyles({
|
|
54
|
+
selector: '.dropdown-wrapper:focus',
|
|
55
|
+
elementBorderSize: 1,
|
|
56
|
+
})}
|
|
57
|
+
|
|
58
|
+
.dropdown-trigger {
|
|
59
|
+
box-sizing: border-box;
|
|
60
|
+
${noUserSelect};
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
${hostClasses['vira-pop-up-trigger-disabled'].selector} {
|
|
64
|
+
${viraDisabledStyles}
|
|
65
|
+
pointer-events: auto;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
${hostClasses['vira-pop-up-trigger-disabled'].selector} .dropdown-wrapper {
|
|
69
|
+
pointer-events: none;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
.pop-up-positioner {
|
|
73
|
+
position: absolute;
|
|
74
|
+
pointer-events: none;
|
|
75
|
+
display: flex;
|
|
76
|
+
flex-direction: column;
|
|
77
|
+
|
|
78
|
+
/* highest possible z-index */
|
|
79
|
+
z-index: 2147483647;
|
|
80
|
+
left: 0;
|
|
81
|
+
right: 0;
|
|
82
|
+
|
|
83
|
+
& > * {
|
|
84
|
+
pointer-events: auto;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
.open-upwards .pop-up-positioner {
|
|
89
|
+
flex-direction: column-reverse;
|
|
90
|
+
}
|
|
91
|
+
`,
|
|
92
|
+
events: {
|
|
93
|
+
navSelect: defineElementEvent(),
|
|
94
|
+
/**
|
|
95
|
+
* - `undefined` indicates that the pop-up just closed.
|
|
96
|
+
* - {@link ShowPopUpResult} indicates that the pop-up just opened.
|
|
97
|
+
*/
|
|
98
|
+
openChange: defineElementEvent(),
|
|
99
|
+
init: defineElementEvent(),
|
|
100
|
+
},
|
|
101
|
+
cleanup({ state, updateState }) {
|
|
102
|
+
updateState({ showPopUpResult: undefined });
|
|
103
|
+
state.popUpManager.destroy();
|
|
104
|
+
},
|
|
105
|
+
init({ state, updateState, host, inputs, dispatch, events }) {
|
|
106
|
+
/** Refocus the trigger and set the result to `undefined` when the pop up closes. */
|
|
107
|
+
state.popUpManager.listen(HidePopUpEvent, () => {
|
|
108
|
+
updateState({ showPopUpResult: undefined });
|
|
109
|
+
dispatch(new events.openChange(undefined));
|
|
110
|
+
if (!inputs.isDisabled) {
|
|
111
|
+
const dropdownWrapper = host.shadowRoot.querySelector('.dropdown-wrapper');
|
|
112
|
+
assert.instanceOf(dropdownWrapper, HTMLButtonElement, 'failed to find dropdown wrapper child');
|
|
113
|
+
dropdownWrapper.focus();
|
|
114
|
+
}
|
|
115
|
+
});
|
|
116
|
+
state.popUpManager.listen(NavSelectEvent, (event) => {
|
|
117
|
+
if (!inputs.keepOpenAfterInteraction) {
|
|
118
|
+
triggerPopUpState({
|
|
119
|
+
open: false,
|
|
120
|
+
callback(showPopUpResult) {
|
|
121
|
+
updateState({
|
|
122
|
+
showPopUpResult,
|
|
123
|
+
});
|
|
124
|
+
},
|
|
125
|
+
host,
|
|
126
|
+
popUpManager: state.popUpManager,
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
dispatch(new events.navSelect(event.detail));
|
|
130
|
+
});
|
|
131
|
+
dispatch(new events.init({
|
|
132
|
+
navController: state.popUpManager.navController,
|
|
133
|
+
popUpManager: state.popUpManager,
|
|
134
|
+
}));
|
|
135
|
+
},
|
|
136
|
+
render({ dispatch, events, state, inputs, updateState, host, slotNames }) {
|
|
137
|
+
function triggerPopUp({ emitEvent, open }, event) {
|
|
138
|
+
if (state.showPopUpResult && inputs.keepOpenAfterInteraction && event) {
|
|
139
|
+
const dropdownTrigger = host.shadowRoot.querySelector('.dropdown-trigger');
|
|
140
|
+
if (dropdownTrigger && !event.composedPath().includes(dropdownTrigger)) {
|
|
141
|
+
/**
|
|
142
|
+
* Prevent closing the pop-up when `keepOpenAfterInteraction` is turned on and
|
|
143
|
+
* the pop-up was interacted with.
|
|
144
|
+
*/
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
triggerPopUpState({
|
|
149
|
+
open,
|
|
150
|
+
callback(showPopUpResult) {
|
|
151
|
+
updateState({ showPopUpResult });
|
|
152
|
+
if (emitEvent) {
|
|
153
|
+
dispatch(new events.openChange(showPopUpResult));
|
|
154
|
+
}
|
|
155
|
+
},
|
|
156
|
+
host,
|
|
157
|
+
popUpManager: state.popUpManager,
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
if (inputs.isDisabled) {
|
|
161
|
+
triggerPopUp({ open: false, emitEvent: false }, undefined);
|
|
162
|
+
}
|
|
163
|
+
else if (inputs.z_debug_forceOpenState != undefined) {
|
|
164
|
+
if (!inputs.z_debug_forceOpenState && state.showPopUpResult) {
|
|
165
|
+
triggerPopUp({ emitEvent: false, open: false }, undefined);
|
|
166
|
+
}
|
|
167
|
+
else if (inputs.z_debug_forceOpenState && !state.showPopUpResult) {
|
|
168
|
+
triggerPopUp({ emitEvent: false, open: true }, undefined);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* These styles do _not_ account for window resizing while the menu is open. I decided this
|
|
173
|
+
* was not a major enough problem to tackle. If it becomes major enough in the future,
|
|
174
|
+
* you'll need to hook into a window _or_ container resize listener inside `PopUpManager`
|
|
175
|
+
* and emit a new `ShowPopUpResult` instance when it changes.
|
|
176
|
+
*/
|
|
177
|
+
const positionerStyles = state.showPopUpResult
|
|
178
|
+
? state.showPopUpResult.popDown
|
|
179
|
+
? /** Dropdown going down position. */
|
|
180
|
+
css `
|
|
181
|
+
bottom: -${state.showPopUpResult.positions.diff.bottom}px;
|
|
182
|
+
top: calc(100% + ${inputs.popUpOffset?.vertical || 0}px);
|
|
183
|
+
left: ${inputs.popUpOffset?.left || 0}px;
|
|
184
|
+
right: ${inputs.popUpOffset?.right || 0}px;
|
|
185
|
+
`
|
|
186
|
+
: /** Dropdown going up position. */
|
|
187
|
+
css `
|
|
188
|
+
top: -${state.showPopUpResult.positions.diff.top}px;
|
|
189
|
+
bottom: calc(100% + ${inputs.popUpOffset?.vertical || 0}px);
|
|
190
|
+
left: ${inputs.popUpOffset?.left || 0}px;
|
|
191
|
+
right: ${inputs.popUpOffset?.right || 0}px;
|
|
192
|
+
`
|
|
193
|
+
: undefined;
|
|
194
|
+
function respondToClick(event) {
|
|
195
|
+
triggerPopUp({ emitEvent: true, open: !state.showPopUpResult }, event);
|
|
196
|
+
}
|
|
197
|
+
return html `
|
|
198
|
+
<button
|
|
199
|
+
?disabled=${!!inputs.isDisabled}
|
|
200
|
+
class="dropdown-wrapper ${classMap({
|
|
201
|
+
open: !!state.showPopUpResult,
|
|
202
|
+
'open-upwards': !state.showPopUpResult?.popDown,
|
|
203
|
+
})}"
|
|
204
|
+
role="listbox"
|
|
205
|
+
aria-expanded=${!!state.showPopUpResult}
|
|
206
|
+
${listen('keydown', (event) => {
|
|
207
|
+
if (!state.showPopUpResult && event.code.startsWith('Arrow')) {
|
|
208
|
+
triggerPopUp({ emitEvent: true, open: true }, event);
|
|
209
|
+
}
|
|
210
|
+
})}
|
|
211
|
+
${listen('click', (event) => {
|
|
212
|
+
/** Detail is 0 if it was a keyboard key (like Enter) that triggered this click. */
|
|
213
|
+
if (event.detail === 0) {
|
|
214
|
+
respondToClick(event);
|
|
215
|
+
}
|
|
216
|
+
})}
|
|
217
|
+
${listen('mousedown', (event) => {
|
|
218
|
+
/** Ignore any clicks that aren't the main button. */
|
|
219
|
+
if (event.button === 0) {
|
|
220
|
+
respondToClick(event);
|
|
221
|
+
}
|
|
222
|
+
})}
|
|
223
|
+
>
|
|
224
|
+
<div class="dropdown-trigger">
|
|
225
|
+
<slot name=${slotNames.trigger}></slot>
|
|
226
|
+
</div>
|
|
227
|
+
<div class="pop-up-positioner" style=${positionerStyles}>
|
|
228
|
+
${renderIf(!!state.showPopUpResult, html `
|
|
229
|
+
<slot name=${slotNames.popUp}></slot>
|
|
230
|
+
`)}
|
|
231
|
+
</div>
|
|
232
|
+
</button>
|
|
233
|
+
`;
|
|
234
|
+
},
|
|
235
|
+
});
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import { css,
|
|
1
|
+
import { css, html } from 'element-vir';
|
|
2
|
+
import { defineViraElement } from './define-vira-element.js';
|
|
2
3
|
/** Use this element to reserve space for bolded text, even if it isn't bold yet. */
|
|
3
|
-
export const ViraBoldText =
|
|
4
|
+
export const ViraBoldText = defineViraElement()({
|
|
4
5
|
tagName: 'vira-bold',
|
|
5
6
|
cssVars: {
|
|
6
7
|
'vira-bold-bold-weight': 'bold',
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { type PartialWithUndefined } from '@augment-vir/common';
|
|
2
|
-
import { type ViraIconSvg } from '
|
|
3
|
-
import {
|
|
4
|
-
import { type
|
|
2
|
+
import { type ViraIconSvg } from '../icons/icon-svg.js';
|
|
3
|
+
import { type ShowPopUpResult } from '../util/pop-up-manager.js';
|
|
4
|
+
import { type MenuItem } from './pop-up/pop-up-menu-item.js';
|
|
5
5
|
/**
|
|
6
6
|
* Test ids for {@link ViraDropdown}.
|
|
7
7
|
*
|
|
@@ -11,17 +11,16 @@ export declare const viraDropdownTestIds: {
|
|
|
11
11
|
trigger: string;
|
|
12
12
|
icon: string;
|
|
13
13
|
prefix: string;
|
|
14
|
-
options: string;
|
|
15
14
|
};
|
|
16
15
|
/**
|
|
17
|
-
*
|
|
16
|
+
* A dropdown element that uses pop-up menus.
|
|
18
17
|
*
|
|
19
18
|
* @category Dropdown
|
|
20
19
|
* @category Elements
|
|
21
20
|
* @see https://electrovir.github.io/element-vir/vira/book/elements/dropdown/vira-dropdown
|
|
22
21
|
*/
|
|
23
22
|
export declare const ViraDropdown: import("element-vir").DeclarativeElementDefinition<"vira-dropdown", {
|
|
24
|
-
options: ReadonlyArray<Readonly<
|
|
23
|
+
options: ReadonlyArray<Readonly<MenuItem>>;
|
|
25
24
|
/** The selected id from the given options. */
|
|
26
25
|
selected: ReadonlyArray<PropertyKey>;
|
|
27
26
|
} & PartialWithUndefined<{
|
|
@@ -32,6 +31,11 @@ export declare const ViraDropdown: import("element-vir").DeclarativeElementDefin
|
|
|
32
31
|
* multiple.
|
|
33
32
|
*/
|
|
34
33
|
isMultiSelect: boolean;
|
|
34
|
+
/**
|
|
35
|
+
* Shows the selection quantity rather than a list of selections. Only used when
|
|
36
|
+
* `isMultiSelect` is `true`.
|
|
37
|
+
*/
|
|
38
|
+
showSelectionCount: boolean;
|
|
35
39
|
icon: ViraIconSvg;
|
|
36
40
|
selectionPrefix: string;
|
|
37
41
|
isDisabled: boolean;
|
|
@@ -40,8 +44,7 @@ export declare const ViraDropdown: import("element-vir").DeclarativeElementDefin
|
|
|
40
44
|
}>, {
|
|
41
45
|
/** `undefined` means the pop up is not currently showing. */
|
|
42
46
|
showPopUpResult: ShowPopUpResult | undefined;
|
|
43
|
-
popUpManager: PopUpManager;
|
|
44
47
|
}, {
|
|
45
48
|
selectedChange: import("element-vir").DefineEvent<PropertyKey[]>;
|
|
46
|
-
openChange: import("element-vir").DefineEvent<
|
|
47
|
-
}, "vira-dropdown-
|
|
49
|
+
openChange: import("element-vir").DefineEvent<ShowPopUpResult | undefined>;
|
|
50
|
+
}, "vira-dropdown-", "vira-dropdown-", readonly []>;
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
import { check } from '@augment-vir/assert';
|
|
2
|
+
import { filterMap } from '@augment-vir/common';
|
|
3
|
+
import { classMap, css, defineElementEvent, html, ifDefined, listen, nothing, testId, } from 'element-vir';
|
|
4
|
+
import { ChevronUp24Icon } from '../icons/index.js';
|
|
5
|
+
import { viraBorders } from '../styles/border.js';
|
|
6
|
+
import { viraFocusCssVars } from '../styles/focus.js';
|
|
7
|
+
import { viraFormCssVars } from '../styles/form-themes.js';
|
|
8
|
+
import { noUserSelect, viraAnimationDurations } from '../styles/index.js';
|
|
9
|
+
import { defineViraElement } from './define-vira-element.js';
|
|
10
|
+
import { ViraMenuTrigger } from './pop-up/vira-menu-trigger.element.js';
|
|
11
|
+
import { ViraIcon } from './vira-icon.element.js';
|
|
12
|
+
/**
|
|
13
|
+
* Test ids for {@link ViraDropdown}.
|
|
14
|
+
*
|
|
15
|
+
* @category Internal
|
|
16
|
+
*/
|
|
17
|
+
export const viraDropdownTestIds = {
|
|
18
|
+
trigger: 'dropdown-trigger',
|
|
19
|
+
icon: 'dropdown-icon',
|
|
20
|
+
prefix: 'dropdown-prefix',
|
|
21
|
+
};
|
|
22
|
+
/**
|
|
23
|
+
* A dropdown element that uses pop-up menus.
|
|
24
|
+
*
|
|
25
|
+
* @category Dropdown
|
|
26
|
+
* @category Elements
|
|
27
|
+
* @see https://electrovir.github.io/element-vir/vira/book/elements/dropdown/vira-dropdown
|
|
28
|
+
*/
|
|
29
|
+
export const ViraDropdown = defineViraElement()({
|
|
30
|
+
tagName: 'vira-dropdown',
|
|
31
|
+
styles: css `
|
|
32
|
+
:host {
|
|
33
|
+
display: inline-flex;
|
|
34
|
+
vertical-align: middle;
|
|
35
|
+
width: 256px;
|
|
36
|
+
${viraFocusCssVars['vira-focus-outline-color'].name}: ${viraFormCssVars['vira-form-focus-color'].value};
|
|
37
|
+
position: relative;
|
|
38
|
+
max-width: 100%;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
${ViraMenuTrigger} {
|
|
42
|
+
width: 100%;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
.selection-display {
|
|
46
|
+
overflow: hidden;
|
|
47
|
+
text-overflow: ellipsis;
|
|
48
|
+
white-space: nowrap;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
.trigger-icon {
|
|
52
|
+
align-self: flex-start;
|
|
53
|
+
will-change: transform;
|
|
54
|
+
transform: rotate(180deg);
|
|
55
|
+
transition: transform
|
|
56
|
+
${viraAnimationDurations['vira-interaction-animation-duration'].value} linear;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
.trigger-icon-wrapper {
|
|
60
|
+
flex-grow: 1;
|
|
61
|
+
display: flex;
|
|
62
|
+
justify-content: flex-end;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
.open {
|
|
66
|
+
& .trigger-icon {
|
|
67
|
+
transform: rotate(0);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
&:not(.open-upwards).dropdown-trigger {
|
|
71
|
+
border-bottom-left-radius: 0;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
&.open-upwards.dropdown-trigger {
|
|
75
|
+
border-top-left-radius: 0;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
.dropdown-trigger {
|
|
80
|
+
${noUserSelect};
|
|
81
|
+
border: 1px solid ${viraFormCssVars['vira-form-border-color'].value};
|
|
82
|
+
height: 100%;
|
|
83
|
+
width: 100%;
|
|
84
|
+
box-sizing: border-box;
|
|
85
|
+
display: flex;
|
|
86
|
+
gap: 8px;
|
|
87
|
+
text-align: left;
|
|
88
|
+
align-items: center;
|
|
89
|
+
padding: 3px;
|
|
90
|
+
padding-left: 10px;
|
|
91
|
+
border-radius: ${viraBorders['vira-form-input-radius'].value};
|
|
92
|
+
background-color: ${viraFormCssVars['vira-form-background-color'].value};
|
|
93
|
+
color: ${viraFormCssVars['vira-form-foreground-color'].value};
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
.using-placeholder {
|
|
97
|
+
opacity: 0.4;
|
|
98
|
+
}
|
|
99
|
+
`,
|
|
100
|
+
events: {
|
|
101
|
+
selectedChange: defineElementEvent(),
|
|
102
|
+
openChange: defineElementEvent(),
|
|
103
|
+
},
|
|
104
|
+
state() {
|
|
105
|
+
return {
|
|
106
|
+
/** `undefined` means the pop up is not currently showing. */
|
|
107
|
+
showPopUpResult: undefined,
|
|
108
|
+
};
|
|
109
|
+
},
|
|
110
|
+
render({ state, inputs, dispatch, events, updateState }) {
|
|
111
|
+
const selectedOptions = filterMap(inputs.selected, (selectedId) => inputs.options.find((option) => option.id === selectedId), check.isTruthy);
|
|
112
|
+
const leadingIconTemplate = inputs.icon
|
|
113
|
+
? html `
|
|
114
|
+
<${ViraIcon.assign({
|
|
115
|
+
icon: inputs.icon,
|
|
116
|
+
})}
|
|
117
|
+
${testId(viraDropdownTestIds.icon)}
|
|
118
|
+
></${ViraIcon}>
|
|
119
|
+
`
|
|
120
|
+
: nothing;
|
|
121
|
+
const shouldUsePlaceholder = !selectedOptions.length;
|
|
122
|
+
const prefixTemplate = inputs.selectionPrefix && !shouldUsePlaceholder
|
|
123
|
+
? html `
|
|
124
|
+
<span class="selected-label-prefix" ${testId(viraDropdownTestIds.prefix)}>
|
|
125
|
+
${inputs.selectionPrefix}
|
|
126
|
+
</span>
|
|
127
|
+
`
|
|
128
|
+
: nothing;
|
|
129
|
+
const selectionDisplay = shouldUsePlaceholder
|
|
130
|
+
? inputs.placeholder || ''
|
|
131
|
+
: inputs.isMultiSelect && inputs.showSelectionCount
|
|
132
|
+
? `${selectedOptions.length} Selected`
|
|
133
|
+
: inputs.isMultiSelect
|
|
134
|
+
? selectedOptions.map((item) => item.label).join(', ')
|
|
135
|
+
: selectedOptions[0]?.label || '';
|
|
136
|
+
return html `
|
|
137
|
+
<${ViraMenuTrigger.assign({
|
|
138
|
+
items: inputs.options,
|
|
139
|
+
selected: inputs.selected,
|
|
140
|
+
isDisabled: inputs.isDisabled,
|
|
141
|
+
isMultiSelect: inputs.isMultiSelect,
|
|
142
|
+
z_debug_forceOpenState: inputs.z_debug_forceOpenState,
|
|
143
|
+
popUpOffset: {
|
|
144
|
+
vertical: -1,
|
|
145
|
+
right: 24,
|
|
146
|
+
},
|
|
147
|
+
})}
|
|
148
|
+
${listen(ViraMenuTrigger.events.openChange, (event) => {
|
|
149
|
+
updateState({ showPopUpResult: event.detail });
|
|
150
|
+
dispatch(new events.openChange(event.detail));
|
|
151
|
+
})}
|
|
152
|
+
${listen(ViraMenuTrigger.events.itemActivate, (event) => {
|
|
153
|
+
dispatch(new events.selectedChange(event.detail));
|
|
154
|
+
})}
|
|
155
|
+
>
|
|
156
|
+
<div
|
|
157
|
+
slot=${ViraMenuTrigger.slotNames.trigger}
|
|
158
|
+
class="dropdown-trigger ${classMap({
|
|
159
|
+
open: !!state.showPopUpResult,
|
|
160
|
+
'open-upwards': !state.showPopUpResult?.popDown,
|
|
161
|
+
})}"
|
|
162
|
+
${testId(viraDropdownTestIds.trigger)}
|
|
163
|
+
>
|
|
164
|
+
${leadingIconTemplate}
|
|
165
|
+
<span
|
|
166
|
+
class="selection-display ${classMap({
|
|
167
|
+
'using-placeholder': shouldUsePlaceholder,
|
|
168
|
+
})}"
|
|
169
|
+
title=${ifDefined(shouldUsePlaceholder ||
|
|
170
|
+
(inputs.isMultiSelect && inputs.showSelectionCount)
|
|
171
|
+
? undefined
|
|
172
|
+
: selectionDisplay)}
|
|
173
|
+
>
|
|
174
|
+
${prefixTemplate} ${selectionDisplay}
|
|
175
|
+
</span>
|
|
176
|
+
<span class="trigger-icon-wrapper">
|
|
177
|
+
<${ViraIcon.assign({ icon: ChevronUp24Icon })}
|
|
178
|
+
class="trigger-icon"
|
|
179
|
+
></${ViraIcon}>
|
|
180
|
+
</span>
|
|
181
|
+
</div>
|
|
182
|
+
</${ViraMenuTrigger}>
|
|
183
|
+
`;
|
|
184
|
+
},
|
|
185
|
+
});
|
package/dist/styles/focus.d.ts
CHANGED
|
@@ -122,7 +122,7 @@ export type PopUpManagerEvents = HidePopUpEvent | NavSelectEvent;
|
|
|
122
122
|
export declare class PopUpManager {
|
|
123
123
|
readonly navController: NavController;
|
|
124
124
|
private listenTarget;
|
|
125
|
-
|
|
125
|
+
options: PopUpManagerOptions;
|
|
126
126
|
private cleanupCallbacks;
|
|
127
127
|
private lastRootElement;
|
|
128
128
|
constructor(navController: NavController, options?: Partial<PopUpManagerOptions> | undefined);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { assert } from '@augment-vir/assert';
|
|
2
2
|
import { mapObjectValues } from '@augment-vir/common';
|
|
3
3
|
import { findOverflowAncestor } from '@augment-vir/web';
|
|
4
|
-
import { NavDirection } from 'device-navigation';
|
|
4
|
+
import { NavActivateEvent, NavDirection } from 'device-navigation';
|
|
5
5
|
import { listenToPageActivation } from 'page-active';
|
|
6
6
|
import { ListenTarget, defineTypedCustomEvent, defineTypedEvent, listenToGlobal, } from 'typed-event-target';
|
|
7
7
|
/**
|
|
@@ -56,6 +56,14 @@ export class PopUpManager {
|
|
|
56
56
|
this.removePopUp();
|
|
57
57
|
}
|
|
58
58
|
}),
|
|
59
|
+
this.navController.listen(NavActivateEvent, (event) => {
|
|
60
|
+
if (event.detail.success) {
|
|
61
|
+
this.listenTarget.dispatch(new NavSelectEvent({ detail: event.detail.coords }));
|
|
62
|
+
this.navController.currentNavEntry?.entry.focus(true);
|
|
63
|
+
event.stopImmediatePropagation();
|
|
64
|
+
event.preventDefault();
|
|
65
|
+
}
|
|
66
|
+
}),
|
|
59
67
|
listenToGlobal('mousedown', (event) => {
|
|
60
68
|
if (this.lastRootElement &&
|
|
61
69
|
event.composedPath().includes(this.lastRootElement)) {
|
|
@@ -102,13 +110,10 @@ export class PopUpManager {
|
|
|
102
110
|
allowWrapping: false,
|
|
103
111
|
});
|
|
104
112
|
}
|
|
105
|
-
else if (keyCode === 'Enter' || keyCode === 'Return')
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
event.stopImmediatePropagation();
|
|
110
|
-
event.preventDefault();
|
|
111
|
-
}
|
|
113
|
+
else if ((keyCode === 'Enter' || keyCode === 'Return' || keyCode === 'Space') &&
|
|
114
|
+
this.navController.enterInto({ fallbackToActivate: true }).success) {
|
|
115
|
+
event.stopImmediatePropagation();
|
|
116
|
+
event.preventDefault();
|
|
112
117
|
}
|
|
113
118
|
}
|
|
114
119
|
}),
|
|
@@ -152,7 +157,13 @@ export class PopUpManager {
|
|
|
152
157
|
const diff = mapObjectValues(emptyPositionRect, (key) => {
|
|
153
158
|
const containerDimension = containerPosition[key];
|
|
154
159
|
const hostDimension = rootPosition[key];
|
|
155
|
-
|
|
160
|
+
/**
|
|
161
|
+
* Chrome will trigger a scroll bar sometimes if the PopUp is too close to the bottom,
|
|
162
|
+
* even if clearly isn't overflowing. The value here was found by experimentation to be
|
|
163
|
+
* the lowest value that wouldn't trigger that.
|
|
164
|
+
*/
|
|
165
|
+
const additionalOffset = key === 'bottom' ? -51 : 0;
|
|
166
|
+
return Math.abs(containerDimension - hostDimension + additionalOffset);
|
|
156
167
|
});
|
|
157
168
|
const useUp = diff.top > diff.bottom + currentOptions.verticalDiffThreshold &&
|
|
158
169
|
diff.bottom < currentOptions.minDownSpace;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "vira",
|
|
3
|
-
"version": "25.
|
|
3
|
+
"version": "25.9.0",
|
|
4
4
|
"description": "A simple and highly versatile design system using element-vir.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"design",
|
|
@@ -25,6 +25,7 @@
|
|
|
25
25
|
"name": "electrovir",
|
|
26
26
|
"url": "https://github.com/electrovir"
|
|
27
27
|
},
|
|
28
|
+
"sideEffects": false,
|
|
28
29
|
"type": "module",
|
|
29
30
|
"main": "dist/index.js",
|
|
30
31
|
"module": "dist/index.js",
|
|
@@ -37,36 +38,36 @@
|
|
|
37
38
|
"test:docs": "virmator docs check"
|
|
38
39
|
},
|
|
39
40
|
"dependencies": {
|
|
40
|
-
"@augment-vir/assert": "^31.
|
|
41
|
-
"@augment-vir/common": "^31.
|
|
42
|
-
"@augment-vir/web": "^31.
|
|
41
|
+
"@augment-vir/assert": "^31.21.0",
|
|
42
|
+
"@augment-vir/common": "^31.21.0",
|
|
43
|
+
"@augment-vir/web": "^31.21.0",
|
|
43
44
|
"colorjs.io": "^0.5.2",
|
|
44
45
|
"date-vir": "^7.3.1",
|
|
45
|
-
"device-navigation": "^4.
|
|
46
|
+
"device-navigation": "^4.5.5",
|
|
46
47
|
"lit-css-vars": "^3.0.11",
|
|
47
48
|
"observavir": "^2.0.5",
|
|
48
49
|
"page-active": "^1.0.1",
|
|
49
|
-
"spa-router-vir": "^5.
|
|
50
|
+
"spa-router-vir": "^5.5.0",
|
|
50
51
|
"type-fest": "^4.41.0",
|
|
51
52
|
"typed-event-target": "^4.1.0"
|
|
52
53
|
},
|
|
53
54
|
"devDependencies": {
|
|
54
|
-
"@augment-vir/test": "^31.
|
|
55
|
+
"@augment-vir/test": "^31.21.0",
|
|
55
56
|
"@web/dev-server-esbuild": "^1.0.4",
|
|
56
57
|
"@web/test-runner": "^0.20.2",
|
|
57
58
|
"@web/test-runner-commands": "^0.9.0",
|
|
58
59
|
"@web/test-runner-playwright": "^0.11.0",
|
|
59
60
|
"@web/test-runner-visual-regression": "^0.10.0",
|
|
60
|
-
"esbuild": "^0.25.
|
|
61
|
+
"esbuild": "^0.25.5",
|
|
61
62
|
"istanbul-smart-text-reporter": "^1.1.5",
|
|
62
63
|
"markdown-code-example-inserter": "^3.0.3",
|
|
63
|
-
"typedoc": "^0.28.
|
|
64
|
+
"typedoc": "^0.28.5",
|
|
64
65
|
"typescript": "5.8.3",
|
|
65
66
|
"vite": "^6.3.5",
|
|
66
67
|
"vite-tsconfig-paths": "^5.1.4"
|
|
67
68
|
},
|
|
68
69
|
"peerDependencies": {
|
|
69
|
-
"element-vir": "^25.
|
|
70
|
+
"element-vir": "^25.9.0"
|
|
70
71
|
},
|
|
71
72
|
"engines": {
|
|
72
73
|
"node": ">=22"
|
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
import { type PopUpManager, type ShowPopUpResult } from '../../util/pop-up-manager.js';
|
|
2
|
-
import { type ViraDropdownOption } from './vira-dropdown-item.element.js';
|
|
3
|
-
/**
|
|
4
|
-
* Filters an array of {@link ViraDropdownOption} based on the given selection.
|
|
5
|
-
*
|
|
6
|
-
* @category Internal
|
|
7
|
-
*/
|
|
8
|
-
export declare function filterToSelectedOptions({ selected, options, isMultiSelect, }: Readonly<{
|
|
9
|
-
selected: ReadonlyArray<PropertyKey>;
|
|
10
|
-
isMultiSelect?: boolean | undefined;
|
|
11
|
-
options: ReadonlyArray<Readonly<ViraDropdownOption>>;
|
|
12
|
-
}>): ViraDropdownOption[];
|
|
13
|
-
/**
|
|
14
|
-
* Verifies that all options have unique ids.
|
|
15
|
-
*
|
|
16
|
-
* @category Internal
|
|
17
|
-
*/
|
|
18
|
-
export declare function assertUniqueIdProps(options: ReadonlyArray<Readonly<{
|
|
19
|
-
id: PropertyKey;
|
|
20
|
-
}>>): void;
|
|
21
|
-
/**
|
|
22
|
-
* Creates a new array of selections based on the current selection and new selection id. This
|
|
23
|
-
* behaves differently when multi select is enabled, hence this function.
|
|
24
|
-
*
|
|
25
|
-
* @category Internal
|
|
26
|
-
*/
|
|
27
|
-
export declare function createNewSelection(
|
|
28
|
-
/** The id of the option that should be newly selected. */
|
|
29
|
-
id: PropertyKey, currentSelection: ReadonlyArray<PropertyKey>, isMultiSelect: boolean): PropertyKey[];
|
|
30
|
-
/**
|
|
31
|
-
* Handles toggles pop up state for `ViraDropdown`.
|
|
32
|
-
*
|
|
33
|
-
* @category Internal
|
|
34
|
-
*/
|
|
35
|
-
export declare function triggerPopUpState({ open, emitEvent }: {
|
|
36
|
-
open: boolean;
|
|
37
|
-
emitEvent: boolean;
|
|
38
|
-
}, { updateState, popUpManager, dispatch, host, }: {
|
|
39
|
-
updateState: (params: {
|
|
40
|
-
showPopUpResult: ShowPopUpResult | undefined;
|
|
41
|
-
}) => void;
|
|
42
|
-
popUpManager: PopUpManager;
|
|
43
|
-
dispatch: (open: boolean) => void;
|
|
44
|
-
host: HTMLElement;
|
|
45
|
-
}): void;
|