@unofficialbox/box-open-elements 0.24.0 → 0.26.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 (40) hide show
  1. package/README.md +13 -13
  2. package/dist/components/actions/button.d.ts +1 -0
  3. package/dist/components/actions/button.js +13 -0
  4. package/dist/components/forms/checkbox.d.ts +1 -0
  5. package/dist/components/forms/checkbox.js +2 -0
  6. package/dist/components/forms/code-editor.d.ts +28 -3
  7. package/dist/components/forms/code-editor.js +71 -6
  8. package/dist/components/forms/combobox.js +7 -8
  9. package/dist/components/forms/number-input.d.ts +1 -0
  10. package/dist/components/forms/number-input.js +4 -1
  11. package/dist/components/forms/select.d.ts +1 -0
  12. package/dist/components/forms/select.js +3 -1
  13. package/dist/components/forms/text-area.js +5 -7
  14. package/dist/components/forms/text-field.d.ts +1 -0
  15. package/dist/components/forms/text-field.js +2 -0
  16. package/dist/components/navigation/accordion.d.ts +7 -0
  17. package/dist/components/navigation/accordion.js +31 -8
  18. package/dist/components/output/code-block.js +3 -1
  19. package/dist/components/overlays/drawer.js +2 -2
  20. package/dist/foundations/geometry/tokens.d.ts +2 -2
  21. package/dist/foundations/geometry/tokens.js +3 -3
  22. package/dist/foundations/icons/box-iconography.generated.d.ts +472 -1
  23. package/dist/foundations/icons/box-iconography.generated.js +473 -2
  24. package/dist/patterns/flow-builder/flow-builder.d.ts +5 -1
  25. package/dist/patterns/flow-builder/flow-builder.js +18 -7
  26. package/dist/patterns/flow-builder/primitives.d.ts +1 -0
  27. package/dist/patterns/flow-builder/primitives.js +11 -4
  28. package/dist/patterns/process-modeler/bridge.d.ts +10 -0
  29. package/dist/patterns/process-modeler/bridge.js +173 -0
  30. package/dist/patterns/process-modeler/design.d.ts +2 -0
  31. package/dist/patterns/process-modeler/design.js +288 -0
  32. package/dist/patterns/process-modeler/geometry.d.ts +20 -0
  33. package/dist/patterns/process-modeler/geometry.js +330 -0
  34. package/dist/patterns/process-modeler/index.d.ts +2 -0
  35. package/dist/patterns/process-modeler/index.js +2 -0
  36. package/dist/patterns/process-modeler/model.d.ts +106 -1
  37. package/dist/patterns/process-modeler/model.js +2 -2
  38. package/dist/patterns/process-modeler/process-modeler.d.ts +115 -3
  39. package/dist/patterns/process-modeler/process-modeler.js +1864 -128
  40. package/package.json +4 -3
package/README.md CHANGED
@@ -12,7 +12,7 @@
12
12
 
