@openvoxproject/voxblocks 0.11.0 → 0.12.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 (41) hide show
  1. package/README.md +1 -1
  2. package/dist/cdn/voxblocks.css +1 -1
  3. package/dist/cdn/voxblocks.js +2011 -1475
  4. package/dist/types/components/accordion/vox-accordion.d.ts +0 -2
  5. package/dist/types/components/code-block/languages.d.ts +28 -0
  6. package/dist/types/components/code-block/vox-code-block.d.ts +34 -0
  7. package/dist/types/components/disclosure/vox-disclosure.d.ts +0 -2
  8. package/dist/types/components/dropdown/vox-dropdown.d.ts +0 -3
  9. package/dist/types/components/header/vox-header.d.ts +0 -2
  10. package/dist/types/components/pagination/vox-pagination.d.ts +4 -6
  11. package/dist/types/components/sidenav/vox-sidenav.d.ts +0 -4
  12. package/dist/types/index.d.ts +2 -0
  13. package/dist/types/internal/field.d.ts +0 -4
  14. package/dist/voxblocks.css +1 -1
  15. package/dist/voxblocks.js +1491 -955
  16. package/package.json +1 -1
  17. package/src/components/accordion/vox-accordion.ts +1 -6
  18. package/src/components/alert/vox-alert.ts +1 -5
  19. package/src/components/avatar/vox-avatar.ts +2 -9
  20. package/src/components/card/vox-card.ts +1 -1
  21. package/src/components/checkbox/vox-checkbox.ts +3 -4
  22. package/src/components/code-block/languages.ts +259 -0
  23. package/src/components/code-block/vox-code-block.ts +302 -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 +8 -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/pagination/vox-pagination.ts +6 -9
  33. package/src/components/radio/vox-radio-group.ts +6 -17
  34. package/src/components/select/vox-select.ts +0 -3
  35. package/src/components/sidenav/vox-sidenav.ts +1 -16
  36. package/src/components/step-indicator/vox-step-indicator.ts +2 -16
  37. package/src/components/tabs/vox-tabs.ts +2 -26
  38. package/src/components/textarea/vox-textarea.ts +0 -2
  39. package/src/index.ts +2 -0
  40. package/src/internal/field.ts +2 -20
  41. package/src/tokens/tokens.css +24 -7
@@ -9,13 +9,9 @@ import { customElement, property } from 'lit/decorators.js';
9
9
  */
10
10
  @customElement('vox-disclosure')
