@openvoxproject/voxblocks 0.11.0 → 0.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. package/README.md +2 -2
  2. package/dist/cdn/voxblocks.css +1 -1
  3. package/dist/cdn/voxblocks.js +1780 -1437
  4. package/dist/types/components/accordion/vox-accordion.d.ts +0 -2
  5. package/dist/types/components/datum/vox-datum.d.ts +25 -0
  6. package/dist/types/components/disclosure/vox-disclosure.d.ts +0 -2
  7. package/dist/types/components/dropdown/vox-dropdown.d.ts +0 -3
  8. package/dist/types/components/header/vox-header.d.ts +0 -2
  9. package/dist/types/components/menu/vox-menu.d.ts +44 -0
  10. package/dist/types/components/pagination/vox-pagination.d.ts +4 -6
  11. package/dist/types/components/record-list/vox-record-list.d.ts +53 -0
  12. package/dist/types/components/sidenav/vox-sidenav.d.ts +0 -4
  13. package/dist/types/index.d.ts +5 -0
  14. package/dist/types/internal/field.d.ts +0 -4
  15. package/dist/voxblocks.css +1 -1
  16. package/dist/voxblocks.js +1622 -1279
  17. package/package.json +1 -1
  18. package/src/components/accordion/vox-accordion.ts +1 -6
  19. package/src/components/alert/vox-alert.ts +1 -5
  20. package/src/components/avatar/vox-avatar.ts +2 -9
  21. package/src/components/card/vox-card.ts +1 -1
  22. package/src/components/checkbox/vox-checkbox.ts +3 -4
  23. package/src/components/datum/vox-datum.ts +76 -0
  24. package/src/components/dialog/vox-dialog.ts +2 -2
  25. package/src/components/disclosure/vox-disclosure.ts +1 -6
  26. package/src/components/dropdown/vox-dropdown.ts +14 -32
  27. package/src/components/empty-state/vox-empty-state.ts +1 -1
  28. package/src/components/file-input/vox-file-input.ts +1 -4
  29. package/src/components/header/vox-header.ts +0 -10
  30. package/src/components/input/vox-input.ts +0 -2
  31. package/src/components/link-hub/vox-link-hub.ts +1 -1
  32. package/src/components/menu/vox-menu.ts +203 -0
  33. package/src/components/pagination/vox-pagination.ts +6 -9
  34. package/src/components/radio/vox-radio-group.ts +6 -17
  35. package/src/components/record-list/vox-record-list.ts +263 -0
  36. package/src/components/select/vox-select.ts +0 -3
  37. package/src/components/sidenav/vox-sidenav.ts +1 -16
  38. package/src/components/step-indicator/vox-step-indicator.ts +2 -16
  39. package/src/components/tabs/vox-tabs.ts +2 -26
  40. package/src/components/textarea/vox-textarea.ts +0 -2
  41. package/src/index.ts +5 -0
  42. package/src/internal/field.ts +2 -20
  43. package/src/tokens/tokens.css +7 -7
@@ -1,6 +1,5 @@
1
- import { html, css, nothing } from 'lit';
1
+ import { html, css } from 'lit';
2
2
  import { customElement, property } from 'lit/decorators.js';
3
- import { ifDefined } from 'lit/directives/if-defined.js';
4
3
  import { VoxFieldElement, fieldStyles } from '../../internal/field.js';
5
4
  import type { VoxRadio } from './vox-radio.js';
6
5
 
