@unofficialbox/box-open-elements 0.27.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 +16 -1
- 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 +10 -0
- package/dist/components/forms/code-editor.js +54 -4
- 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/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/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/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/package.json +1 -1
|
@@ -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
|
}
|
|
@@ -20,6 +20,12 @@ export declare class Dialog extends BaseElement {
|
|
|
20
20
|
set description(value: string);
|
|
21
21
|
get confirmLabel(): string;
|
|
22
22
|
set confirmLabel(value: string);
|
|
23
|
+
get confirmDisabled(): boolean;
|
|
24
|
+
set confirmDisabled(value: boolean);
|
|
25
|
+
get confirmBusy(): boolean;
|
|
26
|
+
set confirmBusy(value: boolean);
|
|
27
|
+
get confirmBusyLabel(): string;
|
|
28
|
+
set confirmBusyLabel(value: string);
|
|
23
29
|
/** Modal size — box-ui-elements Modal sizes. Default `medium`. */
|
|
24
30
|
get size(): "small" | "medium" | "large" | "fullscreen";
|
|
25
31
|
set size(value: "small" | "medium" | "large" | "fullscreen");
|
|
@@ -144,7 +144,7 @@ const dialogStyles = `
|
|
|
144
144
|
export class Dialog extends BaseElement {
|
|
145
145
|
static tagName = DEFAULT_TAG_NAME;
|
|
146
146
|
static get observedAttributes() {
|
|
147
|
-
return ["confirm-label", "description", "heading", "open", "size"];
|
|
147
|
+
return ["confirm-busy", "confirm-busy-label", "confirm-disabled", "confirm-label", "description", "heading", "open", "size"];
|
|
148
148
|
}
|
|
149
149
|
openValue = false;
|
|
150
150
|
wasOpen = false;
|
|
@@ -191,6 +191,17 @@ export class Dialog extends BaseElement {
|
|
|
191
191
|
set confirmLabel(value) {
|
|
192
192
|
this.setAttribute("confirm-label", value);
|
|
193
193
|
}
|
|
194
|
+
get confirmDisabled() { return this.hasAttribute("confirm-disabled"); }
|
|
195
|
+
set confirmDisabled(value) { this.toggleAttribute("confirm-disabled", Boolean(value)); }
|
|
196
|
+
get confirmBusy() { return this.hasAttribute("confirm-busy"); }
|
|
197
|
+
set confirmBusy(value) { this.toggleAttribute("confirm-busy", Boolean(value)); }
|
|
198
|
+
get confirmBusyLabel() { return this.getAttribute("confirm-busy-label") ?? ""; }
|
|
199
|
+
set confirmBusyLabel(value) {
|
|
200
|
+
if (value)
|
|
201
|
+
this.setAttribute("confirm-busy-label", value);
|
|
202
|
+
else
|
|
203
|
+
this.removeAttribute("confirm-busy-label");
|
|
204
|
+
}
|
|
194
205
|
/** Modal size — box-ui-elements Modal sizes. Default `medium`. */
|
|
195
206
|
get size() {
|
|
196
207
|
const size = this.getAttribute("size");
|
|
@@ -252,6 +263,8 @@ export class Dialog extends BaseElement {
|
|
|
252
263
|
return;
|
|
253
264
|
}
|
|
254
265
|
if (target.closest('[part="confirm"]')) {
|
|
266
|
+
if (this.confirmDisabled || this.confirmBusy)
|
|
267
|
+
return;
|
|
255
268
|
this.dispatchEvent(new CustomEvent("confirm", { bubbles: true, composed: true }));
|
|
256
269
|
this.close();
|
|
257
270
|
}
|
|
@@ -348,7 +361,10 @@ export class Dialog extends BaseElement {
|
|
|
348
361
|
this.descriptionEl.hidden = !description;
|
|
349
362
|
}
|
|
350
363
|
if (this.confirmEl) {
|
|
351
|
-
this.confirmEl.textContent = this.
|
|
364
|
+
this.confirmEl.textContent = this.confirmBusy && this.confirmBusyLabel
|
|
365
|
+
? this.confirmBusyLabel : this.confirmLabel;
|
|
366
|
+
this.confirmEl.disabled = this.confirmDisabled || this.confirmBusy;
|
|
367
|
+
this.confirmEl.setAttribute("aria-busy", String(this.confirmBusy));
|
|
352
368
|
}
|
|
353
369
|
if (justOpened) {
|
|
354
370
|
queueMicrotask(() => {
|
|
@@ -26,6 +26,9 @@ export declare class Drawer extends BaseElement {
|
|
|
26
26
|
/** Busy: a saving/loading veil over the body; Close stays reachable. */
|
|
27
27
|
get busy(): boolean;
|
|
28
28
|
set busy(value: boolean);
|
|
29
|
+
/** Hide only the built-in header control; Escape and backdrop still request dismissal. */
|
|
30
|
+
get hideCloseButton(): boolean;
|
|
31
|
+
set hideCloseButton(value: boolean);
|
|
29
32
|
get heading(): string;
|
|
30
33
|
set heading(value: string);
|
|
31
34
|
attributeChangedCallback(name: string, oldValue: string | null, newValue: string | null): void;
|
|
@@ -149,6 +149,8 @@ const drawerStyles = `
|
|
|
149
149
|
box-shadow: none;
|
|
150
150
|
}
|
|
151
151
|
|
|
152
|
+
[part="close"][hidden] { display: none; }
|
|
153
|
+
|
|
152
154
|
${boeNeutralInteractiveStyles('[part="close"]')}
|
|
153
155
|
|
|
154
156
|
[part="body"] {
|
|
@@ -201,7 +203,7 @@ const DRAWER_SIZES = new Set(["small", "medium", "large", "full"]);
|
|
|
201
203
|
export class Drawer extends BaseElement {
|
|
202
204
|
static tagName = DEFAULT_TAG_NAME;
|
|
203
205
|
static get observedAttributes() {
|
|
204
|
-
return ["busy", "description", "heading", "open", "position", "size"];
|
|
206
|
+
return ["busy", "description", "heading", "hide-close-button", "open", "position", "size"];
|
|
205
207
|
}
|
|
206
208
|
openValue = false;
|
|
207
209
|
wasOpen = false;
|
|
@@ -255,6 +257,9 @@ export class Drawer extends BaseElement {
|
|
|
255
257
|
set busy(value) {
|
|
256
258
|
this.toggleAttribute("busy", Boolean(value));
|
|
257
259
|
}
|
|
260
|
+
/** Hide only the built-in header control; Escape and backdrop still request dismissal. */
|
|
261
|
+
get hideCloseButton() { return this.hasAttribute("hide-close-button"); }
|
|
262
|
+
set hideCloseButton(value) { this.toggleAttribute("hide-close-button", Boolean(value)); }
|
|
258
263
|
get heading() {
|
|
259
264
|
return this.getAttribute("heading") ?? "Drawer";
|
|
260
265
|
}
|
|
@@ -381,7 +386,7 @@ export class Drawer extends BaseElement {
|
|
|
381
386
|
this.hostEl.innerHTML = `
|
|
382
387
|
<style>${drawerStyles}</style>
|
|
383
388
|
<dialog part="backdrop" aria-labelledby="drawer-title">
|
|
384
|
-
<div part="drawer">
|
|
389
|
+
<div part="drawer" tabindex="-1">
|
|
385
390
|
<header part="header">
|
|
386
391
|
<div part="meta">
|
|
387
392
|
<h2 id="drawer-title"></h2>
|
|
@@ -421,6 +426,9 @@ export class Drawer extends BaseElement {
|
|
|
421
426
|
this.drawerEl = this.hostEl.querySelector('[part="drawer"]');
|
|
422
427
|
this.titleEl = this.hostEl.querySelector("#drawer-title");
|
|
423
428
|
this.descriptionEl = this.hostEl.querySelector('[part="description"]');
|
|
429
|
+
const closeButton = this.hostEl.querySelector('[part="close"]');
|
|
430
|
+
if (closeButton)
|
|
431
|
+
closeButton.hidden = this.hideCloseButton;
|
|
424
432
|
const isLeft = this.position === "left";
|
|
425
433
|
const isBottom = this.position === "bottom";
|
|
426
434
|
if (this.backdropEl) {
|
|
@@ -456,7 +464,7 @@ export class Drawer extends BaseElement {
|
|
|
456
464
|
}
|
|
457
465
|
if (justOpened) {
|
|
458
466
|
queueMicrotask(() => {
|
|
459
|
-
this.
|
|
467
|
+
(this.hideCloseButton ? this.drawerEl : closeButton)?.focus();
|
|
460
468
|
});
|
|
461
469
|
}
|
|
462
470
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "../components/feedback/mode-indicator.js";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "../components/feedback/mode-indicator.js";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "../components/collections/resource-row.js";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "../components/collections/resource-row.js";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "../components/feedback/verdict-banner.js";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "../components/feedback/verdict-banner.js";
|
package/dist/index.d.ts
CHANGED
|
@@ -24,6 +24,7 @@ export * from "./components/actions/toolbar.js";
|
|
|
24
24
|
export * from "./components/collections/card.js";
|
|
25
25
|
export * from "./components/collections/carousel.js";
|
|
26
26
|
export * from "./components/collections/datalist-item.js";
|
|
27
|
+
export * from "./components/collections/resource-row.js";
|
|
27
28
|
export * from "./components/collections/draggable-list.js";
|
|
28
29
|
export * from "./components/collections/grid-view.js";
|
|
29
30
|
export * from "./components/collections/pagination.js";
|
|
@@ -41,6 +42,7 @@ export * from "./components/feedback/empty-state.js";
|
|
|
41
42
|
export * from "./components/feedback/error-mask.js";
|
|
42
43
|
export * from "./components/feedback/help-text.js";
|
|
43
44
|
export * from "./components/feedback/indicator.js";
|
|
45
|
+
export * from "./components/feedback/mode-indicator.js";
|
|
44
46
|
export * from "./components/feedback/nudge.js";
|
|
45
47
|
export * from "./components/feedback/progress-bar.js";
|
|
46
48
|
export * from "./components/feedback/progress-ring.js";
|
|
@@ -49,6 +51,7 @@ export * from "./components/feedback/skeleton.js";
|
|
|
49
51
|
export * from "./components/feedback/spinner.js";
|
|
50
52
|
export * from "./components/feedback/path.js";
|
|
51
53
|
export * from "./components/feedback/toast.js";
|
|
54
|
+
export * from "./components/feedback/verdict-banner.js";
|
|
52
55
|
export * from "./components/files/drop-zone.js";
|
|
53
56
|
export * from "./components/identity/avatar.js";
|
|
54
57
|
export * from "./components/identity/contact-datalist-item.js";
|
package/dist/index.js
CHANGED
|
@@ -27,6 +27,7 @@ export * from "./components/actions/toolbar.js";
|
|
|
27
27
|
export * from "./components/collections/card.js";
|
|
28
28
|
export * from "./components/collections/carousel.js";
|
|
29
29
|
export * from "./components/collections/datalist-item.js";
|
|
30
|
+
export * from "./components/collections/resource-row.js";
|
|
30
31
|
export * from "./components/collections/draggable-list.js";
|
|
31
32
|
export * from "./components/collections/grid-view.js";
|
|
32
33
|
export * from "./components/collections/pagination.js";
|
|
@@ -45,6 +46,7 @@ export * from "./components/feedback/empty-state.js";
|
|
|
45
46
|
export * from "./components/feedback/error-mask.js";
|
|
46
47
|
export * from "./components/feedback/help-text.js";
|
|
47
48
|
export * from "./components/feedback/indicator.js";
|
|
49
|
+
export * from "./components/feedback/mode-indicator.js";
|
|
48
50
|
export * from "./components/feedback/nudge.js";
|
|
49
51
|
export * from "./components/feedback/progress-bar.js";
|
|
50
52
|
export * from "./components/feedback/progress-ring.js";
|
|
@@ -53,6 +55,7 @@ export * from "./components/feedback/skeleton.js";
|
|
|
53
55
|
export * from "./components/feedback/spinner.js";
|
|
54
56
|
export * from "./components/feedback/path.js";
|
|
55
57
|
export * from "./components/feedback/toast.js";
|
|
58
|
+
export * from "./components/feedback/verdict-banner.js";
|
|
56
59
|
// Components: files
|
|
57
60
|
export * from "./components/files/drop-zone.js";
|
|
58
61
|
// Components: identity
|
|
@@ -9,6 +9,10 @@ export interface ValidationOptions {
|
|
|
9
9
|
*/
|
|
10
10
|
showMessage?: boolean;
|
|
11
11
|
}
|
|
12
|
+
export interface FlowNodeFigure {
|
|
13
|
+
text: string;
|
|
14
|
+
tone?: "neutral" | "warning" | "error";
|
|
15
|
+
}
|
|
12
16
|
/**
|
|
13
17
|
* Host-owned document editing with accessible navigation and responsive inspection.
|
|
14
18
|
* `N` is the host's node type; it defaults to {@link FlowNode}, which the default
|
|
@@ -17,6 +21,7 @@ export interface ValidationOptions {
|
|
|
17
21
|
export declare class FlowBuilder<N extends FlowNodeBase = FlowNode> extends BaseElement {
|
|
18
22
|
static readonly tagName = "box-flow-builder";
|
|
19
23
|
private nodesValue;
|
|
24
|
+
private figuresValue;
|
|
20
25
|
private modelValue;
|
|
21
26
|
private catalogValue;
|
|
22
27
|
private selectedValue;
|
|
@@ -32,6 +37,9 @@ export declare class FlowBuilder<N extends FlowNodeBase = FlowNode> extends Base
|
|
|
32
37
|
get narrow(): boolean;
|
|
33
38
|
/** Focus a rendered card without changing selection or opening the editor. */
|
|
34
39
|
focusNode(node: N): void;
|
|
40
|
+
/** Update one live card figure without rebuilding the flow or moving focus. */
|
|
41
|
+
setNodeFigure(target: N | string | NodePath, figure: FlowNodeFigure | null): void;
|
|
42
|
+
private findNodeById;
|
|
35
43
|
private insertion?;
|
|
36
44
|
private invoker?;
|
|
37
45
|
private spine;
|
|
@@ -12,6 +12,7 @@ import { FLOW_SPINE_PARTS, InsertPoint, KIND_PICKER_PARTS } from "./primitives.j
|
|
|
12
12
|
export class FlowBuilder extends BaseElement {
|
|
13
13
|
static tagName = "box-flow-builder";
|
|
14
14
|
nodesValue = [];
|
|
15
|
+
figuresValue = new Map();
|
|
15
16
|
modelValue = defaultFlowModel;
|
|
16
17
|
catalogValue = [];
|
|
17
18
|
selectedValue = null;
|
|
@@ -27,6 +28,38 @@ export class FlowBuilder extends BaseElement {
|
|
|
27
28
|
get narrow() { return this.narrowValue; }
|
|
28
29
|
/** Focus a rendered card without changing selection or opening the editor. */
|
|
29
30
|
focusNode(node) { this.spine?.focusNode(node); }
|
|
31
|
+
/** Update one live card figure without rebuilding the flow or moving focus. */
|
|
32
|
+
setNodeFigure(target, figure) {
|
|
33
|
+
const node = typeof target === "string" ? this.findNodeById(target)
|
|
34
|
+
: Array.isArray(target) ? nodeAtPath({ body: this.nodes }, target)
|
|
35
|
+
: target;
|
|
36
|
+
if (!node)
|
|
37
|
+
return;
|
|
38
|
+
if (figure)
|
|
39
|
+
this.figuresValue.set(node, figure);
|
|
40
|
+
else
|
|
41
|
+
this.figuresValue.delete(node);
|
|
42
|
+
this.spine?.setNodeFigure(node, figure);
|
|
43
|
+
}
|
|
44
|
+
findNodeById(id) {
|
|
45
|
+
const seen = new Set();
|
|
46
|
+
const visit = (nodes) => {
|
|
47
|
+
for (const node of nodes) {
|
|
48
|
+
if (seen.has(node))
|
|
49
|
+
continue;
|
|
50
|
+
seen.add(node);
|
|
51
|
+
if (node.id === id)
|
|
52
|
+
return node;
|
|
53
|
+
for (const child of this.model.children(node)) {
|
|
54
|
+
const found = visit(child.list);
|
|
55
|
+
if (found)
|
|
56
|
+
return found;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
return null;
|
|
60
|
+
};
|
|
61
|
+
return visit(this.nodes);
|
|
62
|
+
}
|
|
30
63
|
insertion;
|
|
31
64
|
invoker;
|
|
32
65
|
spine;
|
|
@@ -106,6 +139,7 @@ export class FlowBuilder extends BaseElement {
|
|
|
106
139
|
[hidden]{display:none!important} [part=inspector-heading]{font:inherit;font-weight:650;margin:0 0 12px}
|
|
107
140
|
</style><div part="error" hidden></div><div part="layout"><box-flow-spine exportparts="${FLOW_SPINE_PARTS.join(", ")}"></box-flow-spine><div part="inspector" role="region" aria-label="Add a step"><box-kind-picker exportparts="${KIND_PICKER_PARTS.join(", ")}"></box-kind-picker><div part="editor"></div></div></div><div part="picker-popup" hidden><box-kind-picker exportparts="${KIND_PICKER_PARTS.join(", ")}"></box-kind-picker></div><box-drawer position="bottom" size="large"></box-drawer>`;
|
|
108
141
|
this.spine = this.shadowRoot.querySelector("box-flow-spine");
|
|
142
|
+
this.spine.figures = this.figuresValue;
|
|
109
143
|
this.aside = this.shadowRoot.querySelector("[part=inspector]");
|
|
110
144
|
this.palette = this.aside.querySelector("box-kind-picker");
|
|
111
145
|
this.inspector = this.aside.querySelector("[part=editor]");
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { BaseElement } from "../../core/index.js";
|
|
2
2
|
import { type FlowInsertDetail, type FlowKind, type FlowModel, type FlowNodeBase } from "./model.js";
|
|
3
3
|
/** Parts a card exposes; the spine and builder forward them unchanged. */
|
|
4
|
-
export declare const FLOW_CARD_PARTS: readonly ["card", "icon", "title", "kind", "description", "status", "error"];
|
|
4
|
+
export declare const FLOW_CARD_PARTS: readonly ["card", "icon", "title", "kind", "description", "status", "figure", "error"];
|
|
5
5
|
/** Parts the spine exposes (its own plus forwarded card and insert-point parts). */
|
|
6
|
-
export declare const FLOW_SPINE_PARTS: readonly ["spine", "endpoint", "body", "branches", "branch", "branch-header", "branch-label", "branch-remove", "branch-add", "insert", "card", "icon", "title", "kind", "description", "status", "error"];
|
|
6
|
+
export declare const FLOW_SPINE_PARTS: readonly ["spine", "endpoint", "body", "branches", "branch", "branch-header", "branch-label", "branch-remove", "branch-add", "insert", "card", "icon", "title", "kind", "description", "status", "figure", "error"];
|
|
7
7
|
/** Parts a kind picker exposes. */
|
|
8
8
|
export declare const KIND_PICKER_PARTS: readonly ["choices", "group-heading", "choice", "choice-icon", "choice-label", "choice-description"];
|
|
9
9
|
export declare class FlowCard extends BaseElement {
|
|
@@ -13,6 +13,10 @@ export declare class FlowCard extends BaseElement {
|
|
|
13
13
|
model: FlowModel<FlowNodeBase>;
|
|
14
14
|
selected: boolean;
|
|
15
15
|
invalid: boolean;
|
|
16
|
+
figure: {
|
|
17
|
+
text: string;
|
|
18
|
+
tone?: "neutral" | "warning" | "error";
|
|
19
|
+
} | null;
|
|
16
20
|
protected renderTemplate(): void;
|
|
17
21
|
protected setupListeners(): void;
|
|
18
22
|
refresh(): void;
|
|
@@ -49,10 +53,18 @@ export declare class FlowSpine extends BaseElement {
|
|
|
49
53
|
endLabel: string;
|
|
50
54
|
headingLevel: number;
|
|
51
55
|
private cards;
|
|
56
|
+
figures: Map<FlowNodeBase, {
|
|
57
|
+
text: string;
|
|
58
|
+
tone?: "neutral" | "warning" | "error";
|
|
59
|
+
}>;
|
|
52
60
|
protected renderTemplate(): void;
|
|
53
61
|
refresh(): void;
|
|
54
62
|
/** Re-render one card after its fields change, without rebuilding the spine. */
|
|
55
63
|
refreshNode(node: FlowNodeBase): void;
|
|
64
|
+
setNodeFigure(node: FlowNodeBase, figure: {
|
|
65
|
+
text: string;
|
|
66
|
+
tone?: "neutral" | "warning" | "error";
|
|
67
|
+
} | null): void;
|
|
56
68
|
focusNode(node: FlowNodeBase): void;
|
|
57
69
|
select(node: FlowNodeBase | null, invalid?: FlowNodeBase | null): void;
|
|
58
70
|
protected update(): void;
|