@unofficialbox/box-open-elements 0.23.0 → 0.24.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 (36) hide show
  1. package/README.md +15 -13
  2. package/dist/components/actions/button.js +3 -3
  3. package/dist/components/actions/segmented-control.d.ts +1 -0
  4. package/dist/components/actions/segmented-control.js +20 -6
  5. package/dist/components/feedback/alert.js +1 -1
  6. package/dist/components/feedback/progress-bar.js +2 -2
  7. package/dist/components/forms/checkbox.js +4 -2
  8. package/dist/components/forms/code-editor.d.ts +61 -0
  9. package/dist/components/forms/code-editor.js +382 -0
  10. package/dist/components/forms/number-input.js +4 -1
  11. package/dist/core/form-associated.d.ts +1 -0
  12. package/dist/core/form-associated.js +3 -0
  13. package/dist/entries/code-editor.d.ts +1 -0
  14. package/dist/entries/code-editor.js +1 -0
  15. package/dist/foundations/icons/box-iconography.generated.d.ts +1 -472
  16. package/dist/foundations/icons/box-iconography.generated.js +2 -472
  17. package/dist/foundations/icons/glyphs/constants.d.ts +472 -0
  18. package/dist/foundations/icons/glyphs/constants.js +473 -0
  19. package/dist/foundations/icons/glyphs/index.d.ts +1 -1
  20. package/dist/foundations/icons/glyphs/index.js +1 -1
  21. package/dist/index.d.ts +1 -0
  22. package/dist/index.js +1 -0
  23. package/dist/patterns/flow-builder/flow-builder.d.ts +2 -0
  24. package/dist/patterns/flow-builder/flow-builder.js +14 -8
  25. package/dist/patterns/flow-builder/model.d.ts +2 -0
  26. package/dist/patterns/flow-builder/model.js +3 -3
  27. package/dist/patterns/flow-builder/primitives.d.ts +1 -0
  28. package/dist/patterns/flow-builder/primitives.js +4 -3
  29. package/dist/patterns/insights/metric-card.js +2 -2
  30. package/dist/patterns/process-modeler/index.d.ts +2 -0
  31. package/dist/patterns/process-modeler/index.js +2 -0
  32. package/dist/patterns/process-modeler/model.d.ts +95 -0
  33. package/dist/patterns/process-modeler/model.js +113 -0
  34. package/dist/patterns/process-modeler/process-modeler.d.ts +82 -0
  35. package/dist/patterns/process-modeler/process-modeler.js +861 -0
  36. package/package.json +18 -1
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.23.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.24.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
@@ -21,6 +21,8 @@ The library is organized around three layers (see [taxonomy](https://github.com/
21
21
  Core implementation principles:
22
22
 
23
23
  - plain TypeScript modules, no React requirement in the core package — zero runtime dependencies
24
+ - Optional `code-editor` entrypoint uses CodeMirror 6; its dependencies are not
25
+ loaded by the root catalog or other component imports.
24
26
  - state and business logic separate from rendering: controllers and stores, not framework components
25
27
  - standard DOM events where a UI layer needs them
26
28
  - accessibility semantics and keyboard support as part of the component contract
@@ -75,7 +77,7 @@ selection.toggleSelection("1");
75
77
  - optimized component entrypoints: `@unofficialbox/box-open-elements/<name>`
76
78
  - patterns: `@unofficialbox/box-open-elements/patterns/<area>` and `@unofficialbox/box-open-elements/patterns/<area>/<module>`
77
79
 
78
- See the versioned [API guidelines](https://github.com/unofficialbox/box-open-elements/blob/v0.23.0/docs/api-guidelines.md).
80
+ See the versioned [API guidelines](https://github.com/unofficialbox/box-open-elements/blob/v0.24.0/docs/api-guidelines.md).
79
81
 
80
82
  ## Developing
81
83
 
@@ -89,17 +91,17 @@ bun run docs # build + serve the docs site at http://localhost:4600
89
91
 
90
92
  ## Documentation
91
93
 
92
- The versioned docs index is at [docs/README.md](https://github.com/unofficialbox/box-open-elements/blob/v0.23.0/docs/README.md). The most important entries:
93
-
94
- - [Taxonomy](https://github.com/unofficialbox/box-open-elements/blob/v0.23.0/docs/taxonomy.md)
95
- - [Architecture](https://github.com/unofficialbox/box-open-elements/blob/v0.23.0/docs/architecture.md)
96
- - [API Guidelines](https://github.com/unofficialbox/box-open-elements/blob/v0.23.0/docs/api-guidelines.md)
97
- - [Components catalog](https://github.com/unofficialbox/box-open-elements/blob/v0.23.0/docs/components/catalog.md) and [patterns catalog](https://github.com/unofficialbox/box-open-elements/blob/v0.23.0/docs/patterns/catalog.md)
98
- - [Using with React, Angular, Vue, and Svelte](https://github.com/unofficialbox/box-open-elements/blob/v0.23.0/docs/integration/frameworks.md)
99
- - [Design Tokens](https://github.com/unofficialbox/box-open-elements/blob/v0.23.0/docs/foundations/tokens.md)
100
- - [Content Explorer](https://github.com/unofficialbox/box-open-elements/blob/v0.23.0/docs/patterns/content-explorer.md)
101
- - [Box Server Integration](https://github.com/unofficialbox/box-open-elements/blob/v0.23.0/docs/integration/box-server.md)
102
- - [Agent UI guidelines](https://github.com/unofficialbox/box-open-elements/blob/v0.23.0/docs/patterns/agent-ui-guidelines.md), [building blocks](https://github.com/unofficialbox/box-open-elements/blob/v0.23.0/docs/patterns/agent-ui.md), and [motion](https://github.com/unofficialbox/box-open-elements/blob/v0.23.0/docs/foundations/motion.md)
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)
103
105
 
104
106
  ## Contributing
105
107
 
@@ -64,7 +64,7 @@ const buttonStyles = `
64
64
  outline: none;
65
65
  background: var(--boe-token-surface-surface-brand-hover, #006ae9);
66
66
  border-color: var(--boe-token-surface-surface-brand, #0061d5);
67
- box-shadow: ${boeControl.primaryFocusShadow};
67
+ box-shadow: 0 0 0 2px var(--boe-token-surface-surface, #ffffff), 0 0 0 5px var(--boe-token-surface-surface-brand, #0061d5);
68
68
  }
69
69
 
70
70
  button[data-tone="neutral"] {
@@ -86,7 +86,7 @@ const buttonStyles = `
86
86
 
87
87
  button[data-tone="neutral"]:focus-visible {
88
88
  border-color: var(--boe-token-text-text, #222222);
89
- box-shadow: 0 1px 2px rgb(0 0 0 / 10%);
89
+ box-shadow: 0 0 0 2px var(--boe-token-surface-surface, #ffffff), 0 0 0 5px var(--boe-token-surface-surface-brand, #0061d5);
90
90
  }
91
91
 
92
92
  button[data-tone="danger"] {
@@ -106,7 +106,7 @@ const buttonStyles = `
106
106
  }
107
107
 
108
108
  button[data-tone="danger"]:focus-visible {
109
- box-shadow: inset 0 0 0 1px rgb(255 255 255 / 80%), 0 1px 2px rgb(0 0 0 / 10%);
109
+ box-shadow: 0 0 0 2px var(--boe-token-surface-surface, #ffffff), 0 0 0 5px var(--boe-token-surface-surface-brand, #0061d5);
110
110
  }
111
111
 
112
112
  button[data-size="small"] {
@@ -9,6 +9,7 @@ export declare class SegmentedControl extends BaseElement {
9
9
  static get observedAttributes(): string[];
10
10
  private valueInternal;
11
11
  private controlEl;
12
+ private renderedOptions;
12
13
  get disabled(): boolean;
13
14
  set disabled(value: boolean);
14
15
  get label(): string;
@@ -84,6 +84,7 @@ export class SegmentedControl extends BaseElement {
84
84
  }
85
85
  valueInternal = "";
86
86
  controlEl;
87
+ renderedOptions = "";
87
88
  get disabled() {
88
89
  return this.hasAttribute("disabled");
89
90
  }
@@ -121,17 +122,17 @@ export class SegmentedControl extends BaseElement {
121
122
  }
122
123
  }
123
124
  set options(value) {
124
- this.setAttribute("options", JSON.stringify(value));
125
+ const serialized = JSON.stringify(value);
126
+ if (serialized !== this.getAttribute("options"))
127
+ this.setAttribute("options", serialized);
125
128
  }
126
129
  get value() {
127
130
  return this.valueInternal;
128
131
  }
129
132
  set value(nextValue) {
130
- this.valueInternal = nextValue;
133
+ if (nextValue === this.valueInternal)
134
+ return;
131
135
  this.setAttribute("value", nextValue);
132
- if (this.isRendered) {
133
- this.update();
134
- }
135
136
  }
136
137
  attributeChangedCallback(name, oldValue, newValue) {
137
138
  if (name === "value") {
@@ -198,7 +199,20 @@ export class SegmentedControl extends BaseElement {
198
199
  this.controlEl.dataset.layout = this.layout;
199
200
  this.controlEl.setAttribute("aria-label", this.label);
200
201
  this.controlEl.setAttribute("aria-disabled", String(this.disabled));
201
- // Rebuild segments (count may change)
202
+ const serialized = JSON.stringify(options);
203
+ if (serialized === this.renderedOptions) {
204
+ this.controlEl.querySelectorAll('[part="segment"]').forEach((button, index) => {
205
+ const selected = options[index].value === selectedValue;
206
+ button.dataset.layout = this.layout;
207
+ button.dataset.selected = String(selected);
208
+ button.setAttribute("aria-checked", String(selected));
209
+ button.tabIndex = selected ? 0 : -1;
210
+ button.disabled = this.disabled || Boolean(options[index].disabled);
211
+ });
212
+ return;
213
+ }
214
+ this.renderedOptions = serialized;
215
+ // Rebuild only when the options themselves change.
202
216
  this.controlEl.innerHTML = options
203
217
  .map((option, index) => {
204
218
  const position = options.length === 1
@@ -131,7 +131,7 @@ const alertStyles = `
131
131
  border: 0;
132
132
  border-radius: ${boeRadius.med};
133
133
  background: transparent;
134
- color: var(--boe-token-text-text-secondary, #6f6f6f);
134
+ color: var(--boe-token-text-text, #222222);
135
135
  font: inherit;
136
136
  font-size: 13px;
137
137
  font-weight: 700;
@@ -30,8 +30,8 @@ const progressBarStyles = `
30
30
  [part="label"] {
31
31
  font-size: 0.8rem;
32
32
  font-weight: 700;
33
- letter-spacing: 0.08em;
34
- text-transform: uppercase;
33
+ letter-spacing: normal;
34
+ text-transform: none;
35
35
  color: var(--boe-token-text-text-secondary, #6f6f6f);
36
36
  }
37
37
 
@@ -1,4 +1,4 @@
1
- import { FormAssociatedElement, boeFormFieldErrorStyles, formErrorMessageMarkup, } from "../../core/index.js";
1
+ import { FormAssociatedElement, boeFormFieldErrorStyles, boeFormFieldSupportStyles, formErrorMessageMarkup, } from "../../core/index.js";
2
2
  import { boeFocusVisibleStyles } from "../../foundations/tokens/interaction.js";
3
3
  import { boeControl, boeRadius, boeSpace } from "../../foundations/geometry/index.js";
4
4
  import { boeMotionDuration, boeMotionEasing } from "../../foundations/motion/index.js";
@@ -86,12 +86,13 @@ const checkboxStyles = `
86
86
  }
87
87
 
88
88
  ${boeFormFieldErrorStyles}
89
+ ${boeFormFieldSupportStyles}
89
90
  `;
90
91
  export class Checkbox extends FormAssociatedElement {
91
92
  static tagName = DEFAULT_TAG_NAME;
92
93
  static get observedAttributes() {
93
94
  return [
94
- ...FormAssociatedElement.formObservedAttributes,
95
+ ...FormAssociatedElement.fieldObservedAttributes,
95
96
  "checked",
96
97
  "description",
97
98
  "disabled",
@@ -288,6 +289,7 @@ export class Checkbox extends FormAssociatedElement {
288
289
  }
289
290
  this.inputEl.setAttribute("aria-label", this.label);
290
291
  this.labelEl.textContent = this.label;
292
+ this.applyFieldSupport(this.labelEl, this.inputEl, null);
291
293
  const description = this.description;
292
294
  if (description) {
293
295
  this.descriptionEl.hidden = false;
@@ -0,0 +1,61 @@
1
+ import { BaseElement } from "../../core/index.js";
2
+ import { type Completion } from "@codemirror/autocomplete";
3
+ export interface CodeProblem {
4
+ /** One-based line and column positions. */
5
+ line: number;
6
+ col?: number;
7
+ endCol?: number;
8
+ message: string;
9
+ tone: "error" | "warning" | "info";
10
+ }
11
+ export type CodeCompletion = Completion;
12
+ export interface CodeSelection {
13
+ anchor: number;
14
+ head: number;
15
+ }
16
+ /** Optional CodeMirror entrypoint. Import `code-editor`, not the root catalog. */
17
+ export declare class CodeEditor extends BaseElement {
18
+ static readonly tagName = "box-code-editor";
19
+ static get observedAttributes(): string[];
20
+ private valueInternal;
21
+ private problemsInternal;
22
+ private completionsInternal;
23
+ private view?;
24
+ private languageConfig;
25
+ private readonlyConfig;
26
+ private completionsConfig;
27
+ private timer?;
28
+ private pending;
29
+ private syncing;
30
+ private problemIndex;
31
+ private savedSelection;
32
+ private escapeUntil;
33
+ get value(): string;
34
+ set value(value: string);
35
+ get language(): string;
36
+ set language(value: string);
37
+ get readonly(): boolean;
38
+ set readonly(value: boolean);
39
+ get label(): string;
40
+ set label(value: string);
41
+ get problems(): readonly CodeProblem[];
42
+ set problems(value: readonly CodeProblem[]);
43
+ get completions(): readonly CodeCompletion[];
44
+ set completions(value: readonly CodeCompletion[]);
45
+ get selection(): CodeSelection;
46
+ set selection(value: CodeSelection);
47
+ revealLine(line: number): void;
48
+ focus(): void;
49
+ attributeChangedCallback(name: string, oldValue: string | null, newValue: string | null): void;
50
+ connectedCallback(): void;
51
+ disconnectedCallback(): void;
52
+ protected renderTemplate(): void;
53
+ protected setupListeners(): void;
54
+ private languageExtension;
55
+ private completionExtension;
56
+ protected update(): void;
57
+ private flushChange;
58
+ private diagnostics;
59
+ private syncProblems;
60
+ navigateProblem(direction: number): void;
61
+ }
@@ -0,0 +1,382 @@
1
+ import { BaseElement } from "../../core/index.js";
2
+ import { basicSetup } from "codemirror";
3
+ import { Compartment, EditorSelection, EditorState, Prec, } from "@codemirror/state";
4
+ import { EditorView, keymap } from "@codemirror/view";
5
+ import { indentWithTab } from "@codemirror/commands";
6
+ import { javascript } from "@codemirror/lang-javascript";
7
+ import { go } from "@codemirror/lang-go";
8
+ import { autocompletion, closeCompletion, startCompletion, } from "@codemirror/autocomplete";
9
+ import { closeSearchPanel } from "@codemirror/search";
10
+ import { lintGutter, setDiagnostics } from "@codemirror/lint";
11
+ import { HighlightStyle, syntaxHighlighting } from "@codemirror/language";
12
+ import { tags } from "@lezer/highlight";
13
+ /** Optional CodeMirror entrypoint. Import `code-editor`, not the root catalog. */
14
+ export class CodeEditor extends BaseElement {
15
+ static tagName = "box-code-editor";
16
+ static get observedAttributes() {
17
+ return ["language", "readonly", "label", "value"];
18
+ }
19
+ valueInternal = "";
20
+ problemsInternal = [];
21
+ completionsInternal = [];
22
+ view;
23
+ languageConfig = new Compartment();
24
+ readonlyConfig = new Compartment();
25
+ completionsConfig = new Compartment();
26
+ timer;
27
+ pending = false;
28
+ syncing = false;
29
+ problemIndex = -1;
30
+ savedSelection = { anchor: 0, head: 0 };
31
+ escapeUntil = 0;
32
+ get value() {
33
+ return this.valueInternal;
34
+ }
35
+ set value(value) {
36
+ if (this.valueInternal === value)
37
+ return;
38
+ this.valueInternal = value;
39
+ if (this.isRendered)
40
+ this.update();
41
+ }
42
+ get language() {
43
+ return this.getAttribute("language") ?? "typescript";
44
+ }
45
+ set language(value) {
46
+ this.setAttribute("language", value);
47
+ }
48
+ get readonly() {
49
+ return this.hasAttribute("readonly");
50
+ }
51
+ set readonly(value) {
52
+ this.toggleAttribute("readonly", value);
53
+ }
54
+ get label() {
55
+ return this.getAttribute("label") ?? "Code editor";
56
+ }
57
+ set label(value) {
58
+ this.setAttribute("label", value);
59
+ }
60
+ get problems() {
61
+ return this.problemsInternal;
62
+ }
63
+ set problems(value) {
64
+ this.problemsInternal = [...value];
65
+ this.problemIndex = -1;
66
+ this.syncProblems();
67
+ }
68
+ get completions() {
69
+ return this.completionsInternal;
70
+ }
71
+ set completions(value) {
72
+ this.completionsInternal = [...value];
73
+ if (this.isRendered)
74
+ this.update();
75
+ }
76
+ get selection() {
77
+ return this.view
78
+ ? {
79
+ anchor: this.view.state.selection.main.anchor,
80
+ head: this.view.state.selection.main.head,
81
+ }
82
+ : this.savedSelection;
83
+ }
84
+ set selection(value) {
85
+ const clamp = (offset) => Math.max(0, Math.min(this.value.length, Number.isFinite(offset) ? offset : 0));
86
+ this.savedSelection = {
87
+ anchor: clamp(value.anchor),
88
+ head: clamp(value.head),
89
+ };
90
+ this.view?.dispatch({
91
+ selection: EditorSelection.single(this.savedSelection.anchor, this.savedSelection.head),
92
+ scrollIntoView: true,
93
+ });
94
+ }
95
+ revealLine(line) {
96
+ if (!this.view)
97
+ return;
98
+ const target = this.view.state.doc.line(Math.max(1, Math.min(this.view.state.doc.lines, Math.floor(line) || 1)));
99
+ this.selection = { anchor: target.from, head: target.from };
100
+ this.view.focus();
101
+ }
102
+ focus() {
103
+ this.view?.focus();
104
+ }
105
+ attributeChangedCallback(name, oldValue, newValue) {
106
+ if (name === "value")
107
+ this.valueInternal = newValue ?? "";
108
+ super.attributeChangedCallback(name, oldValue, newValue);
109
+ }
110
+ connectedCallback() {
111
+ super.connectedCallback();
112
+ }
113
+ disconnectedCallback() {
114
+ this.flushChange();
115
+ this.savedSelection = this.selection;
116
+ this.view?.destroy();
117
+ this.view = undefined;
118
+ }
119
+ renderTemplate() {
120
+ this.shadowRoot.innerHTML = `<style>
121
+ :host{display:block;min-width:0;color:var(--boe-token-text-text,#222);font:inherit}
122
+ :host([hidden]){display:none!important}
123
+ [part=editor]{border:1px solid var(--boe-token-stroke-stroke,#ddd);border-radius:8px;overflow:hidden}
124
+ .cm-editor{background:var(--boe-token-surface-surface,#fff);color:var(--boe-token-text-text,#222)}
125
+ [part=editor]:focus-within{box-shadow:0 0 0 3px var(--boe-token-surface-surface-brand,#0061d5)}
126
+ .cm-editor.cm-focused{outline:none}
127
+ .cm-scroller{max-height:var(--boe-code-editor-height,420px);min-height:160px;overflow:auto;font-family:monospace;font-size:14px;line-height:1.6}
128
+ .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
+ .cm-cursor{border-left-color:var(--boe-token-text-text,#222)}
130
+ .cm-activeLine,.cm-activeLineGutter,.cm-selectionBackground{background:color-mix(in srgb,var(--boe-token-surface-surface-brand,#0061d5) 12%,transparent)!important}
131
+ .cm-matchingBracket,.cm-selectionMatch{background:color-mix(in srgb,var(--boe-token-surface-surface-brand,#0061d5) 15%,transparent)!important;color:inherit!important}
132
+ .cm-nonmatchingBracket{color:var(--boe-token-text-status-text-error,#b92340)!important}
133
+ .cm-searchMatch{background:color-mix(in srgb,var(--boe-token-text-status-text-warning,#9a6500) 20%,transparent);outline:none}
134
+ .cm-searchMatch-selected,.cm-tooltip-autocomplete ul li[aria-selected=true]{background:var(--boe-token-surface-surface-brand,#0061d5)!important;color:var(--boe-token-text-text-on-brand,#fff)!important}
135
+ .cm-panels input,.cm-panels button,.cm-foldPlaceholder{background:var(--boe-token-surface-surface,#fff);color:var(--boe-token-text-text,#222);border:1px solid var(--boe-token-stroke-stroke,#ddd)}
136
+ .cm-lintRange-error,.cm-lintRange-warning,.cm-lintRange-info{background-image:none;text-decoration-line:underline;text-decoration-style:wavy;text-decoration-thickness:1px;text-underline-offset:3px}
137
+ .cm-lintRange-error,.cm-diagnostic-error{color:inherit;text-decoration-color:var(--boe-token-text-status-text-error,#b92340);border-left-color:var(--boe-token-text-status-text-error,#b92340)}
138
+ .cm-lintRange-warning,.cm-diagnostic-warning{text-decoration-color:var(--boe-token-text-status-text-warning,#9a6500);border-left-color:var(--boe-token-text-status-text-warning,#9a6500)}
139
+ .cm-lintRange-info,.cm-diagnostic-info{text-decoration-color:var(--boe-token-surface-surface-brand,#0061d5);border-left-color:var(--boe-token-surface-surface-brand,#0061d5)}
140
+ .cm-lint-marker-error{background-image:radial-gradient(circle,var(--boe-token-text-status-text-error,#b92340) 45%,transparent 50%)!important}
141
+ .cm-lint-marker-warning{background-image:radial-gradient(circle,var(--boe-token-text-status-text-warning,#9a6500) 45%,transparent 50%)!important}
142
+ .cm-lint-marker-info{background-image:radial-gradient(circle,var(--boe-token-surface-surface-brand,#0061d5) 45%,transparent 50%)!important}
143
+ [part=help],[part=problems]{font-size:13px;color:var(--boe-token-text-text-secondary,#666);margin:8px 0}
144
+ [part=toolbar]{display:flex;gap:8px;align-items:center;flex-wrap:wrap}
145
+ button{font:inherit;color:inherit;background:var(--boe-token-surface-surface,#fff);border:1px solid var(--boe-token-stroke-stroke,#ddd);border-radius:8px;padding:8px;min-height:36px}
146
+ button:focus-visible{outline:3px solid var(--boe-token-surface-surface-brand,#0061d5);outline-offset:2px}
147
+ @media(prefers-reduced-motion:reduce){*{animation:none!important;transition:none!important;scroll-behavior:auto!important}}
148
+ </style><div part="editor"></div><p part="help" id="editor-help">Press Escape then Tab to leave the editor. Use Control-Space for suggestions.</p><div part="toolbar"><button type="button" data-direction="-1">Previous problem</button><button type="button" data-direction="1">Next problem</button><span part="problems" role="status" aria-live="polite"></span></div>`;
149
+ }
150
+ setupListeners() {
151
+ this.shadowRoot.querySelectorAll("button[data-direction]").forEach((button) => button.addEventListener("click", () => this.navigateProblem(Number(button.dataset.direction))));
152
+ // Keep the advertised escape hatch independent of completion/search keymaps.
153
+ this.shadowRoot.addEventListener("keydown", (event) => {
154
+ const key = event;
155
+ if (!key.target.closest(".cm-content"))
156
+ return;
157
+ if (key.ctrlKey && (key.code === "Space" || key.key === " ")) {
158
+ key.preventDefault();
159
+ key.stopPropagation();
160
+ if (this.view && !this.readonly)
161
+ startCompletion(this.view);
162
+ }
163
+ else if (key.key === "Escape")
164
+ this.escapeUntil = Date.now() + 2000;
165
+ else if (key.key === "Tab" && Date.now() <= this.escapeUntil) {
166
+ key.preventDefault();
167
+ key.stopPropagation();
168
+ this.escapeUntil = 0;
169
+ const target = this.shadowRoot.querySelector("button:not(:disabled)") ?? this.shadowRoot.querySelector("[part=help]");
170
+ target.setAttribute("tabindex", "0");
171
+ target.focus();
172
+ }
173
+ else if (!key.ctrlKey &&
174
+ !key.metaKey &&
175
+ !key.altKey &&
176
+ key.key !== "Shift")
177
+ this.escapeUntil = 0;
178
+ }, { capture: true });
179
+ }
180
+ languageExtension() {
181
+ return this.language === "go"
182
+ ? go()
183
+ : javascript({ typescript: this.language === "typescript" });
184
+ }
185
+ completionExtension() {
186
+ return autocompletion({
187
+ override: [
188
+ (context) => {
189
+ const word = context.matchBefore(/[\w$]+(?:\.[\w$]*)*/);
190
+ if (!word && !context.explicit)
191
+ return null;
192
+ let from = word?.from ?? context.pos;
193
+ // A full SDK name replaces its prefix; a method replaces only the
194
+ // identifier after the receiver's dot (for example flow().st).
195
+ if (word?.text.includes(".") &&
196
+ !this.completionsInternal.some((option) => option.label.startsWith(word.text))) {
197
+ from += word.text.lastIndexOf(".") + 1;
198
+ }
199
+ return { from, options: [...this.completionsInternal] };
200
+ },
201
+ ],
202
+ });
203
+ }
204
+ update() {
205
+ if (!this.view) {
206
+ this.view = new EditorView({
207
+ parent: this.shadowRoot.querySelector("[part=editor]"),
208
+ root: this.shadowRoot,
209
+ state: EditorState.create({
210
+ doc: this.value,
211
+ extensions: [
212
+ basicSetup,
213
+ lintGutter(),
214
+ keymap.of([indentWithTab]),
215
+ Prec.highest(keymap.of([
216
+ {
217
+ key: "Escape",
218
+ run: (view) => {
219
+ closeCompletion(view);
220
+ closeSearchPanel(view);
221
+ view.setTabFocusMode(2000);
222
+ return true;
223
+ },
224
+ },
225
+ ])),
226
+ this.languageConfig.of(this.languageExtension()),
227
+ this.readonlyConfig.of([
228
+ EditorState.readOnly.of(this.readonly),
229
+ EditorView.editable.of(!this.readonly),
230
+ ]),
231
+ this.completionsConfig.of(this.completionExtension()),
232
+ syntaxHighlighting(HighlightStyle.define([
233
+ {
234
+ tag: [
235
+ tags.variableName,
236
+ tags.definition(tags.variableName),
237
+ tags.propertyName,
238
+ tags.operator,
239
+ tags.punctuation,
240
+ ],
241
+ color: "var(--boe-token-text-text,#222)",
242
+ },
243
+ {
244
+ tag: tags.keyword,
245
+ color: "var(--boe-token-surface-surface-brand,#0061d5)",
246
+ },
247
+ {
248
+ tag: [
249
+ tags.typeName,
250
+ tags.className,
251
+ tags.function(tags.variableName),
252
+ ],
253
+ color: "var(--boe-token-surface-surface-brand,#0061d5)",
254
+ },
255
+ {
256
+ tag: tags.string,
257
+ color: "var(--boe-token-text-status-text-success,#138a58)",
258
+ },
259
+ {
260
+ tag: tags.comment,
261
+ color: "var(--boe-token-text-text-secondary,#666)",
262
+ },
263
+ {
264
+ tag: tags.number,
265
+ color: "var(--boe-token-text-status-text-warning,#9a6500)",
266
+ },
267
+ {
268
+ tag: [tags.bool, tags.null, tags.atom],
269
+ color: "var(--boe-token-text-status-text-warning,#9a6500)",
270
+ },
271
+ ])),
272
+ EditorView.updateListener.of((update) => {
273
+ if (update.docChanged && !this.syncing) {
274
+ this.valueInternal = update.state.doc.toString();
275
+ this.pending = true;
276
+ clearTimeout(this.timer);
277
+ this.timer = setTimeout(() => this.flushChange(), 200);
278
+ }
279
+ }),
280
+ EditorView.domEventHandlers({
281
+ blur: () => {
282
+ this.flushChange();
283
+ return false;
284
+ },
285
+ }),
286
+ ],
287
+ }),
288
+ });
289
+ this.selection = this.savedSelection;
290
+ }
291
+ else {
292
+ this.syncing = true;
293
+ try {
294
+ if (this.view.state.doc.toString() !== this.value) {
295
+ clearTimeout(this.timer);
296
+ this.pending = false;
297
+ this.view.dispatch({
298
+ changes: {
299
+ from: 0,
300
+ to: this.view.state.doc.length,
301
+ insert: this.value,
302
+ },
303
+ });
304
+ }
305
+ this.view.dispatch({
306
+ effects: [
307
+ this.languageConfig.reconfigure(this.languageExtension()),
308
+ this.readonlyConfig.reconfigure([
309
+ EditorState.readOnly.of(this.readonly),
310
+ EditorView.editable.of(!this.readonly),
311
+ ]),
312
+ this.completionsConfig.reconfigure(this.completionExtension()),
313
+ ],
314
+ });
315
+ }
316
+ finally {
317
+ this.syncing = false;
318
+ }
319
+ }
320
+ this.view.contentDOM.setAttribute("aria-label", this.label);
321
+ this.view.contentDOM.setAttribute("tabindex", "0");
322
+ this.view.contentDOM.setAttribute("aria-describedby", "editor-help");
323
+ this.syncProblems();
324
+ }
325
+ flushChange() {
326
+ clearTimeout(this.timer);
327
+ if (!this.pending)
328
+ return;
329
+ this.pending = false;
330
+ this.dispatchEvent(new CustomEvent("value-changed", {
331
+ detail: { value: this.value },
332
+ bubbles: true,
333
+ composed: true,
334
+ }));
335
+ }
336
+ diagnostics() {
337
+ if (!this.view)
338
+ return [];
339
+ const doc = this.view.state.doc;
340
+ return this.problems
341
+ .filter((problem) => Number.isInteger(problem.line) &&
342
+ problem.line >= 1 &&
343
+ problem.line <= doc.lines)
344
+ .map((problem) => {
345
+ const line = doc.line(problem.line);
346
+ const column = Number.isFinite(problem.col) ? problem.col : 1;
347
+ const endColumn = Number.isFinite(problem.endCol)
348
+ ? problem.endCol
349
+ : column + 1;
350
+ const from = line.from + Math.max(0, Math.min(line.length, column - 1));
351
+ const to = Math.max(from, Math.min(line.to, line.from + endColumn - 1));
352
+ return { from, to, message: problem.message, severity: problem.tone };
353
+ });
354
+ }
355
+ syncProblems() {
356
+ if (!this.view)
357
+ return;
358
+ const diagnostics = this.diagnostics();
359
+ this.view.dispatch(setDiagnostics(this.view.state, diagnostics));
360
+ this.shadowRoot.querySelector("[part=problems]").textContent =
361
+ `${diagnostics.length} problem${diagnostics.length === 1 ? "" : "s"}`;
362
+ this.shadowRoot.querySelectorAll("button[data-direction]").forEach((button) => (button.disabled = diagnostics.length === 0));
363
+ }
364
+ navigateProblem(direction) {
365
+ const diagnostics = this.diagnostics();
366
+ if (!diagnostics.length)
367
+ return;
368
+ this.problemIndex =
369
+ this.problemIndex < 0
370
+ ? direction < 0
371
+ ? diagnostics.length - 1
372
+ : 0
373
+ : (this.problemIndex + (direction < 0 ? -1 : 1) + diagnostics.length) %
374
+ diagnostics.length;
375
+ const problem = diagnostics[this.problemIndex];
376
+ this.selection = { anchor: problem.from, head: problem.to };
377
+ this.focus();
378
+ this.shadowRoot.querySelector("[part=problems]").textContent =
379
+ `Problem ${this.problemIndex + 1} of ${diagnostics.length}: ${problem.message}`;
380
+ }
381
+ }
382
+ CodeEditor.register();
@@ -35,6 +35,9 @@ const numberInputStyles = `
35
35
  }
36
36
 
37
37
  [part="input"] {
38
+ width: 100%;
39
+ min-width: 0;
40
+ box-sizing: border-box;
38
41
  font: inherit;
39
42
  color: var(--boe-token-text-text, #222222);
40
43
  padding: 0.45rem 0.7rem;
@@ -64,7 +67,7 @@ export class NumberInput extends FormAssociatedElement {
64
67
  static tagName = DEFAULT_TAG_NAME;
65
68
  static get observedAttributes() {
66
69
  return [
67
- ...FormAssociatedElement.formObservedAttributes,
70
+ ...FormAssociatedElement.fieldObservedAttributes,
68
71
  "disabled",
69
72
  "label",
70
73
  "max",