@unofficialbox/box-open-elements 0.21.0 → 0.23.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 -12
- package/dist/components/actions/menu-item.js +1 -1
- package/dist/components/actions/menu.js +1 -1
- package/dist/components/collections/carousel.js +1 -1
- package/dist/components/collections/draggable-list.js +1 -1
- package/dist/components/collections/grid-view.js +1 -1
- package/dist/components/collections/pagination.js +1 -1
- package/dist/components/collections/result-blocks.d.ts +13 -1
- package/dist/components/collections/result-blocks.js +22 -10
- package/dist/components/collections/tree-grid.js +1 -1
- package/dist/components/feedback/alert.js +1 -1
- package/dist/components/feedback/chip.js +1 -1
- package/dist/components/feedback/empty-state.js +1 -1
- package/dist/components/feedback/nudge.js +1 -1
- package/dist/components/feedback/status-icon.d.ts +2 -0
- package/dist/components/feedback/status-icon.js +6 -5
- package/dist/components/feedback/toast.js +1 -1
- package/dist/components/files/drop-zone.js +1 -1
- package/dist/components/forms/category-selector.js +1 -1
- package/dist/components/forms/checkbox-group.js +1 -1
- package/dist/components/forms/checkbox.js +1 -1
- package/dist/components/forms/combobox.js +1 -1
- package/dist/components/forms/date-field.js +1 -1
- package/dist/components/forms/dropdown.js +1 -1
- package/dist/components/forms/multi-select.js +1 -1
- package/dist/components/forms/number-input.js +1 -1
- package/dist/components/forms/radio-group.js +1 -1
- package/dist/components/forms/range-slider.js +1 -1
- package/dist/components/forms/slider.js +1 -1
- package/dist/components/forms/tile-group.js +1 -1
- package/dist/components/layout/sidebar-toggle-button.js +1 -1
- package/dist/components/navigation/accordion.js +1 -1
- package/dist/components/navigation/tabs.js +1 -1
- package/dist/components/output/code-block.js +1 -1
- package/dist/components/overlays/dialog.js +1 -1
- package/dist/components/overlays/drawer.js +1 -1
- package/dist/components/overlays/guide-tooltip.js +1 -1
- package/dist/components/overlays/popover.js +1 -1
- package/dist/components/overlays/tooltip.js +1 -1
- package/dist/foundations/motion/scroll-pin.d.ts +10 -2
- package/dist/foundations/motion/scroll-pin.js +43 -7
- package/dist/foundations/motion/tokens.d.ts +1 -0
- package/dist/foundations/motion/tokens.js +1 -0
- package/dist/foundations/profiles/built-ins.js +8 -0
- package/dist/foundations/profiles/registry.d.ts +1 -0
- package/dist/foundations/profiles/registry.js +1 -0
- package/dist/foundations/profiles/types.d.ts +1 -0
- package/dist/foundations/status/index.d.ts +4 -2
- package/dist/foundations/status/index.js +10 -6
- package/dist/foundations/theming/controller.js +3 -1
- package/dist/foundations/tokens/box-defaults.d.ts +2 -0
- package/dist/foundations/tokens/box-defaults.js +32 -5
- package/dist/foundations/tokens/index.d.ts +1 -1
- package/dist/foundations/tokens/index.js +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/patterns/agent-chat/agent-chat.js +9 -10
- package/dist/patterns/agent-chat/controller.js +8 -4
- package/dist/patterns/agent-chat/ndjson.d.ts +1 -0
- package/dist/patterns/agent-chat/ndjson.js +20 -4
- package/dist/patterns/agent-chat/types.d.ts +7 -0
- package/dist/patterns/agent-workspace/agent-workspace.d.ts +13 -1
- package/dist/patterns/agent-workspace/agent-workspace.js +47 -15
- package/dist/patterns/agent-workspace/controller.d.ts +7 -1
- package/dist/patterns/agent-workspace/controller.js +41 -3
- package/dist/patterns/call-console/call-console.d.ts +10 -0
- package/dist/patterns/call-console/call-console.js +23 -8
- package/dist/patterns/call-console/controller.d.ts +2 -1
- package/dist/patterns/call-console/controller.js +5 -3
- package/dist/patterns/call-console/http-transport.js +13 -2
- package/dist/patterns/call-console/types.d.ts +1 -1
- package/dist/patterns/call-console/types.js +2 -2
- package/dist/patterns/comments/comment-thread.js +1 -1
- package/dist/patterns/content-explorer/adapters/action-menu.js +1 -1
- package/dist/patterns/content-explorer/adapters/list.js +1 -1
- package/dist/patterns/content-explorer/adapters/table.js +1 -1
- package/dist/patterns/content-explorer/adapters/toolbar.js +1 -1
- package/dist/patterns/content-uploader/content-uploader.js +1 -1
- package/dist/patterns/file-request/file-request-builder.js +1 -1
- package/dist/patterns/flow-builder/flow-builder.d.ts +45 -14
- package/dist/patterns/flow-builder/flow-builder.js +54 -11
- package/dist/patterns/flow-builder/model.d.ts +57 -9
- package/dist/patterns/flow-builder/model.js +20 -8
- package/dist/patterns/flow-builder/primitives.d.ts +17 -7
- package/dist/patterns/flow-builder/primitives.js +80 -16
- package/dist/patterns/governance/governance-panel.js +1 -1
- package/dist/patterns/insights/chart-panel.js +1 -1
- package/dist/patterns/insights/metric-card.js +1 -1
- package/dist/patterns/item/bulk-action-bar.js +1 -1
- package/dist/patterns/item/item-details-panel.js +1 -1
- package/dist/patterns/item/item-form.js +1 -1
- package/dist/patterns/item/preview-header.js +1 -1
- package/dist/patterns/metadata/metadata-filter-builder.js +1 -1
- package/dist/patterns/metadata/metadata-inspector.js +1 -1
- package/dist/patterns/preview/annotation-inspector.js +1 -1
- package/dist/patterns/preview/annotation-toolbar.js +1 -1
- package/dist/patterns/run/progress.d.ts +1 -1
- package/dist/patterns/run/progress.js +1 -1
- package/dist/patterns/run/run-summary.js +18 -4
- package/dist/patterns/run/run-trace.js +19 -5
- package/dist/patterns/search/filter-bar.js +1 -1
- package/dist/patterns/search/saved-view-picker.js +1 -1
- package/dist/patterns/search/search-results-header.js +1 -1
- package/dist/patterns/share/invite-collaborators-modal.js +1 -1
- package/dist/patterns/share/permission-matrix.js +1 -1
- package/dist/patterns/share/share-panel.js +1 -1
- package/dist/patterns/share/unified-share-modal.js +1 -1
- package/dist/patterns/task/review-queue-item.js +1 -1
- package/dist/patterns/task/task-assignment-panel.js +1 -1
- package/dist/patterns/undo/index.js +5 -3
- package/docs/api-guidelines.md +284 -0
- package/docs/foundations/motion.md +83 -0
- package/docs/patterns/agent-ui-guidelines.md +57 -0
- package/docs/patterns/agent-ui.md +197 -0
- package/package.json +6 -2
|
@@ -1,16 +1,30 @@
|
|
|
1
1
|
import { BaseElement } from "../../core/index.js";
|
|
2
|
-
import { type FlowKind, type FlowNode, type NodePath } from "./model.js";
|
|
3
|
-
export type InspectorRenderer = (node:
|
|
4
|
-
|
|
5
|
-
|
|
2
|
+
import { type FlowKind, type FlowModel, type FlowNode, type FlowNodeBase, type NodePath } from "./model.js";
|
|
3
|
+
export type InspectorRenderer<N extends FlowNodeBase = FlowNode> = (node: N, container: HTMLElement) => void | (() => void);
|
|
4
|
+
export interface ValidationOptions {
|
|
5
|
+
/**
|
|
6
|
+
* Show the message in the builder's own error line (default `true`). Pass
|
|
7
|
+
* `false` when the host already shows the error persistently, so the
|
|
8
|
+
* message isn't on screen twice; the step is still marked and announced.
|
|
9
|
+
*/
|
|
10
|
+
showMessage?: boolean;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Host-owned document editing with accessible navigation and responsive inspection.
|
|
14
|
+
* `N` is the host's node type; it defaults to {@link FlowNode}, which the default
|
|
15
|
+
* model reads. Hosts with their own document shape set {@link model}.
|
|
16
|
+
*/
|
|
17
|
+
export declare class FlowBuilder<N extends FlowNodeBase = FlowNode> extends BaseElement {
|
|
6
18
|
static readonly tagName = "box-flow-builder";
|
|
7
19
|
private nodesValue;
|
|
20
|
+
private modelValue;
|
|
8
21
|
private catalogValue;
|
|
9
22
|
private selectedValue;
|
|
10
23
|
private invalidValue;
|
|
11
24
|
private renderer?;
|
|
12
25
|
private cleanupInspector?;
|
|
13
26
|
private inspected;
|
|
27
|
+
private headingEl?;
|
|
14
28
|
private renderedRenderer?;
|
|
15
29
|
private observer?;
|
|
16
30
|
private narrow;
|
|
@@ -24,14 +38,25 @@ export declare class FlowBuilder extends BaseElement {
|
|
|
24
38
|
private inspector;
|
|
25
39
|
private drawer;
|
|
26
40
|
private error;
|
|
27
|
-
get
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
get
|
|
32
|
-
set
|
|
33
|
-
|
|
34
|
-
|
|
41
|
+
static get observedAttributes(): string[];
|
|
42
|
+
get nodes(): N[];
|
|
43
|
+
set nodes(value: N[]);
|
|
44
|
+
/** How to read the host document; defaults to `body` / `branches` ({@link FlowNode}). */
|
|
45
|
+
get model(): FlowModel<N>;
|
|
46
|
+
set model(value: FlowModel<N>);
|
|
47
|
+
/** Text above the first step (`start-label`). */
|
|
48
|
+
get startLabel(): string;
|
|
49
|
+
set startLabel(value: string);
|
|
50
|
+
/** Text below the last step (`end-label`). */
|
|
51
|
+
get endLabel(): string;
|
|
52
|
+
set endLabel(value: string);
|
|
53
|
+
get catalog(): readonly FlowKind<N>[];
|
|
54
|
+
set catalog(value: readonly FlowKind<N>[]);
|
|
55
|
+
get selected(): N | null;
|
|
56
|
+
set selected(node: N | null);
|
|
57
|
+
get renderInspector(): InspectorRenderer<N> | undefined;
|
|
58
|
+
set renderInspector(renderer: InspectorRenderer<N> | undefined);
|
|
59
|
+
attributeChangedCallback(name: string, oldValue: string | null, newValue: string | null): void;
|
|
35
60
|
connectedCallback(): void;
|
|
36
61
|
disconnectedCallback(): void;
|
|
37
62
|
private setNarrow;
|
|
@@ -39,10 +64,16 @@ export declare class FlowBuilder extends BaseElement {
|
|
|
39
64
|
protected setupListeners(): void;
|
|
40
65
|
private closePicker;
|
|
41
66
|
private insert;
|
|
42
|
-
select(node:
|
|
67
|
+
select(node: N | null): void;
|
|
43
68
|
/** Mark validation without opening a sheet over the error on a narrow screen. */
|
|
44
|
-
setValidation(message: string, path?: NodePath, document?: unknown): void;
|
|
69
|
+
setValidation(message: string, path?: NodePath, document?: unknown, options?: ValidationOptions): void;
|
|
45
70
|
refresh(): void;
|
|
71
|
+
/**
|
|
72
|
+
* Re-render one step's card and the inspector heading after the host edits
|
|
73
|
+
* its fields. Structural edits (adding, removing, moving steps) need
|
|
74
|
+
* {@link refresh}.
|
|
75
|
+
*/
|
|
76
|
+
refreshNode(node: N): void;
|
|
46
77
|
protected update(): void;
|
|
47
78
|
private updateInspector;
|
|
48
79
|
}
|
|
@@ -1,17 +1,23 @@
|
|
|
1
1
|
import { BaseElement } from "../../core/index.js";
|
|
2
2
|
import { announce } from "../../foundations/a11y/index.js";
|
|
3
|
-
import { nodeAtPath, nodeTitle } from "./model.js";
|
|
4
|
-
import { InsertPoint } from "./primitives.js";
|
|
5
|
-
/**
|
|
3
|
+
import { defaultFlowModel, nodeAtPath, nodeDescription, nodeTitle } from "./model.js";
|
|
4
|
+
import { FLOW_SPINE_PARTS, InsertPoint, KIND_PICKER_PARTS } from "./primitives.js";
|
|
5
|
+
/**
|
|
6
|
+
* Host-owned document editing with accessible navigation and responsive inspection.
|
|
7
|
+
* `N` is the host's node type; it defaults to {@link FlowNode}, which the default
|
|
8
|
+
* model reads. Hosts with their own document shape set {@link model}.
|
|
9
|
+
*/
|
|
6
10
|
export class FlowBuilder extends BaseElement {
|
|
7
11
|
static tagName = "box-flow-builder";
|
|
8
12
|
nodesValue = [];
|
|
13
|
+
modelValue = defaultFlowModel;
|
|
9
14
|
catalogValue = [];
|
|
10
15
|
selectedValue = null;
|
|
11
16
|
invalidValue = null;
|
|
12
17
|
renderer;
|
|
13
18
|
cleanupInspector;
|
|
14
19
|
inspected;
|
|
20
|
+
headingEl;
|
|
15
21
|
renderedRenderer;
|
|
16
22
|
observer;
|
|
17
23
|
narrow = false;
|
|
@@ -25,8 +31,18 @@ export class FlowBuilder extends BaseElement {
|
|
|
25
31
|
inspector;
|
|
26
32
|
drawer;
|
|
27
33
|
error;
|
|
34
|
+
static get observedAttributes() { return ["start-label", "end-label"]; }
|
|
28
35
|
get nodes() { return this.nodesValue; }
|
|
29
36
|
set nodes(value) { this.nodesValue = value; this.refresh(); }
|
|
37
|
+
/** How to read the host document; defaults to `body` / `branches` ({@link FlowNode}). */
|
|
38
|
+
get model() { return this.modelValue; }
|
|
39
|
+
set model(value) { this.modelValue = value; this.refresh(); }
|
|
40
|
+
/** Text above the first step (`start-label`). */
|
|
41
|
+
get startLabel() { return this.getAttribute("start-label") ?? "Flow starts"; }
|
|
42
|
+
set startLabel(value) { this.setAttribute("start-label", value); }
|
|
43
|
+
/** Text below the last step (`end-label`). */
|
|
44
|
+
get endLabel() { return this.getAttribute("end-label") ?? "Flow ends"; }
|
|
45
|
+
set endLabel(value) { this.setAttribute("end-label", value); }
|
|
30
46
|
get catalog() { return this.catalogValue; }
|
|
31
47
|
set catalog(value) { this.catalogValue = value; this.refresh(); }
|
|
32
48
|
get selected() { return this.selectedValue; }
|
|
@@ -34,6 +50,11 @@ export class FlowBuilder extends BaseElement {
|
|
|
34
50
|
get renderInspector() { return this.renderer; }
|
|
35
51
|
set renderInspector(renderer) { this.renderer = renderer; if (this.isRendered)
|
|
36
52
|
this.updateInspector(); }
|
|
53
|
+
attributeChangedCallback(name, oldValue, newValue) {
|
|
54
|
+
super.attributeChangedCallback(name, oldValue, newValue);
|
|
55
|
+
if (oldValue !== newValue)
|
|
56
|
+
this.refresh();
|
|
57
|
+
}
|
|
37
58
|
connectedCallback() {
|
|
38
59
|
super.connectedCallback();
|
|
39
60
|
if (typeof ResizeObserver !== "undefined") {
|
|
@@ -68,7 +89,7 @@ export class FlowBuilder extends BaseElement {
|
|
|
68
89
|
[part=picker-popup]{position:fixed;z-index:1000;max-width:min(360px,calc(100vw - 24px));max-height:65vh;overflow:auto;padding:16px;background:var(--boe-token-surface-surface,#fff);border:1px solid var(--boe-token-stroke-stroke,#ddd);border-radius:12px;box-shadow:0 8px 32px #0003}
|
|
69
90
|
[part=error]{color:var(--boe-token-text-status-text-error,#b92340);margin-bottom:16px;overflow-wrap:anywhere}
|
|
70
91
|
[hidden]{display:none!important} h2{font:inherit;font-weight:650;margin:0 0 12px}
|
|
71
|
-
</style><div part="error" hidden></div><div part="layout"><box-flow-spine></box-flow-spine><aside part="inspector"><box-kind-picker></box-kind-picker><div part="editor"></div></aside></div><div part="picker-popup" hidden><box-kind-picker></box-kind-picker></div><box-drawer position="bottom" size="large"></box-drawer>`;
|
|
92
|
+
</style><div part="error" hidden></div><div part="layout"><box-flow-spine exportparts="${FLOW_SPINE_PARTS.join(", ")}"></box-flow-spine><aside part="inspector"><box-kind-picker exportparts="${KIND_PICKER_PARTS.join(", ")}"></box-kind-picker><div part="editor"></div></aside></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>`;
|
|
72
93
|
this.spine = this.shadowRoot.querySelector("box-flow-spine");
|
|
73
94
|
this.aside = this.shadowRoot.querySelector("aside");
|
|
74
95
|
this.palette = this.aside.querySelector("box-kind-picker");
|
|
@@ -129,7 +150,7 @@ export class FlowBuilder extends BaseElement {
|
|
|
129
150
|
this.refresh();
|
|
130
151
|
// Keep the newly inserted card visible on small screens; explicit selection opens editing.
|
|
131
152
|
this.spine.focusNode(node);
|
|
132
|
-
announce(`Added ${nodeTitle(node, this.catalog)}`, "polite", this.ownerDocument);
|
|
153
|
+
announce(`Added ${nodeTitle(node, this.catalog, this.model)}`, "polite", this.ownerDocument);
|
|
133
154
|
this.dispatchEvent(new CustomEvent("flow-changed", { detail: { nodes: this.nodes, node, reason: "insert" }, bubbles: true, composed: true }));
|
|
134
155
|
}
|
|
135
156
|
select(node) {
|
|
@@ -141,10 +162,11 @@ export class FlowBuilder extends BaseElement {
|
|
|
141
162
|
this.dispatchEvent(new CustomEvent("selection-changed", { detail: { node }, bubbles: true, composed: true }));
|
|
142
163
|
}
|
|
143
164
|
/** Mark validation without opening a sheet over the error on a narrow screen. */
|
|
144
|
-
setValidation(message, path = [], document = { body: this.nodes }) {
|
|
165
|
+
setValidation(message, path = [], document = { body: this.nodes }, options = {}) {
|
|
145
166
|
this.invalidValue = message ? nodeAtPath(document, path) : null;
|
|
146
|
-
|
|
147
|
-
this.error.
|
|
167
|
+
const show = options.showMessage ?? true;
|
|
168
|
+
this.error.textContent = show ? message : "";
|
|
169
|
+
this.error.hidden = !message || !show;
|
|
148
170
|
this.spine.select(this.selected, this.invalidValue);
|
|
149
171
|
if (message) {
|
|
150
172
|
if (!this.narrow && this.invalidValue)
|
|
@@ -156,9 +178,28 @@ export class FlowBuilder extends BaseElement {
|
|
|
156
178
|
}
|
|
157
179
|
refresh() { if (this.isRendered)
|
|
158
180
|
this.update(); }
|
|
181
|
+
/**
|
|
182
|
+
* Re-render one step's card and the inspector heading after the host edits
|
|
183
|
+
* its fields. Structural edits (adding, removing, moving steps) need
|
|
184
|
+
* {@link refresh}.
|
|
185
|
+
*/
|
|
186
|
+
refreshNode(node) {
|
|
187
|
+
if (!this.isRendered)
|
|
188
|
+
return;
|
|
189
|
+
this.spine.refreshNode(node);
|
|
190
|
+
if (node === this.selected) {
|
|
191
|
+
const title = nodeTitle(node, this.catalog, this.model);
|
|
192
|
+
if (this.headingEl)
|
|
193
|
+
this.headingEl.textContent = title;
|
|
194
|
+
this.drawer.heading = title;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
159
197
|
update() {
|
|
160
198
|
this.spine.nodes = this.nodes;
|
|
161
199
|
this.spine.catalog = this.catalog;
|
|
200
|
+
this.spine.model = this.model;
|
|
201
|
+
this.spine.startLabel = this.startLabel;
|
|
202
|
+
this.spine.endLabel = this.endLabel;
|
|
162
203
|
this.spine.selected = this.selected;
|
|
163
204
|
this.spine.invalid = this.invalidValue;
|
|
164
205
|
this.spine.refresh();
|
|
@@ -183,16 +224,18 @@ export class FlowBuilder extends BaseElement {
|
|
|
183
224
|
this.renderedRenderer = this.renderer;
|
|
184
225
|
if (this.selected) {
|
|
185
226
|
const heading = document.createElement("h2");
|
|
186
|
-
heading.
|
|
227
|
+
heading.setAttribute("part", "inspector-heading");
|
|
228
|
+
heading.textContent = nodeTitle(this.selected, this.catalog, this.model);
|
|
187
229
|
this.inspector.append(heading);
|
|
230
|
+
this.headingEl = heading;
|
|
188
231
|
if (this.renderer)
|
|
189
232
|
this.cleanupInspector = this.renderer(this.selected, this.inspector) || undefined;
|
|
190
233
|
else {
|
|
191
234
|
const description = document.createElement("p");
|
|
192
|
-
description.textContent = this.selected
|
|
235
|
+
description.textContent = nodeDescription(this.selected, this.catalog, this.model) || "Select a step to inspect its configuration.";
|
|
193
236
|
this.inspector.append(description);
|
|
194
237
|
}
|
|
195
|
-
this.drawer.heading = nodeTitle(this.selected, this.catalog);
|
|
238
|
+
this.drawer.heading = nodeTitle(this.selected, this.catalog, this.model);
|
|
196
239
|
}
|
|
197
240
|
this.drawer.open = this.narrow && Boolean(this.selected);
|
|
198
241
|
}
|
|
@@ -1,6 +1,13 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
/**
|
|
2
|
+
* The least the flow builder needs from a node. Hosts keep their own
|
|
3
|
+
* document shape; a {@link FlowModel} tells the builder how to read it.
|
|
4
|
+
*/
|
|
5
|
+
export interface FlowNodeBase {
|
|
3
6
|
kind: string;
|
|
7
|
+
}
|
|
8
|
+
/** The document shape the default model reads. */
|
|
9
|
+
export interface FlowNode extends FlowNodeBase {
|
|
10
|
+
id?: string;
|
|
4
11
|
title?: string;
|
|
5
12
|
description?: string;
|
|
6
13
|
status?: string;
|
|
@@ -11,23 +18,64 @@ export interface FlowBranch {
|
|
|
11
18
|
label: string;
|
|
12
19
|
body: FlowNode[];
|
|
13
20
|
}
|
|
14
|
-
|
|
21
|
+
/** One list of steps nested under a node. */
|
|
22
|
+
export interface FlowChildList {
|
|
23
|
+
/** A branch label. Unlabeled lists render as the node's inline body; labeled lists render as branch columns. */
|
|
24
|
+
label?: string;
|
|
25
|
+
/** The host's own array. The builder splices new steps into it. */
|
|
26
|
+
list: FlowNodeBase[];
|
|
27
|
+
/** Show a remove control on this branch; the builder emits `branch-remove-request`. */
|
|
28
|
+
removable?: boolean;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* How the builder reads a host document. Every traversal, title and insert
|
|
32
|
+
* label goes through the model, so hosts pass their own nodes — identity
|
|
33
|
+
* (selection, undo, validation paths) is preserved.
|
|
34
|
+
*/
|
|
35
|
+
export interface FlowModel<N extends FlowNodeBase = FlowNodeBase> {
|
|
36
|
+
/** Nested lists under `node`, in display order. */
|
|
37
|
+
children(node: N): FlowChildList[];
|
|
38
|
+
/** A distinct name for the node; falls back to its kind's label. */
|
|
39
|
+
title?(node: N): string | undefined;
|
|
40
|
+
/** One sentence about what the node does at run time; falls back to its kind's description. */
|
|
41
|
+
description?(node: N): string | undefined;
|
|
42
|
+
/** A short status line shown on the card. */
|
|
43
|
+
status?(node: N): string | undefined;
|
|
44
|
+
/** When it returns a label, the node's branches get an add control; the builder emits `branch-add-request`. */
|
|
45
|
+
addBranchLabel?(node: N): string | undefined;
|
|
46
|
+
}
|
|
47
|
+
/** Reads {@link FlowNode}: `body` renders inline and `branches` render as labeled columns. */
|
|
48
|
+
export declare const defaultFlowModel: FlowModel<FlowNode>;
|
|
49
|
+
export interface FlowKind<N extends FlowNodeBase = FlowNodeBase> {
|
|
15
50
|
kind: string;
|
|
16
51
|
label: string;
|
|
17
52
|
description: string;
|
|
18
53
|
group?: string;
|
|
19
54
|
/** Supply an authored SVG node (for example from a named glyph import). */
|
|
20
55
|
icon?: () => Node;
|
|
21
|
-
|
|
56
|
+
/**
|
|
57
|
+
* Icon color. `accent` (the default) uses the brand color; `neutral` uses
|
|
58
|
+
* secondary text, so a host can reserve the accent for its primary object.
|
|
59
|
+
*/
|
|
60
|
+
tone?: "accent" | "neutral";
|
|
61
|
+
create(): N;
|
|
22
62
|
}
|
|
23
63
|
export interface FlowInsertDetail {
|
|
24
|
-
list:
|
|
64
|
+
list: FlowNodeBase[];
|
|
25
65
|
index: number;
|
|
26
66
|
label: string;
|
|
27
67
|
}
|
|
68
|
+
export interface FlowBranchRequestDetail {
|
|
69
|
+
node: FlowNodeBase;
|
|
70
|
+
/** The branch's position among the node's labeled lists (remove requests only). */
|
|
71
|
+
index?: number;
|
|
72
|
+
/** The branch's own list (remove requests only). */
|
|
73
|
+
list?: FlowNodeBase[];
|
|
74
|
+
}
|
|
28
75
|
export type NodePath = readonly (string | number)[];
|
|
29
|
-
export declare const nodeTitle: (node:
|
|
30
|
-
export declare
|
|
31
|
-
export declare function
|
|
76
|
+
export declare const nodeTitle: (node: FlowNodeBase, catalog?: readonly FlowKind[], model?: FlowModel<FlowNodeBase>) => string;
|
|
77
|
+
export declare const nodeDescription: (node: FlowNodeBase, catalog?: readonly FlowKind[], model?: FlowModel<FlowNodeBase>) => string;
|
|
78
|
+
export declare function cardLabel(node: FlowNodeBase, catalog?: readonly FlowKind[], invalid?: boolean, model?: FlowModel<FlowNodeBase>): string;
|
|
79
|
+
export declare function insertLabel(list: readonly FlowNodeBase[], index: number, where?: string, catalog?: readonly FlowKind[], model?: FlowModel<FlowNodeBase>): string;
|
|
32
80
|
/** Return the deepest node on a validation path, including paths ending in a field. */
|
|
33
|
-
export declare function nodeAtPath(document: unknown, path: NodePath):
|
|
81
|
+
export declare function nodeAtPath(document: unknown, path: NodePath): FlowNodeBase | null;
|
|
@@ -1,17 +1,29 @@
|
|
|
1
|
-
|
|
2
|
-
export
|
|
1
|
+
/** Reads {@link FlowNode}: `body` renders inline and `branches` render as labeled columns. */
|
|
2
|
+
export const defaultFlowModel = {
|
|
3
|
+
children: node => [
|
|
4
|
+
...(node.body ? [{ list: node.body }] : []),
|
|
5
|
+
...(node.branches ?? []).map(branch => ({ label: branch.label, list: branch.body })),
|
|
6
|
+
],
|
|
7
|
+
title: node => node.title,
|
|
8
|
+
description: node => node.description,
|
|
9
|
+
status: node => node.status,
|
|
10
|
+
};
|
|
11
|
+
export const nodeTitle = (node, catalog = [], model = defaultFlowModel) => model.title?.(node) || catalog.find(kind => kind.kind === node.kind)?.label || "Step";
|
|
12
|
+
export const nodeDescription = (node, catalog = [], model = defaultFlowModel) => model.description?.(node) || catalog.find(kind => kind.kind === node.kind)?.description || "";
|
|
13
|
+
export function cardLabel(node, catalog = [], invalid = false, model = defaultFlowModel) {
|
|
3
14
|
const kind = catalog.find(entry => entry.kind === node.kind);
|
|
4
|
-
const title = nodeTitle(node, catalog);
|
|
5
|
-
return [title, kind?.label !== title ? kind?.label : "", node
|
|
15
|
+
const title = nodeTitle(node, catalog, model);
|
|
16
|
+
return [title, kind?.label !== title ? kind?.label : "", nodeDescription(node, catalog, model), model.status?.(node), invalid ? "Needs attention" : ""].filter(Boolean).join(". ");
|
|
6
17
|
}
|
|
7
|
-
export function insertLabel(list, index, where = "in the flow", catalog = []) {
|
|
18
|
+
export function insertLabel(list, index, where = "in the flow", catalog = [], model = defaultFlowModel) {
|
|
19
|
+
const title = (node) => nodeTitle(node, catalog, model);
|
|
8
20
|
if (!list.length)
|
|
9
21
|
return `Add the first step ${where}`;
|
|
10
22
|
if (index === 0)
|
|
11
|
-
return `Insert before ${
|
|
23
|
+
return `Insert before ${title(list[0])} ${where}`;
|
|
12
24
|
if (index >= list.length)
|
|
13
|
-
return `Insert after ${
|
|
14
|
-
return `Insert between ${
|
|
25
|
+
return `Insert after ${title(list[list.length - 1])} ${where}`;
|
|
26
|
+
return `Insert between ${title(list[index - 1])} and ${title(list[index])} ${where}`;
|
|
15
27
|
}
|
|
16
28
|
/** Return the deepest node on a validation path, including paths ending in a field. */
|
|
17
29
|
export function nodeAtPath(document, path) {
|
|
@@ -1,9 +1,16 @@
|
|
|
1
1
|
import { BaseElement } from "../../core/index.js";
|
|
2
|
-
import { type FlowInsertDetail, type FlowKind, type
|
|
2
|
+
import { type FlowInsertDetail, type FlowKind, type FlowModel, type FlowNodeBase } from "./model.js";
|
|
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"];
|
|
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"];
|
|
7
|
+
/** Parts a kind picker exposes. */
|
|
8
|
+
export declare const KIND_PICKER_PARTS: readonly ["choices", "group-heading", "choice", "choice-icon", "choice-label", "choice-description"];
|
|
3
9
|
export declare class FlowCard extends BaseElement {
|
|
4
10
|
static readonly tagName = "box-flow-card";
|
|
5
|
-
node:
|
|
11
|
+
node: FlowNodeBase;
|
|
6
12
|
catalog: readonly FlowKind[];
|
|
13
|
+
model: FlowModel<FlowNodeBase>;
|
|
7
14
|
selected: boolean;
|
|
8
15
|
invalid: boolean;
|
|
9
16
|
protected renderTemplate(): void;
|
|
@@ -32,16 +39,19 @@ export declare class KindPicker extends BaseElement {
|
|
|
32
39
|
}
|
|
33
40
|
export declare class FlowSpine extends BaseElement {
|
|
34
41
|
static readonly tagName = "box-flow-spine";
|
|
35
|
-
nodes:
|
|
42
|
+
nodes: FlowNodeBase[];
|
|
36
43
|
catalog: readonly FlowKind[];
|
|
37
|
-
|
|
38
|
-
|
|
44
|
+
model: FlowModel<FlowNodeBase>;
|
|
45
|
+
selected: FlowNodeBase | null;
|
|
46
|
+
invalid: FlowNodeBase | null;
|
|
39
47
|
startLabel: string;
|
|
40
48
|
endLabel: string;
|
|
41
49
|
private cards;
|
|
42
50
|
protected renderTemplate(): void;
|
|
43
51
|
refresh(): void;
|
|
44
|
-
|
|
45
|
-
|
|
52
|
+
/** Re-render one card after its fields change, without rebuilding the spine. */
|
|
53
|
+
refreshNode(node: FlowNodeBase): void;
|
|
54
|
+
focusNode(node: FlowNodeBase): void;
|
|
55
|
+
select(node: FlowNodeBase | null, invalid?: FlowNodeBase | null): void;
|
|
46
56
|
protected update(): void;
|
|
47
57
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { BaseElement } from "../../core/index.js";
|
|
2
|
-
import { boeFocusVisibleStyles } from "../../foundations/tokens/
|
|
3
|
-
import { cardLabel, insertLabel, nodeTitle } from "./model.js";
|
|
2
|
+
import { boeFocusVisibleStyles } from "../../foundations/tokens/interaction.js";
|
|
3
|
+
import { cardLabel, defaultFlowModel, insertLabel, nodeDescription, nodeTitle } from "./model.js";
|
|
4
4
|
const styles = `
|
|
5
5
|
:host { display:block; min-width:0; color:var(--boe-token-text-text,#222); font:inherit; }
|
|
6
6
|
:host([hidden]) { display:none!important; }
|
|
@@ -8,10 +8,17 @@ const styles = `
|
|
|
8
8
|
${boeFocusVisibleStyles("button")}
|
|
9
9
|
`;
|
|
10
10
|
function emit(host, name, detail) { host.dispatchEvent(new CustomEvent(name, { detail, bubbles: true, composed: true })); }
|
|
11
|
+
/** Parts a card exposes; the spine and builder forward them unchanged. */
|
|
12
|
+
export const FLOW_CARD_PARTS = ["card", "icon", "title", "kind", "description", "status", "error"];
|
|
13
|
+
/** Parts the spine exposes (its own plus forwarded card and insert-point parts). */
|
|
14
|
+
export const FLOW_SPINE_PARTS = ["spine", "endpoint", "body", "branches", "branch", "branch-header", "branch-label", "branch-remove", "branch-add", "insert", ...FLOW_CARD_PARTS];
|
|
15
|
+
/** Parts a kind picker exposes. */
|
|
16
|
+
export const KIND_PICKER_PARTS = ["choices", "group-heading", "choice", "choice-icon", "choice-label", "choice-description"];
|
|
11
17
|
export class FlowCard extends BaseElement {
|
|
12
18
|
static tagName = "box-flow-card";
|
|
13
|
-
node = {
|
|
19
|
+
node = { kind: "" };
|
|
14
20
|
catalog = [];
|
|
21
|
+
model = defaultFlowModel;
|
|
15
22
|
selected = false;
|
|
16
23
|
invalid = false;
|
|
17
24
|
renderTemplate() {
|
|
@@ -20,7 +27,7 @@ export class FlowCard extends BaseElement {
|
|
|
20
27
|
button[aria-pressed=true] { border-color:var(--boe-token-surface-surface-brand,#0061d5); background:color-mix(in srgb,var(--boe-token-surface-surface-brand,#0061d5) 8%,var(--boe-token-surface-surface,#fff)); }
|
|
21
28
|
button[data-invalid=true] { border-color:var(--boe-token-text-status-text-error,#b92340); }
|
|
22
29
|
button.has-icon{grid-template-columns:20px minmax(0,1fr);column-gap:12px} button.has-icon [part=icon]{grid-column:1;grid-row:1/span 4} button.has-icon [part=title],button.has-icon [part=kind],button.has-icon [part=description],button.has-icon [part=status],button.has-icon [part=error]{grid-column:2}
|
|
23
|
-
[part=icon]{color:var(--boe-token-surface-surface-brand,#0061d5);width:20px;height:20px} [part=icon] svg{display:block;width:20px;height:20px} [part=title] { font-weight:650; } [part=kind],[part=description] { color:var(--boe-token-text-text-secondary,#666); } [part=error] { color:var(--boe-token-text-status-text-error,#b92340); }
|
|
30
|
+
[part=icon]{color:var(--boe-token-surface-surface-brand,#0061d5);width:20px;height:20px} [part=icon][data-tone=neutral]{color:var(--boe-token-text-text-secondary,#666)} [part=icon] svg{display:block;width:20px;height:20px} [part=title] { font-weight:650; } [part=kind],[part=description] { color:var(--boe-token-text-text-secondary,#666); } [part=error] { color:var(--boe-token-text-status-text-error,#b92340); }
|
|
24
31
|
[hidden] { display:none!important; }
|
|
25
32
|
</style><button type="button" part="card"><span part="icon" aria-hidden="true"></span><span part="title"></span><span part="kind"></span><span part="description"></span><span part="status"></span><span part="error">Needs attention</span></button>`;
|
|
26
33
|
}
|
|
@@ -30,16 +37,17 @@ export class FlowCard extends BaseElement {
|
|
|
30
37
|
focus(options) { this.shadowRoot?.querySelector("button")?.focus(options); }
|
|
31
38
|
update() {
|
|
32
39
|
const kind = this.catalog.find(kind => kind.kind === this.node.kind);
|
|
33
|
-
const title = nodeTitle(this.node, this.catalog);
|
|
40
|
+
const title = nodeTitle(this.node, this.catalog, this.model);
|
|
34
41
|
const button = this.shadowRoot.querySelector("button");
|
|
35
42
|
button.setAttribute("aria-pressed", String(this.selected));
|
|
36
43
|
button.classList.toggle("has-icon", Boolean(kind?.icon));
|
|
37
|
-
button.setAttribute("aria-label", cardLabel(this.node, this.catalog, this.invalid));
|
|
44
|
+
button.setAttribute("aria-label", cardLabel(this.node, this.catalog, this.invalid, this.model));
|
|
38
45
|
button.dataset.invalid = String(this.invalid);
|
|
39
46
|
const iconEl = this.shadowRoot.querySelector("[part=icon]");
|
|
40
47
|
iconEl.replaceChildren(kind?.icon?.() ?? "");
|
|
41
48
|
iconEl.hidden = !kind?.icon;
|
|
42
|
-
|
|
49
|
+
iconEl.dataset.tone = kind?.tone ?? "accent";
|
|
50
|
+
for (const [part, text] of Object.entries({ title, kind: kind?.label !== title ? kind?.label ?? "" : "", description: nodeDescription(this.node, this.catalog, this.model), status: this.model.status?.(this.node) || "" })) {
|
|
43
51
|
const element = this.shadowRoot.querySelector(`[part=${part}]`);
|
|
44
52
|
element.textContent = text;
|
|
45
53
|
element.hidden = !text;
|
|
@@ -59,7 +67,7 @@ export class KindPicker extends BaseElement {
|
|
|
59
67
|
static tagName = "box-kind-picker";
|
|
60
68
|
catalog = [];
|
|
61
69
|
variant = "inline";
|
|
62
|
-
renderTemplate() { this.shadowRoot.innerHTML = `<style>${styles} [part=choices]{display:grid;gap:8px} button{text-align:start;padding:10px;border:1px solid var(--boe-token-stroke-stroke,#ddd);border-radius:8px;background:var(--boe-token-surface-surface,#fff)} small{display:block;color:var(--boe-token-text-text-secondary,#666)} h3{font:inherit;font-weight:650;margin:12px 0 4px}</style><div part="choices"></div>`; }
|
|
70
|
+
renderTemplate() { this.shadowRoot.innerHTML = `<style>${styles} [part=choices]{display:grid;gap:8px} button{text-align:start;padding:10px;border:1px solid var(--boe-token-stroke-stroke,#ddd);border-radius:8px;background:var(--boe-token-surface-surface,#fff)} button.has-icon{display:grid;grid-template-columns:20px minmax(0,1fr);column-gap:10px;align-items:start} [part=choice-icon]{grid-row:1/span 2;width:20px;height:20px;color:var(--boe-token-surface-surface-brand,#0061d5)} [part=choice-icon][data-tone=neutral]{color:var(--boe-token-text-text-secondary,#666)} [part=choice-icon] svg{display:block;width:20px;height:20px} [part=choice-label]{display:block} small{display:block;color:var(--boe-token-text-text-secondary,#666)} h3{font:inherit;font-weight:650;margin:12px 0 4px}</style><div part="choices"></div>`; }
|
|
63
71
|
setupListeners() {
|
|
64
72
|
this.shadowRoot.addEventListener("keydown", event => {
|
|
65
73
|
const e = event;
|
|
@@ -96,17 +104,32 @@ export class KindPicker extends BaseElement {
|
|
|
96
104
|
const heading = document.createElement("h3");
|
|
97
105
|
heading.textContent = kind.group;
|
|
98
106
|
heading.setAttribute("role", "presentation");
|
|
107
|
+
heading.setAttribute("part", "group-heading");
|
|
99
108
|
choices.append(heading);
|
|
100
109
|
group = kind.group;
|
|
101
110
|
}
|
|
102
111
|
const button = document.createElement("button");
|
|
103
112
|
button.type = "button";
|
|
104
|
-
button.
|
|
113
|
+
button.setAttribute("part", "choice");
|
|
114
|
+
if (kind.icon) {
|
|
115
|
+
const icon = document.createElement("span");
|
|
116
|
+
icon.setAttribute("part", "choice-icon");
|
|
117
|
+
icon.setAttribute("aria-hidden", "true");
|
|
118
|
+
icon.dataset.tone = kind.tone ?? "accent";
|
|
119
|
+
icon.append(kind.icon());
|
|
120
|
+
button.classList.add("has-icon");
|
|
121
|
+
button.append(icon);
|
|
122
|
+
}
|
|
123
|
+
const label = document.createElement("span");
|
|
124
|
+
label.setAttribute("part", "choice-label");
|
|
125
|
+
label.textContent = kind.label;
|
|
126
|
+
button.append(label);
|
|
105
127
|
if (this.variant === "menu") {
|
|
106
128
|
button.setAttribute("role", "menuitem");
|
|
107
129
|
button.tabIndex = index === 0 ? 0 : -1;
|
|
108
130
|
}
|
|
109
131
|
const description = document.createElement("small");
|
|
132
|
+
description.setAttribute("part", "choice-description");
|
|
110
133
|
description.textContent = kind.description;
|
|
111
134
|
button.append(description);
|
|
112
135
|
button.addEventListener("click", () => emit(this, "kind-pick", { kind }));
|
|
@@ -120,14 +143,17 @@ export class FlowSpine extends BaseElement {
|
|
|
120
143
|
static tagName = "box-flow-spine";
|
|
121
144
|
nodes = [];
|
|
122
145
|
catalog = [];
|
|
146
|
+
model = defaultFlowModel;
|
|
123
147
|
selected = null;
|
|
124
148
|
invalid = null;
|
|
125
149
|
startLabel = "Flow starts";
|
|
126
150
|
endLabel = "Flow ends";
|
|
127
151
|
cards = new Map();
|
|
128
|
-
renderTemplate() { this.shadowRoot.innerHTML = `<style>${styles} [part=spine]{display:grid;gap:0;min-width:0} [part=branches]{display:flex;gap:16px;overflow-x:auto;max-width:100%;padding:8px} [part=branch]{flex:1 0 240px;min-width:0} [part=body]{margin:0 0 0 16px;border-inline-start:2px solid var(--boe-token-stroke-stroke,#ddd);padding:8px 12px} [part=endpoint]{text-align:center;font-weight:600;padding:12px} h3{font:inherit;font-weight:600}</style><div part="spine"></div>`; }
|
|
152
|
+
renderTemplate() { this.shadowRoot.innerHTML = `<style>${styles} [part=spine]{display:grid;gap:0;min-width:0} [part=branches]{display:flex;gap:16px;overflow-x:auto;max-width:100%;padding:8px} [part=branch]{flex:1 0 240px;min-width:0} [part=body]{margin:0 0 0 16px;border-inline-start:2px solid var(--boe-token-stroke-stroke,#ddd);padding:8px 12px} [part=endpoint]{text-align:center;font-weight:600;padding:12px} [part=branch-header]{display:flex;align-items:center;justify-content:space-between;gap:8px;margin:1em 0} h3{font:inherit;font-weight:600;margin:0} [part=branch-remove],[part=branch-add]{border:1px solid var(--boe-token-stroke-stroke,#ddd);border-radius:16px;background:var(--boe-token-surface-surface,#fff);padding:4px 10px} [part=branch-add]{justify-self:center;margin:4px 0}</style><div part="spine"></div>`; }
|
|
129
153
|
refresh() { if (this.isRendered)
|
|
130
154
|
this.update(); }
|
|
155
|
+
/** Re-render one card after its fields change, without rebuilding the spine. */
|
|
156
|
+
refreshNode(node) { this.cards.get(node)?.refresh(); }
|
|
131
157
|
focusNode(node) { this.cards.get(node)?.focus(); }
|
|
132
158
|
select(node, invalid = this.invalid) { this.selected = node; this.invalid = invalid; for (const [item, card] of this.cards) {
|
|
133
159
|
card.selected = item === node;
|
|
@@ -137,11 +163,13 @@ export class FlowSpine extends BaseElement {
|
|
|
137
163
|
update() {
|
|
138
164
|
const root = this.shadowRoot.querySelector("[part=spine]");
|
|
139
165
|
const seen = new Set();
|
|
166
|
+
const title = (node) => nodeTitle(node, this.catalog, this.model);
|
|
140
167
|
const listView = (list, where, ancestors) => {
|
|
141
168
|
const fragment = document.createDocumentFragment();
|
|
142
169
|
for (let index = 0; index <= list.length; index++) {
|
|
143
170
|
const point = document.createElement(InsertPoint.tagName);
|
|
144
|
-
point.
|
|
171
|
+
point.setAttribute("exportparts", "button: insert");
|
|
172
|
+
point.detail = { list, index, label: insertLabel(list, index, where, this.catalog, this.model) };
|
|
145
173
|
fragment.append(point);
|
|
146
174
|
if (index === list.length)
|
|
147
175
|
break;
|
|
@@ -150,26 +178,62 @@ export class FlowSpine extends BaseElement {
|
|
|
150
178
|
continue;
|
|
151
179
|
seen.add(node);
|
|
152
180
|
const card = this.cards.get(node) ?? document.createElement(FlowCard.tagName);
|
|
181
|
+
card.setAttribute("exportparts", FLOW_CARD_PARTS.join(", "));
|
|
153
182
|
this.cards.set(node, card);
|
|
154
183
|
card.node = node;
|
|
155
184
|
card.catalog = this.catalog;
|
|
185
|
+
card.model = this.model;
|
|
156
186
|
card.selected = node === this.selected;
|
|
157
187
|
card.invalid = node === this.invalid;
|
|
158
188
|
card.refresh();
|
|
159
189
|
fragment.append(card);
|
|
160
190
|
const nextAncestors = new Set([...ancestors, node]);
|
|
161
|
-
|
|
191
|
+
const children = this.model.children(node);
|
|
192
|
+
for (const child of children.filter(child => child.label === undefined)) {
|
|
162
193
|
const body = document.createElement("section");
|
|
163
194
|
body.setAttribute("part", "body");
|
|
164
|
-
body.setAttribute("aria-label", `${
|
|
165
|
-
body.append(listView(
|
|
195
|
+
body.setAttribute("aria-label", `${title(node)} steps`);
|
|
196
|
+
body.append(listView(child.list, `inside ${title(node)}`, nextAncestors));
|
|
166
197
|
fragment.append(body);
|
|
167
198
|
}
|
|
168
|
-
|
|
199
|
+
const branchLists = children.filter(child => child.label !== undefined);
|
|
200
|
+
const addLabel = this.model.addBranchLabel?.(node);
|
|
201
|
+
if (branchLists.length || addLabel) {
|
|
169
202
|
const branches = document.createElement("div");
|
|
170
203
|
branches.setAttribute("part", "branches");
|
|
171
|
-
|
|
204
|
+
branchLists.forEach((branch, branchIndex) => {
|
|
205
|
+
const label = branch.label;
|
|
206
|
+
const column = document.createElement("section");
|
|
207
|
+
column.setAttribute("part", "branch");
|
|
208
|
+
column.setAttribute("aria-label", label);
|
|
209
|
+
const header = document.createElement("div");
|
|
210
|
+
header.setAttribute("part", "branch-header");
|
|
211
|
+
const heading = document.createElement("h3");
|
|
212
|
+
heading.setAttribute("part", "branch-label");
|
|
213
|
+
heading.textContent = label;
|
|
214
|
+
header.append(heading);
|
|
215
|
+
if (branch.removable) {
|
|
216
|
+
const remove = document.createElement("button");
|
|
217
|
+
remove.type = "button";
|
|
218
|
+
remove.setAttribute("part", "branch-remove");
|
|
219
|
+
remove.textContent = "Remove";
|
|
220
|
+
remove.setAttribute("aria-label", `Remove ${label} from ${title(node)}`);
|
|
221
|
+
remove.addEventListener("click", () => emit(this, "branch-remove-request", { node, index: branchIndex, list: branch.list }));
|
|
222
|
+
header.append(remove);
|
|
223
|
+
}
|
|
224
|
+
column.append(header, listView(branch.list, `in ${label}`, nextAncestors));
|
|
225
|
+
branches.append(column);
|
|
226
|
+
});
|
|
172
227
|
fragment.append(branches);
|
|
228
|
+
if (addLabel) {
|
|
229
|
+
const add = document.createElement("button");
|
|
230
|
+
add.type = "button";
|
|
231
|
+
add.setAttribute("part", "branch-add");
|
|
232
|
+
add.textContent = addLabel;
|
|
233
|
+
add.setAttribute("aria-label", `${addLabel} to ${title(node)}`);
|
|
234
|
+
add.addEventListener("click", () => emit(this, "branch-add-request", { node }));
|
|
235
|
+
fragment.append(add);
|
|
236
|
+
}
|
|
173
237
|
}
|
|
174
238
|
}
|
|
175
239
|
return fragment;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { BaseElement } from "../../core/index.js";
|
|
2
2
|
import { boePanel } from "../../foundations/geometry/index.js";
|
|
3
|
-
import { boeBrandInteractiveStyles, boeNeutralInteractiveStyles, } from "../../foundations/tokens/
|
|
3
|
+
import { boeBrandInteractiveStyles, boeNeutralInteractiveStyles, } from "../../foundations/tokens/interaction.js";
|
|
4
4
|
const DEFAULT_TAG_NAME = "box-governance-panel";
|
|
5
5
|
const escapeHtml = (value) => value
|
|
6
6
|
.replaceAll("&", "&")
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { BaseElement } from "../../core/index.js";
|
|
2
2
|
import { boePanel } from "../../foundations/geometry/index.js";
|
|
3
|
-
import { boeBrandInteractiveStyles, boeNeutralInteractiveStyles, } from "../../foundations/tokens/
|
|
3
|
+
import { boeBrandInteractiveStyles, boeNeutralInteractiveStyles, } from "../../foundations/tokens/interaction.js";
|
|
4
4
|
const DEFAULT_TAG_NAME = "box-chart-panel";
|
|
5
5
|
const escapeHtml = (value) => value
|
|
6
6
|
.replaceAll("&", "&")
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { BaseElement } from "../../core/index.js";
|
|
2
2
|
import { boePanel } from "../../foundations/geometry/index.js";
|
|
3
|
-
import { boeBrandInteractiveStyles, boeNeutralInteractiveStyles, } from "../../foundations/tokens/
|
|
3
|
+
import { boeBrandInteractiveStyles, boeNeutralInteractiveStyles, } from "../../foundations/tokens/interaction.js";
|
|
4
4
|
const DEFAULT_TAG_NAME = "box-metric-card";
|
|
5
5
|
const escapeHtml = (value) => value
|
|
6
6
|
.replaceAll("&", "&")
|