vira 28.5.1 → 28.7.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 +2 -0
- package/dist/elements/index.js +2 -0
- package/dist/elements/shared-text-input-logic.d.ts +10 -8
- package/dist/elements/vira-checkbox.element.d.ts +1 -0
- package/dist/elements/vira-checkbox.element.js +14 -9
- package/dist/elements/vira-form.element.d.ts +69 -0
- package/dist/elements/vira-form.element.js +137 -0
- package/dist/elements/vira-input.element.d.ts +12 -3
- package/dist/elements/vira-input.element.js +39 -32
- package/dist/elements/vira-select.element.d.ts +48 -0
- package/dist/elements/vira-select.element.js +240 -0
- package/dist/styles/focus.js +1 -1
- package/dist/styles/form-styles.d.ts +5 -2
- package/dist/styles/form-styles.js +5 -2
- package/dist/styles/native-styles.js +1 -0
- package/package.json +1 -1
package/dist/elements/index.d.ts
CHANGED
|
@@ -13,9 +13,11 @@ export * from './vira-checkbox.element.js';
|
|
|
13
13
|
export * from './vira-collapsible-wrapper.element.js';
|
|
14
14
|
export * from './vira-dropdown.element.js';
|
|
15
15
|
export * from './vira-error.element.js';
|
|
16
|
+
export * from './vira-form.element.js';
|
|
16
17
|
export * from './vira-icon.element.js';
|
|
17
18
|
export * from './vira-image.element.js';
|
|
18
19
|
export * from './vira-input.element.js';
|
|
19
20
|
export * from './vira-link.element.js';
|
|
20
21
|
export * from './vira-modal.element.js';
|
|
21
22
|
export * from './vira-progress.element.js';
|
|
23
|
+
export * from './vira-select.element.js';
|
package/dist/elements/index.js
CHANGED
|
@@ -13,9 +13,11 @@ export * from './vira-checkbox.element.js';
|
|
|
13
13
|
export * from './vira-collapsible-wrapper.element.js';
|
|
14
14
|
export * from './vira-dropdown.element.js';
|
|
15
15
|
export * from './vira-error.element.js';
|
|
16
|
+
export * from './vira-form.element.js';
|
|
16
17
|
export * from './vira-icon.element.js';
|
|
17
18
|
export * from './vira-image.element.js';
|
|
18
19
|
export * from './vira-input.element.js';
|
|
19
20
|
export * from './vira-link.element.js';
|
|
20
21
|
export * from './vira-modal.element.js';
|
|
21
22
|
export * from './vira-progress.element.js';
|
|
23
|
+
export * from './vira-select.element.js';
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type PartialWithUndefined } from '@augment-vir/common';
|
|
1
2
|
import { type AttributeValues } from 'element-vir';
|
|
2
3
|
/**
|
|
3
4
|
* Inputs shared between the multiple input elements.
|
|
@@ -6,10 +7,11 @@ import { type AttributeValues } from 'element-vir';
|
|
|
6
7
|
*/
|
|
7
8
|
export type SharedTextInputElementInputs = {
|
|
8
9
|
value: string;
|
|
10
|
+
} & PartialWithUndefined<{
|
|
9
11
|
/** Shown when no other text is present. Input restrictions do not apply to this property. */
|
|
10
|
-
placeholder
|
|
12
|
+
placeholder: string;
|
|
11
13
|
/** Set to true to trigger disabled styles and to block all user input. */
|
|
12
|
-
disabled
|
|
14
|
+
disabled: boolean;
|
|
13
15
|
/**
|
|
14
16
|
* Only letters in the given string or matches to the given RegExp will be allowed.
|
|
15
17
|
* blockedInputs takes precedence over this input.
|
|
@@ -17,16 +19,16 @@ export type SharedTextInputElementInputs = {
|
|
|
17
19
|
* For example: if allowedInputs is set to "abcd" and blockedInputs is set to "d", only "a",
|
|
18
20
|
* "b", or "c" letters will be allowed.
|
|
19
21
|
*/
|
|
20
|
-
allowedInputs
|
|
22
|
+
allowedInputs: string | RegExp;
|
|
21
23
|
/** Any letters in the given string or matches to the given RegExp will be blocked. */
|
|
22
|
-
blockedInputs
|
|
24
|
+
blockedInputs: string | RegExp;
|
|
23
25
|
/** Disable all browser helps like spellchecking, autocomplete, etc. */
|
|
24
|
-
disableBrowserHelps
|
|
26
|
+
disableBrowserHelps: boolean;
|
|
25
27
|
/** Set this to true to make the whole element size to only fit the input text. */
|
|
26
|
-
fitText
|
|
28
|
+
fitText: boolean;
|
|
27
29
|
/** A set of attributes that will be applied to the inner native text element. */
|
|
28
|
-
attributePassthrough
|
|
29
|
-
}
|
|
30
|
+
attributePassthrough: AttributeValues;
|
|
31
|
+
}>;
|
|
30
32
|
/**
|
|
31
33
|
* Inputs used to check if the current input element value is allowed.
|
|
32
34
|
*
|
|
@@ -34,22 +34,22 @@ export const ViraCheckbox = defineViraElement()({
|
|
|
34
34
|
|
|
35
35
|
label {
|
|
36
36
|
display: inline-flex;
|
|
37
|
-
|
|
37
|
+
flex-direction: column;
|
|
38
|
+
align-items: flex-start;
|
|
39
|
+
gap: 4px;
|
|
38
40
|
|
|
39
41
|
&.disabled {
|
|
40
42
|
cursor: not-allowed;
|
|
41
43
|
}
|
|
42
44
|
|
|
43
|
-
& .text {
|
|
45
|
+
& .label-text {
|
|
44
46
|
cursor: pointer;
|
|
45
|
-
|
|
46
|
-
line-height: 24px;
|
|
47
|
-
}
|
|
47
|
+
font-weight: ${viraFormCssVars['vira-form-label-font-weight'].value};
|
|
48
48
|
}
|
|
49
49
|
}
|
|
50
50
|
|
|
51
51
|
${ViraIcon} {
|
|
52
|
-
${viraIconCssVars['vira-icon-stroke-width'].name}:
|
|
52
|
+
${viraIconCssVars['vira-icon-stroke-width'].name}: 2px;
|
|
53
53
|
opacity: 0;
|
|
54
54
|
}
|
|
55
55
|
|
|
@@ -71,6 +71,10 @@ export const ViraCheckbox = defineViraElement()({
|
|
|
71
71
|
}
|
|
72
72
|
}
|
|
73
73
|
|
|
74
|
+
&.error {
|
|
75
|
+
border-color: ${viraFormCssVars['vira-form-error-foreground-color'].value};
|
|
76
|
+
}
|
|
77
|
+
|
|
74
78
|
&:hover {
|
|
75
79
|
background-color: ${viraFormCssVars['vira-form-selection-hover-background-color']
|
|
76
80
|
.value};
|
|
@@ -98,7 +102,7 @@ export const ViraCheckbox = defineViraElement()({
|
|
|
98
102
|
const textLabel = inputs.label
|
|
99
103
|
? html `
|
|
100
104
|
<span
|
|
101
|
-
class="text"
|
|
105
|
+
class="label-text"
|
|
102
106
|
${attributes(inputs.attributePassthrough?.['text'])}
|
|
103
107
|
style=${ifDefined(inputs.stylePassthrough?.['text'])}
|
|
104
108
|
>
|
|
@@ -113,12 +117,14 @@ export const ViraCheckbox = defineViraElement()({
|
|
|
113
117
|
})}
|
|
114
118
|
${attributes(inputs.attributePassthrough?.label)}
|
|
115
119
|
style=${ifDefined(inputs.stylePassthrough?.label)}
|
|
116
|
-
${listen('
|
|
120
|
+
${listen('mousedown', updateValue)}
|
|
117
121
|
>
|
|
122
|
+
${textLabel}
|
|
118
123
|
<span
|
|
119
124
|
class="custom-checkbox ${classMap({
|
|
120
125
|
checked: inputs.value,
|
|
121
126
|
disabled: !!inputs.disabled,
|
|
127
|
+
error: !!inputs.hasError,
|
|
122
128
|
})}"
|
|
123
129
|
role="checkbox"
|
|
124
130
|
aria-checked=${inputs.value ? 'true' : 'false'}
|
|
@@ -136,7 +142,6 @@ export const ViraCheckbox = defineViraElement()({
|
|
|
136
142
|
style=${ifDefined(inputs.stylePassthrough?.[ViraIcon.tagName])}
|
|
137
143
|
></${ViraIcon}>
|
|
138
144
|
</span>
|
|
139
|
-
${textLabel}
|
|
140
145
|
</label>
|
|
141
146
|
`;
|
|
142
147
|
},
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { type PartialWithUndefined } from '@augment-vir/common';
|
|
2
|
+
import { type ViraIconSvg } from '../icons/icon-svg.js';
|
|
3
|
+
import { type ViraSelectOption } from './vira-select.element.js';
|
|
4
|
+
/**
|
|
5
|
+
* Form field types for {@link ViraFormField}.
|
|
6
|
+
*
|
|
7
|
+
* @category Internal
|
|
8
|
+
*/
|
|
9
|
+
export declare enum ViraFormFieldType {
|
|
10
|
+
Text = "text",
|
|
11
|
+
/** Allows auto complete for _existing_ passwords used on this website (for login). */
|
|
12
|
+
ExistingPassword = "existing-password",
|
|
13
|
+
/** Allows auto complete for _new_ passwords used on this website (for login). */
|
|
14
|
+
NewPassword = "new-password",
|
|
15
|
+
Email = "email",
|
|
16
|
+
Select = "select",
|
|
17
|
+
Checkbox = "checkbox"
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* An individual form field for {@link ViraFormFields}.
|
|
21
|
+
*
|
|
22
|
+
* @category Internal
|
|
23
|
+
*/
|
|
24
|
+
export type ViraFormField = {
|
|
25
|
+
type: ViraFormFieldType.Text | ViraFormFieldType.ExistingPassword | ViraFormFieldType.NewPassword | ViraFormFieldType.Email;
|
|
26
|
+
label: string;
|
|
27
|
+
value: string;
|
|
28
|
+
placeholder?: string | undefined;
|
|
29
|
+
disabled?: boolean | undefined;
|
|
30
|
+
icon?: ViraIconSvg | undefined;
|
|
31
|
+
hasError?: boolean | undefined;
|
|
32
|
+
isUsername?: boolean | undefined;
|
|
33
|
+
} | {
|
|
34
|
+
type: ViraFormFieldType.Select;
|
|
35
|
+
label: string;
|
|
36
|
+
value: string | undefined;
|
|
37
|
+
options: ReadonlyArray<Readonly<ViraSelectOption>>;
|
|
38
|
+
placeholder?: string | undefined;
|
|
39
|
+
disabled?: boolean | undefined;
|
|
40
|
+
icon?: ViraIconSvg | undefined;
|
|
41
|
+
hasError?: boolean | undefined;
|
|
42
|
+
} | {
|
|
43
|
+
type: ViraFormFieldType.Checkbox;
|
|
44
|
+
label: string;
|
|
45
|
+
value: boolean;
|
|
46
|
+
disabled?: boolean | undefined;
|
|
47
|
+
hasError?: boolean | undefined;
|
|
48
|
+
};
|
|
49
|
+
/**
|
|
50
|
+
* A collection of form fields for {@link ViraForm}.
|
|
51
|
+
*
|
|
52
|
+
* @category Internal
|
|
53
|
+
*/
|
|
54
|
+
export type ViraFormFields = Record<string, ViraFormField>;
|
|
55
|
+
/**
|
|
56
|
+
* A form element.
|
|
57
|
+
*
|
|
58
|
+
* @category Elements
|
|
59
|
+
* @see https://electrovir.github.io/vira/book/elements/vira-form
|
|
60
|
+
*/
|
|
61
|
+
export declare const ViraForm: import("element-vir").DeclarativeElementDefinition<"vira-form", Readonly<{
|
|
62
|
+
fields: Readonly<ViraFormFields>;
|
|
63
|
+
} & PartialWithUndefined<{
|
|
64
|
+
showClearButtons: boolean;
|
|
65
|
+
}>>, {}, {
|
|
66
|
+
valueChange: import("element-vir").DefineEvent<{
|
|
67
|
+
key: string;
|
|
68
|
+
} & ViraFormField>;
|
|
69
|
+
}, "vira-form-", "vira-form-", readonly ["actionButtons"], readonly []>;
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import { getObjectTypedEntries } from '@augment-vir/common';
|
|
2
|
+
import { css, defineElementEvent, html, listen } from 'element-vir';
|
|
3
|
+
import { defineViraElement } from './define-vira-element.js';
|
|
4
|
+
import { ViraCheckbox } from './vira-checkbox.element.js';
|
|
5
|
+
import { ViraInput, ViraInputType } from './vira-input.element.js';
|
|
6
|
+
import { ViraSelect } from './vira-select.element.js';
|
|
7
|
+
/**
|
|
8
|
+
* Form field types for {@link ViraFormField}.
|
|
9
|
+
*
|
|
10
|
+
* @category Internal
|
|
11
|
+
*/
|
|
12
|
+
export var ViraFormFieldType;
|
|
13
|
+
(function (ViraFormFieldType) {
|
|
14
|
+
ViraFormFieldType["Text"] = "text";
|
|
15
|
+
/** Allows auto complete for _existing_ passwords used on this website (for login). */
|
|
16
|
+
ViraFormFieldType["ExistingPassword"] = "existing-password";
|
|
17
|
+
/** Allows auto complete for _new_ passwords used on this website (for login). */
|
|
18
|
+
ViraFormFieldType["NewPassword"] = "new-password";
|
|
19
|
+
ViraFormFieldType["Email"] = "email";
|
|
20
|
+
ViraFormFieldType["Select"] = "select";
|
|
21
|
+
ViraFormFieldType["Checkbox"] = "checkbox";
|
|
22
|
+
})(ViraFormFieldType || (ViraFormFieldType = {}));
|
|
23
|
+
/**
|
|
24
|
+
* A form element.
|
|
25
|
+
*
|
|
26
|
+
* @category Elements
|
|
27
|
+
* @see https://electrovir.github.io/vira/book/elements/vira-form
|
|
28
|
+
*/
|
|
29
|
+
export const ViraForm = defineViraElement()({
|
|
30
|
+
tagName: 'vira-form',
|
|
31
|
+
slotNames: ['actionButtons'],
|
|
32
|
+
events: {
|
|
33
|
+
valueChange: defineElementEvent(),
|
|
34
|
+
},
|
|
35
|
+
styles: css `
|
|
36
|
+
form {
|
|
37
|
+
display: flex;
|
|
38
|
+
flex-direction: column;
|
|
39
|
+
gap: 10px;
|
|
40
|
+
}
|
|
41
|
+
`,
|
|
42
|
+
render({ inputs, slotNames, dispatch, events }) {
|
|
43
|
+
const formFields = getObjectTypedEntries(inputs.fields).map(([key, field,]) => {
|
|
44
|
+
if (field.type === ViraFormFieldType.Checkbox) {
|
|
45
|
+
return html `
|
|
46
|
+
<${ViraCheckbox.assign({
|
|
47
|
+
value: field.value,
|
|
48
|
+
disabled: field.disabled,
|
|
49
|
+
hasError: field.hasError,
|
|
50
|
+
label: field.label,
|
|
51
|
+
})}
|
|
52
|
+
${listen(ViraCheckbox.events.valueChange, (event) => {
|
|
53
|
+
dispatch(new events.valueChange({
|
|
54
|
+
key,
|
|
55
|
+
...field,
|
|
56
|
+
value: event.detail,
|
|
57
|
+
}));
|
|
58
|
+
})}
|
|
59
|
+
></${ViraCheckbox}>
|
|
60
|
+
`;
|
|
61
|
+
}
|
|
62
|
+
else if (field.type === ViraFormFieldType.Select) {
|
|
63
|
+
return html `
|
|
64
|
+
<${ViraSelect.assign({
|
|
65
|
+
options: field.options,
|
|
66
|
+
value: field.value,
|
|
67
|
+
placeholder: field.placeholder,
|
|
68
|
+
disabled: field.disabled,
|
|
69
|
+
label: field.label,
|
|
70
|
+
hasError: field.hasError,
|
|
71
|
+
icon: field.icon,
|
|
72
|
+
})}
|
|
73
|
+
${listen(ViraSelect.events.valueChange, (event) => {
|
|
74
|
+
dispatch(new events.valueChange({
|
|
75
|
+
key,
|
|
76
|
+
...field,
|
|
77
|
+
value: event.detail,
|
|
78
|
+
}));
|
|
79
|
+
})}
|
|
80
|
+
></${ViraSelect}>
|
|
81
|
+
`;
|
|
82
|
+
}
|
|
83
|
+
else {
|
|
84
|
+
return html `
|
|
85
|
+
<${ViraInput.assign({
|
|
86
|
+
value: field.value,
|
|
87
|
+
disabled: field.disabled,
|
|
88
|
+
hasError: field.hasError,
|
|
89
|
+
icon: field.icon,
|
|
90
|
+
label: field.label,
|
|
91
|
+
placeholder: field.placeholder,
|
|
92
|
+
showClearButton: inputs.showClearButtons,
|
|
93
|
+
attributePassthrough: field.isUsername
|
|
94
|
+
? {
|
|
95
|
+
autocomplete: 'username',
|
|
96
|
+
}
|
|
97
|
+
: field.type === ViraFormFieldType.NewPassword
|
|
98
|
+
? {
|
|
99
|
+
autocomplete: 'new-password',
|
|
100
|
+
}
|
|
101
|
+
: field.type === ViraFormFieldType.ExistingPassword
|
|
102
|
+
? {
|
|
103
|
+
autocomplete: 'password',
|
|
104
|
+
}
|
|
105
|
+
: field.type === ViraFormFieldType.Email
|
|
106
|
+
? {
|
|
107
|
+
autocomplete: 'email',
|
|
108
|
+
}
|
|
109
|
+
: {},
|
|
110
|
+
type: [
|
|
111
|
+
ViraFormFieldType.NewPassword,
|
|
112
|
+
ViraFormFieldType.ExistingPassword,
|
|
113
|
+
].includes(field.type)
|
|
114
|
+
? ViraInputType.Password
|
|
115
|
+
: field.type === ViraFormFieldType.Email
|
|
116
|
+
? ViraInputType.Email
|
|
117
|
+
: ViraInputType.Default,
|
|
118
|
+
})}
|
|
119
|
+
${listen(ViraInput.events.valueChange, (event) => {
|
|
120
|
+
dispatch(new events.valueChange({
|
|
121
|
+
key,
|
|
122
|
+
...field,
|
|
123
|
+
value: event.detail,
|
|
124
|
+
}));
|
|
125
|
+
})}
|
|
126
|
+
></${ViraInput}>
|
|
127
|
+
`;
|
|
128
|
+
}
|
|
129
|
+
});
|
|
130
|
+
return html `
|
|
131
|
+
<form ${listen('submit', (event) => event.preventDefault())}>
|
|
132
|
+
${formFields}
|
|
133
|
+
<slot name=${slotNames.actionButtons}></slot>
|
|
134
|
+
</form>
|
|
135
|
+
`;
|
|
136
|
+
},
|
|
137
|
+
});
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { type PartialWithUndefined } from '@augment-vir/common';
|
|
2
2
|
import { type ViraIconSvg } from '../icons/index.js';
|
|
3
|
-
import { type SharedTextInputElementInputs } from './shared-text-input-logic.js';
|
|
4
3
|
export * from './shared-text-input-logic.js';
|
|
5
4
|
/**
|
|
6
5
|
* Input types for {@link ViraInput}.
|
|
@@ -30,7 +29,17 @@ export declare const ViraInput: import("element-vir").DeclarativeElementDefiniti
|
|
|
30
29
|
hasError: boolean;
|
|
31
30
|
showClearButton: boolean;
|
|
32
31
|
type: ViraInputType;
|
|
33
|
-
}> &
|
|
32
|
+
}> & {
|
|
33
|
+
value: string;
|
|
34
|
+
} & PartialWithUndefined<{
|
|
35
|
+
placeholder: string;
|
|
36
|
+
disabled: boolean;
|
|
37
|
+
allowedInputs: string | RegExp;
|
|
38
|
+
blockedInputs: string | RegExp;
|
|
39
|
+
disableBrowserHelps: boolean;
|
|
40
|
+
fitText: boolean;
|
|
41
|
+
attributePassthrough: import("element-vir").AttributeValues;
|
|
42
|
+
}>>, {
|
|
34
43
|
forcedInputWidth: number;
|
|
35
44
|
showPassword: boolean;
|
|
36
45
|
/**
|
|
@@ -50,4 +59,4 @@ export declare const ViraInput: import("element-vir").DeclarativeElementDefiniti
|
|
|
50
59
|
* that was blocked out of programmatic "value" property assignments.
|
|
51
60
|
*/
|
|
52
61
|
inputBlocked: import("element-vir").DefineEvent<string>;
|
|
53
|
-
}, "vira-input-disabled" | "vira-input-fit-text" | "vira-input-clear-button-shown" | "vira-input-error", "vira-input-
|
|
62
|
+
}, "vira-input-disabled" | "vira-input-fit-text" | "vira-input-clear-button-shown" | "vira-input-error", "vira-input-action-button-color" | "vira-input-clear-button-hover-color" | "vira-input-clear-button-active-color" | "vira-input-show-password-button-hover-color" | "vira-input-show-password-button-active-color" | "vira-input-padding-horizontal" | "vira-input-padding-vertical", readonly [], readonly []>;
|
|
@@ -34,11 +34,6 @@ export var ViraInputType;
|
|
|
34
34
|
export const ViraInput = defineViraElement()({
|
|
35
35
|
tagName: 'vira-input',
|
|
36
36
|
cssVars: {
|
|
37
|
-
'vira-input-background-color': 'white',
|
|
38
|
-
'vira-input-placeholder-color': '#cccccc',
|
|
39
|
-
'vira-input-text-color': '#000000',
|
|
40
|
-
'vira-input-border-color': '#cccccc',
|
|
41
|
-
'vira-input-text-selection-color': '#cfe9ff',
|
|
42
37
|
'vira-input-action-button-color': '#aaaaaa',
|
|
43
38
|
'vira-input-clear-button-hover-color': '#ff0000',
|
|
44
39
|
'vira-input-clear-button-active-color': '#b30000',
|
|
@@ -48,7 +43,6 @@ export const ViraInput = defineViraElement()({
|
|
|
48
43
|
'vira-input-show-password-button-active-color': '#0261ba',
|
|
49
44
|
'vira-input-padding-horizontal': '10px',
|
|
50
45
|
'vira-input-padding-vertical': '6px',
|
|
51
|
-
'vira-input-label-font-weight': 'bold',
|
|
52
46
|
},
|
|
53
47
|
styles: ({ hostClasses, cssVars }) => {
|
|
54
48
|
return css `
|
|
@@ -57,7 +51,7 @@ export const ViraInput = defineViraElement()({
|
|
|
57
51
|
display: inline-flex;
|
|
58
52
|
width: 224px;
|
|
59
53
|
box-sizing: border-box;
|
|
60
|
-
color: ${
|
|
54
|
+
color: ${viraFormCssVars['vira-form-foreground-color'].value};
|
|
61
55
|
}
|
|
62
56
|
|
|
63
57
|
label {
|
|
@@ -67,19 +61,16 @@ export const ViraInput = defineViraElement()({
|
|
|
67
61
|
gap: 2px;
|
|
68
62
|
width: 100%;
|
|
69
63
|
max-width: 100%;
|
|
64
|
+
cursor: text;
|
|
70
65
|
|
|
71
66
|
& .input-label {
|
|
72
|
-
font-weight: ${
|
|
67
|
+
font-weight: ${viraFormCssVars['vira-form-label-font-weight'].value};
|
|
73
68
|
text-align: left;
|
|
74
69
|
flex-shrink: 0;
|
|
75
70
|
flex-wrap: wrap;
|
|
76
71
|
}
|
|
77
72
|
}
|
|
78
73
|
|
|
79
|
-
${hostClasses['vira-input-disabled'].selector} {
|
|
80
|
-
${viraDisabledStyles};
|
|
81
|
-
}
|
|
82
|
-
|
|
83
74
|
${hostClasses['vira-input-fit-text'].selector} {
|
|
84
75
|
width: unset;
|
|
85
76
|
}
|
|
@@ -144,9 +135,7 @@ export const ViraInput = defineViraElement()({
|
|
|
144
135
|
.wrapper-border {
|
|
145
136
|
top: -1px;
|
|
146
137
|
left: -1px;
|
|
147
|
-
border: 1px solid ${
|
|
148
|
-
transition: border
|
|
149
|
-
${viraAnimationDurations['vira-interaction-animation-duration'].value};
|
|
138
|
+
border: 1px solid ${viraFormCssVars['vira-form-border-color'].value};
|
|
150
139
|
}
|
|
151
140
|
|
|
152
141
|
.input-wrapper {
|
|
@@ -159,7 +148,7 @@ export const ViraInput = defineViraElement()({
|
|
|
159
148
|
position: relative;
|
|
160
149
|
padding: 0 ${cssVars['vira-input-padding-horizontal'].value};
|
|
161
150
|
border-radius: ${viraBorders['vira-form-input-radius'].value};
|
|
162
|
-
background-color: ${
|
|
151
|
+
background-color: ${viraFormCssVars['vira-form-background-color'].value};
|
|
163
152
|
/*
|
|
164
153
|
Border colors are actually applied via the .wrapper-border class. However, we must
|
|
165
154
|
apply a border here still so that it takes up space.
|
|
@@ -195,11 +184,11 @@ export const ViraInput = defineViraElement()({
|
|
|
195
184
|
}
|
|
196
185
|
|
|
197
186
|
::selection {
|
|
198
|
-
background: ${
|
|
187
|
+
background: ${viraFormCssVars['vira-form-text-selection-color']
|
|
199
188
|
.value}; /* WebKit/Blink Browsers */
|
|
200
189
|
}
|
|
201
190
|
::-moz-selection {
|
|
202
|
-
background: ${
|
|
191
|
+
background: ${viraFormCssVars['vira-form-text-selection-color']
|
|
203
192
|
.value}; /* Gecko Browsers */
|
|
204
193
|
}
|
|
205
194
|
|
|
@@ -209,7 +198,7 @@ export const ViraInput = defineViraElement()({
|
|
|
209
198
|
}
|
|
210
199
|
|
|
211
200
|
input::placeholder {
|
|
212
|
-
color: ${
|
|
201
|
+
color: ${viraFormCssVars['vira-form-placeholder-color'].value};
|
|
213
202
|
}
|
|
214
203
|
|
|
215
204
|
.suffix {
|
|
@@ -251,6 +240,25 @@ export const ViraInput = defineViraElement()({
|
|
|
251
240
|
border-color: ${viraFormCssVars['vira-form-error-foreground-color'].value};
|
|
252
241
|
}
|
|
253
242
|
}
|
|
243
|
+
|
|
244
|
+
${hostClasses['vira-input-disabled'].selector} {
|
|
245
|
+
cursor: not-allowed;
|
|
246
|
+
|
|
247
|
+
& label,
|
|
248
|
+
& .input-wrapper {
|
|
249
|
+
cursor: not-allowed;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
& input,
|
|
253
|
+
& .wrapper-border,
|
|
254
|
+
& input::placeholder {
|
|
255
|
+
${viraDisabledStyles};
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
& .focus-border {
|
|
259
|
+
display: none;
|
|
260
|
+
}
|
|
261
|
+
}
|
|
254
262
|
`;
|
|
255
263
|
},
|
|
256
264
|
events: {
|
|
@@ -299,16 +307,7 @@ export const ViraInput = defineViraElement()({
|
|
|
299
307
|
width: ${state.forcedInputWidth}px;
|
|
300
308
|
`
|
|
301
309
|
: nothing;
|
|
302
|
-
const
|
|
303
|
-
/**
|
|
304
|
-
* Some browsers leaks passwords with their browser helps (like Chrome with
|
|
305
|
-
* spellchecking).
|
|
306
|
-
*/
|
|
307
|
-
inputs.type === ViraInputType.Password;
|
|
308
|
-
const inputTemplate = html `
|
|
309
|
-
<span
|
|
310
|
-
class="input-wrapper"
|
|
311
|
-
${listen('mousedown', (event) => {
|
|
310
|
+
const mousedownListener = listen('mousedown', (event) => {
|
|
312
311
|
const eventTarget = extractEventTarget(event, HTMLElement, {
|
|
313
312
|
useOriginalTarget: true,
|
|
314
313
|
});
|
|
@@ -317,8 +316,15 @@ export const ViraInput = defineViraElement()({
|
|
|
317
316
|
event.preventDefault();
|
|
318
317
|
inputElement.focus();
|
|
319
318
|
}
|
|
320
|
-
})
|
|
321
|
-
|
|
319
|
+
});
|
|
320
|
+
const shouldBlockBrowserHelps = inputs.disableBrowserHelps ||
|
|
321
|
+
/**
|
|
322
|
+
* Some browsers leaks passwords with their browser helps (like Chrome with
|
|
323
|
+
* spellchecking).
|
|
324
|
+
*/
|
|
325
|
+
inputs.type === ViraInputType.Password;
|
|
326
|
+
const inputTemplate = html `
|
|
327
|
+
<span class="input-wrapper" ${inputs.label ? nothing : mousedownListener}>
|
|
322
328
|
${iconTemplate}
|
|
323
329
|
${renderIf(!!inputs.fitText, html `
|
|
324
330
|
<span
|
|
@@ -334,6 +340,7 @@ export const ViraInput = defineViraElement()({
|
|
|
334
340
|
<input
|
|
335
341
|
id=${ifDefined(inputs.label ? state.randomId : undefined)}
|
|
336
342
|
aria-label=${ifDefined(inputs.label || undefined)}
|
|
343
|
+
autofocus=${false}
|
|
337
344
|
type=${calculateEffectiveInputType(inputs.type, state.showPassword)}
|
|
338
345
|
style=${forcedInputWidthStyles}
|
|
339
346
|
autocomplete=${ifDefined(shouldBlockBrowserHelps ? 'off' : undefined)}
|
|
@@ -408,7 +415,7 @@ export const ViraInput = defineViraElement()({
|
|
|
408
415
|
`;
|
|
409
416
|
if (inputs.label) {
|
|
410
417
|
return html `
|
|
411
|
-
<label for=${state.randomId}>
|
|
418
|
+
<label for=${state.randomId} ${mousedownListener}>
|
|
412
419
|
<span class="input-label">${inputs.label}</span>
|
|
413
420
|
${inputTemplate}
|
|
414
421
|
</label>
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { type PartialWithUndefined } from '@augment-vir/common';
|
|
2
|
+
import { type AttributeValues } from 'element-vir';
|
|
3
|
+
import { type ViraIconSvg } from '../icons/index.js';
|
|
4
|
+
/**
|
|
5
|
+
* Options for {@link ViraSelect}.
|
|
6
|
+
*
|
|
7
|
+
* @category Dropdown
|
|
8
|
+
* @category Elements
|
|
9
|
+
* @see https://electrovir.github.io/vira/book/elements/vira-select
|
|
10
|
+
*/
|
|
11
|
+
export type ViraSelectOption = {
|
|
12
|
+
/** A value or id, used to keep track of which option is selected. */
|
|
13
|
+
value: string;
|
|
14
|
+
label: string;
|
|
15
|
+
} & PartialWithUndefined<{
|
|
16
|
+
disabled: boolean;
|
|
17
|
+
}>;
|
|
18
|
+
/**
|
|
19
|
+
* Similar to {@link ViraDropdown} but is, instead, simply a wrapper for `<select>` and nothing more.
|
|
20
|
+
*
|
|
21
|
+
* @category Dropdown
|
|
22
|
+
* @category Elements
|
|
23
|
+
* @see https://electrovir.github.io/vira/book/elements/vira-select
|
|
24
|
+
*/
|
|
25
|
+
export declare const ViraSelect: import("element-vir").DeclarativeElementDefinition<"vira-select", Readonly<{
|
|
26
|
+
options: ReadonlyArray<Readonly<ViraSelectOption>>;
|
|
27
|
+
/** The currently selected option value. */
|
|
28
|
+
value: undefined | string;
|
|
29
|
+
} & PartialWithUndefined<{
|
|
30
|
+
icon: Readonly<ViraIconSvg>;
|
|
31
|
+
placeholder: string;
|
|
32
|
+
label: string;
|
|
33
|
+
disabled: boolean;
|
|
34
|
+
attributePassthrough: Readonly<PartialWithUndefined<{
|
|
35
|
+
label: AttributeValues;
|
|
36
|
+
select: AttributeValues;
|
|
37
|
+
option: AttributeValues;
|
|
38
|
+
}>>;
|
|
39
|
+
hasError: boolean;
|
|
40
|
+
}>>, {
|
|
41
|
+
/**
|
|
42
|
+
* Used to couple the label and select together. This is not applied if no label is
|
|
43
|
+
* provided.
|
|
44
|
+
*/
|
|
45
|
+
randomId: string;
|
|
46
|
+
}, {
|
|
47
|
+
valueChange: import("element-vir").DefineEvent<string>;
|
|
48
|
+
}, "vira-select-disabled" | "vira-select-error", "vira-select-padding-horizontal" | "vira-select-padding-vertical" | "vira-select-icon-padding", readonly [], readonly []>;
|
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
import { randomString } from '@augment-vir/common';
|
|
2
|
+
import { extractEventTarget } from '@augment-vir/web';
|
|
3
|
+
import { attributes, classMap, css, defineElementEvent, html, ifDefined, listen, nothing, } from 'element-vir';
|
|
4
|
+
import { ChevronUp24Icon } from '../icons/index.js';
|
|
5
|
+
import { viraDisabledStyles } from '../styles/disabled.js';
|
|
6
|
+
import { createFocusStyles } from '../styles/focus.js';
|
|
7
|
+
import { viraFormCssVars } from '../styles/form-styles.js';
|
|
8
|
+
import { viraAnimationDurations, viraBorders } from '../styles/index.js';
|
|
9
|
+
import { noNativeFormStyles } from '../styles/native-styles.js';
|
|
10
|
+
import { defineViraElement } from './define-vira-element.js';
|
|
11
|
+
import { ViraIcon } from './vira-icon.element.js';
|
|
12
|
+
/**
|
|
13
|
+
* Similar to {@link ViraDropdown} but is, instead, simply a wrapper for `<select>` and nothing more.
|
|
14
|
+
*
|
|
15
|
+
* @category Dropdown
|
|
16
|
+
* @category Elements
|
|
17
|
+
* @see https://electrovir.github.io/vira/book/elements/vira-select
|
|
18
|
+
*/
|
|
19
|
+
export const ViraSelect = defineViraElement()({
|
|
20
|
+
tagName: 'vira-select',
|
|
21
|
+
state() {
|
|
22
|
+
return {
|
|
23
|
+
/**
|
|
24
|
+
* Used to couple the label and select together. This is not applied if no label is
|
|
25
|
+
* provided.
|
|
26
|
+
*/
|
|
27
|
+
randomId: randomString(32),
|
|
28
|
+
};
|
|
29
|
+
},
|
|
30
|
+
events: {
|
|
31
|
+
valueChange: defineElementEvent(),
|
|
32
|
+
},
|
|
33
|
+
cssVars: {
|
|
34
|
+
'vira-select-padding-horizontal': '10px',
|
|
35
|
+
'vira-select-padding-vertical': '6px',
|
|
36
|
+
'vira-select-icon-padding': '44px',
|
|
37
|
+
},
|
|
38
|
+
hostClasses: {
|
|
39
|
+
'vira-select-disabled': ({ inputs }) => !!inputs.disabled,
|
|
40
|
+
'vira-select-error': ({ inputs }) => !!inputs.hasError,
|
|
41
|
+
},
|
|
42
|
+
styles: ({ hostClasses, cssVars }) => css `
|
|
43
|
+
:host {
|
|
44
|
+
position: relative;
|
|
45
|
+
display: inline-flex;
|
|
46
|
+
width: 223px;
|
|
47
|
+
box-sizing: border-box;
|
|
48
|
+
color: ${viraFormCssVars['vira-form-foreground-color'].value};
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
.select-wrapper {
|
|
52
|
+
${noNativeFormStyles};
|
|
53
|
+
max-width: 100%;
|
|
54
|
+
flex-grow: 1;
|
|
55
|
+
display: inline-flex;
|
|
56
|
+
box-sizing: border-box;
|
|
57
|
+
align-items: center;
|
|
58
|
+
position: relative;
|
|
59
|
+
border-radius: ${viraBorders['vira-form-input-radius'].value};
|
|
60
|
+
background-color: ${viraFormCssVars['vira-form-background-color'].value};
|
|
61
|
+
/*
|
|
62
|
+
Border colors are actually applied via the .wrapper-border class. However, we must
|
|
63
|
+
apply a border here still so that it takes up space.
|
|
64
|
+
*/
|
|
65
|
+
border: 1px solid transparent;
|
|
66
|
+
cursor: pointer;
|
|
67
|
+
|
|
68
|
+
& select {
|
|
69
|
+
appearance: none;
|
|
70
|
+
-webkit-appearance: none;
|
|
71
|
+
-moz-appearance: none;
|
|
72
|
+
font: inherit;
|
|
73
|
+
outline: none;
|
|
74
|
+
width: 100%;
|
|
75
|
+
border: none;
|
|
76
|
+
background: none;
|
|
77
|
+
border-radius: inherit;
|
|
78
|
+
padding: ${cssVars['vira-select-padding-vertical'].value} 31px
|
|
79
|
+
${cssVars['vira-select-padding-vertical'].value}
|
|
80
|
+
${cssVars['vira-select-padding-horizontal'].value};
|
|
81
|
+
cursor: pointer;
|
|
82
|
+
overflow: hidden;
|
|
83
|
+
text-overflow: ellipsis;
|
|
84
|
+
|
|
85
|
+
&:focus:focus-visible:not([aria-disabled='true']) ~ .focus-border {
|
|
86
|
+
${createFocusStyles({
|
|
87
|
+
elementBorderSize: 0,
|
|
88
|
+
noNesting: true,
|
|
89
|
+
})}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
&.placeholder {
|
|
93
|
+
color: ${viraFormCssVars['vira-form-placeholder-color'].value};
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
&.with-icon {
|
|
97
|
+
padding-left: ${cssVars['vira-select-icon-padding'].value};
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
& ${ViraIcon} {
|
|
102
|
+
position: absolute;
|
|
103
|
+
pointer-events: none;
|
|
104
|
+
|
|
105
|
+
&.trigger-icon {
|
|
106
|
+
transform: rotate(180deg);
|
|
107
|
+
right: 3px;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
&.input-icon {
|
|
111
|
+
left: 10px;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
& .border-style {
|
|
116
|
+
position: absolute;
|
|
117
|
+
top: 0;
|
|
118
|
+
left: 0;
|
|
119
|
+
width: 100%;
|
|
120
|
+
height: 100%;
|
|
121
|
+
border-radius: ${viraBorders['vira-form-input-radius'].value};
|
|
122
|
+
z-index: 0;
|
|
123
|
+
pointer-events: none;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
& .wrapper-border {
|
|
127
|
+
top: -1px;
|
|
128
|
+
left: -1px;
|
|
129
|
+
border: 1px solid ${viraFormCssVars['vira-form-border-color'].value};
|
|
130
|
+
transition: border
|
|
131
|
+
${viraAnimationDurations['vira-interaction-animation-duration'].value};
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
label {
|
|
136
|
+
display: flex;
|
|
137
|
+
flex-direction: column;
|
|
138
|
+
justify-content: flex-start;
|
|
139
|
+
gap: 2px;
|
|
140
|
+
width: 100%;
|
|
141
|
+
max-width: 100%;
|
|
142
|
+
|
|
143
|
+
& .select-label {
|
|
144
|
+
font-weight: ${viraFormCssVars['vira-form-label-font-weight'].value};
|
|
145
|
+
text-align: left;
|
|
146
|
+
flex-shrink: 0;
|
|
147
|
+
flex-wrap: wrap;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
${hostClasses['vira-select-disabled'].selector} {
|
|
152
|
+
cursor: not-allowed;
|
|
153
|
+
|
|
154
|
+
& label {
|
|
155
|
+
cursor: not-allowed;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
& select,
|
|
159
|
+
& .wrapper-border {
|
|
160
|
+
${viraDisabledStyles}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
${hostClasses['vira-select-error'].selector} {
|
|
165
|
+
& .wrapper-border {
|
|
166
|
+
border-color: ${viraFormCssVars['vira-form-error-foreground-color'].value};
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
`,
|
|
170
|
+
render({ inputs, state, dispatch, events }) {
|
|
171
|
+
const value = inputs.value || undefined;
|
|
172
|
+
const placeholderOptionTemplate = inputs.placeholder || value == undefined
|
|
173
|
+
? html `
|
|
174
|
+
<option value="" disabled ?selected=${value == undefined}>
|
|
175
|
+
${inputs.placeholder}
|
|
176
|
+
</option>
|
|
177
|
+
`
|
|
178
|
+
: nothing;
|
|
179
|
+
const selectTemplate = html `
|
|
180
|
+
<span class="select-wrapper">
|
|
181
|
+
<select
|
|
182
|
+
.value=${ifDefined(value)}
|
|
183
|
+
class=${classMap({
|
|
184
|
+
placeholder: !value && !!inputs.placeholder,
|
|
185
|
+
'with-icon': !!inputs.icon,
|
|
186
|
+
})}
|
|
187
|
+
tabindex=${inputs.disabled ? -1 : 0}
|
|
188
|
+
id=${ifDefined(inputs.label ? state.randomId : undefined)}
|
|
189
|
+
aria-label=${ifDefined(inputs.label || undefined)}
|
|
190
|
+
aria-disabled=${ifDefined(inputs.disabled ? 'true' : undefined)}
|
|
191
|
+
${listen('input', (event) => {
|
|
192
|
+
const selectElement = extractEventTarget(event, HTMLSelectElement);
|
|
193
|
+
const newValue = selectElement.value;
|
|
194
|
+
if (selectElement.value !== value) {
|
|
195
|
+
selectElement.selectedIndex = inputs.options.findIndex((option) => option.value === value);
|
|
196
|
+
}
|
|
197
|
+
dispatch(new events.valueChange(newValue));
|
|
198
|
+
})}
|
|
199
|
+
${attributes(inputs.attributePassthrough?.select)}
|
|
200
|
+
>
|
|
201
|
+
${placeholderOptionTemplate}
|
|
202
|
+
${inputs.options.map((option) => {
|
|
203
|
+
return html `
|
|
204
|
+
<option
|
|
205
|
+
?selected=${option.value === value}
|
|
206
|
+
aria-label=${option.label}
|
|
207
|
+
?disabled=${option.disabled}
|
|
208
|
+
value=${option.value}
|
|
209
|
+
>
|
|
210
|
+
${option.label}
|
|
211
|
+
</option>
|
|
212
|
+
`;
|
|
213
|
+
})}
|
|
214
|
+
</select>
|
|
215
|
+
<!--
|
|
216
|
+
These separate style elements are necessary so that we can select them as
|
|
217
|
+
siblings of the focused <select> element.
|
|
218
|
+
-->
|
|
219
|
+
|
|
220
|
+
<div class="border-style focus-border"></div>
|
|
221
|
+
|
|
222
|
+
<div class="border-style wrapper-border"></div>
|
|
223
|
+
|
|
224
|
+
<${ViraIcon.assign({ icon: inputs.icon })} class="input-icon"></${ViraIcon}>
|
|
225
|
+
<${ViraIcon.assign({ icon: ChevronUp24Icon })} class="trigger-icon"></${ViraIcon}>
|
|
226
|
+
</span>
|
|
227
|
+
`;
|
|
228
|
+
if (inputs.label) {
|
|
229
|
+
return html `
|
|
230
|
+
<label for=${state.randomId} ${attributes(inputs.attributePassthrough?.label)}>
|
|
231
|
+
<span class="select-label">${inputs.label}</span>
|
|
232
|
+
${selectTemplate}
|
|
233
|
+
</label>
|
|
234
|
+
`;
|
|
235
|
+
}
|
|
236
|
+
else {
|
|
237
|
+
return selectTemplate;
|
|
238
|
+
}
|
|
239
|
+
},
|
|
240
|
+
});
|
package/dist/styles/focus.js
CHANGED
|
@@ -10,7 +10,7 @@ import { viraBorders } from './border.js';
|
|
|
10
10
|
*/
|
|
11
11
|
export const viraFocusCssVars = defineCssVars({
|
|
12
12
|
'vira-focus-outline-color': '#59b1ff',
|
|
13
|
-
'vira-focus-outline-border-radius': css `calc(${viraBorders['vira-form-input-radius'].value} +
|
|
13
|
+
'vira-focus-outline-border-radius': css `calc(${viraBorders['vira-form-input-radius'].value} + 2px)`,
|
|
14
14
|
});
|
|
15
15
|
/**
|
|
16
16
|
* Create styles that look like an outline for the given selector.
|
|
@@ -6,12 +6,15 @@
|
|
|
6
6
|
*/
|
|
7
7
|
export declare const viraFormCssVars: import("lit-css-vars").CssVarDefinitions<{
|
|
8
8
|
readonly 'vira-form-border-color': "#cccccc";
|
|
9
|
+
readonly 'vira-form-placeholder-color': "#cccccc";
|
|
9
10
|
readonly 'vira-form-background-color': "white";
|
|
10
11
|
readonly 'vira-form-foreground-color': "black";
|
|
11
|
-
readonly 'vira-form-selection-
|
|
12
|
+
readonly 'vira-form-text-selection-color': "#cfe9ff";
|
|
13
|
+
readonly 'vira-form-selection-hover-background-color': "#e6f9fe";
|
|
12
14
|
readonly 'vira-form-selection-hover-foreground-color': "black";
|
|
13
|
-
readonly 'vira-form-selection-active-background-color': "#
|
|
15
|
+
readonly 'vira-form-selection-active-background-color': "#e6f9fe";
|
|
14
16
|
readonly 'vira-form-selection-active-foreground-color': "black";
|
|
15
17
|
readonly 'vira-form-error-foreground-color': "red";
|
|
16
18
|
readonly 'vira-form-success-foreground-color': "green";
|
|
19
|
+
readonly 'vira-form-label-font-weight': "bold";
|
|
17
20
|
}>;
|
|
@@ -7,12 +7,15 @@ import { defineCssVars } from 'lit-css-vars';
|
|
|
7
7
|
*/
|
|
8
8
|
export const viraFormCssVars = defineCssVars({
|
|
9
9
|
'vira-form-border-color': '#cccccc',
|
|
10
|
+
'vira-form-placeholder-color': '#cccccc',
|
|
10
11
|
'vira-form-background-color': 'white',
|
|
11
12
|
'vira-form-foreground-color': 'black',
|
|
12
|
-
'vira-form-selection-
|
|
13
|
+
'vira-form-text-selection-color': '#cfe9ff',
|
|
14
|
+
'vira-form-selection-hover-background-color': '#e6f9fe',
|
|
13
15
|
'vira-form-selection-hover-foreground-color': 'black',
|
|
14
|
-
'vira-form-selection-active-background-color': '#
|
|
16
|
+
'vira-form-selection-active-background-color': '#e6f9fe',
|
|
15
17
|
'vira-form-selection-active-foreground-color': 'black',
|
|
16
18
|
'vira-form-error-foreground-color': 'red',
|
|
17
19
|
'vira-form-success-foreground-color': 'green',
|
|
20
|
+
'vira-form-label-font-weight': 'bold',
|
|
18
21
|
});
|