vira 28.6.0 → 28.7.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.
@@ -13,6 +13,7 @@ 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';
@@ -13,6 +13,7 @@ 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';
@@ -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?: string;
12
+ placeholder: string;
11
13
  /** Set to true to trigger disabled styles and to block all user input. */
12
- disabled?: boolean;
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?: string | RegExp;
22
+ allowedInputs: string | RegExp;
21
23
  /** Any letters in the given string or matches to the given RegExp will be blocked. */
22
- blockedInputs?: string | RegExp;
24
+ blockedInputs: string | RegExp;
23
25
  /** Disable all browser helps like spellchecking, autocomplete, etc. */
24
- disableBrowserHelps?: boolean;
26
+ disableBrowserHelps: boolean;
25
27
  /** Set this to true to make the whole element size to only fit the input text. */
26
- fitText?: boolean;
28
+ fitText: boolean;
27
29
  /** A set of attributes that will be applied to the inner native text element. */
28
- attributePassthrough?: AttributeValues | undefined;
29
- };
30
+ attributePassthrough: AttributeValues;
31
+ }>;
30
32
  /**
31
33
  * Inputs used to check if the current input element value is allowed.
32
34
  *
@@ -17,6 +17,7 @@ export type ViraCheckboxInputs = PartialWithUndefined<{
17
17
  attributePassthrough: Record<ViraCheckboxInnerElements, AttributeValues>;
18
18
  disabled: boolean;
19
19
  label: string;
20
+ hasError: boolean;
20
21
  }> & {
21
22
  value: boolean;
22
23
  };
@@ -34,22 +34,22 @@ export const ViraCheckbox = defineViraElement()({
34
34
 
35
35
  label {
36
36
  display: inline-flex;
37
- gap: 8px;
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
- &::first-line {
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}: 3px;
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('click', updateValue)}
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 [], readonly []>;
@@ -0,0 +1,136 @@
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
+ events: {
32
+ valueChange: defineElementEvent(),
33
+ },
34
+ styles: css `
35
+ form {
36
+ display: flex;
37
+ flex-direction: column;
38
+ gap: 10px;
39
+ }
40
+ `,
41
+ render({ inputs, dispatch, events }) {
42
+ const formFields = getObjectTypedEntries(inputs.fields).map(([key, field,]) => {
43
+ if (field.type === ViraFormFieldType.Checkbox) {
44
+ return html `
45
+ <${ViraCheckbox.assign({
46
+ value: field.value,
47
+ disabled: field.disabled,
48
+ hasError: field.hasError,
49
+ label: field.label,
50
+ })}
51
+ ${listen(ViraCheckbox.events.valueChange, (event) => {
52
+ dispatch(new events.valueChange({
53
+ key,
54
+ ...field,
55
+ value: event.detail,
56
+ }));
57
+ })}
58
+ ></${ViraCheckbox}>
59
+ `;
60
+ }
61
+ else if (field.type === ViraFormFieldType.Select) {
62
+ return html `
63
+ <${ViraSelect.assign({
64
+ options: field.options,
65
+ value: field.value,
66
+ placeholder: field.placeholder,
67
+ disabled: field.disabled,
68
+ label: field.label,
69
+ hasError: field.hasError,
70
+ icon: field.icon,
71
+ })}
72
+ ${listen(ViraSelect.events.valueChange, (event) => {
73
+ dispatch(new events.valueChange({
74
+ key,
75
+ ...field,
76
+ value: event.detail,
77
+ }));
78
+ })}
79
+ ></${ViraSelect}>
80
+ `;
81
+ }
82
+ else {
83
+ return html `
84
+ <${ViraInput.assign({
85
+ value: field.value,
86
+ disabled: field.disabled,
87
+ hasError: field.hasError,
88
+ icon: field.icon,
89
+ label: field.label,
90
+ placeholder: field.placeholder,
91
+ showClearButton: inputs.showClearButtons,
92
+ attributePassthrough: field.isUsername
93
+ ? {
94
+ autocomplete: 'username',
95
+ }
96
+ : field.type === ViraFormFieldType.NewPassword
97
+ ? {
98
+ autocomplete: 'new-password',
99
+ }
100
+ : field.type === ViraFormFieldType.ExistingPassword
101
+ ? {
102
+ autocomplete: 'password',
103
+ }
104
+ : field.type === ViraFormFieldType.Email
105
+ ? {
106
+ autocomplete: 'email',
107
+ }
108
+ : {},
109
+ type: [
110
+ ViraFormFieldType.NewPassword,
111
+ ViraFormFieldType.ExistingPassword,
112
+ ].includes(field.type)
113
+ ? ViraInputType.Password
114
+ : field.type === ViraFormFieldType.Email
115
+ ? ViraInputType.Email
116
+ : ViraInputType.Default,
117
+ })}
118
+ ${listen(ViraInput.events.valueChange, (event) => {
119
+ dispatch(new events.valueChange({
120
+ key,
121
+ ...field,
122
+ value: event.detail,
123
+ }));
124
+ })}
125
+ ></${ViraInput}>
126
+ `;
127
+ }
128
+ });
129
+ return html `
130
+ <form ${listen('submit', (event) => event.preventDefault())}>
131
+ ${formFields}
132
+ <slot></slot>
133
+ </form>
134
+ `;
135
+ },
136
+ });
@@ -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
- }> & SharedTextInputElementInputs>, {
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
  /**
@@ -61,6 +61,7 @@ export const ViraInput = defineViraElement()({
61
61
  gap: 2px;
62
62
  width: 100%;
63
63
  max-width: 100%;
64
+ cursor: text;
64
65
 
65
66
  & .input-label {
66
67
  font-weight: ${viraFormCssVars['vira-form-label-font-weight'].value};
@@ -339,6 +340,7 @@ export const ViraInput = defineViraElement()({
339
340
  <input
340
341
  id=${ifDefined(inputs.label ? state.randomId : undefined)}
341
342
  aria-label=${ifDefined(inputs.label || undefined)}
343
+ autofocus=${false}
342
344
  type=${calculateEffectiveInputType(inputs.type, state.showPassword)}
343
345
  style=${forcedInputWidthStyles}
344
346
  autocomplete=${ifDefined(shouldBlockBrowserHelps ? 'off' : undefined)}
@@ -168,18 +168,20 @@ export const ViraSelect = defineViraElement()({
168
168
  }
169
169
  `,
170
170
  render({ inputs, state, dispatch, events }) {
171
- const placeholderOptionTemplate = inputs.placeholder
171
+ const value = inputs.value || undefined;
172
+ const placeholderOptionTemplate = inputs.placeholder || value == undefined
172
173
  ? html `
173
- <option value="" disabled ?selected=${inputs.value == undefined}>
174
- ${inputs.placeholder}
175
- </option>
176
- `
174
+ <option value="" disabled ?selected=${value == undefined}>
175
+ ${inputs.placeholder}
176
+ </option>
177
+ `
177
178
  : nothing;
178
179
  const selectTemplate = html `
179
180
  <span class="select-wrapper">
180
181
  <select
182
+ .value=${ifDefined(value)}
181
183
  class=${classMap({
182
- placeholder: !inputs.value && !!inputs.placeholder,
184
+ placeholder: !value && !!inputs.placeholder,
183
185
  'with-icon': !!inputs.icon,
184
186
  })}
185
187
  tabindex=${inputs.disabled ? -1 : 0}
@@ -187,8 +189,12 @@ export const ViraSelect = defineViraElement()({
187
189
  aria-label=${ifDefined(inputs.label || undefined)}
188
190
  aria-disabled=${ifDefined(inputs.disabled ? 'true' : undefined)}
189
191
  ${listen('input', (event) => {
190
- const element = extractEventTarget(event, HTMLSelectElement);
191
- dispatch(new events.valueChange(element.value));
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));
192
198
  })}
193
199
  ${attributes(inputs.attributePassthrough?.select)}
194
200
  >
@@ -196,9 +202,10 @@ export const ViraSelect = defineViraElement()({
196
202
  ${inputs.options.map((option) => {
197
203
  return html `
198
204
  <option
199
- ?selected=${option.value === inputs.value}
205
+ ?selected=${option.value === value}
200
206
  aria-label=${option.label}
201
207
  ?disabled=${option.disabled}
208
+ value=${option.value}
202
209
  >
203
210
  ${option.label}
204
211
  </option>
@@ -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} + 4px)`,
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.
@@ -10,9 +10,9 @@ export declare const viraFormCssVars: import("lit-css-vars").CssVarDefinitions<{
10
10
  readonly 'vira-form-background-color': "white";
11
11
  readonly 'vira-form-foreground-color': "black";
12
12
  readonly 'vira-form-text-selection-color': "#cfe9ff";
13
- readonly 'vira-form-selection-hover-background-color': "#d2eaff";
13
+ readonly 'vira-form-selection-hover-background-color': "#e6f9fe";
14
14
  readonly 'vira-form-selection-hover-foreground-color': "black";
15
- readonly 'vira-form-selection-active-background-color': "#d2eaff";
15
+ readonly 'vira-form-selection-active-background-color': "#e6f9fe";
16
16
  readonly 'vira-form-selection-active-foreground-color': "black";
17
17
  readonly 'vira-form-error-foreground-color': "red";
18
18
  readonly 'vira-form-success-foreground-color': "green";
@@ -11,9 +11,9 @@ export const viraFormCssVars = defineCssVars({
11
11
  'vira-form-background-color': 'white',
12
12
  'vira-form-foreground-color': 'black',
13
13
  'vira-form-text-selection-color': '#cfe9ff',
14
- 'vira-form-selection-hover-background-color': '#d2eaff',
14
+ 'vira-form-selection-hover-background-color': '#e6f9fe',
15
15
  'vira-form-selection-hover-foreground-color': 'black',
16
- 'vira-form-selection-active-background-color': '#d2eaff',
16
+ 'vira-form-selection-active-background-color': '#e6f9fe',
17
17
  'vira-form-selection-active-foreground-color': 'black',
18
18
  'vira-form-error-foreground-color': 'red',
19
19
  'vira-form-success-foreground-color': 'green',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vira",
3
- "version": "28.6.0",
3
+ "version": "28.7.1",
4
4
  "description": "A simple and highly versatile design system using element-vir.",
5
5
  "keywords": [
6
6
  "design",