@unofficialbox/box-open-elements 0.26.0 → 0.28.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.
- package/README.md +13 -13
- package/dist/components/actions/button.d.ts +3 -0
- package/dist/components/actions/button.js +26 -7
- package/dist/components/collections/resource-row.d.ts +28 -0
- package/dist/components/collections/resource-row.js +75 -0
- package/dist/components/feedback/mode-indicator.d.ts +21 -0
- package/dist/components/feedback/mode-indicator.js +67 -0
- package/dist/components/feedback/progress-steps.d.ts +4 -0
- package/dist/components/feedback/progress-steps.js +34 -2
- package/dist/components/feedback/verdict-banner.d.ts +23 -0
- package/dist/components/feedback/verdict-banner.js +59 -0
- package/dist/components/forms/code-editor.d.ts +19 -0
- package/dist/components/forms/code-editor.js +102 -33
- package/dist/components/forms/combobox.d.ts +1 -0
- package/dist/components/forms/combobox.js +9 -5
- package/dist/components/forms/number-input.d.ts +1 -0
- package/dist/components/forms/number-input.js +11 -0
- package/dist/components/forms/text-area.d.ts +1 -0
- package/dist/components/forms/text-area.js +2 -0
- package/dist/components/forms/text-field.d.ts +3 -0
- package/dist/components/forms/text-field.js +15 -1
- package/dist/components/forms/tile-group.d.ts +6 -0
- package/dist/components/forms/tile-group.js +29 -2
- package/dist/components/layout/app-shell.js +13 -3
- package/dist/components/layout/nav-sidebar.js +14 -2
- package/dist/components/navigation/accordion.d.ts +3 -0
- package/dist/components/navigation/accordion.js +21 -5
- package/dist/components/output/code-block.d.ts +3 -0
- package/dist/components/output/code-block.js +12 -2
- package/dist/components/overlays/command-palette.js +4 -0
- package/dist/components/overlays/dialog.d.ts +6 -0
- package/dist/components/overlays/dialog.js +18 -2
- package/dist/components/overlays/drawer.d.ts +3 -0
- package/dist/components/overlays/drawer.js +11 -3
- package/dist/core/index.d.ts +1 -1
- package/dist/core/index.js +1 -1
- package/dist/entries/mode-indicator.d.ts +1 -0
- package/dist/entries/mode-indicator.js +1 -0
- package/dist/entries/resource-row.d.ts +1 -0
- package/dist/entries/resource-row.js +1 -0
- package/dist/entries/verdict-banner.d.ts +1 -0
- package/dist/entries/verdict-banner.js +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +3 -0
- package/dist/patterns/call-console/call-console.js +1 -1
- package/dist/patterns/flow-builder/flow-builder.d.ts +8 -0
- package/dist/patterns/flow-builder/flow-builder.js +34 -0
- package/dist/patterns/flow-builder/primitives.d.ts +14 -2
- package/dist/patterns/flow-builder/primitives.js +28 -5
- package/dist/patterns/form-wizard/form-wizard.js +18 -3
- package/dist/patterns/form-wizard/wizard-summary.js +11 -2
- package/dist/patterns/process-modeler/design.d.ts +1 -1
- package/dist/patterns/process-modeler/design.js +1 -1
- package/package.json +1 -1
|
@@ -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", "wrap"];
|
|
17
|
+
return ["current-line-style", "fill-height", "hide-help", "hide-problems", "language", "readonly", "label", "value", "wrap"];
|
|
18
18
|
}
|
|
19
19
|
valueInternal = "";
|
|
20
20
|
problemsInternal = [];
|
|
@@ -24,6 +24,7 @@ export class CodeEditor extends BaseElement {
|
|
|
24
24
|
readonlyConfig = new Compartment();
|
|
25
25
|
completionsConfig = new Compartment();
|
|
26
26
|
wrapConfig = new Compartment();
|
|
27
|
+
wrapIndentConfig = new Compartment();
|
|
27
28
|
highlightsConfig = new Compartment();
|
|
28
29
|
highlightsInternal = [];
|
|
29
30
|
source;
|
|
@@ -63,12 +64,48 @@ export class CodeEditor extends BaseElement {
|
|
|
63
64
|
}
|
|
64
65
|
get wrap() { return this.hasAttribute("wrap"); }
|
|
65
66
|
set wrap(value) { this.toggleAttribute("wrap", value); }
|
|
67
|
+
get currentLineStyle() {
|
|
68
|
+
const value = this.getAttribute("current-line-style");
|
|
69
|
+
return value === "border" || value === "none" ? value : "fill";
|
|
70
|
+
}
|
|
71
|
+
set currentLineStyle(value) { this.setAttribute("current-line-style", value); }
|
|
72
|
+
get hideHelp() { return this.hasAttribute("hide-help"); }
|
|
73
|
+
set hideHelp(value) { this.toggleAttribute("hide-help", value); }
|
|
74
|
+
get hideProblems() { return this.hasAttribute("hide-problems"); }
|
|
75
|
+
set hideProblems(value) { this.toggleAttribute("hide-problems", value); }
|
|
76
|
+
get fillHeight() { return this.hasAttribute("fill-height"); }
|
|
77
|
+
set fillHeight(value) { this.toggleAttribute("fill-height", value); }
|
|
66
78
|
get completionSource() { return this.source; }
|
|
67
79
|
set completionSource(value) { this.source = value; if (this.isRendered)
|
|
68
80
|
this.update(); }
|
|
69
81
|
get highlights() { return this.highlightsInternal.map(range => ({ ...range })); }
|
|
70
82
|
set highlights(value) { this.highlightsInternal = value.map(range => ({ ...range })); if (this.isRendered)
|
|
71
83
|
this.update(); }
|
|
84
|
+
wrapIndentExtension() {
|
|
85
|
+
return EditorView.decorations.of(view => {
|
|
86
|
+
if (!this.wrap)
|
|
87
|
+
return Decoration.none;
|
|
88
|
+
const ranges = [];
|
|
89
|
+
const seen = new Set();
|
|
90
|
+
for (const visible of view.visibleRanges) {
|
|
91
|
+
const first = view.state.doc.lineAt(visible.from).number;
|
|
92
|
+
const last = view.state.doc.lineAt(visible.to).number;
|
|
93
|
+
for (let number = first; number <= last; number++) {
|
|
94
|
+
const line = view.state.doc.line(number);
|
|
95
|
+
if (seen.has(line.from))
|
|
96
|
+
continue;
|
|
97
|
+
seen.add(line.from);
|
|
98
|
+
const whitespace = line.text.match(/^[\t ]*/)?.[0] ?? "";
|
|
99
|
+
const columns = Math.min(24, whitespace.replaceAll("\t", " ").length);
|
|
100
|
+
if (columns)
|
|
101
|
+
ranges.push(Decoration.line({ attributes: {
|
|
102
|
+
style: `padding-left:${columns}ch;text-indent:-${columns}ch`,
|
|
103
|
+
} }).range(line.from));
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
return Decoration.set(ranges, true);
|
|
107
|
+
});
|
|
108
|
+
}
|
|
72
109
|
highlightExtension() {
|
|
73
110
|
return EditorView.decorations.of(view => {
|
|
74
111
|
const doc = view.state.doc;
|
|
@@ -138,6 +175,19 @@ export class CodeEditor extends BaseElement {
|
|
|
138
175
|
scrollIntoView: true,
|
|
139
176
|
});
|
|
140
177
|
}
|
|
178
|
+
/** One-based line containing a UTF-16 document offset, including before mount. */
|
|
179
|
+
lineAt(position) {
|
|
180
|
+
const doc = this.view?.state.doc ?? EditorState.create({ doc: this.value }).doc;
|
|
181
|
+
const offset = Math.max(0, Math.min(doc.length, Number.isFinite(position) ? Math.floor(position) : 0));
|
|
182
|
+
const { number, from, to } = doc.lineAt(offset);
|
|
183
|
+
return { number, from, to };
|
|
184
|
+
}
|
|
185
|
+
/** Start offset of a one-based line; out-of-range lines clamp to the document. */
|
|
186
|
+
lineStart(number) {
|
|
187
|
+
const doc = this.view?.state.doc ?? EditorState.create({ doc: this.value }).doc;
|
|
188
|
+
const line = Math.max(1, Math.min(doc.lines, Number.isFinite(number) ? Math.floor(number) : 1));
|
|
189
|
+
return doc.line(line).from;
|
|
190
|
+
}
|
|
141
191
|
revealLine(line, options = {}) {
|
|
142
192
|
if (!this.view)
|
|
143
193
|
return;
|
|
@@ -166,16 +216,24 @@ export class CodeEditor extends BaseElement {
|
|
|
166
216
|
}
|
|
167
217
|
renderTemplate() {
|
|
168
218
|
this.shadowRoot.innerHTML = `<style>
|
|
169
|
-
:host{display:
|
|
219
|
+
:host{display:flex;flex-direction:column;min-width:0;min-height:0;color:var(--boe-token-text-text,#222);font:inherit}
|
|
170
220
|
:host([hidden]){display:none!important}
|
|
171
|
-
[part=editor]{border:1px solid var(--boe-token-stroke-stroke,#ddd);border-radius:8px;overflow:hidden}
|
|
172
|
-
.cm-editor{background:var(--boe-token-surface-surface,#fff);color:var(--boe-token-text-text,#222)}
|
|
221
|
+
[part=editor]{border:1px solid var(--boe-token-stroke-stroke,#ddd);border-radius:8px;overflow:hidden;min-height:0;flex:1 1 auto}
|
|
222
|
+
.cm-editor{background:var(--boe-code-background,var(--boe-token-surface-surface,#fff));color:var(--boe-code-foreground,var(--boe-token-text-text,#222))}
|
|
173
223
|
[part=editor]:focus-within{box-shadow:0 0 0 3px var(--boe-token-surface-surface-brand,#0061d5)}
|
|
174
224
|
.cm-editor.cm-focused{outline:none}
|
|
175
|
-
.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}
|
|
176
|
-
|
|
177
|
-
.cm-
|
|
178
|
-
.cm-
|
|
225
|
+
.cm-scroller{max-height:var(--boe-code-editor-height,420px);min-height:160px;overflow:auto;font-family:var(--boe-code-font-family,monospace);font-size:var(--boe-code-font-size,14px);line-height:var(--boe-code-line-height,1.6)}
|
|
226
|
+
:host([fill-height]){height:100%}
|
|
227
|
+
:host([fill-height]) [part=editor],:host([fill-height]) .cm-editor,:host([fill-height]) .cm-scroller{height:100%;max-height:none;min-height:0}
|
|
228
|
+
.cm-gutters,.cm-panels,.cm-tooltip{background:var(--boe-token-surface-surface-secondary,#fbfbfb)!important;color:var(--boe-code-foreground,var(--boe-token-text-text,#222))!important;border-color:var(--boe-token-stroke-stroke,#ddd)!important}
|
|
229
|
+
.cm-gutters{color:var(--boe-code-gutter,var(--boe-token-text-text,#222))!important;border-right:var(--boe-code-gutter-border,1px solid var(--boe-token-stroke-stroke,#ddd))!important}
|
|
230
|
+
.cm-cursor{border-left-color:var(--boe-code-caret,var(--boe-token-text-text,#222))}
|
|
231
|
+
.cm-activeLine,.cm-activeLineGutter{background:color-mix(in srgb,var(--boe-token-surface-surface-brand,#0061d5) 12%,transparent)!important}
|
|
232
|
+
:host([current-line-style=border]) .cm-activeLine{background:transparent!important;box-shadow:inset 0 1px var(--boe-code-current-line-border,var(--boe-token-stroke-stroke,#ddd)),inset 0 -1px var(--boe-code-current-line-border,var(--boe-token-stroke-stroke,#ddd))}
|
|
233
|
+
:host([current-line-style=none]) .cm-activeLine{background:transparent!important}
|
|
234
|
+
:host([current-line-style=border]) .cm-activeLineGutter,:host([current-line-style=none]) .cm-activeLineGutter{background:transparent!important}
|
|
235
|
+
.cm-activeLineGutter{color:var(--boe-code-gutter-active,var(--boe-code-gutter,var(--boe-token-text-text,#222)))!important}
|
|
236
|
+
.cm-selectionBackground,.cm-content ::selection{background:var(--boe-code-selection,color-mix(in srgb,var(--boe-token-surface-surface-brand,#0061d5) 12%,transparent))!important}
|
|
179
237
|
.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)}
|
|
180
238
|
.cm-matchingBracket,.cm-selectionMatch{background:color-mix(in srgb,var(--boe-token-surface-surface-brand,#0061d5) 15%,transparent)!important;color:inherit!important}
|
|
181
239
|
.cm-nonmatchingBracket{color:var(--boe-token-text-status-text-error,#b92340)!important}
|
|
@@ -190,6 +248,7 @@ export class CodeEditor extends BaseElement {
|
|
|
190
248
|
.cm-lint-marker-warning{background-image:radial-gradient(circle,var(--boe-token-text-status-text-warning,#9a6500) 45%,transparent 50%)!important}
|
|
191
249
|
.cm-lint-marker-info{background-image:radial-gradient(circle,var(--boe-token-surface-surface-brand,#0061d5) 45%,transparent 50%)!important}
|
|
192
250
|
[part=help],[part=problems]{font-size:13px;color:var(--boe-token-text-text-secondary,#666);margin:8px 0}
|
|
251
|
+
[part=help][hidden],[part=toolbar][hidden]{display:none}
|
|
193
252
|
[part=toolbar]{display:flex;gap:8px;align-items:center;flex-wrap:wrap}
|
|
194
253
|
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}
|
|
195
254
|
button:focus-visible{outline:3px solid var(--boe-token-surface-surface-brand,#0061d5);outline-offset:2px}
|
|
@@ -281,45 +340,49 @@ export class CodeEditor extends BaseElement {
|
|
|
281
340
|
]),
|
|
282
341
|
this.completionsConfig.of(this.completionExtension()),
|
|
283
342
|
this.wrapConfig.of(this.wrap ? EditorView.lineWrapping : []),
|
|
343
|
+
this.wrapIndentConfig.of(this.wrapIndentExtension()),
|
|
284
344
|
this.highlightsConfig.of(this.highlightExtension()),
|
|
285
345
|
syntaxHighlighting(HighlightStyle.define([
|
|
286
346
|
{
|
|
287
|
-
tag:
|
|
288
|
-
|
|
289
|
-
tags.definition(tags.variableName),
|
|
290
|
-
tags.propertyName,
|
|
291
|
-
tags.operator,
|
|
292
|
-
tags.punctuation,
|
|
293
|
-
],
|
|
294
|
-
color: "var(--boe-token-text-text,#222)",
|
|
347
|
+
tag: tags.controlKeyword,
|
|
348
|
+
color: "var(--boe-code-control,var(--boe-code-keyword,var(--boe-token-surface-surface-brand,#0061d5)))",
|
|
295
349
|
},
|
|
296
350
|
{
|
|
297
|
-
tag: tags.keyword,
|
|
298
|
-
color: "var(--boe-token-surface-surface-brand,#0061d5)",
|
|
351
|
+
tag: [tags.keyword, tags.moduleKeyword, tags.definitionKeyword, tags.operatorKeyword],
|
|
352
|
+
color: "var(--boe-code-keyword,var(--boe-token-surface-surface-brand,#0061d5))",
|
|
299
353
|
},
|
|
300
354
|
{
|
|
301
|
-
tag: [
|
|
302
|
-
|
|
303
|
-
tags.className,
|
|
304
|
-
tags.function(tags.variableName),
|
|
305
|
-
],
|
|
306
|
-
color: "var(--boe-token-surface-surface-brand,#0061d5)",
|
|
355
|
+
tag: [tags.function(tags.variableName), tags.function(tags.propertyName)],
|
|
356
|
+
color: "var(--boe-code-function,var(--boe-token-surface-surface-brand,#0061d5))",
|
|
307
357
|
},
|
|
308
358
|
{
|
|
309
|
-
tag: tags.
|
|
310
|
-
color: "var(--boe-token-
|
|
359
|
+
tag: [tags.typeName, tags.className, tags.namespace],
|
|
360
|
+
color: "var(--boe-code-type,var(--boe-token-surface-surface-brand,#0061d5))",
|
|
311
361
|
},
|
|
312
362
|
{
|
|
313
|
-
tag: tags.
|
|
314
|
-
color: "var(--boe-token-text-text
|
|
363
|
+
tag: tags.propertyName,
|
|
364
|
+
color: "var(--boe-code-property,var(--boe-token-text-text,#222))",
|
|
365
|
+
},
|
|
366
|
+
{
|
|
367
|
+
tag: [tags.variableName, tags.definition(tags.variableName)],
|
|
368
|
+
color: "var(--boe-code-variable,var(--boe-token-text-text,#222))",
|
|
369
|
+
},
|
|
370
|
+
{
|
|
371
|
+
tag: [tags.string, tags.special(tags.string)],
|
|
372
|
+
color: "var(--boe-code-string,var(--boe-token-text-status-text-success,#138a58))",
|
|
373
|
+
},
|
|
374
|
+
{
|
|
375
|
+
tag: [tags.number, tags.bool, tags.null, tags.atom],
|
|
376
|
+
color: "var(--boe-code-number,var(--boe-token-text-status-text-warning,#9a6500))",
|
|
315
377
|
},
|
|
316
378
|
{
|
|
317
|
-
tag: tags.
|
|
318
|
-
color: "var(--boe-token-text-
|
|
379
|
+
tag: tags.comment,
|
|
380
|
+
color: "var(--boe-code-comment,var(--boe-token-text-text-secondary,#666))",
|
|
381
|
+
fontStyle: "var(--boe-code-comment-style,normal)",
|
|
319
382
|
},
|
|
320
383
|
{
|
|
321
|
-
tag: [tags.
|
|
322
|
-
color: "var(--boe-token-text-
|
|
384
|
+
tag: [tags.punctuation, tags.bracket, tags.operator, tags.separator],
|
|
385
|
+
color: "var(--boe-code-punctuation,var(--boe-token-text-text,#222))",
|
|
323
386
|
},
|
|
324
387
|
])),
|
|
325
388
|
EditorView.updateListener.of((update) => {
|
|
@@ -369,6 +432,7 @@ export class CodeEditor extends BaseElement {
|
|
|
369
432
|
]),
|
|
370
433
|
this.completionsConfig.reconfigure(this.completionExtension()),
|
|
371
434
|
this.wrapConfig.reconfigure(this.wrap ? EditorView.lineWrapping : []),
|
|
435
|
+
this.wrapIndentConfig.reconfigure(this.wrapIndentExtension()),
|
|
372
436
|
this.highlightsConfig.reconfigure(this.highlightExtension()),
|
|
373
437
|
],
|
|
374
438
|
});
|
|
@@ -379,7 +443,12 @@ export class CodeEditor extends BaseElement {
|
|
|
379
443
|
}
|
|
380
444
|
this.view.contentDOM.setAttribute("aria-label", this.label);
|
|
381
445
|
this.view.contentDOM.setAttribute("tabindex", "0");
|
|
382
|
-
this.
|
|
446
|
+
if (this.hideHelp)
|
|
447
|
+
this.view.contentDOM.removeAttribute("aria-describedby");
|
|
448
|
+
else
|
|
449
|
+
this.view.contentDOM.setAttribute("aria-describedby", "editor-help");
|
|
450
|
+
this.shadowRoot.querySelector('[part=help]').hidden = this.hideHelp;
|
|
451
|
+
this.shadowRoot.querySelector('[part=toolbar]').hidden = this.hideProblems;
|
|
383
452
|
this.syncProblems();
|
|
384
453
|
}
|
|
385
454
|
flushChange() {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { FormAssociatedElement, boeFormFieldErrorStyles, formErrorMessageMarkup, } from "../../core/index.js";
|
|
1
|
+
import { FormAssociatedElement, FORM_LABEL_ID, boeFormFieldErrorStyles, boeFormFieldSupportStyles, formDescriptionMarkup, 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
4
|
import { boeControl, boeOverlay, boeRadius } from "../../foundations/geometry/index.js";
|
|
@@ -137,12 +137,13 @@ const comboboxStyles = `
|
|
|
137
137
|
}
|
|
138
138
|
|
|
139
139
|
${boeFormFieldErrorStyles}
|
|
140
|
+
${boeFormFieldSupportStyles}
|
|
140
141
|
`;
|
|
141
142
|
export class Combobox extends FormAssociatedElement {
|
|
142
143
|
static tagName = DEFAULT_TAG_NAME;
|
|
143
144
|
static get observedAttributes() {
|
|
144
145
|
return [
|
|
145
|
-
...FormAssociatedElement.
|
|
146
|
+
...FormAssociatedElement.fieldObservedAttributes,
|
|
146
147
|
"disabled",
|
|
147
148
|
"label",
|
|
148
149
|
"options",
|
|
@@ -159,6 +160,7 @@ export class Combobox extends FormAssociatedElement {
|
|
|
159
160
|
listboxId = `box-combobox-list-${Math.random().toString(36).slice(2, 10)}`;
|
|
160
161
|
inputEl;
|
|
161
162
|
labelEl;
|
|
163
|
+
descriptionEl;
|
|
162
164
|
controlEl;
|
|
163
165
|
listboxEl;
|
|
164
166
|
errorEl;
|
|
@@ -372,7 +374,7 @@ export class Combobox extends FormAssociatedElement {
|
|
|
372
374
|
this.shadowRoot.innerHTML = `
|
|
373
375
|
<style>${comboboxStyles}</style>
|
|
374
376
|
<div part="field">
|
|
375
|
-
<span part="label" id="${
|
|
377
|
+
<span part="label" id="${FORM_LABEL_ID}"></span>
|
|
376
378
|
<div part="control">
|
|
377
379
|
<input
|
|
378
380
|
type="text"
|
|
@@ -384,11 +386,13 @@ export class Combobox extends FormAssociatedElement {
|
|
|
384
386
|
aria-controls="${this.listboxId}"
|
|
385
387
|
/>
|
|
386
388
|
</div>
|
|
387
|
-
|
|
389
|
+
${formDescriptionMarkup()}
|
|
390
|
+
<ul part="listbox" id="${this.listboxId}" role="listbox" aria-labelledby="${FORM_LABEL_ID}" hidden></ul>
|
|
388
391
|
${formErrorMessageMarkup()}
|
|
389
392
|
</div>
|
|
390
393
|
`;
|
|
391
394
|
this.labelEl = this.shadowRoot.querySelector('[part="label"]');
|
|
395
|
+
this.descriptionEl = this.shadowRoot.querySelector('[part="description"]');
|
|
392
396
|
this.inputEl = this.shadowRoot.querySelector('[part="input"]');
|
|
393
397
|
this.controlEl = this.shadowRoot.querySelector('[part="control"]');
|
|
394
398
|
this.listboxEl = this.shadowRoot.querySelector('[part="listbox"]');
|
|
@@ -511,7 +515,7 @@ export class Combobox extends FormAssociatedElement {
|
|
|
511
515
|
}
|
|
512
516
|
this.labelEl.textContent = this.label;
|
|
513
517
|
this.inputEl.placeholder = this.placeholder;
|
|
514
|
-
this.
|
|
518
|
+
this.applyFieldSupport(this.labelEl, this.inputEl, this.descriptionEl);
|
|
515
519
|
if (this.shadowRoot?.activeElement !== this.inputEl && this.filterText == null) {
|
|
516
520
|
this.inputEl.value = this.getDisplayValue();
|
|
517
521
|
}
|
|
@@ -85,6 +85,7 @@ export class NumberInput extends FormAssociatedElement {
|
|
|
85
85
|
labelEl;
|
|
86
86
|
errorEl;
|
|
87
87
|
descriptionEl;
|
|
88
|
+
clampStatusEl;
|
|
88
89
|
get disabled() {
|
|
89
90
|
return this.hasAttribute("disabled");
|
|
90
91
|
}
|
|
@@ -183,6 +184,14 @@ export class NumberInput extends FormAssociatedElement {
|
|
|
183
184
|
}
|
|
184
185
|
syncValue(nextValue) {
|
|
185
186
|
const normalizedValue = this.clamp(nextValue);
|
|
187
|
+
if (normalizedValue !== nextValue) {
|
|
188
|
+
this.inputEl.value = String(normalizedValue);
|
|
189
|
+
const boundary = nextValue > normalizedValue ? "most" : "least";
|
|
190
|
+
this.clampStatusEl.textContent = `Changed to ${normalizedValue}, the ${boundary} allowed`;
|
|
191
|
+
}
|
|
192
|
+
else {
|
|
193
|
+
this.clampStatusEl.textContent = "";
|
|
194
|
+
}
|
|
186
195
|
this.valueInternal = normalizedValue;
|
|
187
196
|
this.setAttribute("value", String(normalizedValue));
|
|
188
197
|
this.syncFormAssociation();
|
|
@@ -202,6 +211,7 @@ export class NumberInput extends FormAssociatedElement {
|
|
|
202
211
|
<span part="label"></span>
|
|
203
212
|
${formDescriptionMarkup()}
|
|
204
213
|
<input type="number" part="input" />
|
|
214
|
+
<span part="clamp-status" role="status" style="position:absolute;width:1px;height:1px;overflow:hidden;clip-path:inset(50%)"></span>
|
|
205
215
|
${formErrorMessageMarkup()}
|
|
206
216
|
</label>
|
|
207
217
|
`;
|
|
@@ -209,6 +219,7 @@ export class NumberInput extends FormAssociatedElement {
|
|
|
209
219
|
this.inputEl = this.shadowRoot.querySelector('[part="input"]');
|
|
210
220
|
this.errorEl = this.shadowRoot.querySelector('[part="error-message"]');
|
|
211
221
|
this.descriptionEl = this.shadowRoot.querySelector('[part="description"]');
|
|
222
|
+
this.clampStatusEl = this.shadowRoot.querySelector('[part="clamp-status"]');
|
|
212
223
|
}
|
|
213
224
|
setupListeners() {
|
|
214
225
|
this.inputEl.addEventListener("input", event => {
|
|
@@ -36,6 +36,9 @@ export declare class TextField extends FormAssociatedElement {
|
|
|
36
36
|
*/
|
|
37
37
|
get reveal(): boolean;
|
|
38
38
|
set reveal(value: boolean);
|
|
39
|
+
/** Noun used in the Show/Hide control's accessible name. Defaults to the field label. */
|
|
40
|
+
get revealLabel(): string;
|
|
41
|
+
set revealLabel(value: string);
|
|
39
42
|
/** Shows a trailing spinner (e.g. while validating/looking up asynchronously). */
|
|
40
43
|
get loading(): boolean;
|
|
41
44
|
set loading(value: boolean);
|
|
@@ -125,6 +125,8 @@ const textFieldStyles = `
|
|
|
125
125
|
appearance: none;
|
|
126
126
|
border: 0;
|
|
127
127
|
padding: 0.15rem 0.4rem;
|
|
128
|
+
min-inline-size: 24px;
|
|
129
|
+
min-block-size: 24px;
|
|
128
130
|
border-radius: 4px;
|
|
129
131
|
background: transparent;
|
|
130
132
|
font: inherit;
|
|
@@ -169,6 +171,7 @@ export class TextField extends FormAssociatedElement {
|
|
|
169
171
|
"label",
|
|
170
172
|
"placeholder",
|
|
171
173
|
"reveal",
|
|
174
|
+
"reveal-label",
|
|
172
175
|
"type",
|
|
173
176
|
"loading",
|
|
174
177
|
"valid",
|
|
@@ -223,6 +226,16 @@ export class TextField extends FormAssociatedElement {
|
|
|
223
226
|
set reveal(value) {
|
|
224
227
|
this.toggleAttribute("reveal", Boolean(value));
|
|
225
228
|
}
|
|
229
|
+
/** Noun used in the Show/Hide control's accessible name. Defaults to the field label. */
|
|
230
|
+
get revealLabel() {
|
|
231
|
+
return this.getAttribute("reveal-label") ?? "";
|
|
232
|
+
}
|
|
233
|
+
set revealLabel(value) {
|
|
234
|
+
if (value)
|
|
235
|
+
this.setAttribute("reveal-label", value);
|
|
236
|
+
else
|
|
237
|
+
this.removeAttribute("reveal-label");
|
|
238
|
+
}
|
|
226
239
|
/** Shows a trailing spinner (e.g. while validating/looking up asynchronously). */
|
|
227
240
|
get loading() {
|
|
228
241
|
return this.hasAttribute("loading");
|
|
@@ -357,7 +370,8 @@ export class TextField extends FormAssociatedElement {
|
|
|
357
370
|
// The visible word and the accessible name agree; aria-pressed makes
|
|
358
371
|
// the toggle state machine explicit to AT.
|
|
359
372
|
this.revealEl.textContent = this.revealed ? "Hide" : "Show";
|
|
360
|
-
this.
|
|
373
|
+
const noun = this.revealLabel || (this.hasAttribute("label") ? this.label : "password");
|
|
374
|
+
this.revealEl.setAttribute("aria-label", `${this.revealed ? "Hide" : "Show"} ${noun}`);
|
|
361
375
|
this.revealEl.setAttribute("aria-pressed", String(this.revealed));
|
|
362
376
|
}
|
|
363
377
|
else {
|
|
@@ -3,8 +3,14 @@ import { BaseElement } from "../../core/index.js";
|
|
|
3
3
|
export interface TileOption {
|
|
4
4
|
description?: string;
|
|
5
5
|
disabled?: boolean;
|
|
6
|
+
disabledReason?: string;
|
|
6
7
|
id: string;
|
|
7
8
|
label: string;
|
|
9
|
+
meta?: string;
|
|
10
|
+
status?: {
|
|
11
|
+
label: string;
|
|
12
|
+
tone?: "neutral" | "success" | "warning" | "error";
|
|
13
|
+
};
|
|
8
14
|
}
|
|
9
15
|
/** Attribute payloads are author input — validate every record. */
|
|
10
16
|
export declare const isTileOptionRecord: (value: unknown) => value is TileOption;
|
|
@@ -14,7 +14,14 @@ export const isTileOptionRecord = (value) => {
|
|
|
14
14
|
return false;
|
|
15
15
|
}
|
|
16
16
|
const item = value;
|
|
17
|
-
return typeof item.id === "string" && typeof item.label === "string"
|
|
17
|
+
return typeof item.id === "string" && typeof item.label === "string"
|
|
18
|
+
&& (item.description === undefined || typeof item.description === "string")
|
|
19
|
+
&& (item.meta === undefined || typeof item.meta === "string")
|
|
20
|
+
&& (item.disabledReason === undefined || typeof item.disabledReason === "string")
|
|
21
|
+
&& (item.status === undefined || (typeof item.status === "object" && item.status !== null
|
|
22
|
+
&& typeof item.status.label === "string"
|
|
23
|
+
&& [undefined, "neutral", "success", "warning", "error"]
|
|
24
|
+
.includes(item.status.tone)));
|
|
18
25
|
};
|
|
19
26
|
const tileStyles = `
|
|
20
27
|
:host {
|
|
@@ -109,6 +116,17 @@ const tileStyles = `
|
|
|
109
116
|
[part="tile-description"]:empty {
|
|
110
117
|
display: none;
|
|
111
118
|
}
|
|
119
|
+
|
|
120
|
+
[part="tile-meta"], [part="tile-status"], [part="tile-disabled-reason"] {
|
|
121
|
+
font-size: 0.76rem;
|
|
122
|
+
color: var(--boe-token-text-text-secondary, #6f6f6f);
|
|
123
|
+
overflow-wrap: normal;
|
|
124
|
+
}
|
|
125
|
+
[part="tile-status"] { font-weight: 700; }
|
|
126
|
+
[part="tile-status"][data-tone="success"] { color: var(--boe-token-text-status-text-success, #176b45); }
|
|
127
|
+
[part="tile-status"][data-tone="warning"] { color: var(--boe-token-text-status-text-warning, #805500); }
|
|
128
|
+
[part="tile-status"][data-tone="error"] { color: var(--boe-token-text-status-text-error, #ad1b36); }
|
|
129
|
+
.boe-sr-only { position: absolute; inline-size: 1px; block-size: 1px; overflow: hidden; clip-path: inset(50%); }
|
|
112
130
|
`;
|
|
113
131
|
/**
|
|
114
132
|
* Choose one option, or several, as cards rather than radios.
|
|
@@ -199,8 +217,11 @@ export class TileGroup extends BaseElement {
|
|
|
199
217
|
rebuild() {
|
|
200
218
|
const type = this.multiple ? "checkbox" : "radio";
|
|
201
219
|
this.groupEl.innerHTML = this.options
|
|
202
|
-
.map(option => {
|
|
220
|
+
.map((option, index) => {
|
|
203
221
|
const id = escapeHtml(option.id);
|
|
222
|
+
const detailsId = `tile-details-${index}`;
|
|
223
|
+
const details = [option.description, option.meta, option.status?.label, option.disabledReason]
|
|
224
|
+
.filter(Boolean).join(". ");
|
|
204
225
|
return `
|
|
205
226
|
<label part="tile" data-option-id="${id}" data-selected="false" data-disabled="${option.disabled ? "true" : "false"}">
|
|
206
227
|
<input
|
|
@@ -208,10 +229,16 @@ export class TileGroup extends BaseElement {
|
|
|
208
229
|
type="${type}"
|
|
209
230
|
name="${escapeHtml(this.name)}"
|
|
210
231
|
value="${id}"
|
|
232
|
+
aria-label="${escapeHtml(option.label)}"
|
|
233
|
+
${details ? `aria-describedby="${detailsId}"` : ""}
|
|
211
234
|
${option.disabled ? "disabled" : ""}
|
|
212
235
|
/>
|
|
213
236
|
<span part="tile-label">${escapeHtml(option.label)}</span>
|
|
214
237
|
<span part="tile-description">${escapeHtml(option.description ?? "")}</span>
|
|
238
|
+
${option.meta ? `<span part="tile-meta">${escapeHtml(option.meta)}</span>` : ""}
|
|
239
|
+
${option.status ? `<span part="tile-status" data-tone="${escapeHtml(option.status.tone ?? "neutral")}">${escapeHtml(option.status.label)}</span>` : ""}
|
|
240
|
+
${option.disabledReason ? `<span part="tile-disabled-reason">${escapeHtml(option.disabledReason)}</span>` : ""}
|
|
241
|
+
${details ? `<span id="${detailsId}" class="boe-sr-only">${escapeHtml(details)}</span>` : ""}
|
|
215
242
|
</label>
|
|
216
243
|
`;
|
|
217
244
|
})
|
|
@@ -189,9 +189,9 @@ export class AppShell extends BaseElement {
|
|
|
189
189
|
</div>
|
|
190
190
|
</header>
|
|
191
191
|
<div part="frame">
|
|
192
|
-
<
|
|
192
|
+
<div part="nav" role="navigation" aria-label="Primary">
|
|
193
193
|
<slot name="nav"></slot>
|
|
194
|
-
</
|
|
194
|
+
</div>
|
|
195
195
|
<main part="main">
|
|
196
196
|
<slot></slot>
|
|
197
197
|
</main>
|
|
@@ -240,6 +240,17 @@ export class AppShell extends BaseElement {
|
|
|
240
240
|
return;
|
|
241
241
|
}
|
|
242
242
|
this.navEl.hidden = !this.hasNamedSlotContent("nav", this.navSlot);
|
|
243
|
+
// A slotted NavSidebar supplies its own labelled nav landmark. Keep this
|
|
244
|
+
// wrapper for layout, but never nest a second navigation landmark around it.
|
|
245
|
+
const childOwnsNavigation = Array.from(this.children).some(el => el.getAttribute("slot") === "nav" && el.localName === "box-nav-sidebar");
|
|
246
|
+
if (childOwnsNavigation) {
|
|
247
|
+
this.navEl.removeAttribute("role");
|
|
248
|
+
this.navEl.removeAttribute("aria-label");
|
|
249
|
+
}
|
|
250
|
+
else {
|
|
251
|
+
this.navEl.setAttribute("role", "navigation");
|
|
252
|
+
this.navEl.setAttribute("aria-label", this.navLabel);
|
|
253
|
+
}
|
|
243
254
|
this.asideEl.hidden = !this.hasNamedSlotContent("aside", this.asideSlot);
|
|
244
255
|
this.footerEl.hidden = !this.hasNamedSlotContent("footer", this.footerSlot);
|
|
245
256
|
}
|
|
@@ -250,7 +261,6 @@ export class AppShell extends BaseElement {
|
|
|
250
261
|
const heading = this.heading;
|
|
251
262
|
this.shellEl.setAttribute("aria-label", heading);
|
|
252
263
|
this.titleEl.textContent = heading;
|
|
253
|
-
this.navEl.setAttribute("aria-label", this.navLabel);
|
|
254
264
|
this.asideEl.setAttribute("aria-label", this.asideLabel);
|
|
255
265
|
this.syncLandmarkVisibility();
|
|
256
266
|
}
|
|
@@ -37,8 +37,8 @@ const navSidebarStyles = `
|
|
|
37
37
|
transition: inline-size ${boeMotionDuration.medium} ${boeMotionEasing.standard};
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
-
/* Collapsed: shrink to an icon strip.
|
|
41
|
-
|
|
40
|
+
/* Collapsed: shrink to an icon strip. Keep the header when it owns the
|
|
41
|
+
companion toggle; otherwise branding-only headers can disappear. */
|
|
42
42
|
[part="sidebar"][data-collapsed="true"] {
|
|
43
43
|
inline-size: 3.75rem;
|
|
44
44
|
padding-inline: 0.55rem;
|
|
@@ -55,6 +55,14 @@ const navSidebarStyles = `
|
|
|
55
55
|
display: none;
|
|
56
56
|
}
|
|
57
57
|
|
|
58
|
+
[part="sidebar"][data-collapsed="true"][data-header-toggle="true"] [part="header"] {
|
|
59
|
+
display: block;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
[part="sidebar"][data-collapsed="true"] [part="header"] ::slotted(:not(box-sidebar-toggle-button)) {
|
|
63
|
+
display: none;
|
|
64
|
+
}
|
|
65
|
+
|
|
58
66
|
[part="body"] {
|
|
59
67
|
display: flex;
|
|
60
68
|
flex-direction: column;
|
|
@@ -192,6 +200,9 @@ export class NavSidebar extends BaseElement {
|
|
|
192
200
|
?.addEventListener("slotchange", () => {
|
|
193
201
|
this.update();
|
|
194
202
|
});
|
|
203
|
+
this.shadowRoot
|
|
204
|
+
?.querySelector('slot[name="header"]')
|
|
205
|
+
?.addEventListener("slotchange", () => this.update());
|
|
195
206
|
}
|
|
196
207
|
/**
|
|
197
208
|
* Collapsing hides `[data-nav-label]`, which is a row's only accessible
|
|
@@ -232,6 +243,7 @@ export class NavSidebar extends BaseElement {
|
|
|
232
243
|
return;
|
|
233
244
|
}
|
|
234
245
|
this.sidebarEl.dataset.collapsed = this.collapsed ? "true" : "false";
|
|
246
|
+
this.sidebarEl.dataset.headerToggle = Array.from(this.children).some(el => el.getAttribute("slot") === "header" && el.localName === "box-sidebar-toggle-button") ? "true" : "false";
|
|
235
247
|
this.sidebarEl.setAttribute("aria-label", this.label);
|
|
236
248
|
this.syncCollapsedRowNames();
|
|
237
249
|
}
|
|
@@ -11,6 +11,9 @@ export declare class Accordion extends BaseElement {
|
|
|
11
11
|
/** Flat variant with no outer card border/background. */
|
|
12
12
|
get borderless(): boolean;
|
|
13
13
|
set borderless(value: boolean);
|
|
14
|
+
/** Omit region landmarks when many accordions or panels share a label. */
|
|
15
|
+
get plainPanels(): boolean;
|
|
16
|
+
set plainPanels(value: boolean);
|
|
14
17
|
private valueInternal;
|
|
15
18
|
private valuesInternal;
|
|
16
19
|
get multiple(): boolean;
|
|
@@ -107,7 +107,7 @@ const accordionStyles = `
|
|
|
107
107
|
export class Accordion extends BaseElement {
|
|
108
108
|
static tagName = DEFAULT_TAG_NAME;
|
|
109
109
|
static get observedAttributes() {
|
|
110
|
-
return ["items", "label", "value", "values", "multiple", "borderless"];
|
|
110
|
+
return ["items", "label", "value", "values", "multiple", "borderless", "plain-panels"];
|
|
111
111
|
}
|
|
112
112
|
/** Flat variant with no outer card border/background. */
|
|
113
113
|
get borderless() {
|
|
@@ -116,6 +116,9 @@ export class Accordion extends BaseElement {
|
|
|
116
116
|
set borderless(value) {
|
|
117
117
|
this.toggleAttribute("borderless", Boolean(value));
|
|
118
118
|
}
|
|
119
|
+
/** Omit region landmarks when many accordions or panels share a label. */
|
|
120
|
+
get plainPanels() { return this.hasAttribute("plain-panels"); }
|
|
121
|
+
set plainPanels(value) { this.toggleAttribute("plain-panels", Boolean(value)); }
|
|
119
122
|
valueInternal = "";
|
|
120
123
|
valuesInternal = [];
|
|
121
124
|
get multiple() { return this.hasAttribute("multiple"); }
|
|
@@ -196,8 +199,7 @@ export class Accordion extends BaseElement {
|
|
|
196
199
|
<div
|
|
197
200
|
part="panel"
|
|
198
201
|
id="${panelId}"
|
|
199
|
-
role="region"
|
|
200
|
-
aria-labelledby="${triggerId}"
|
|
202
|
+
${this.plainPanels ? "" : `role="region" aria-labelledby="${triggerId}"`}
|
|
201
203
|
${isOpen ? "" : "hidden"}
|
|
202
204
|
><slot name="panel-${escapeHtml(item.value)}">${escapeHtml(item.content ?? "")}</slot></div>
|
|
203
205
|
</section>
|
|
@@ -261,8 +263,14 @@ export class Accordion extends BaseElement {
|
|
|
261
263
|
this.valueInternal = items[0].value;
|
|
262
264
|
this.setAttribute("value", this.valueInternal);
|
|
263
265
|
}
|
|
264
|
-
this.
|
|
265
|
-
|
|
266
|
+
if (this.plainPanels) {
|
|
267
|
+
this.accordionEl.removeAttribute("role");
|
|
268
|
+
this.accordionEl.removeAttribute("aria-label");
|
|
269
|
+
}
|
|
270
|
+
else {
|
|
271
|
+
this.accordionEl.setAttribute("role", "region");
|
|
272
|
+
this.accordionEl.setAttribute("aria-label", this.label);
|
|
273
|
+
}
|
|
266
274
|
if (itemsJson !== this.lastItemsJson) {
|
|
267
275
|
this.accordionEl.innerHTML = this.renderItemsMarkup(items);
|
|
268
276
|
this.lastItemsJson = itemsJson;
|
|
@@ -284,6 +292,14 @@ export class Accordion extends BaseElement {
|
|
|
284
292
|
}
|
|
285
293
|
if (panel) {
|
|
286
294
|
panel.hidden = !isOpen;
|
|
295
|
+
if (this.plainPanels) {
|
|
296
|
+
panel.removeAttribute("role");
|
|
297
|
+
panel.removeAttribute("aria-labelledby");
|
|
298
|
+
}
|
|
299
|
+
else {
|
|
300
|
+
panel.setAttribute("role", "region");
|
|
301
|
+
panel.setAttribute("aria-labelledby", trigger?.id ?? "");
|
|
302
|
+
}
|
|
287
303
|
}
|
|
288
304
|
});
|
|
289
305
|
}
|