13
13
  Browse the full catalog on the **[live docs site](https://unofficialbox.github.io/box-open-elements)** — live previews, an events/properties inspector, per-framework code snippets (React / Angular / Vue / Svelte / HTML), and the foundations pages.
14
14
 
15
- The library is organized around three layers (see [taxonomy](https://github.com/unofficialbox/box-open-elements/blob/v0.24.0/docs/taxonomy.md) for the canonical model):
15
+ The library is organized around three layers (see [taxonomy](https://github.com/unofficialbox/box-open-elements/blob/v0.26.0/docs/taxonomy.md) for the canonical model):
16
16
 
17
17
  - **Foundations** — design decisions as data: tokens, color, typography, iconography, accessibility, theming
18
18
  - **Components** — accessible Web Components for single controls, organized by category
@@ -77,7 +77,7 @@ selection.toggleSelection("1");
77
77
  - optimized component entrypoints: `@unofficialbox/box-open-elements/<name>`
78
78
  - patterns: `@unofficialbox/box-open-elements/patterns/<area>` and `@unofficialbox/box-open-elements/patterns/<area>/<module>`
79
79
 
80
- See the versioned [API guidelines](https://github.com/unofficialbox/box-open-elements/blob/v0.24.0/docs/api-guidelines.md).
80
+ See the versioned [API guidelines](https://github.com/unofficialbox/box-open-elements/blob/v0.26.0/docs/api-guidelines.md).
81
81
 
82
82
  ## Developing
83
83
 
@@ -91,17 +91,17 @@ bun run docs # build + serve the docs site at http://localhost:4600
91
91
 
92
92
  ## Documentation
93
93
 
94
- The versioned docs index is at [docs/README.md](https://github.com/unofficialbox/box-open-elements/blob/v0.24.0/docs/README.md). The most important entries:
95
-
96
- - [Taxonomy](https://github.com/unofficialbox/box-open-elements/blob/v0.24.0/docs/taxonomy.md)
97
- - [Architecture](https://github.com/unofficialbox/box-open-elements/blob/v0.24.0/docs/architecture.md)
98
- - [API Guidelines](https://github.com/unofficialbox/box-open-elements/blob/v0.24.0/docs/api-guidelines.md)
99
- - [Components catalog](https://github.com/unofficialbox/box-open-elements/blob/v0.24.0/docs/components/catalog.md) and [patterns catalog](https://github.com/unofficialbox/box-open-elements/blob/v0.24.0/docs/patterns/catalog.md)
100
- - [Using with React, Angular, Vue, and Svelte](https://github.com/unofficialbox/box-open-elements/blob/v0.24.0/docs/integration/frameworks.md)
101
- - [Design Tokens](https://github.com/unofficialbox/box-open-elements/blob/v0.24.0/docs/foundations/tokens.md)
102
- - [Content Explorer](https://github.com/unofficialbox/box-open-elements/blob/v0.24.0/docs/patterns/content-explorer.md)
103
- - [Box Server Integration](https://github.com/unofficialbox/box-open-elements/blob/v0.24.0/docs/integration/box-server.md)
104
- - [Agent UI guidelines](https://github.com/unofficialbox/box-open-elements/blob/v0.24.0/docs/patterns/agent-ui-guidelines.md), [building blocks](https://github.com/unofficialbox/box-open-elements/blob/v0.24.0/docs/patterns/agent-ui.md), and [motion](https://github.com/unofficialbox/box-open-elements/blob/v0.24.0/docs/foundations/motion.md)
94
+ The versioned docs index is at [docs/README.md](https://github.com/unofficialbox/box-open-elements/blob/v0.26.0/docs/README.md). The most important entries:
95
+
96
+ - [Taxonomy](https://github.com/unofficialbox/box-open-elements/blob/v0.26.0/docs/taxonomy.md)
97
+ - [Architecture](https://github.com/unofficialbox/box-open-elements/blob/v0.26.0/docs/architecture.md)
98
+ - [API Guidelines](https://github.com/unofficialbox/box-open-elements/blob/v0.26.0/docs/api-guidelines.md)
99
+ - [Components catalog](https://github.com/unofficialbox/box-open-elements/blob/v0.26.0/docs/components/catalog.md) and [patterns catalog](https://github.com/unofficialbox/box-open-elements/blob/v0.26.0/docs/patterns/catalog.md)
100
+ - [Using with React, Angular, Vue, and Svelte](https://github.com/unofficialbox/box-open-elements/blob/v0.26.0/docs/integration/frameworks.md)
101
+ - [Design Tokens](https://github.com/unofficialbox/box-open-elements/blob/v0.26.0/docs/foundations/tokens.md)
102
+ - [Content Explorer](https://github.com/unofficialbox/box-open-elements/blob/v0.26.0/docs/patterns/content-explorer.md)
103
+ - [Box Server Integration](https://github.com/unofficialbox/box-open-elements/blob/v0.26.0/docs/integration/box-server.md)
104
+ - [Agent UI guidelines](https://github.com/unofficialbox/box-open-elements/blob/v0.26.0/docs/patterns/agent-ui-guidelines.md), [building blocks](https://github.com/unofficialbox/box-open-elements/blob/v0.26.0/docs/patterns/agent-ui.md), and [motion](https://github.com/unofficialbox/box-open-elements/blob/v0.26.0/docs/foundations/motion.md)
105
105
 
106
106
  ## Contributing
107
107
 
@@ -5,6 +5,7 @@ export declare class Button extends BaseElement {
5
5
  static formAssociated: boolean;
6
6
  static get observedAttributes(): string[];
7
7
  private buttonEl;
8
+ focus(options?: FocusOptions): void;
8
9
  private spinnerEl;
9
10
  private iconEl;
10
11
  private labelEl;
@@ -74,6 +74,17 @@ const buttonStyles = `
74
74
  box-shadow: none;
75
75
  }
76
76
 
77
+ button[data-tone="text"], button[data-tone="text"]:hover:not(:disabled),
78
+ button[data-tone="text"]:active:not(:disabled), button[data-tone="text"]:focus-visible {
79
+ background: transparent;
80
+ border-color: transparent;
81
+ color: var(--boe-token-surface-surface-brand, #0061d5);
82
+ padding-inline: 0;
83
+ white-space: normal;
84
+ text-align: start;
85
+ }
86
+ button[data-tone="text"]:hover:not(:disabled) { text-decoration: underline; }
87
+
77
88
  button[data-tone="neutral"]:hover:not(:disabled) {
78
89
  background: color-mix(in srgb, var(--boe-token-surface-surface, #ffffff) 97%, black 3%);
79
90
  border-color: ${boeControl.buttonBorder};
@@ -176,6 +187,8 @@ export class Button extends BaseElement {
176
187
  return ["disabled", "label", "size", "tone", "is-loading", "type"];
177
188
  }
178
189
  buttonEl;
190
+ focus(options) { if (!this.disabled)
191
+ this.buttonEl?.focus(options); }
179
192
  spinnerEl;
180
193
  iconEl;
181
194
  labelEl;
@@ -7,6 +7,7 @@ export declare class Checkbox extends FormAssociatedElement {
7
7
  private indeterminateInternal;
8
8
  private valueInternal;
9
9
  private inputEl;
10
+ focus(options?: FocusOptions): void;
10
11
  private labelEl;
11
12
  private descriptionEl;
12
13
  private fieldEl;
@@ -105,6 +105,8 @@ export class Checkbox extends FormAssociatedElement {
105
105
  indeterminateInternal = false;
106
106
  valueInternal = DEFAULT_VALUE;
107
107
  inputEl;
108
+ focus(options) { if (!this.disabled)
109
+ this.inputEl?.focus(options); }
108
110
  labelEl;
109
111
  descriptionEl;
110
112
  fieldEl;
@@ -1,14 +1,22 @@
1
1
  import { BaseElement } from "../../core/index.js";
2
- import { type Completion } from "@codemirror/autocomplete";
2
+ import { type Completion, type CompletionSource } from "@codemirror/autocomplete";
3
3
  export interface CodeProblem {
4
4
  /** One-based line and column positions. */
5
- line: number;
5
+ line?: number;
6
+ /** UTF-16 offsets; when supplied, these take precedence over line/column. */
7
+ from?: number;
8
+ to?: number;
6
9
  col?: number;
7
10
  endCol?: number;
8
11
  message: string;
9
12
  tone: "error" | "warning" | "info";
10
13
  }
11
14
  export type CodeCompletion = Completion;
15
+ export type CodeCompletionSource = CompletionSource;
16
+ export interface CodeHighlight {
17
+ from: number;
18
+ to: number;
19
+ }
12
20
  export interface CodeSelection {
13
21
  anchor: number;
14
22
  head: number;
@@ -24,6 +32,10 @@ export declare class CodeEditor extends BaseElement {
24
32
  private languageConfig;
25
33
  private readonlyConfig;
26
34
  private completionsConfig;
35
+ private wrapConfig;
36
+ private highlightsConfig;
37
+ private highlightsInternal;
38
+ private source?;
27
39
  private timer?;
28
40
  private pending;
29
41
  private syncing;
@@ -32,6 +44,17 @@ export declare class CodeEditor extends BaseElement {
32
44
  private escapeUntil;
33
45
  get value(): string;
34
46
  set value(value: string);
47
+ /** Host replacements are silent and excluded from the typing undo stack. */
48
+ setValue(value: string, options?: {
49
+ addToHistory?: boolean;
50
+ }): void;
51
+ get wrap(): boolean;
52
+ set wrap(value: boolean);
53
+ get completionSource(): CodeCompletionSource | undefined;
54
+ set completionSource(value: CodeCompletionSource | undefined);
55
+ get highlights(): readonly CodeHighlight[];
56
+ set highlights(value: readonly CodeHighlight[]);
57
+ private highlightExtension;
35
58
  get language(): string;
36
59
  set language(value: string);
37
60
  get readonly(): boolean;
@@ -44,7 +67,9 @@ export declare class CodeEditor extends BaseElement {
44
67
  set completions(value: readonly CodeCompletion[]);
45
68
  get selection(): CodeSelection;
46
69
  set selection(value: CodeSelection);
47
- revealLine(line: number): void;
70
+ revealLine(line: number, options?: {
71
+ center?: boolean;
72
+ }): void;
48
73
  focus(): void;
49
74
  attributeChangedCallback(name: string, oldValue: string | null, newValue: string | null): void;
50
75
  connectedCallback(): void;
@@ -1,7 +1,7 @@
1
1
  import { BaseElement } from "../../core/index.js";
2
2
  import { basicSetup } from "codemirror";
3
- import { Compartment, EditorSelection, EditorState, Prec, } from "@codemirror/state";
4
- import { EditorView, keymap } from "@codemirror/view";
3
+ import { Compartment, EditorSelection, EditorState, Prec, Transaction, } from "@codemirror/state";
4
+ import { Decoration, EditorView, keymap } from "@codemirror/view";
5
5
  import { indentWithTab } from "@codemirror/commands";
6
6
  import { javascript } from "@codemirror/lang-javascript";
7
7
  import { go } from "@codemirror/lang-go";
@@ -14,7 +14,7 @@ import { tags } from "@lezer/highlight";
14
14
  export class CodeEditor extends BaseElement {
15
15
  static tagName = "box-code-editor";
16
16
  static get observedAttributes() {
17
- return ["language", "readonly", "label", "value"];
17
+ return ["language", "readonly", "label", "value", "wrap"];
18
18
  }
19
19
  valueInternal = "";
20
20
  problemsInternal = [];
@@ -23,6 +23,10 @@ export class CodeEditor extends BaseElement {
23
23
  languageConfig = new Compartment();
24
24
  readonlyConfig = new Compartment();
25
25
  completionsConfig = new Compartment();
26
+ wrapConfig = new Compartment();
27
+ highlightsConfig = new Compartment();
28
+ highlightsInternal = [];
29
+ source;
26
30
  timer;
27
31
  pending = false;
28
32
  syncing = false;
@@ -39,6 +43,48 @@ export class CodeEditor extends BaseElement {
39
43
  if (this.isRendered)
40
44
  this.update();
41
45
  }
46
+ /** Host replacements are silent and excluded from the typing undo stack. */
47
+ setValue(value, options = {}) {
48
+ if (!options.addToHistory || !this.view) {
49
+ this.value = value;
50
+ return;
51
+ }
52
+ clearTimeout(this.timer);
53
+ this.pending = false;
54
+ this.syncing = true;
55
+ try {
56
+ this.view.dispatch({ changes: { from: 0, to: this.view.state.doc.length, insert: value } });
57
+ this.valueInternal = value;
58
+ }
59
+ finally {
60
+ this.syncing = false;
61
+ }
62
+ this.syncProblems();
63
+ }
64
+ get wrap() { return this.hasAttribute("wrap"); }
65
+ set wrap(value) { this.toggleAttribute("wrap", value); }
66
+ get completionSource() { return this.source; }
67
+ set completionSource(value) { this.source = value; if (this.isRendered)
68
+ this.update(); }
69
+ get highlights() { return this.highlightsInternal.map(range => ({ ...range })); }
70
+ set highlights(value) { this.highlightsInternal = value.map(range => ({ ...range })); if (this.isRendered)
71
+ this.update(); }
72
+ highlightExtension() {
73
+ return EditorView.decorations.of(view => {
74
+ const doc = view.state.doc;
75
+ const lines = new Set();
76
+ for (const range of this.highlightsInternal) {
77
+ if (!Number.isFinite(range.from) || !Number.isFinite(range.to))
78
+ continue;
79
+ const from = Math.max(0, Math.min(doc.length, range.from));
80
+ const to = Math.max(from, Math.min(doc.length, range.to));
81
+ for (let line = doc.lineAt(from).number; line <= doc.lineAt(Math.max(from, to - 1)).number; line++) {
82
+ lines.add(doc.line(line).from);
83
+ }
84
+ }
85
+ return Decoration.set([...lines].sort((a, b) => a - b).map(from => Decoration.line({ class: "boe-code-highlight", attributes: { part: "highlight" } }).range(from)));
86
+ });
87
+ }
42
88
  get language() {
43
89
  return this.getAttribute("language") ?? "typescript";
44
90
  }
@@ -92,11 +138,13 @@ export class CodeEditor extends BaseElement {
92
138
  scrollIntoView: true,
93
139
  });
94
140
  }
95
- revealLine(line) {
141
+ revealLine(line, options = {}) {
96
142
  if (!this.view)
97
143
  return;
98
144
  const target = this.view.state.doc.line(Math.max(1, Math.min(this.view.state.doc.lines, Math.floor(line) || 1)));
99
145
  this.selection = { anchor: target.from, head: target.from };
146
+ if (options.center)
147
+ this.view.dispatch({ effects: EditorView.scrollIntoView(target.from, { y: "center" }) });
100
148
  this.view.focus();
101
149
  }
102
150
  focus() {
@@ -128,6 +176,7 @@ export class CodeEditor extends BaseElement {
128
176
  .cm-gutters,.cm-panels,.cm-tooltip{background:var(--boe-token-surface-surface-secondary,#fbfbfb)!important;color:var(--boe-token-text-text,#222)!important;border-color:var(--boe-token-stroke-stroke,#ddd)!important}
129
177
  .cm-cursor{border-left-color:var(--boe-token-text-text,#222)}
130
178
  .cm-activeLine,.cm-activeLineGutter,.cm-selectionBackground{background:color-mix(in srgb,var(--boe-token-surface-surface-brand,#0061d5) 12%,transparent)!important}
179
+ .boe-code-highlight{background:var(--boe-code-highlight-background,color-mix(in srgb,var(--boe-token-surface-surface-brand,#0061d5) 10%,transparent))!important;box-shadow:inset 3px 0 var(--boe-token-surface-surface-brand,#0061d5)}
131
180
  .cm-matchingBracket,.cm-selectionMatch{background:color-mix(in srgb,var(--boe-token-surface-surface-brand,#0061d5) 15%,transparent)!important;color:inherit!important}
132
181
  .cm-nonmatchingBracket{color:var(--boe-token-text-status-text-error,#b92340)!important}
133
182
  .cm-searchMatch{background:color-mix(in srgb,var(--boe-token-text-status-text-warning,#9a6500) 20%,transparent);outline:none}
@@ -183,6 +232,8 @@ export class CodeEditor extends BaseElement {
183
232
  : javascript({ typescript: this.language === "typescript" });
184
233
  }
185
234
  completionExtension() {
235
+ if (this.source)
236
+ return autocompletion({ override: [this.source] });
186
237
  return autocompletion({
187
238
  override: [
188
239
  (context) => {
@@ -229,6 +280,8 @@ export class CodeEditor extends BaseElement {
229
280
  EditorView.editable.of(!this.readonly),
230
281
  ]),
231
282
  this.completionsConfig.of(this.completionExtension()),
283
+ this.wrapConfig.of(this.wrap ? EditorView.lineWrapping : []),
284
+ this.highlightsConfig.of(this.highlightExtension()),
232
285
  syntaxHighlighting(HighlightStyle.define([
233
286
  {
234
287
  tag: [
@@ -270,6 +323,10 @@ export class CodeEditor extends BaseElement {
270
323
  },
271
324
  ])),
272
325
  EditorView.updateListener.of((update) => {
326
+ if (update.selectionSet && !this.syncing && !update.startState.selection.eq(update.state.selection)) {
327
+ const { anchor, head } = update.state.selection.main;
328
+ this.dispatchEvent(new CustomEvent("selection-changed", { detail: { anchor, head }, bubbles: true, composed: true }));
329
+ }
273
330
  if (update.docChanged && !this.syncing) {
274
331
  this.valueInternal = update.state.doc.toString();
275
332
  this.pending = true;
@@ -295,6 +352,7 @@ export class CodeEditor extends BaseElement {
295
352
  clearTimeout(this.timer);
296
353
  this.pending = false;
297
354
  this.view.dispatch({
355
+ annotations: Transaction.addToHistory.of(false),
298
356
  changes: {
299
357
  from: 0,
300
358
  to: this.view.state.doc.length,
@@ -310,6 +368,8 @@ export class CodeEditor extends BaseElement {
310
368
  EditorView.editable.of(!this.readonly),
311
369
  ]),
312
370
  this.completionsConfig.reconfigure(this.completionExtension()),
371
+ this.wrapConfig.reconfigure(this.wrap ? EditorView.lineWrapping : []),
372
+ this.highlightsConfig.reconfigure(this.highlightExtension()),
313
373
  ],
314
374
  });
315
375
  }
@@ -338,10 +398,15 @@ export class CodeEditor extends BaseElement {
338
398
  return [];
339
399
  const doc = this.view.state.doc;
340
400
  return this.problems
341
- .filter((problem) => Number.isInteger(problem.line) &&
401
+ .filter((problem) => Number.isFinite(problem.from) || (Number.isInteger(problem.line) &&
342
402
  problem.line >= 1 &&
343
- problem.line <= doc.lines)
403
+ problem.line <= doc.lines))
344
404
  .map((problem) => {
405
+ if (Number.isFinite(problem.from)) {
406
+ const from = Math.max(0, Math.min(doc.length, Math.floor(problem.from)));
407
+ const to = Math.max(from, Math.min(doc.length, Number.isFinite(problem.to) ? Math.floor(problem.to) : from + 1));
408
+ return { from, to, message: problem.message, severity: problem.tone };
409
+ }
345
410
  const line = doc.line(problem.line);
346
411
  const column = Number.isFinite(problem.col) ? problem.col : 1;
347
412
  const endColumn = Number.isFinite(problem.endCol)
@@ -1,7 +1,7 @@
1
1
  import { FormAssociatedElement, boeFormFieldErrorStyles, formErrorMessageMarkup, } from "../../core/index.js";
2
2
  import { boeNeutralInteractiveStyles } from "../../foundations/tokens/interaction.js";
3
3
  import { boeMotionDuration, boeMotionEasing } from "../../foundations/motion/index.js";
4
- import { boeOverlay, boeRadius } from "../../foundations/geometry/index.js";
4
+ import { boeControl, boeOverlay, boeRadius } from "../../foundations/geometry/index.js";
5
5
  import { parsePlacement, trackAnchor, } from "../../foundations/overlay/index.js";
6
6
  const DEFAULT_TAG_NAME = "box-combobox";
7
7
  const escapeHtml = (value) => value
@@ -29,11 +29,9 @@ const comboboxStyles = `
29
29
  }
30
30
 
31
31
  [part="label"] {
32
- font-size: 0.8rem;
32
+ font-size: ${boeControl.fontSize};
33
33
  font-weight: 700;
34
- letter-spacing: 0.08em;
35
- text-transform: uppercase;
36
- color: var(--boe-token-text-text-secondary, #6f6f6f);
34
+ color: var(--boe-token-text-text, #222222);
37
35
  }
38
36
 
39
37
  [part="control"] {
@@ -48,7 +46,7 @@ const comboboxStyles = `
48
46
  font: inherit;
49
47
  color: var(--boe-token-text-text, #222222);
50
48
  padding: 0.45rem 0.7rem;
51
- border: 1px solid color-mix(in srgb, var(--boe-token-stroke-stroke, #e8e8e8) 78%, var(--boe-token-surface-surface, #ffffff) 22%);
49
+ border: 1px solid ${boeControl.inputBorder};
52
50
  border-radius: ${boeRadius.control};
53
51
  background: var(--boe-token-surface-surface, #ffffff);
54
52
  transition:
@@ -62,6 +60,7 @@ const comboboxStyles = `
62
60
  }
63
61
 
64
62
  ${boeNeutralInteractiveStyles('[part="input"]')}
63
+ [part="input"]:hover:not(:disabled) { border-color: ${boeControl.inputBorder}; }
65
64
 
66
65
  [part="input"]:focus-visible {
67
66
  border-color: var(--boe-token-surface-surface-brand, #0061d5);
@@ -126,8 +125,8 @@ const comboboxStyles = `
126
125
  padding: 6px 10px 2px;
127
126
  font-size: 0.72rem;
128
127
  font-weight: 700;
129
- letter-spacing: 0.06em;
130
- text-transform: uppercase;
128
+ letter-spacing: normal;
129
+ text-transform: none;
131
130
  color: var(--boe-token-text-text-secondary, #6f6f6f);
132
131
  }
133
132
 
@@ -5,6 +5,7 @@ export declare class NumberInput extends FormAssociatedElement {
5
5
  static get observedAttributes(): string[];
6
6
  private valueInternal;
7
7
  private inputEl;
8
+ focus(options?: FocusOptions): void;
8
9
  private labelEl;
9
10
  private errorEl;
10
11
  private descriptionEl;
@@ -41,7 +41,7 @@ const numberInputStyles = `
41
41
  font: inherit;
42
42
  color: var(--boe-token-text-text, #222222);
43
43
  padding: 0.45rem 0.7rem;
44
- border: 1px solid color-mix(in srgb, var(--boe-token-stroke-stroke, #e8e8e8) 78%, var(--boe-token-surface-surface, #ffffff) 22%);
44
+ border: 1px solid ${boeControl.inputBorder};
45
45
  border-radius: ${boeRadius.control};
46
46
  background: var(--boe-token-surface-surface, #ffffff);
47
47
  transition:
@@ -55,6 +55,7 @@ const numberInputStyles = `
55
55
  }
56
56
 
57
57
  ${boeNeutralInteractiveStyles('[part="input"]')}
58
+ [part="input"]:hover:not(:disabled) { border-color: ${boeControl.inputBorder}; }
58
59
 
59
60
  [part="input"]:focus-visible {
60
61
  border-color: var(--boe-token-surface-surface-brand, #0061d5);
@@ -79,6 +80,8 @@ export class NumberInput extends FormAssociatedElement {
79
80
  }
80
81
  valueInternal = 0;
81
82
  inputEl;
83
+ focus(options) { if (!this.disabled)
84
+ this.inputEl?.focus(options); }
82
85
  labelEl;
83
86
  errorEl;
84
87
  descriptionEl;
@@ -17,6 +17,7 @@ export declare class Select extends FormAssociatedElement {
17
17
  private valueInternal;
18
18
  private valuesInternal;
19
19
  private selectEl;
20
+ focus(options?: FocusOptions): void;
20
21
  private labelEl;
21
22
  private descriptionEl;
22
23
  private errorEl;
@@ -64,7 +64,7 @@ const selectStyles = `
64
64
  }
65
65
 
66
66
  [part="select"]:hover:not(:disabled) {
67
- border-color: var(--boe-token-stroke-stroke-hover, #bcbcbc);
67
+ border-color: ${boeControl.inputBorder};
68
68
  }
69
69
 
70
70
  [part="select"]:focus-visible {
@@ -150,6 +150,8 @@ export class Select extends FormAssociatedElement {
150
150
  valueInternal = "";
151
151
  valuesInternal = [];
152
152
  selectEl;
153
+ focus(options) { if (!this.disabled)
154
+ this.selectEl?.focus(options); }
153
155
  labelEl;
154
156
  descriptionEl;
155
157
  errorEl;
@@ -1,6 +1,6 @@
1
1
  import { FormAssociatedElement, boeFormFieldErrorStyles, formErrorMessageMarkup, } from "../../core/index.js";
2
2
  import { boeMotionDuration, boeMotionEasing } from "../../foundations/motion/index.js";
3
- import { boeRadius } from "../../foundations/geometry/index.js";
3
+ import { boeControl, boeRadius } from "../../foundations/geometry/index.js";
4
4
  const DEFAULT_TAG_NAME = "box-text-area";
5
5
  const textAreaStyles = `
6
6
  :host {
@@ -21,11 +21,9 @@ const textAreaStyles = `
21
21
  }
22
22
 
23
23
  [part="label"] {
24
- font-size: 0.8rem;
24
+ font-size: ${boeControl.fontSize};
25
25
  font-weight: 700;
26
- letter-spacing: 0.08em;
27
- text-transform: uppercase;
28
- color: var(--boe-token-text-text-secondary, #6f6f6f);
26
+ color: var(--boe-token-text-text, #222222);
29
27
  }
30
28
 
31
29
  [part="textarea"] {
@@ -33,7 +31,7 @@ const textAreaStyles = `
33
31
  font: inherit;
34
32
  color: var(--boe-token-text-text, #222222);
35
33
  padding: 0.45rem 0.7rem;
36
- border: 1px solid color-mix(in srgb, var(--boe-token-stroke-stroke, #e8e8e8) 78%, var(--boe-token-surface-surface, #ffffff) 22%);
34
+ border: 1px solid ${boeControl.inputBorder};
37
35
  border-radius: ${boeRadius.control};
38
36
  background: var(--boe-token-surface-surface, #ffffff);
39
37
  resize: vertical;
@@ -49,7 +47,7 @@ const textAreaStyles = `
49
47
  }
50
48
 
51
49
  [part="textarea"]:hover:not(:disabled) {
52
- border-color: var(--boe-token-stroke-stroke-hover, #bcbcbc);
50
+ border-color: ${boeControl.inputBorder};
53
51
  }
54
52
 
55
53
  [part="textarea"]:focus-visible {
@@ -10,6 +10,7 @@ export declare class TextField extends FormAssociatedElement {
10
10
  private valueInternal;
11
11
  private revealed;
12
12
  private inputEl;
13
+ focus(options?: FocusOptions): void;
13
14
  private labelEl;
14
15
  private descriptionEl;
15
16
  private errorEl;
@@ -178,6 +178,8 @@ export class TextField extends FormAssociatedElement {
178
178
  valueInternal = "";
179
179
  revealed = false;
180
180
  inputEl;
181
+ focus(options) { if (!this.disabled)
182
+ this.inputEl?.focus(options); }
181
183
  labelEl;
182
184
  descriptionEl;
183
185
  errorEl;
@@ -1,6 +1,7 @@
1
1
  import { BaseElement } from "../../core/index.js";
2
2
  type BoxAccordionItem = {
3
3
  content?: string;
4
+ summary?: string;
4
5
  label: string;
5
6
  value: string;
6
7
  };
@@ -11,6 +12,12 @@ export declare class Accordion extends BaseElement {
11
12
  get borderless(): boolean;
12
13
  set borderless(value: boolean);
13
14
  private valueInternal;
15
+ private valuesInternal;
16
+ get multiple(): boolean;
17
+ set multiple(value: boolean);
18
+ get values(): string[];
19
+ set values(value: string[]);
20
+ private isOpen;
14
21
  private lastItemsJson;
15
22
  private accordionEl;
16
23
  get label(): string;
@@ -66,6 +66,10 @@ const accordionStyles = `
66
66
  [part="heading"] {
67
67
  font-weight: 600;
68
68
  }
69
+ [part="summary"] {
70
+ color: var(--boe-token-text-text-secondary, #6f6f6f);
71
+ font-weight: 400;
72
+ }
69
73
 
70
74
  [part="indicator"] {
71
75
  inline-size: 1.35rem;
@@ -103,7 +107,7 @@ const accordionStyles = `
103
107
  export class Accordion extends BaseElement {
104
108
  static tagName = DEFAULT_TAG_NAME;
105
109
  static get observedAttributes() {
106
- return ["items", "label", "value", "borderless"];
110
+ return ["items", "label", "value", "values", "multiple", "borderless"];
107
111
  }
108
112
  /** Flat variant with no outer card border/background. */
109
113
  get borderless() {
@@ -113,6 +117,12 @@ export class Accordion extends BaseElement {
113
117
  this.toggleAttribute("borderless", Boolean(value));
114
118
  }
115
119
  valueInternal = "";
120
+ valuesInternal = [];
121
+ get multiple() { return this.hasAttribute("multiple"); }
122
+ set multiple(value) { this.toggleAttribute("multiple", value); }
123
+ get values() { return [...this.valuesInternal]; }
124
+ set values(value) { this.setAttribute("values", JSON.stringify([...new Set(value)])); }
125
+ isOpen(value) { return this.multiple ? this.valuesInternal.includes(value) : value === this.valueInternal; }
116
126
  lastItemsJson = "";
117
127
  accordionEl;
118
128
  get label() {
@@ -151,12 +161,21 @@ export class Accordion extends BaseElement {
151
161
  if (name === "value") {
152
162
  this.valueInternal = this.getAttribute("value") ?? "";
153
163
  }
164
+ if (name === "values") {
165
+ try {
166
+ const values = JSON.parse(newValue ?? "[]");
167
+ this.valuesInternal = Array.isArray(values) ? [...new Set(values.filter((v) => typeof v === "string"))] : [];
168
+ }
169
+ catch {
170
+ this.valuesInternal = [];
171
+ }
172
+ }
154
173
  super.attributeChangedCallback(name, oldValue, newValue);
155
174
  }
156
- renderItemsMarkup(items, selectedValue) {
175
+ renderItemsMarkup(items) {
157
176
  return items
158
177
  .map(item => {
159
- const isOpen = item.value === selectedValue;
178
+ const isOpen = this.isOpen(item.value);
160
179
  const panelId = `panel-${escapeHtml(item.value)}`;
161
180
  const triggerId = `trigger-${escapeHtml(item.value)}`;
162
181
  return `
@@ -170,7 +189,7 @@ export class Accordion extends BaseElement {
170
189
  aria-expanded="${String(isOpen)}"
171
190
  aria-controls="${panelId}"
172
191
  >
173
- ${escapeHtml(item.label)}
192
+ <span part="label">${escapeHtml(item.label)}${item.summary ? ` <span part="summary">${escapeHtml(item.summary)}</span>` : ""}</span>
174
193
  <span part="indicator" aria-hidden="true">${isOpen ? "−" : "+"}</span>
175
194
  </button>
176
195
  </h3>
@@ -206,6 +225,11 @@ export class Accordion extends BaseElement {
206
225
  if (!nextValue) {
207
226
  return;
208
227
  }
228
+ if (this.multiple) {
229
+ this.values = this.isOpen(nextValue) ? this.valuesInternal.filter(value => value !== nextValue) : [...this.valuesInternal, nextValue];
230
+ this.dispatchEvent(new CustomEvent("values-changed", { detail: { values: this.values }, bubbles: true, composed: true }));
231
+ return;
232
+ }
209
233
  if (nextValue === this.valueInternal) {
210
234
  this.valueInternal = "";
211
235
  this.setAttribute("value", "");
@@ -233,22 +257,21 @@ export class Accordion extends BaseElement {
233
257
  }
234
258
  const items = this.items;
235
259
  const itemsJson = this.getAttribute("items") ?? "";
236
- if (!this.hasAttribute("value") && items.length > 0 && this.valueInternal === "") {
260
+ if (!this.multiple && !this.hasAttribute("value") && items.length > 0 && this.valueInternal === "") {
237
261
  this.valueInternal = items[0].value;
238
262
  this.setAttribute("value", this.valueInternal);
239
263
  }
240
- const selectedValue = this.valueInternal;
241
264
  this.accordionEl.setAttribute("role", "region");
242
265
  this.accordionEl.setAttribute("aria-label", this.label);
243
266
  if (itemsJson !== this.lastItemsJson) {
244
- this.accordionEl.innerHTML = this.renderItemsMarkup(items, selectedValue);
267
+ this.accordionEl.innerHTML = this.renderItemsMarkup(items);
245
268
  this.lastItemsJson = itemsJson;
246
269
  return;
247
270
  }
248
271
  this.accordionEl.querySelectorAll('[part="item"]').forEach(node => {
249
272
  const section = node;
250
273
  const value = section.dataset.value ?? "";
251
- const isOpen = value === selectedValue;
274
+ const isOpen = this.isOpen(value);
252
275
  section.dataset.open = String(isOpen);
253
276
  const trigger = section.querySelector('[part="trigger"]');
254
277
  const indicator = section.querySelector('[part="indicator"]');
@@ -90,6 +90,7 @@ const codeStyles = `
90
90
  }
91
91
 
92
92
  ${boeFocusVisibleStyles('[part="copy"]')}
93
+ ${boeFocusVisibleStyles('[part="pre"]')}
93
94
  `;
94
95
  /**
95
96
  * A block of code, rendered as written.
@@ -192,7 +193,7 @@ export class CodeBlock extends BaseElement {
192
193
  this.shadowRoot.innerHTML = `
193
194
  <style>${codeStyles}</style>
194
195
  <div part="block">
195
- <pre part="pre"><code part="code"><slot></slot></code></pre>
196
+ <pre part="pre" tabindex="0" role="region"><code part="code"><slot></slot></code></pre>
196
197
  <button type="button" part="copy" data-copied="false"></button>
197
198
  </div>
198
199
  `;
@@ -221,6 +222,7 @@ export class CodeBlock extends BaseElement {
221
222
  this.slotEl.textContent = "";
222
223
  }
223
224
  const language = this.language;
225
+ this.preEl.setAttribute("aria-label", language ? `${language} code` : "Source code");
224
226
  if (language) {
225
227
  this.codeEl.setAttribute("data-language", language);
226
228
  this.codeEl.setAttribute("class", `language-${language}`);