11
11
  export class VoxDisclosure extends LitElement {
12
- private static nextId = 0;
13
-
14
12
  @property() summary = 'Show details';
15
13
  @property({ type: Boolean, reflect: true }) open = false;
16
14
 
17
- private readonly panelId = `vox-disclosure-panel-${VoxDisclosure.nextId++}`;
18
-
19
15
  static styles = css`
20
16
  :host {
21
17
  display: block;
@@ -81,7 +77,6 @@ export class VoxDisclosure extends LitElement {
81
77
  <button
82
78
  class="trigger"
83
79
  aria-expanded=${this.open ? 'true' : 'false'}
84
- aria-controls=${this.panelId}
85
80
  @click=${this.toggle}
86
81
  >
87
82
  <svg class="chevron" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
@@ -89,7 +84,7 @@ export class VoxDisclosure extends LitElement {
89
84
  </svg>
90
85
  ${this.summary}
91
86
  </button>
92
- <div id=${this.panelId} class="panel" ?hidden=${!this.open}>
87
+ <div class="panel" ?hidden=${!this.open}>
93
88
  <slot></slot>
94
89
  </div>
95
90
  `;
@@ -122,14 +122,12 @@ export class VoxDropdown extends LitElement {
122
122
  super.connectedCallback();
123
123
  document.addEventListener('click', this.handleOutsideClick);
124
124
  this.addEventListener('keydown', this.handleKeydown);
125
- this.addEventListener('focusout', this.handleFocusOut);
126
125
  }
127
126
 
128
127
  disconnectedCallback() {
129
128
  super.disconnectedCallback();
130
129
  document.removeEventListener('click', this.handleOutsideClick);
131
130
  this.removeEventListener('keydown', this.handleKeydown);
132
- this.removeEventListener('focusout', this.handleFocusOut);
133
131
  }
134
132
 
135
133
  private handleOutsideClick = (event: MouseEvent) => {
@@ -138,20 +136,6 @@ export class VoxDropdown extends LitElement {
138
136
  }
139
137
  };
140
138
 
141
- /** Close when focus leaves the component entirely (e.g. Tab past the last item). */
142
- private handleFocusOut = (event: FocusEvent) => {
143
- const next = event.relatedTarget as Node | null;
144
- if (this.open && !(next && this.contains(next))) {
145
- this.open = false;
146
- }
147
- };
148
-
149
- private focusItem(index: number) {
150
- const items = [...this.querySelectorAll<HTMLElement>('a, button')];
151
- if (items.length === 0) return;
152
- items[(index + items.length) % items.length].focus();
153
- }
154
-
155
139
  private handleKeydown = (event: KeyboardEvent) => {
156
140
  if (event.key === 'Escape' && this.open) {
157
141
  this.open = false;
@@ -159,27 +143,20 @@ export class VoxDropdown extends LitElement {
159
143
  return;
160
144
  }
161
145
 
162
- if (event.key === 'ArrowDown' || event.key === 'ArrowUp') {
146
+ if ((event.key === 'ArrowDown' || event.key === 'ArrowUp') && this.open) {
163
147
  event.preventDefault();
164
- const delta = event.key === 'ArrowDown' ? 1 : -1;
165
- if (!this.open) {
166
- this.open = true;
167
- this.updateComplete.then(() =>
168
- this.focusItem(event.key === 'ArrowDown' ? 0 : -1),
169
- );
170
- return;
171
- }
172
148
  const items = [...this.querySelectorAll<HTMLElement>('a, button')];
173
- const index = items.indexOf(document.activeElement as HTMLElement);
174
- this.focusItem(Math.max(index, 0) + delta);
149
+ if (items.length === 0) return;
150
+ const active = document.activeElement as HTMLElement;
151
+ const index = items.indexOf(active);
152
+ const delta = event.key === 'ArrowDown' ? 1 : -1;
153
+ const next = items[(Math.max(index, 0) + delta + items.length) % items.length];
154
+ next.focus();
175
155
  }
176
156
  };
177
157
 
178
158
  private toggle() {
179
159
  this.open = !this.open;
180
- if (this.open) {
181
- this.updateComplete.then(() => this.focusItem(0));
182
- }
183
160
  }
184
161
 
185
162
  render() {
@@ -188,7 +165,6 @@ export class VoxDropdown extends LitElement {
188
165
  class="trigger"
189
166
  aria-expanded=${this.open ? 'true' : 'false'}
190
167
  aria-haspopup="true"
191
- aria-controls="menu"
192
168
  @click=${this.toggle}
193
169
  >
194
170
  ${this.label}
@@ -196,7 +172,7 @@ export class VoxDropdown extends LitElement {
196
172
  <path d="m6 9 6 6 6-6" />
197
173
  </svg>
198
174
  </button>
199
- <div id="menu" class="menu">
175
+ <div class="menu">
200
176
  <slot @click=${() => (this.open = false)}></slot>
201
177
  </div>
202
178
  `;
@@ -60,7 +60,7 @@ export class VoxEmptyState extends LitElement {
60
60
  render() {
61
61
  return html`
62
62
  <div class="empty">
63
- <div class="icon" aria-hidden="true"><slot name="icon"></slot></div>
63
+ <div class="icon"><slot name="icon"></slot></div>
64
64
  <h3 class="heading">${this.heading}</h3>
65
65
  <div class="body"><slot></slot></div>
66
66
  <div class="actions"><slot name="actions"></slot></div>
@@ -84,9 +84,8 @@ export class VoxFileInput extends VoxFieldElement {
84
84
  for (const file of files) data.append(this.name, file);
85
85
  this.internals.setFormValue(files.length > 0 ? data : null);
86
86
 
87
- this.invalid = this.required && files.length === 0;
88
87
  this.internals.setValidity(
89
- this.invalid ? { valueMissing: true } : {},
88
+ this.required && files.length === 0 ? { valueMissing: true } : {},
90
89
  'Please select a file.',
91
90
  this.inputEl,
92
91
  );
@@ -104,8 +103,6 @@ export class VoxFileInput extends VoxFieldElement {
104
103
  accept=${ifDefined(this.accept)}
105
104
  ?multiple=${this.multiple}
106
105
  ?disabled=${this.disabled}
107
- aria-describedby=${ifDefined(this.noteId)}
108
- aria-invalid=${this.invalid ? 'true' : 'false'}
109
106
  @change=${this.handleChange}
110
107
  />
111
108
  <span class="picker">
@@ -157,14 +157,12 @@ export class VoxHeader extends LitElement {
157
157
  super.connectedCallback();
158
158
  document.addEventListener('click', this.handleOutsideClick);
159
159
  this.addEventListener('keydown', this.handleKeydown);
160
- this.addEventListener('focusout', this.handleFocusOut);
161
160
  }
162
161
 
163
162
  disconnectedCallback() {
164
163
  super.disconnectedCallback();
165
164
  document.removeEventListener('click', this.handleOutsideClick);
166
165
  this.removeEventListener('keydown', this.handleKeydown);
167
- this.removeEventListener('focusout', this.handleFocusOut);
168
166
  }
169
167
 
170
168
  private handleOutsideClick = (event: MouseEvent) => {
@@ -173,14 +171,6 @@ export class VoxHeader extends LitElement {
173
171
  }
174
172
  };
175
173
 
176
- /** Close the mobile menu once focus tabs past its last item. */
177
- private handleFocusOut = (event: FocusEvent) => {
178
- const next = event.relatedTarget as Node | null;
179
- if (this.mobileOpen && !(next && this.contains(next))) {
180
- this.mobileOpen = false;
181
- }
182
- };
183
-
184
174
  private handleKeydown = (event: KeyboardEvent) => {
185
175
  if (event.key === 'Escape' && this.mobileOpen) {
186
176
  this.mobileOpen = false;
@@ -56,8 +56,6 @@ export class VoxInput extends VoxFieldElement {
56
56
  ?readonly=${this.readonly}
57
57
  ?disabled=${this.disabled}
58
58
  aria-label=${this.label ? nothing : 'text input'}
59
- aria-describedby=${ifDefined(this.noteId)}
60
- aria-invalid=${this.invalid ? 'true' : 'false'}
61
59
  @input=${this.handleInput}
62
60
  @change=${this.handleChange}
63
61
  />
@@ -106,7 +106,7 @@ export class VoxLinkHubItem extends LitElement {
106
106
  return html`
107
107
  <a href=${this.href}>
108
108
  <span class="heading">
109
- <span class="icon ${this.hasIcon ? 'has-icon' : ''}" aria-hidden="true">
109
+ <span class="icon ${this.hasIcon ? 'has-icon' : ''}">
110
110
  <slot name="icon" @slotchange=${this.handleIconSlotChange}></slot>
111
111
  </span>
112
112
  ${this.heading}
@@ -2,20 +2,18 @@ import { LitElement, html, css } from 'lit';
2
2
  import { customElement, property } from 'lit/decorators.js';
3
3
 
4
4
  /**
5
- * Page-number navigation. Children are plain links, with the current
6
- * page as a non-interactive `<span aria-current="page">` — like
7
- * `<vox-breadcrumbs>`, it shouldn't be a link to itself:
5
+ * Page-number navigation. Children are plain links; mark the current
6
+ * page with `aria-current="page"`:
8
7
  *
9
8
  * ```html
10
9
  * <vox-pagination>
11
10
  * <a href="?page=1">1</a>
12
- * <span aria-current="page">2</span>
11
+ * <a href="?page=2" aria-current="page">2</a>
13
12
  * <a href="?page=3">3</a>
14
13
  * </vox-pagination>
15
14
  * ```
16
15
  *
17
- * @slot - `<a>` elements (including any prev/next links) and the
18
- * current-page `<span>`.
16
+ * @slot - `<a>` elements (including any prev/next links).
19
17
  */
20
18
  @customElement('vox-pagination')
21
19
  export class VoxPagination extends LitElement {
@@ -33,8 +31,7 @@ export class VoxPagination extends LitElement {
33
31
  flex-wrap: wrap;
34
32
  }
35
33
 
36
- ::slotted(a),
37
- ::slotted(span) {
34
+ ::slotted(a) {
38
35
  display: inline-flex;
39
36
  align-items: center;
40
37
  justify-content: center;
@@ -56,7 +53,7 @@ export class VoxPagination extends LitElement {
56
53
  background-color: var(--vox-color-brand-soft);
57
54
  }
58
55
 
59
- ::slotted([aria-current='page']) {
56
+ ::slotted(a[aria-current='page']) {
60
57
  background-color: var(--vox-color-brand-3);
61
58
  color: var(--vox-color-text-inverse);
62
59
  font-weight: 600;
@@ -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
@@ -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
@@ -46,6 +46,7 @@ export { VoxBadge } from './components/badge/vox-badge.js';
46
46
  export { VoxCalendarTile } from './components/calendar-tile/vox-calendar-tile.js';
47
47
  export { VoxCallout } from './components/callout/vox-callout.js';
48
48
  export { VoxCard } from './components/card/vox-card.js';
49
+ export { VoxCodeBlock } from './components/code-block/vox-code-block.js';
49
50
  export { VoxCtaBand } from './components/cta-band/vox-cta-band.js';
50
51
  export { VoxEmptyState } from './components/empty-state/vox-empty-state.js';
51
52
  export { VoxFooter, VoxFooterColumn } from './components/footer/vox-footer.js';
@@ -64,6 +65,7 @@ export { VoxTimeline, VoxTimelineItem } from './components/timeline/vox-timeline
64
65
  export type { IconName, IconSize } from './components/icon/vox-icon.js';
65
66
  export type { ButtonVariant, ButtonSize } from './components/button/vox-button.js';
66
67
  export type { CalloutVariant } from './components/callout/vox-callout.js';
68
+ export type { CodeBlockLanguage } from './components/code-block/vox-code-block.js';
67
69
  export type { BadgeVariant } from './components/badge/vox-badge.js';
68
70
  export type { AlertVariant } from './components/alert/vox-alert.js';
69
71
  export type { AvatarSize } from './components/avatar/vox-avatar.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,10 +29,19 @@
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
+ /* Code syntax highlighting — one palette hue per token role */
37
+ --vox-code-comment: var(--vox-palette-gray-700);
38
+ --vox-code-keyword: var(--vox-palette-purple-700);
39
+ --vox-code-string: var(--vox-palette-green-800);
40
+ --vox-code-number: var(--vox-palette-gold-800);
41
+ --vox-code-function: var(--vox-palette-blue-700);
42
+ --vox-code-property: var(--vox-palette-teal-800);
43
+ --vox-code-tag: var(--vox-palette-red-700);
44
+
36
45
  /* Surfaces */
37
46
  --vox-color-bg: #f5faf9;
38
47
  --vox-color-bg-alt: #e8f1ef;
@@ -42,12 +51,12 @@
42
51
  /* Text */
43
52
  --vox-color-text-1: #12262c;
44
53
  --vox-color-text-2: #4c6469;
45
- --vox-color-text-3: #5c7377;
54
+ --vox-color-text-3: #86999c;
46
55
  --vox-color-text-inverse: #ffffff;
47
56
 
48
57
  /* Lines */
49
58
  --vox-color-divider: rgba(18, 38, 44, 0.12);
50
- --vox-color-border: rgba(18, 38, 44, 0.55);
59
+ --vox-color-border: rgba(18, 38, 44, 0.28);
51
60
 
52
61
  /* Typography */
53
62
  --vox-font-family-base: 'Inter', ui-sans-serif, system-ui, -apple-system,
@@ -92,10 +101,18 @@
92
101
  --vox-color-warning-1: #f9b44e;
93
102
  --vox-color-warning-soft: rgba(234, 179, 8, 0.16);
94
103
  --vox-color-danger-1: #f66f81;
95
- --vox-color-danger-2: #d13b52;
104
+ --vox-color-danger-2: #f14158;
96
105
  --vox-color-danger-3: #b62a3c;
97
106
  --vox-color-danger-soft: rgba(244, 63, 94, 0.16);
98
107
 
108
+ --vox-code-comment: var(--vox-palette-gray-400);
109
+ --vox-code-keyword: var(--vox-palette-purple-300);
110
+ --vox-code-string: var(--vox-palette-green-400);
111
+ --vox-code-number: var(--vox-palette-gold-400);
112
+ --vox-code-function: var(--vox-palette-blue-300);
113
+ --vox-code-property: var(--vox-palette-teal-400);
114
+ --vox-code-tag: var(--vox-palette-red-300);
115
+
99
116
  --vox-color-bg: #0d1417;
100
117
  --vox-color-bg-alt: #131b1f;
101
118
  --vox-color-bg-soft: #131b1f;
@@ -103,11 +120,11 @@
103
120
 
104
121
  --vox-color-text-1: #dff3f0;
105
122
  --vox-color-text-2: #9fb8bb;
106
- --vox-color-text-3: #88999c;
123
+ --vox-color-text-3: #5f7275;
107
124
  --vox-color-text-inverse: #ffffff;
108
125
 
109
126
  --vox-color-divider: rgba(159, 184, 187, 0.24);
110
- --vox-color-border: rgba(159, 184, 187, 0.6);
127
+ --vox-color-border: rgba(159, 184, 187, 0.5);
111
128
 
112
129
  --vox-shadow-1: 0 1px 2px rgba(0, 0, 0, 0.2), 0 1px 2px rgba(0, 0, 0, 0.24);
113
130
  --vox-shadow-2: 0 3px 12px rgba(0, 0, 0, 0.26), 0 1px 4px rgba(0, 0, 0, 0.26);