vira 22.1.3 → 22.2.1
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/README.md +1 -1
- package/dist/elements/define-vira-element.d.ts +1 -1
- package/dist/elements/dropdown/dropdown-helpers.d.ts +22 -0
- package/dist/elements/dropdown/dropdown-helpers.js +56 -0
- package/dist/elements/dropdown/dropdown.mock.d.ts +13 -0
- package/dist/elements/dropdown/dropdown.mock.js +18 -0
- package/dist/elements/dropdown/vira-dropdown-item.element.d.ts +16 -0
- package/dist/elements/dropdown/vira-dropdown-item.element.js +63 -0
- package/dist/elements/dropdown/vira-dropdown-options.element.d.ts +16 -0
- package/dist/elements/dropdown/vira-dropdown-options.element.js +100 -0
- package/dist/elements/dropdown/vira-dropdown.element.d.ts +37 -0
- package/dist/elements/dropdown/vira-dropdown.element.js +310 -0
- package/dist/elements/index.d.ts +3 -0
- package/dist/elements/index.js +3 -0
- package/dist/elements/vira-button.element.js +3 -3
- package/dist/elements/vira-collapsible-wrapper.element.d.ts +1 -4
- package/dist/elements/vira-collapsible-wrapper.element.js +3 -6
- package/dist/elements/vira-input.element.d.ts +1 -1
- package/dist/elements/vira-input.element.js +7 -7
- package/dist/icons/icon-svgs/check-24.icon.d.ts +1 -0
- package/dist/icons/icon-svgs/check-24.icon.js +16 -0
- package/dist/icons/icon-svgs/chevron-up-24.icon.d.ts +1 -0
- package/dist/icons/icon-svgs/chevron-up-24.icon.js +23 -0
- package/dist/icons/index.d.ts +4 -0
- package/dist/icons/index.js +6 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/styles/border.d.ts +3 -0
- package/dist/styles/border.js +4 -0
- package/dist/styles/color.d.ts +1 -1
- package/dist/styles/disabled.js +1 -0
- package/dist/styles/focus.d.ts +2 -2
- package/dist/styles/focus.js +5 -5
- package/dist/styles/form-themes.d.ts +8 -0
- package/dist/styles/form-themes.js +10 -0
- package/dist/styles/index.d.ts +3 -1
- package/dist/styles/index.js +3 -1
- package/dist/styles/scrollbar.js +4 -2
- package/dist/styles/shadows.d.ts +5 -0
- package/dist/styles/shadows.js +18 -0
- package/dist/styles/user-select.js +6 -3
- package/dist/util/index.d.ts +1 -0
- package/dist/util/index.js +1 -0
- package/dist/util/pop-up-manager.d.ts +90 -0
- package/dist/util/pop-up-manager.js +169 -0
- package/package.json +12 -8
- package/dist/styles/vira-css-vars.d.ts +0 -3
- package/dist/styles/vira-css-vars.js +0 -4
package/README.md
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
export declare const ViraTagNamePrefix = "vira-";
|
|
2
2
|
export type ViraTagName = `${typeof ViraTagNamePrefix}${string}`;
|
|
3
|
-
export declare const defineViraElement: <Inputs extends {}>() => <TagName extends `vira-${string}`, StateInit extends {}, EventsInit extends {}, HostClassKeys extends `${TagName}-${string}`, CssVarKeys extends `${TagName}-${string}`, SlotNames extends readonly string[]>(inputs: import("element-vir").VerifiedElementInit<TagName, Inputs, StateInit, EventsInit, HostClassKeys, CssVarKeys, SlotNames>) => import("element-vir").DeclarativeElementDefinition<TagName, Inputs, StateInit, EventsInit, HostClassKeys, CssVarKeys, SlotNames>, defineViraElementNoInputs: <TagName_1 extends `vira-${string}`, Inputs_1 extends {}, StateInit_1 extends {}, EventsInit_1 extends {}, HostClassKeys_1 extends `${TagName_1}-${string}`, CssVarKeys_1 extends `${TagName_1}-${string}`, SlotNames_1 extends readonly string[]>(inputs: import("element-vir").VerifiedElementNoInputsInit<TagName_1, Inputs_1, StateInit_1, EventsInit_1, HostClassKeys_1, CssVarKeys_1, SlotNames_1>) => import("element-vir").DeclarativeElementDefinition<TagName_1, Inputs_1, StateInit_1, EventsInit_1, HostClassKeys_1, CssVarKeys_1, SlotNames_1>;
|
|
3
|
+
export declare const defineViraElement: <Inputs extends {}>() => <const TagName extends `vira-${string}`, StateInit extends {}, EventsInit extends {}, const HostClassKeys extends `${TagName}-${string}`, const CssVarKeys extends `${TagName}-${string}`, const SlotNames extends readonly string[]>(inputs: import("element-vir").VerifiedElementInit<TagName, Inputs, StateInit, EventsInit, HostClassKeys, CssVarKeys, SlotNames>) => import("element-vir").DeclarativeElementDefinition<TagName, Inputs, StateInit, EventsInit, HostClassKeys, CssVarKeys, SlotNames>, defineViraElementNoInputs: <const TagName_1 extends `vira-${string}`, Inputs_1 extends {}, StateInit_1 extends {}, EventsInit_1 extends {}, const HostClassKeys_1 extends `${TagName_1}-${string}`, const CssVarKeys_1 extends `${TagName_1}-${string}`, const SlotNames_1 extends readonly string[]>(inputs: import("element-vir").VerifiedElementNoInputsInit<TagName_1, Inputs_1, StateInit_1, EventsInit_1, HostClassKeys_1, CssVarKeys_1, SlotNames_1>) => import("element-vir").DeclarativeElementDefinition<TagName_1, Inputs_1, StateInit_1, EventsInit_1, HostClassKeys_1, CssVarKeys_1, SlotNames_1>;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { PopUpManager, ShowPopUpResult } from '../../util/pop-up-manager';
|
|
2
|
+
import { ViraDropdownOption } from './vira-dropdown-item.element';
|
|
3
|
+
export declare function filterToSelectedOptions({ selected, options, isMultiSelect, }: Readonly<{
|
|
4
|
+
selected: ReadonlyArray<PropertyKey>;
|
|
5
|
+
isMultiSelect?: boolean | undefined;
|
|
6
|
+
options: ReadonlyArray<Readonly<ViraDropdownOption>>;
|
|
7
|
+
}>): ViraDropdownOption[];
|
|
8
|
+
export declare function assertUniqueIdProps(options: ReadonlyArray<Readonly<{
|
|
9
|
+
id: PropertyKey;
|
|
10
|
+
}>>): void;
|
|
11
|
+
export declare function createNewSelection(id: PropertyKey, currentSelection: ReadonlyArray<PropertyKey>, isMultiSelect: boolean): PropertyKey[];
|
|
12
|
+
export declare function triggerPopUpState({ open, emitEvent }: {
|
|
13
|
+
open: boolean;
|
|
14
|
+
emitEvent: boolean;
|
|
15
|
+
}, { updateState, popUpManager, dispatch, host, }: {
|
|
16
|
+
updateState: (params: {
|
|
17
|
+
showPopUpResult: ShowPopUpResult | undefined;
|
|
18
|
+
}) => void;
|
|
19
|
+
popUpManager: PopUpManager;
|
|
20
|
+
dispatch: (open: boolean) => void;
|
|
21
|
+
host: HTMLElement;
|
|
22
|
+
}): void;
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { joinWithFinalConjunction } from '@augment-vir/common';
|
|
2
|
+
import { ViraDropdown } from './vira-dropdown.element';
|
|
3
|
+
export function filterToSelectedOptions({ selected, options, isMultiSelect, }) {
|
|
4
|
+
if (selected.length && options.length) {
|
|
5
|
+
const selectedOptions = options.filter((option) => selected.includes(option.id));
|
|
6
|
+
if (selectedOptions.length > 1 && !isMultiSelect) {
|
|
7
|
+
console.error(`${ViraDropdown.tagName} has multiple selections but \`isMultiSelect\` is not \`true\`. Truncating to the first selection.`);
|
|
8
|
+
return selectedOptions.slice(0, 1);
|
|
9
|
+
}
|
|
10
|
+
else {
|
|
11
|
+
return selectedOptions;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
else {
|
|
15
|
+
return [];
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
export function assertUniqueIdProps(options) {
|
|
19
|
+
const usedIds = new Set();
|
|
20
|
+
const duplicateIds = [];
|
|
21
|
+
options.forEach((option) => {
|
|
22
|
+
if (usedIds.has(option.id)) {
|
|
23
|
+
duplicateIds.push(option.id);
|
|
24
|
+
}
|
|
25
|
+
else {
|
|
26
|
+
usedIds.add(option.id);
|
|
27
|
+
}
|
|
28
|
+
});
|
|
29
|
+
if (duplicateIds.length) {
|
|
30
|
+
throw new Error(`Duplicate option ids were given to ViraDropdown: ${joinWithFinalConjunction(duplicateIds)}`);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
export function createNewSelection(id, currentSelection, isMultiSelect) {
|
|
34
|
+
if (isMultiSelect) {
|
|
35
|
+
return currentSelection.includes(id)
|
|
36
|
+
? currentSelection.filter((entry) => entry !== id)
|
|
37
|
+
: [
|
|
38
|
+
...currentSelection,
|
|
39
|
+
id,
|
|
40
|
+
];
|
|
41
|
+
}
|
|
42
|
+
else {
|
|
43
|
+
return [id];
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
export function triggerPopUpState({ open, emitEvent }, { updateState, popUpManager, dispatch, host, }) {
|
|
47
|
+
if (open) {
|
|
48
|
+
updateState({ showPopUpResult: popUpManager.showPopUp(host) });
|
|
49
|
+
}
|
|
50
|
+
else {
|
|
51
|
+
popUpManager.removePopUp();
|
|
52
|
+
}
|
|
53
|
+
if (emitEvent) {
|
|
54
|
+
dispatch(open);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export declare const mockOptions: readonly [{
|
|
2
|
+
readonly label: "Option 0";
|
|
3
|
+
readonly id: 0;
|
|
4
|
+
}, {
|
|
5
|
+
readonly label: "Option 1";
|
|
6
|
+
readonly id: 1;
|
|
7
|
+
}, {
|
|
8
|
+
readonly label: "Option 2";
|
|
9
|
+
readonly id: 2;
|
|
10
|
+
}, {
|
|
11
|
+
readonly label: "Option 3";
|
|
12
|
+
readonly id: 3;
|
|
13
|
+
}];
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { PartialAndUndefined } from '@augment-vir/common';
|
|
2
|
+
import { HTMLTemplateResult } from 'element-vir';
|
|
3
|
+
export type ViraDropdownOption = {
|
|
4
|
+
/** Each `id` must be unique across all options. */
|
|
5
|
+
id: PropertyKey;
|
|
6
|
+
label: string;
|
|
7
|
+
} & PartialAndUndefined<{
|
|
8
|
+
disabled?: boolean | undefined;
|
|
9
|
+
hoverText?: string | undefined;
|
|
10
|
+
/** An optional custom template for this option. */
|
|
11
|
+
template?: HTMLTemplateResult | undefined;
|
|
12
|
+
}>;
|
|
13
|
+
export declare const ViraDropdownItem: import("element-vir").DeclarativeElementDefinition<"vira-dropdown-item", {
|
|
14
|
+
label: string;
|
|
15
|
+
selected: boolean;
|
|
16
|
+
}, {}, {}, "vira-dropdown-item-selected", `vira-dropdown-item-${string}`, readonly string[]>;
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { css, html } from 'element-vir';
|
|
2
|
+
import { Check24Icon } from '../../icons/icon-svgs/check-24.icon';
|
|
3
|
+
import { noUserSelect, viraAnimationDurations } from '../../styles';
|
|
4
|
+
import { viraBorders } from '../../styles/border';
|
|
5
|
+
import { defineViraElement } from '../define-vira-element';
|
|
6
|
+
import { ViraIcon } from '../vira-icon.element';
|
|
7
|
+
export const ViraDropdownItem = defineViraElement()({
|
|
8
|
+
tagName: 'vira-dropdown-item',
|
|
9
|
+
hostClasses: {
|
|
10
|
+
'vira-dropdown-item-selected': ({ inputs }) => inputs.selected,
|
|
11
|
+
},
|
|
12
|
+
styles: ({ hostClasses }) => css `
|
|
13
|
+
:host {
|
|
14
|
+
display: flex;
|
|
15
|
+
${noUserSelect};
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
.option {
|
|
19
|
+
pointer-events: none;
|
|
20
|
+
min-height: 24px;
|
|
21
|
+
display: flex;
|
|
22
|
+
align-items: center;
|
|
23
|
+
padding: 8px;
|
|
24
|
+
padding-left: 0;
|
|
25
|
+
text-align: left;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
${hostClasses['vira-dropdown-item-selected'].selector} ${ViraIcon} {
|
|
29
|
+
opacity: 1;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/*
|
|
33
|
+
The check icon looks centered when it has a border.
|
|
34
|
+
However, it does not have a border here.
|
|
35
|
+
*/
|
|
36
|
+
${ViraIcon} {
|
|
37
|
+
transition: opacity
|
|
38
|
+
${viraAnimationDurations['vira-interaction-animation-duration'].value};
|
|
39
|
+
opacity: 0;
|
|
40
|
+
margin-top: -4px;
|
|
41
|
+
margin-right: -2px;
|
|
42
|
+
margin-left: 2px;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
.dropdown-wrapper:not(.reverse-direction) .option:last-of-type {
|
|
46
|
+
border-radius: 0 0 ${viraBorders['vira-form-input-radius'].value}
|
|
47
|
+
${viraBorders['vira-form-input-radius'].value};
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
.dropdown-wrapper.reverse-direction .option:first-of-type {
|
|
51
|
+
border-radius: ${viraBorders['vira-form-input-radius'].value}
|
|
52
|
+
${viraBorders['vira-form-input-radius'].value} 0 0;
|
|
53
|
+
}
|
|
54
|
+
`,
|
|
55
|
+
renderCallback({ inputs }) {
|
|
56
|
+
return html `
|
|
57
|
+
<div class="option">
|
|
58
|
+
<${ViraIcon.assign({ icon: Check24Icon })}></${ViraIcon}>
|
|
59
|
+
<slot>${inputs.label}</slot>
|
|
60
|
+
</div>
|
|
61
|
+
`;
|
|
62
|
+
},
|
|
63
|
+
});
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { ViraDropdownOption } from './vira-dropdown-item.element';
|
|
2
|
+
export declare const viraDropdownOptionsTestIds: {
|
|
3
|
+
option: string;
|
|
4
|
+
};
|
|
5
|
+
export declare const ViraDropdownOptions: import("element-vir").DeclarativeElementDefinition<"vira-dropdown-options", Readonly<{
|
|
6
|
+
/** All dropdown options to show to the user. */
|
|
7
|
+
options: ReadonlyArray<Readonly<ViraDropdownOption>>;
|
|
8
|
+
/**
|
|
9
|
+
* The currently selected dropdown options. Note that this must be a reference subset of the
|
|
10
|
+
* options input. Meaning, entries in this array must be the exact same objects (by
|
|
11
|
+
* reference) as entries in the `options` input array for them to be marked as selected.
|
|
12
|
+
*/
|
|
13
|
+
selectedOptions: ReadonlyArray<Readonly<ViraDropdownOption>>;
|
|
14
|
+
}>, {}, {
|
|
15
|
+
selectionChange: import("element-vir").DefinedTypedEventNameDefinition<Readonly<ViraDropdownOption>>;
|
|
16
|
+
}, `vira-dropdown-options-${string}`, `vira-dropdown-options-${string}`, readonly string[]>;
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { nav, navSelector } from 'device-navigation';
|
|
2
|
+
import { classMap, css, defineElementEvent, html, ifDefined, listen, nothing, testId, } from 'element-vir';
|
|
3
|
+
import { viraDisabledStyles } from '../../styles';
|
|
4
|
+
import { viraBorders } from '../../styles/border';
|
|
5
|
+
import { viraFormCssVars } from '../../styles/form-themes';
|
|
6
|
+
import { viraShadows } from '../../styles/shadows';
|
|
7
|
+
import { defineViraElement } from '../define-vira-element';
|
|
8
|
+
import { ViraDropdownItem } from './vira-dropdown-item.element';
|
|
9
|
+
export const viraDropdownOptionsTestIds = {
|
|
10
|
+
option: 'dropdown-option',
|
|
11
|
+
};
|
|
12
|
+
export const ViraDropdownOptions = defineViraElement()({
|
|
13
|
+
tagName: 'vira-dropdown-options',
|
|
14
|
+
events: {
|
|
15
|
+
selectionChange: defineElementEvent(),
|
|
16
|
+
},
|
|
17
|
+
styles: css `
|
|
18
|
+
:host {
|
|
19
|
+
display: flex;
|
|
20
|
+
flex-direction: column;
|
|
21
|
+
|
|
22
|
+
pointer-events: auto;
|
|
23
|
+
width: 100%;
|
|
24
|
+
max-height: 100%;
|
|
25
|
+
overflow-y: auto;
|
|
26
|
+
z-index: 99;
|
|
27
|
+
border-radius: ${viraBorders['vira-form-input-radius'].value};
|
|
28
|
+
border-top-left-radius: 0;
|
|
29
|
+
border-top-right-radius: 0;
|
|
30
|
+
background-color: ${viraFormCssVars['vira-form-background-color'].value};
|
|
31
|
+
border: 1px solid ${viraFormCssVars['vira-form-border-color'].value};
|
|
32
|
+
color: ${viraFormCssVars['vira-form-foreground-color'].value};
|
|
33
|
+
${viraShadows.menuShadow}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
.dropdown-item {
|
|
37
|
+
background-color: white;
|
|
38
|
+
outline: none;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
${navSelector.css.selected('.dropdown-item:not(.disabled)')} {
|
|
42
|
+
background-color: ${viraFormCssVars['vira-form-selection-hover-background-color']
|
|
43
|
+
.value};
|
|
44
|
+
outline: none;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
${ViraDropdownItem} {
|
|
48
|
+
pointer-events: none;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
.dropdown-item.disabled {
|
|
52
|
+
${viraDisabledStyles};
|
|
53
|
+
pointer-events: auto;
|
|
54
|
+
}
|
|
55
|
+
`,
|
|
56
|
+
renderCallback({ inputs, dispatch, events }) {
|
|
57
|
+
const optionTemplates = inputs.options.map((option) => {
|
|
58
|
+
const innerTemplate = option.template ||
|
|
59
|
+
html `
|
|
60
|
+
<${ViraDropdownItem.assign({
|
|
61
|
+
label: option.label,
|
|
62
|
+
selected: inputs.selectedOptions.includes(option),
|
|
63
|
+
})}></${ViraDropdownItem}>
|
|
64
|
+
`;
|
|
65
|
+
return html `
|
|
66
|
+
<div
|
|
67
|
+
class="dropdown-item ${classMap({
|
|
68
|
+
disabled: !!option.disabled,
|
|
69
|
+
})}"
|
|
70
|
+
${testId(viraDropdownOptionsTestIds.option)}
|
|
71
|
+
title=${ifDefined(option.hoverText || undefined)}
|
|
72
|
+
role="option"
|
|
73
|
+
${option.disabled ? nothing : nav()}
|
|
74
|
+
${listen('mousedown', (event) => {
|
|
75
|
+
/**
|
|
76
|
+
* Prevent this mousedown event from propagating to the window, which would
|
|
77
|
+
* then trigger the dropdown to close.
|
|
78
|
+
*/
|
|
79
|
+
event.stopPropagation();
|
|
80
|
+
})}
|
|
81
|
+
${listen('mouseup', (event) => {
|
|
82
|
+
/**
|
|
83
|
+
* Prevent this event from propagating to the window, which would then
|
|
84
|
+
* trigger the dropdown to close.
|
|
85
|
+
*/
|
|
86
|
+
event.stopPropagation();
|
|
87
|
+
if (!option.disabled) {
|
|
88
|
+
dispatch(new events.selectionChange(option));
|
|
89
|
+
}
|
|
90
|
+
})}
|
|
91
|
+
>
|
|
92
|
+
${innerTemplate}
|
|
93
|
+
</div>
|
|
94
|
+
`;
|
|
95
|
+
});
|
|
96
|
+
return html `
|
|
97
|
+
<slot>${optionTemplates}</slot>
|
|
98
|
+
`;
|
|
99
|
+
},
|
|
100
|
+
});
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { PartialAndUndefined } from '@augment-vir/common';
|
|
2
|
+
import { NavController } from 'device-navigation';
|
|
3
|
+
import { ViraIconSvg } from '../../icons/icon-svg';
|
|
4
|
+
import { PopUpManager, ShowPopUpResult } from '../../util/pop-up-manager';
|
|
5
|
+
import { ViraDropdownOption } from './vira-dropdown-item.element';
|
|
6
|
+
export declare const viraDropdownTestIds: {
|
|
7
|
+
trigger: string;
|
|
8
|
+
icon: string;
|
|
9
|
+
prefix: string;
|
|
10
|
+
options: string;
|
|
11
|
+
};
|
|
12
|
+
export declare const ViraDropdown: import("element-vir").DeclarativeElementDefinition<"vira-dropdown", {
|
|
13
|
+
options: ReadonlyArray<Readonly<ViraDropdownOption>>;
|
|
14
|
+
/** The selected id from the given options. */
|
|
15
|
+
selected: ReadonlyArray<PropertyKey>;
|
|
16
|
+
} & PartialAndUndefined<{
|
|
17
|
+
/** Text to show if nothing is selected. */
|
|
18
|
+
placeholder: string;
|
|
19
|
+
/**
|
|
20
|
+
* If false, this will behave like a single select dropdown, otherwise you can select
|
|
21
|
+
* multiple.
|
|
22
|
+
*/
|
|
23
|
+
isMultiSelect: boolean;
|
|
24
|
+
icon: ViraIconSvg;
|
|
25
|
+
selectionPrefix: string;
|
|
26
|
+
isDisabled: boolean;
|
|
27
|
+
/** For debugging purposes only. Very bad for actual production code use. */
|
|
28
|
+
z_debug_forceOpenState: boolean;
|
|
29
|
+
}>, {
|
|
30
|
+
/** `undefined` means the pop up is not currently showing. */
|
|
31
|
+
showPopUpResult: ShowPopUpResult | undefined;
|
|
32
|
+
popUpManager: PopUpManager;
|
|
33
|
+
navController: NavController | undefined;
|
|
34
|
+
}, {
|
|
35
|
+
selectedChange: import("element-vir").DefinedTypedEventNameDefinition<PropertyKey[]>;
|
|
36
|
+
openChange: import("element-vir").DefinedTypedEventNameDefinition<boolean>;
|
|
37
|
+
}, "vira-dropdown-disabled", `vira-dropdown-${string}`, readonly string[]>;
|
|
@@ -0,0 +1,310 @@
|
|
|
1
|
+
import { NavController } from 'device-navigation';
|
|
2
|
+
import { classMap, css, defineElementEvent, html, ifDefined, listen, perInstance, renderIf, testId, } from 'element-vir';
|
|
3
|
+
import { assertInstanceOf } from 'run-time-assertions';
|
|
4
|
+
import { ChevronUp24Icon } from '../../icons/index';
|
|
5
|
+
import { noNativeFormStyles, noUserSelect, viraAnimationDurations, viraDisabledStyles, } from '../../styles';
|
|
6
|
+
import { viraBorders } from '../../styles/border';
|
|
7
|
+
import { createFocusStyles, viraFocusCssVars } from '../../styles/focus';
|
|
8
|
+
import { viraFormCssVars } from '../../styles/form-themes';
|
|
9
|
+
import { viraShadows } from '../../styles/shadows';
|
|
10
|
+
import { HidePopUpEvent, NavSelectEvent, PopUpManager, } from '../../util/pop-up-manager';
|
|
11
|
+
import { defineViraElement } from '../define-vira-element';
|
|
12
|
+
import { ViraIcon } from '../vira-icon.element';
|
|
13
|
+
import { assertUniqueIdProps, createNewSelection, filterToSelectedOptions, triggerPopUpState, } from './dropdown-helpers';
|
|
14
|
+
import { ViraDropdownOptions } from './vira-dropdown-options.element';
|
|
15
|
+
export const viraDropdownTestIds = {
|
|
16
|
+
trigger: 'dropdown-trigger',
|
|
17
|
+
icon: 'dropdown-icon',
|
|
18
|
+
prefix: 'dropdown-prefix',
|
|
19
|
+
options: 'dropdown-options',
|
|
20
|
+
};
|
|
21
|
+
export const ViraDropdown = defineViraElement()({
|
|
22
|
+
tagName: 'vira-dropdown',
|
|
23
|
+
hostClasses: {
|
|
24
|
+
'vira-dropdown-disabled': ({ inputs }) => !!inputs.isDisabled,
|
|
25
|
+
},
|
|
26
|
+
styles: ({ hostClasses }) => css `
|
|
27
|
+
:host {
|
|
28
|
+
display: inline-flex;
|
|
29
|
+
vertical-align: middle;
|
|
30
|
+
width: 256px;
|
|
31
|
+
${viraFocusCssVars['vira-focus-outline-color'].name}: ${viraFormCssVars['vira-form-focus-color'].value};
|
|
32
|
+
position: relative;
|
|
33
|
+
max-width: 100%;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
.dropdown-wrapper {
|
|
37
|
+
${noNativeFormStyles};
|
|
38
|
+
max-width: 100%;
|
|
39
|
+
align-self: stretch;
|
|
40
|
+
flex-grow: 1;
|
|
41
|
+
position: relative;
|
|
42
|
+
border-radius: ${viraBorders['vira-form-input-radius'].value};
|
|
43
|
+
transition: border-radius
|
|
44
|
+
${viraAnimationDurations['vira-interaction-animation-duration'].value};
|
|
45
|
+
outline: none;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
${createFocusStyles({
|
|
49
|
+
selector: '.dropdown-wrapper:focus',
|
|
50
|
+
elementBorderSize: 1,
|
|
51
|
+
})}
|
|
52
|
+
|
|
53
|
+
.selection-display {
|
|
54
|
+
overflow: hidden;
|
|
55
|
+
text-overflow: ellipsis;
|
|
56
|
+
white-space: nowrap;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
.trigger-icon {
|
|
60
|
+
transform: rotate(0);
|
|
61
|
+
transition: ${viraAnimationDurations['vira-interaction-animation-duration'].value}
|
|
62
|
+
linear transform;
|
|
63
|
+
align-self: flex-start;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
.trigger-icon-wrapper {
|
|
67
|
+
flex-grow: 1;
|
|
68
|
+
display: flex;
|
|
69
|
+
justify-content: flex-end;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
.dropdown-wrapper.open .trigger-icon {
|
|
73
|
+
transform: rotate(180deg);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
.dropdown-wrapper.open:not(.open-upwards) {
|
|
77
|
+
border-bottom-left-radius: 0;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
.open-upwards.dropdown-wrapper.open {
|
|
81
|
+
border-top-left-radius: 0;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
.dropdown-trigger {
|
|
85
|
+
border: 1px solid ${viraFormCssVars['vira-form-border-color'].value};
|
|
86
|
+
height: 100%;
|
|
87
|
+
width: 100%;
|
|
88
|
+
transition: inherit;
|
|
89
|
+
box-sizing: border-box;
|
|
90
|
+
display: flex;
|
|
91
|
+
gap: 8px;
|
|
92
|
+
text-align: left;
|
|
93
|
+
align-items: center;
|
|
94
|
+
padding: 3px;
|
|
95
|
+
padding-left: 10px;
|
|
96
|
+
${noUserSelect};
|
|
97
|
+
border-radius: inherit;
|
|
98
|
+
background-color: ${viraFormCssVars['vira-form-background-color'].value};
|
|
99
|
+
color: ${viraFormCssVars['vira-form-foreground-color'].value};
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
.open-upwards ${ViraDropdownOptions} {
|
|
103
|
+
border-bottom-left-radius: 0;
|
|
104
|
+
border-bottom-right-radius: 0;
|
|
105
|
+
${viraShadows.menuShadowReversed}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
${hostClasses['vira-dropdown-disabled'].selector} {
|
|
109
|
+
${viraDisabledStyles}
|
|
110
|
+
pointer-events: auto;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
${hostClasses['vira-dropdown-disabled'].selector} .dropdown-wrapper {
|
|
114
|
+
pointer-events: none;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
.pop-up-positioner {
|
|
118
|
+
position: absolute;
|
|
119
|
+
pointer-events: none;
|
|
120
|
+
display: flex;
|
|
121
|
+
flex-direction: column;
|
|
122
|
+
|
|
123
|
+
/* highest possible z-index */
|
|
124
|
+
z-index: 2147483647;
|
|
125
|
+
/* space for the caret icon */
|
|
126
|
+
right: 28px;
|
|
127
|
+
/* minus the border width */
|
|
128
|
+
top: calc(100% - 1px);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
.using-placeholder {
|
|
132
|
+
opacity: 0.4;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
.open-upwards .pop-up-positioner {
|
|
136
|
+
flex-direction: column-reverse;
|
|
137
|
+
/* minus the border width */
|
|
138
|
+
bottom: calc(100% - 1px);
|
|
139
|
+
}
|
|
140
|
+
`,
|
|
141
|
+
events: {
|
|
142
|
+
selectedChange: defineElementEvent(),
|
|
143
|
+
openChange: defineElementEvent(),
|
|
144
|
+
},
|
|
145
|
+
stateInitStatic: {
|
|
146
|
+
/** `undefined` means the pop up is not currently showing. */
|
|
147
|
+
showPopUpResult: undefined,
|
|
148
|
+
popUpManager: perInstance(() => new PopUpManager()),
|
|
149
|
+
navController: undefined,
|
|
150
|
+
},
|
|
151
|
+
cleanupCallback({ state, updateState }) {
|
|
152
|
+
updateState({ showPopUpResult: undefined });
|
|
153
|
+
state.popUpManager.destroy();
|
|
154
|
+
},
|
|
155
|
+
initCallback({ state, updateState, host, inputs, dispatch, events }) {
|
|
156
|
+
state.popUpManager.listen(HidePopUpEvent, () => {
|
|
157
|
+
updateState({ showPopUpResult: undefined });
|
|
158
|
+
if (!inputs.isDisabled) {
|
|
159
|
+
const dropdownWrapper = host.shadowRoot.querySelector('.dropdown-wrapper');
|
|
160
|
+
assertInstanceOf(dropdownWrapper, HTMLButtonElement, 'failed to find dropdown wrapper child');
|
|
161
|
+
dropdownWrapper.focus();
|
|
162
|
+
}
|
|
163
|
+
});
|
|
164
|
+
state.popUpManager.listen(NavSelectEvent, (event) => {
|
|
165
|
+
const optionIndex = event.detail.x;
|
|
166
|
+
const option = inputs.options[optionIndex];
|
|
167
|
+
if (!option) {
|
|
168
|
+
throw new Error(`Found no dropdown option at index '${optionIndex}'`);
|
|
169
|
+
}
|
|
170
|
+
/** Only close upon option selection if the dropdown is not multi select. */
|
|
171
|
+
if (!inputs.isMultiSelect) {
|
|
172
|
+
triggerPopUpState({ emitEvent: true, open: false }, {
|
|
173
|
+
dispatch: (openState) => {
|
|
174
|
+
dispatch(new events.openChange(openState));
|
|
175
|
+
},
|
|
176
|
+
host,
|
|
177
|
+
popUpManager: state.popUpManager,
|
|
178
|
+
updateState,
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
dispatch(new events.selectedChange(createNewSelection(option.id, inputs.selected, !!inputs.isMultiSelect)));
|
|
182
|
+
});
|
|
183
|
+
updateState({ navController: new NavController(host) });
|
|
184
|
+
},
|
|
185
|
+
renderCallback({ dispatch, events, state, inputs, updateState, host }) {
|
|
186
|
+
assertUniqueIdProps(inputs.options);
|
|
187
|
+
function triggerPopUp(param) {
|
|
188
|
+
triggerPopUpState(param, {
|
|
189
|
+
dispatch: (openState) => {
|
|
190
|
+
dispatch(new events.openChange(openState));
|
|
191
|
+
},
|
|
192
|
+
host,
|
|
193
|
+
popUpManager: state.popUpManager,
|
|
194
|
+
updateState,
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
if (inputs.isDisabled) {
|
|
198
|
+
triggerPopUp({ open: false, emitEvent: false });
|
|
199
|
+
}
|
|
200
|
+
else if (inputs.z_debug_forceOpenState != undefined) {
|
|
201
|
+
if (!inputs.z_debug_forceOpenState && state.showPopUpResult) {
|
|
202
|
+
triggerPopUp({ emitEvent: false, open: false });
|
|
203
|
+
}
|
|
204
|
+
else if (inputs.z_debug_forceOpenState && !state.showPopUpResult) {
|
|
205
|
+
triggerPopUp({ emitEvent: false, open: true });
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
const selectedOptions = filterToSelectedOptions(inputs);
|
|
209
|
+
const leadingIconTemplate = inputs.icon
|
|
210
|
+
? html `
|
|
211
|
+
<${ViraIcon.assign({
|
|
212
|
+
icon: inputs.icon,
|
|
213
|
+
})}
|
|
214
|
+
${testId(viraDropdownTestIds.icon)}
|
|
215
|
+
></${ViraIcon}>
|
|
216
|
+
`
|
|
217
|
+
: '';
|
|
218
|
+
const positionerStyles = state.showPopUpResult
|
|
219
|
+
? state.showPopUpResult.popDown
|
|
220
|
+
? /** Dropdown going down position. */
|
|
221
|
+
css `
|
|
222
|
+
bottom: -${state.showPopUpResult.positions.diff.bottom}px;
|
|
223
|
+
`
|
|
224
|
+
: /** Dropdown going up position. */
|
|
225
|
+
css `
|
|
226
|
+
top: -${state.showPopUpResult.positions.diff.top}px;
|
|
227
|
+
`
|
|
228
|
+
: undefined;
|
|
229
|
+
function respondToClick() {
|
|
230
|
+
triggerPopUp({ emitEvent: true, open: !state.showPopUpResult });
|
|
231
|
+
}
|
|
232
|
+
const shouldUsePlaceholder = !selectedOptions.length;
|
|
233
|
+
const prefixTemplate = inputs.selectionPrefix && !shouldUsePlaceholder
|
|
234
|
+
? html `
|
|
235
|
+
<span class="selected-label-prefix" ${testId(viraDropdownTestIds.prefix)}>
|
|
236
|
+
${inputs.selectionPrefix}
|
|
237
|
+
</span>
|
|
238
|
+
`
|
|
239
|
+
: '';
|
|
240
|
+
const selectionDisplay = shouldUsePlaceholder
|
|
241
|
+
? inputs.placeholder || ''
|
|
242
|
+
: selectedOptions.map((item) => item.label).join(', ');
|
|
243
|
+
return html `
|
|
244
|
+
<button
|
|
245
|
+
?disabled=${!!inputs.isDisabled}
|
|
246
|
+
class="dropdown-wrapper ${classMap({
|
|
247
|
+
open: !!state.showPopUpResult,
|
|
248
|
+
'open-upwards': !state.showPopUpResult?.popDown,
|
|
249
|
+
})}"
|
|
250
|
+
${testId(viraDropdownTestIds.trigger)}
|
|
251
|
+
role="listbox"
|
|
252
|
+
aria-expanded=${!!state.showPopUpResult}
|
|
253
|
+
${listen('keydown', (event) => {
|
|
254
|
+
if (!state.showPopUpResult && event.code.startsWith('Arrow')) {
|
|
255
|
+
triggerPopUp({ emitEvent: true, open: true });
|
|
256
|
+
}
|
|
257
|
+
})}
|
|
258
|
+
${listen('click', (event) => {
|
|
259
|
+
/** Detail is 0 if it was a keyboard key (like Enter) that triggered this click. */
|
|
260
|
+
if (event.detail === 0) {
|
|
261
|
+
respondToClick();
|
|
262
|
+
}
|
|
263
|
+
})}
|
|
264
|
+
${listen('mousedown', (event) => {
|
|
265
|
+
/** Ignore any clicks that aren't the main button. */
|
|
266
|
+
if (event.button === 0) {
|
|
267
|
+
respondToClick();
|
|
268
|
+
}
|
|
269
|
+
})}
|
|
270
|
+
>
|
|
271
|
+
<div class="dropdown-trigger">
|
|
272
|
+
${leadingIconTemplate}
|
|
273
|
+
<span
|
|
274
|
+
class="selection-display ${classMap({
|
|
275
|
+
'using-placeholder': shouldUsePlaceholder,
|
|
276
|
+
})}"
|
|
277
|
+
title=${ifDefined(shouldUsePlaceholder ? selectionDisplay : undefined)}
|
|
278
|
+
>
|
|
279
|
+
${prefixTemplate} ${selectionDisplay}
|
|
280
|
+
</span>
|
|
281
|
+
<span class="trigger-icon-wrapper">
|
|
282
|
+
<${ViraIcon.assign({ icon: ChevronUp24Icon })}
|
|
283
|
+
class="trigger-icon"
|
|
284
|
+
></${ViraIcon}>
|
|
285
|
+
</span>
|
|
286
|
+
</div>
|
|
287
|
+
<div class="pop-up-positioner" style=${positionerStyles}>
|
|
288
|
+
${renderIf(!!state.showPopUpResult, html `
|
|
289
|
+
<${ViraDropdownOptions.assign({
|
|
290
|
+
options: inputs.options,
|
|
291
|
+
selectedOptions,
|
|
292
|
+
})}
|
|
293
|
+
${listen(ViraDropdownOptions.events.selectionChange, (event) => {
|
|
294
|
+
/**
|
|
295
|
+
* Only close upon option selection if the dropdown is not multi
|
|
296
|
+
* select.
|
|
297
|
+
*/
|
|
298
|
+
if (!inputs.isMultiSelect) {
|
|
299
|
+
triggerPopUp({ emitEvent: true, open: false });
|
|
300
|
+
}
|
|
301
|
+
dispatch(new events.selectedChange(createNewSelection(event.detail.id, inputs.selected, !!inputs.isMultiSelect)));
|
|
302
|
+
})}
|
|
303
|
+
${testId(viraDropdownTestIds.options)}
|
|
304
|
+
></${ViraDropdownOptions}>
|
|
305
|
+
`)}
|
|
306
|
+
</div>
|
|
307
|
+
</button>
|
|
308
|
+
`;
|
|
309
|
+
},
|
|
310
|
+
});
|