@@ -36,9 +35,8 @@ export class VoxRadioGroup extends VoxFieldElement {
36
35
 
37
36
  updated() {
38
37
  this.internals.setFormValue(this.value || null);
39
- this.invalid = this.required && !this.value;
40
38
  this.internals.setValidity(
41
- this.invalid ? { valueMissing: true } : {},
39
+ this.required && !this.value ? { valueMissing: true } : {},
42
40
  'Please select an option.',
43
41
  this,
44
42
  );
@@ -48,12 +46,10 @@ export class VoxRadioGroup extends VoxFieldElement {
48
46
  private syncRadios() {
49
47
  const radios = this.radios;
50
48
  const anyChecked = radios.some((r) => r.value === this.value && this.value !== '');
51
- const firstEnabled = radios.find((r) => !r.disabled);
52
- radios.forEach((radio) => {
49
+ radios.forEach((radio, index) => {
53
50
  radio.checked = this.value !== '' && radio.value === this.value;
54
- // Roving tabindex: the checked radio is tabbable, else the first
55
- // enabled one (a disabled radio can't receive focus at all).
56
- const tabbable = anyChecked ? radio.checked : radio === firstEnabled;
51
+ // Roving tabindex: the checked radio is tabbable, else the first one.
52
+ const tabbable = anyChecked ? radio.checked : index === 0;
57
53
  radio.shadowRoot
58
54
  ?.querySelector('.radio')
59
55
  ?.setAttribute('tabindex', tabbable ? '0' : '-1');
@@ -93,14 +89,7 @@ export class VoxRadioGroup extends VoxFieldElement {
93
89
 
94
90
  render() {
95
91
  return html`
96
- <div
97
- class="field"
98
- role="radiogroup"
99
- aria-label=${this.label || nothing}
100
- aria-describedby=${ifDefined(this.noteId)}
101
- aria-invalid=${this.invalid ? 'true' : 'false'}
102
- aria-required=${this.required ? 'true' : 'false'}
103
- >
92
+ <div class="field" role="radiogroup" aria-label=${this.label}>
104
93
  ${this.label
105
94
  ? html`<span class="label">
106
95
  ${this.label}${this.required
@@ -0,0 +1,263 @@
1
+ import { LitElement, html, css, nothing } from 'lit';
2
+ import { customElement, property } from 'lit/decorators.js';
3
+
4
+ export type RecordListItemSize = 'md' | 'sm';
5
+
6
+ /**
7
+ * A list of linked records — search results, an admin index, anything
8
+ * that's a stack of "go to this thing" rows. A grid (heading, meta, end,
9
+ * plus a trailing filler column) is always defined here; regular
10
+ * `size="md"` items ignore the first three tracks and just render their own
11
+ * full-width flex row, while `size="sm"` items opt into them via `subgrid`
12
+ * so their columns line up into a real table across every row, sized to
13
+ * each column's widest cell like a native `<table>`. The filler column
14
+ * absorbs whatever width the three content columns don't use, so every
15
+ * item — table row or not — still spans the list's full width instead of
16
+ * shrinking to fit its content.
17
+ *
18
+ * @slot - `<vox-record-list-item>` elements.
19
+ */
20
+ @customElement('vox-record-list')
21
+ export class VoxRecordList extends LitElement {
22
+ static styles = css`
23
+ :host {
24
+ display: grid;
25
+ grid-template-columns: repeat(3, max-content) 1fr;
26
+ column-gap: var(--vox-space-4);
27
+ }
28
+ `;
29
+
30
+ render() {
31
+ return html`<slot></slot>`;
32
+ }
33
+ }
34
+
35
+ /**
36
+ * One row in a `<vox-record-list>`: a heading link plus optional metadata
37
+ * — pass `<vox-datum>` elements (or anything else) into the default slot.
38
+ * Becomes fully navigable (heading and trailing arrow both link to `href`)
39
+ * when `href` is set; otherwise renders as a static row.
40
+ *
41
+ * At `size="sm"`, the arrow drops out and heading/meta/`end` become real
42
+ * table columns (via CSS `subgrid`, aligned against every other
43
+ * `size="sm"` row in the same `<vox-record-list>`) — suited to a dense
44
+ * activity/run log rather than an indexed record. `heading` and the meta
45
+ * column each link to `href` individually, since a single grid row can't
46
+ * itself be one native link the way a block row can.
47
+ *
48
+ * @slot - Metadata, e.g. `<vox-datum>` elements.
49
+ * @slot end - Trailing content after the meta, e.g. a `<vox-badge>` status.
50
+ */
51
+ @customElement('vox-record-list-item')
52
+ export class VoxRecordListItem extends LitElement {
53
+ @property() heading = '';
54
+ @property() href?: string;
55
+ @property({ reflect: true }) size: RecordListItemSize = 'md';
56
+
57
+ static styles = css`
58
+ :host {
59
+ display: block;
60
+ grid-column: 1 / -1;
61
+ font-family: var(--vox-font-family-base);
62
+ }
63
+
64
+ .row {
65
+ display: flex;
66
+ align-items: center;
67
+ justify-content: space-between;
68
+ gap: var(--vox-space-4);
69
+ padding: var(--vox-space-4) 0;
70
+ border-bottom: 1px solid var(--vox-color-divider);
71
+ }
72
+
73
+ :host(:last-child) .row {
74
+ border-bottom: none;
75
+ }
76
+
77
+ .main {
78
+ display: flex;
79
+ flex-direction: column;
80
+ gap: var(--vox-space-1);
81
+ min-width: 0;
82
+ }
83
+
84
+ .heading {
85
+ font-size: 16px;
86
+ font-weight: 600;
87
+ line-height: 1.4;
88
+ }
89
+
90
+ a.heading {
91
+ color: var(--vox-color-brand-1);
92
+ text-decoration: none;
93
+ }
94
+
95
+ a.heading:hover,
96
+ a.heading:focus-visible {
97
+ text-decoration: underline;
98
+ }
99
+
100
+ a.heading:focus-visible {
101
+ outline: 2px solid var(--vox-color-brand-1);
102
+ outline-offset: 2px;
103
+ }
104
+
105
+ span.heading {
106
+ color: var(--vox-color-text-1);
107
+ }
108
+
109
+ .meta {
110
+ display: flex;
111
+ flex-wrap: wrap;
112
+ align-items: center;
113
+ gap: var(--vox-space-4);
114
+ }
115
+
116
+ .meta:not(.has-meta) {
117
+ display: none;
118
+ }
119
+
120
+ .arrow {
121
+ display: flex;
122
+ flex: none;
123
+ align-items: center;
124
+ justify-content: center;
125
+ width: 36px;
126
+ height: 36px;
127
+ border-radius: var(--vox-radius-full);
128
+ background-color: var(--vox-color-bg-soft);
129
+ color: var(--vox-color-brand-1);
130
+ text-decoration: none;
131
+ transition: background-color var(--vox-transition-fast);
132
+ }
133
+
134
+ .arrow:hover {
135
+ background-color: var(--vox-color-brand-soft);
136
+ }
137
+
138
+ .arrow:focus-visible {
139
+ outline: 2px solid var(--vox-color-brand-1);
140
+ outline-offset: 2px;
141
+ }
142
+
143
+ .arrow svg {
144
+ width: 16px;
145
+ height: 16px;
146
+ }
147
+
148
+ .end:not(.has-end) {
149
+ display: none;
150
+ }
151
+
152
+ /* Small size: heading, meta, and end each become their own subgrid
153
+ column, aligned against the same column in every other size="sm"
154
+ row in the parent <vox-record-list> — the arrow drops out since
155
+ there's no room for it at this density, and the whole "row" is
156
+ really 3 separate grid cells rather than one box, so heading and
157
+ the meta cell each link to href individually. */
158
+ :host([size='sm']) {
159
+ display: grid;
160
+ grid-template-columns: subgrid;
161
+ grid-column: 1 / -1;
162
+ align-items: baseline;
163
+ column-gap: var(--vox-space-4);
164
+ padding: var(--vox-space-2) 0;
165
+ border-bottom: 1px solid var(--vox-color-divider);
166
+ font-size: 13px;
167
+ }
168
+
169
+ :host([size='sm']:last-child) {
170
+ border-bottom: none;
171
+ }
172
+
173
+ :host([size='sm']) .meta {
174
+ flex-wrap: nowrap;
175
+ gap: var(--vox-space-2);
176
+ }
177
+
178
+ .cell {
179
+ color: var(--vox-color-text-2);
180
+ text-decoration: none;
181
+ }
182
+
183
+ a.cell:hover,
184
+ a.cell:focus-visible {
185
+ text-decoration: underline;
186
+ }
187
+
188
+ a.cell:focus-visible {
189
+ outline: 2px solid var(--vox-color-brand-1);
190
+ outline-offset: 2px;
191
+ }
192
+ `;
193
+
194
+ private hasMeta = false;
195
+ private hasEnd = false;
196
+
197
+ private handleMetaSlotChange(event: Event) {
198
+ const slot = event.target as HTMLSlotElement;
199
+ this.hasMeta = slot.assignedNodes({ flatten: true }).length > 0;
200
+ this.requestUpdate();
201
+ }
202
+
203
+ private handleEndSlotChange(event: Event) {
204
+ const slot = event.target as HTMLSlotElement;
205
+ this.hasEnd = slot.assignedNodes({ flatten: true }).length > 0;
206
+ this.requestUpdate();
207
+ }
208
+
209
+ render() {
210
+ if (this.size === 'sm') {
211
+ const metaSlot = html`
212
+ <span class="meta ${this.hasMeta ? 'has-meta' : ''}">
213
+ <slot @slotchange=${this.handleMetaSlotChange}></slot>
214
+ </span>
215
+ `;
216
+
217
+ return html`
218
+ ${this.href
219
+ ? html`<a class="cell" href=${this.href}>${this.heading}</a>`
220
+ : html`<span class="cell">${this.heading}</span>`}
221
+ ${this.hasMeta && this.href
222
+ ? html`<a class="cell" href=${this.href}>${metaSlot}</a>`
223
+ : html`<span class="cell">${metaSlot}</span>`}
224
+ <span class="cell">
225
+ <slot name="end" @slotchange=${this.handleEndSlotChange}></slot>
226
+ </span>
227
+ `;
228
+ }
229
+
230
+ return html`
231
+ <div class="row">
232
+ <div class="main">
233
+ ${this.href
234
+ ? html`<a class="heading" href=${this.href}>${this.heading}</a>`
235
+ : html`<span class="heading">${this.heading}</span>`}
236
+ <div class="meta ${this.hasMeta ? 'has-meta' : ''}">
237
+ <slot @slotchange=${this.handleMetaSlotChange}></slot>
238
+ </div>
239
+ </div>
240
+ <span class="end ${this.hasEnd ? 'has-end' : ''}">
241
+ <slot name="end" @slotchange=${this.handleEndSlotChange}></slot>
242
+ </span>
243
+ ${this.href
244
+ ? html`
245
+ <a class="arrow" href=${this.href} aria-label="View ${this.heading}">
246
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
247
+ <path d="M5 12h14" />
248
+ <path d="m13 6 6 6-6 6" />
249
+ </svg>
250
+ </a>
251
+ `
252
+ : nothing}
253
+ </div>
254
+ `;
255
+ }
256
+ }
257
+
258
+ declare global {
259
+ interface HTMLElementTagNameMap {
260
+ 'vox-record-list': VoxRecordList;
261
+ 'vox-record-list-item': VoxRecordListItem;
262
+ }
263
+ }
@@ -1,6 +1,5 @@
1
1
  import { html, css } from 'lit';
2
2
  import { customElement, property, query } from 'lit/decorators.js';
3
- import { ifDefined } from 'lit/directives/if-defined.js';
4
3
  import { VoxFieldElement, fieldStyles } from '../../internal/field.js';
5
4
 
6
5
  /**
@@ -74,8 +73,6 @@ export class VoxSelect extends VoxFieldElement {
74
73
  class="control"
75
74
  ?required=${this.required}
76
75
  ?disabled=${this.disabled}
77
- aria-describedby=${ifDefined(this.noteId)}
78
- aria-invalid=${this.invalid ? 'true' : 'false'}
79
76
  @change=${this.handleChange}
80
77
  ></select>
81
78
  ${this.renderNote()}
@@ -94,14 +94,12 @@ export class VoxSidenav extends LitElement {
94
94
  super.connectedCallback();
95
95
  document.addEventListener('click', this.handleOutsideClick);
96
96
  this.addEventListener('keydown', this.handleKeydown);
97
- this.addEventListener('focusout', this.handleFocusOut);
98
97
  }
99
98
 
100
99
  disconnectedCallback() {
101
100
  super.disconnectedCallback();
102
101
  document.removeEventListener('click', this.handleOutsideClick);
103
102
  this.removeEventListener('keydown', this.handleKeydown);
104
- this.removeEventListener('focusout', this.handleFocusOut);
105
103
  }
106
104
 
107
105
  private handleOutsideClick = (event: MouseEvent) => {
@@ -110,14 +108,6 @@ export class VoxSidenav extends LitElement {
110
108
  }
111
109
  };
112
110
 
113
- /** Close the mobile menu once focus tabs past its last item. */
114
- private handleFocusOut = (event: FocusEvent) => {
115
- const next = event.relatedTarget as Node | null;
116
- if (this.mobileOpen && !(next && this.contains(next))) {
117
- this.mobileOpen = false;
118
- }
119
- };
120
-
121
111
  private handleKeydown = (event: KeyboardEvent) => {
122
112
  if (event.key === 'Escape' && this.mobileOpen) {
123
113
  this.mobileOpen = false;
@@ -180,13 +170,9 @@ export class VoxSidenav extends LitElement {
180
170
  */
181
171
  @customElement('vox-sidenav-group')
182
172
  export class VoxSidenavGroup extends LitElement {
183
- private static nextId = 0;
184
-
185
173
  @property() heading = '';
186
174
  @property({ type: Boolean, reflect: true }) open = false;
187
175
 
188
- private readonly itemsId = `vox-sidenav-group-items-${VoxSidenavGroup.nextId++}`;
189
-
190
176
  static styles = css`
191
177
  :host {
192
178
  display: block;
@@ -254,7 +240,6 @@ export class VoxSidenavGroup extends LitElement {
254
240
  <button
255
241
  class="trigger"
256
242
  aria-expanded=${this.open ? 'true' : 'false'}
257
- aria-controls=${this.itemsId}
258
243
  @click=${this.toggle}
259
244
  >
260
245
  ${this.heading}
@@ -262,7 +247,7 @@ export class VoxSidenavGroup extends LitElement {
262
247
  <path d="m9 6 6 6-6 6" />
263
248
  </svg>
264
249
  </button>
265
- <div id=${this.itemsId} class="items"><slot></slot></div>
250
+ <div class="items"><slot></slot></div>
266
251
  `;
267
252
  }
268
253
  }
@@ -1,4 +1,4 @@
1
- import { LitElement, html, css, nothing } from 'lit';
1
+ import { LitElement, html, css } from 'lit';
2
2
  import { customElement, property } from 'lit/decorators.js';
3
3
 
4
4
  export type StepState = 'complete' | 'current' | 'upcoming';
@@ -122,15 +122,6 @@ export class VoxStep extends LitElement {
122
122
  font-weight: 600;
123
123
  color: var(--vox-color-text-1);
124
124
  }
125
-
126
- .visually-hidden {
127
- position: absolute;
128
- width: 1px;
129
- height: 1px;
130
- overflow: hidden;
131
- clip: rect(0 0 0 0);
132
- white-space: nowrap;
133
- }
134
125
  `;
135
126
 
136
127
  render() {
@@ -147,12 +138,7 @@ export class VoxStep extends LitElement {
147
138
  </svg>`
148
139
  : this.number}
149
140
  </span>
150
- <span class="label">
151
- ${this.label}
152
- ${this.state !== 'current'
153
- ? html`<span class="visually-hidden"> (${this.state})</span>`
154
- : nothing}
155
- </span>
141
+ <span class="label">${this.label}</span>
156
142
  </div>
157
143
  `;
158
144
  }
@@ -65,18 +65,11 @@ export class VoxTabs extends LitElement {
65
65
  }
66
66
 
67
67
  private handleKeydown(event: KeyboardEvent) {
68
- const tabs = this.tabs;
69
- if (event.key === 'Home' || event.key === 'End') {
70
- event.preventDefault();
71
- const target = tabs[event.key === 'Home' ? 0 : tabs.length - 1];
72
- target.select();
73
- target.focusTab();
74
- return;
75
- }
76
68
  const deltas: Record<string, number> = { ArrowRight: 1, ArrowLeft: -1 };
77
69
  const delta = deltas[event.key];
78
70
  if (!delta) return;
79
71
  event.preventDefault();
72
+ const tabs = this.tabs;
80
73
  const index = tabs.findIndex((t) => t.selected);
81
74
  const next = tabs[(index + delta + tabs.length) % tabs.length];
82
75
  next.select();
@@ -157,11 +150,9 @@ export class VoxTab extends LitElement {
157
150
  render() {
158
151
  return html`
159
152
  <button
160
- id="tab-${this.panel}"
161
153
  class="tab"
162
154
  role="tab"
163
155
  aria-selected=${this.selected ? 'true' : 'false'}
164
- aria-controls="panel-${this.panel}"
165
156
  tabindex=${this.selected ? '0' : '-1'}
166
157
  @click=${this.select}
167
158
  >
@@ -194,25 +185,10 @@ export class VoxTabPanel extends LitElement {
194
185
  :host([active]) {
195
186
  display: block;
196
187
  }
197
-
198
- div:focus-visible {
199
- outline: 2px solid var(--vox-color-brand-1);
200
- outline-offset: 2px;
201
- border-radius: var(--vox-radius-sm);
202
- }
203
188
  `;
204
189
 
205
190
  render() {
206
- return html`
207
- <div
208
- id="panel-${this.name}"
209
- role="tabpanel"
210
- aria-labelledby="tab-${this.name}"
211
- tabindex="0"
212
- >
213
- <slot></slot>
214
- </div>
215
- `;
191
+ return html`<div role="tabpanel"><slot></slot></div>`;
216
192
  }
217
193
  }
218
194
 
@@ -59,8 +59,6 @@ export class VoxTextarea extends VoxFieldElement {
59
59
  ?required=${this.required}
60
60
  ?readonly=${this.readonly}
61
61
  ?disabled=${this.disabled}
62
- aria-describedby=${ifDefined(this.noteId)}
63
- aria-invalid=${this.invalid ? 'true' : 'false'}
64
62
  @input=${this.handleInput}
65
63
  @change=${this.handleChange}
66
64
  ></textarea>
package/src/index.ts CHANGED
@@ -38,6 +38,7 @@ export { VoxToc, VoxTocItem } from './components/toc/vox-toc.js';
38
38
  export { VoxDialog } from './components/dialog/vox-dialog.js';
39
39
  export { VoxDisclosure } from './components/disclosure/vox-disclosure.js';
40
40
  export { VoxDropdown } from './components/dropdown/vox-dropdown.js';
41
+ export { VoxMenu } from './components/menu/vox-menu.js';
41
42
 
42
43
  // Page content
43
44
  export { VoxAccordion, VoxAccordionItem } from './components/accordion/vox-accordion.js';
@@ -47,6 +48,7 @@ export { VoxCalendarTile } from './components/calendar-tile/vox-calendar-tile.js
47
48
  export { VoxCallout } from './components/callout/vox-callout.js';
48
49
  export { VoxCard } from './components/card/vox-card.js';
49
50
  export { VoxCtaBand } from './components/cta-band/vox-cta-band.js';
51
+ export { VoxDatum } from './components/datum/vox-datum.js';
50
52
  export { VoxEmptyState } from './components/empty-state/vox-empty-state.js';
51
53
  export { VoxFooter, VoxFooterColumn } from './components/footer/vox-footer.js';
52
54
  export { VoxGrid } from './components/grid/vox-grid.js';
@@ -55,6 +57,7 @@ export { VoxLinkHub, VoxLinkHubItem } from './components/link-hub/vox-link-hub.j
55
57
  export { VoxLoader } from './components/loader/vox-loader.js';
56
58
  export { VoxPagination } from './components/pagination/vox-pagination.js';
57
59
  export { VoxQuote } from './components/quote/vox-quote.js';
60
+ export { VoxRecordList, VoxRecordListItem } from './components/record-list/vox-record-list.js';
58
61
  export { VoxSponsor, VoxSponsorTier } from './components/sponsor/vox-sponsor.js';
59
62
  export { VoxStat } from './components/stat/vox-stat.js';
60
63
  export { VoxStepIndicator, VoxStep } from './components/step-indicator/vox-step-indicator.js';
@@ -69,3 +72,5 @@ export type { AlertVariant } from './components/alert/vox-alert.js';
69
72
  export type { AvatarSize } from './components/avatar/vox-avatar.js';
70
73
  export type { LoaderSize } from './components/loader/vox-loader.js';
71
74
  export type { StepState } from './components/step-indicator/vox-step-indicator.js';
75
+ export type { MenuPlacement } from './components/menu/vox-menu.js';
76
+ export type { RecordListItemSize } from './components/record-list/vox-record-list.js';
@@ -1,5 +1,5 @@
1
1
  import { LitElement, html, css, nothing } from 'lit';
2
- import { property, state } from 'lit/decorators.js';
2
+ import { property } from 'lit/decorators.js';
3
3
 
4
4
  /**
5
5
  * Shared base for form-associated VoxBlocks controls. Uses ElementInternals
@@ -10,9 +10,6 @@ export class VoxFieldElement extends LitElement {
10
10
 
11
11
  protected internals: ElementInternals;
12
12
 
13
- /** Whether the control currently fails validation; drives `aria-invalid`. */
14
- @state() protected invalid = false;
15
-
16
13
  @property() name = '';
17
14
  @property() label = '';
18
15
  @property() note = '';
@@ -49,7 +46,6 @@ export class VoxFieldElement extends LitElement {
49
46
  inner: HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement,
50
47
  ) {
51
48
  this.internals.setValidity(inner.validity, inner.validationMessage, inner);
52
- this.invalid = !inner.validity.valid;
53
49
  }
54
50
 
55
51
  protected renderLabel(forId: string) {
@@ -63,13 +59,8 @@ export class VoxFieldElement extends LitElement {
63
59
  `;
64
60
  }
65
61
 
66
- /** `note`'s id when present, for `aria-describedby` on the native control. */
67
- protected get noteId(): string | undefined {
68
- return this.note ? 'note' : undefined;
69
- }
70
-
71
62
  protected renderNote() {
72
- return this.note ? html`<p class="note" id="note">${this.note}</p>` : nothing;
63
+ return this.note ? html`<p class="note">${this.note}</p>` : nothing;
73
64
  }
74
65
  }
75
66
 
@@ -128,15 +119,6 @@ export const fieldStyles = css`
128
119
  box-shadow: 0 0 0 3px var(--vox-color-brand-soft);
129
120
  }
130
121
 
131
- .control[aria-invalid='true'] {
132
- border-color: var(--vox-color-danger-1);
133
- }
134
-
135
- .control[aria-invalid='true']:focus {
136
- border-color: var(--vox-color-danger-1);
137
- box-shadow: 0 0 0 3px var(--vox-color-danger-soft);
138
- }
139
-
140
122
  /* Corner flattening when placed inside a <vox-input-group>. */
141
123
  :host([data-vox-group]) .control {
142
124
  border-radius: 0;
@@ -29,8 +29,8 @@
29
29
  --vox-color-warning-1: #915930;
30
30
  --vox-color-warning-soft: rgba(234, 179, 8, 0.14);
31
31
  --vox-color-danger-1: #b8272c;
32
- --vox-color-danger-2: #c9333a;
33
- --vox-color-danger-3: #d5393e;
32
+ --vox-color-danger-2: #d5393e;
33
+ --vox-color-danger-3: #e0575b;
34
34
  --vox-color-danger-soft: rgba(244, 63, 94, 0.14);
35
35
 
36
36
  /* Surfaces */
@@ -42,12 +42,12 @@
42
42
  /* Text */
43
43
  --vox-color-text-1: #12262c;
44
44
  --vox-color-text-2: #4c6469;
45
- --vox-color-text-3: #5c7377;
45
+ --vox-color-text-3: #86999c;
46
46
  --vox-color-text-inverse: #ffffff;
47
47
 
48
48
  /* Lines */
49
49
  --vox-color-divider: rgba(18, 38, 44, 0.12);
50
- --vox-color-border: rgba(18, 38, 44, 0.55);
50
+ --vox-color-border: rgba(18, 38, 44, 0.28);
51
51
 
52
52
  /* Typography */
53
53
  --vox-font-family-base: 'Inter', ui-sans-serif, system-ui, -apple-system,
@@ -92,7 +92,7 @@
92
92
  --vox-color-warning-1: #f9b44e;
93
93
  --vox-color-warning-soft: rgba(234, 179, 8, 0.16);
94
94
  --vox-color-danger-1: #f66f81;
95
- --vox-color-danger-2: #d13b52;
95
+ --vox-color-danger-2: #f14158;
96
96
  --vox-color-danger-3: #b62a3c;
97
97
  --vox-color-danger-soft: rgba(244, 63, 94, 0.16);
98
98
 
@@ -103,11 +103,11 @@
103
103
 
104
104
  --vox-color-text-1: #dff3f0;
105
105
  --vox-color-text-2: #9fb8bb;
106
- --vox-color-text-3: #88999c;
106
+ --vox-color-text-3: #5f7275;
107
107
  --vox-color-text-inverse: #ffffff;
108
108
 
109
109
  --vox-color-divider: rgba(159, 184, 187, 0.24);
110
- --vox-color-border: rgba(159, 184, 187, 0.6);
110
+ --vox-color-border: rgba(159, 184, 187, 0.5);
111
111
 
112
112
  --vox-shadow-1: 0 1px 2px rgba(0, 0, 0, 0.2), 0 1px 2px rgba(0, 0, 0, 0.24);
113
113
  --vox-shadow-2: 0 3px 12px rgba(0, 0, 0, 0.26), 0 1px 4px rgba(0, 0, 0, 0.26);