@unofficialbox/box-open-elements 0.20.0 → 0.22.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/dist/components/actions/menu-item.d.ts +2 -0
- package/dist/components/actions/menu-item.js +4 -0
- package/dist/components/collections/card.js +6 -0
- package/dist/components/collections/result-blocks.js +4 -1
- package/dist/components/feedback/alert.d.ts +2 -0
- package/dist/components/feedback/alert.js +17 -13
- package/dist/components/feedback/toast.d.ts +10 -1
- package/dist/components/feedback/toast.js +69 -27
- package/dist/components/forms/number-input.d.ts +1 -0
- package/dist/components/forms/number-input.js +9 -6
- package/dist/components/forms/search-field.js +16 -0
- package/dist/components/overlays/context-menu.d.ts +2 -0
- package/dist/components/overlays/context-menu.js +7 -0
- package/dist/components/overlays/dialog.js +19 -3
- package/dist/components/overlays/popover.js +6 -1
- package/dist/element-maps.d.ts +11 -1
- package/dist/foundations/a11y/announcer.d.ts +12 -0
- package/dist/foundations/a11y/announcer.js +75 -0
- package/dist/foundations/a11y/focus.js +46 -25
- package/dist/foundations/a11y/index.d.ts +2 -0
- package/dist/foundations/a11y/index.js +1 -0
- package/dist/foundations/icons/box-iconography.d.ts +472 -472
- package/dist/foundations/icons/box-iconography.generated.d.ts +1151 -679
- package/dist/foundations/icons/box-iconography.generated.js +1006 -3588
- package/dist/foundations/icons/glyphs/index.d.ts +1 -0
- package/dist/foundations/icons/glyphs/index.js +2 -0
- package/dist/foundations/icons/glyphs/package.json +1 -0
- package/dist/foundations/overlay/positioning.js +7 -0
- package/dist/foundations/status/index.d.ts +1 -1
- package/dist/foundations/status/index.js +8 -6
- package/dist/foundations/tokens/box-dark.js +4 -1
- package/dist/foundations/tokens/box-defaults.js +3 -0
- package/dist/foundations/tokens/registry.d.ts +3 -0
- package/dist/foundations/tokens/registry.js +3 -0
- package/dist/foundations/tokens/types.d.ts +3 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/patterns/agent-chat/agent-chat.js +12 -4
- package/dist/patterns/call-console/call-console.d.ts +3 -0
- package/dist/patterns/call-console/call-console.js +114 -20
- package/dist/patterns/content-explorer/adapters/action-menu.js +7 -5
- package/dist/patterns/content-explorer/adapters/toolbar.js +6 -2
- package/dist/patterns/file-request/file-request-builder.js +3 -0
- package/dist/patterns/flow-builder/flow-builder.d.ts +79 -0
- package/dist/patterns/flow-builder/flow-builder.js +243 -0
- package/dist/patterns/flow-builder/index.d.ts +3 -0
- package/dist/patterns/flow-builder/index.js +3 -0
- package/dist/patterns/flow-builder/model.d.ts +81 -0
- package/dist/patterns/flow-builder/model.js +40 -0
- package/dist/patterns/flow-builder/primitives.d.ts +57 -0
- package/dist/patterns/flow-builder/primitives.js +256 -0
- package/dist/patterns/lineage/lineage-graph.js +26 -6
- package/dist/patterns/notifications/notification-inbox.js +19 -3
- package/dist/patterns/preview/annotation-inspector.js +2 -2
- package/dist/patterns/undo/index.d.ts +19 -0
- package/dist/patterns/undo/index.js +106 -0
- package/package.json +11 -2
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
import { BaseElement } from "../../core/index.js";
|
|
2
|
+
import { boeFocusVisibleStyles } from "../../foundations/tokens/index.js";
|
|
3
|
+
import { cardLabel, defaultFlowModel, insertLabel, nodeDescription, nodeTitle } from "./model.js";
|
|
4
|
+
const styles = `
|
|
5
|
+
:host { display:block; min-width:0; color:var(--boe-token-text-text,#222); font:inherit; }
|
|
6
|
+
:host([hidden]) { display:none!important; }
|
|
7
|
+
button { font:inherit; color:inherit; cursor:pointer; }
|
|
8
|
+
${boeFocusVisibleStyles("button")}
|
|
9
|
+
`;
|
|
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"];
|
|
17
|
+
export class FlowCard extends BaseElement {
|
|
18
|
+
static tagName = "box-flow-card";
|
|
19
|
+
node = { kind: "" };
|
|
20
|
+
catalog = [];
|
|
21
|
+
model = defaultFlowModel;
|
|
22
|
+
selected = false;
|
|
23
|
+
invalid = false;
|
|
24
|
+
renderTemplate() {
|
|
25
|
+
this.shadowRoot.innerHTML = `<style>${styles}
|
|
26
|
+
button { width:100%; text-align:start; display:grid; gap:6px; padding:16px; border:1px solid var(--boe-token-stroke-stroke,#ddd); border-radius:var(--boe-profile-radius-med,8px); background:var(--boe-token-surface-surface,#fff); overflow-wrap:anywhere; }
|
|
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)); }
|
|
28
|
+
button[data-invalid=true] { border-color:var(--boe-token-text-status-text-error,#b92340); }
|
|
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}
|
|
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); }
|
|
31
|
+
[hidden] { display:none!important; }
|
|
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>`;
|
|
33
|
+
}
|
|
34
|
+
setupListeners() { this.shadowRoot.querySelector("button").addEventListener("click", () => emit(this, "node-select", { node: this.node })); }
|
|
35
|
+
refresh() { if (this.isRendered)
|
|
36
|
+
this.update(); }
|
|
37
|
+
focus(options) { this.shadowRoot?.querySelector("button")?.focus(options); }
|
|
38
|
+
update() {
|
|
39
|
+
const kind = this.catalog.find(kind => kind.kind === this.node.kind);
|
|
40
|
+
const title = nodeTitle(this.node, this.catalog, this.model);
|
|
41
|
+
const button = this.shadowRoot.querySelector("button");
|
|
42
|
+
button.setAttribute("aria-pressed", String(this.selected));
|
|
43
|
+
button.classList.toggle("has-icon", Boolean(kind?.icon));
|
|
44
|
+
button.setAttribute("aria-label", cardLabel(this.node, this.catalog, this.invalid, this.model));
|
|
45
|
+
button.dataset.invalid = String(this.invalid);
|
|
46
|
+
const iconEl = this.shadowRoot.querySelector("[part=icon]");
|
|
47
|
+
iconEl.replaceChildren(kind?.icon?.() ?? "");
|
|
48
|
+
iconEl.hidden = !kind?.icon;
|
|
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) || "" })) {
|
|
51
|
+
const element = this.shadowRoot.querySelector(`[part=${part}]`);
|
|
52
|
+
element.textContent = text;
|
|
53
|
+
element.hidden = !text;
|
|
54
|
+
}
|
|
55
|
+
this.shadowRoot.querySelector("[part=error]").hidden = !this.invalid;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
export class InsertPoint extends BaseElement {
|
|
59
|
+
static tagName = "box-insert-point";
|
|
60
|
+
detail = { list: [], index: 0, label: "Add the first step" };
|
|
61
|
+
renderTemplate() { this.shadowRoot.innerHTML = `<style>${styles} :host{display:flex;flex-direction:column;align-items:center;text-align:center;min-width:0} :host::before,:host::after{content:"";height:10px;border-inline-start:2px solid var(--boe-token-stroke-stroke,#ddd)} button{display:grid;place-items:center;min-width:32px;min-height:32px;border:1px solid var(--boe-token-stroke-stroke,#ddd);border-radius:50%;background:var(--boe-token-surface-surface,#fff);font-weight:700;line-height:1;padding:0 9px} button:hover{border-color:var(--boe-token-surface-surface-brand,#0061d5);color:var(--boe-token-surface-surface-brand,#0061d5)} :host([data-empty]) button{border-radius:16px;font-weight:600;padding:6px 12px}</style><button type="button" part="button" aria-haspopup="menu"></button>`; }
|
|
62
|
+
setupListeners() { this.shadowRoot.querySelector("button").addEventListener("click", () => emit(this, "insert-request", this.detail)); }
|
|
63
|
+
focus(options) { this.shadowRoot?.querySelector("button")?.focus(options); }
|
|
64
|
+
update() { const button = this.shadowRoot.querySelector("button"); const empty = !this.detail.list.length; this.toggleAttribute("data-empty", empty); button.textContent = empty ? this.detail.label : "+"; button.setAttribute("aria-label", this.detail.label); }
|
|
65
|
+
}
|
|
66
|
+
export class KindPicker extends BaseElement {
|
|
67
|
+
static tagName = "box-kind-picker";
|
|
68
|
+
catalog = [];
|
|
69
|
+
variant = "inline";
|
|
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>`; }
|
|
71
|
+
setupListeners() {
|
|
72
|
+
this.shadowRoot.addEventListener("keydown", event => {
|
|
73
|
+
const e = event;
|
|
74
|
+
if (this.variant !== "menu")
|
|
75
|
+
return;
|
|
76
|
+
if (e.key === "Escape") {
|
|
77
|
+
e.preventDefault();
|
|
78
|
+
emit(this, "picker-cancel", {});
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
const buttons = Array.from(this.shadowRoot.querySelectorAll("button"));
|
|
82
|
+
if (!buttons.length)
|
|
83
|
+
return;
|
|
84
|
+
const current = buttons.indexOf(this.shadowRoot.activeElement);
|
|
85
|
+
const next = e.key === "Home" ? 0 : e.key === "End" ? buttons.length - 1 : ["ArrowDown", "ArrowRight"].includes(e.key) ? (current + 1) % buttons.length : ["ArrowUp", "ArrowLeft"].includes(e.key) ? (current - 1 + buttons.length) % buttons.length : -1;
|
|
86
|
+
if (next < 0)
|
|
87
|
+
return;
|
|
88
|
+
e.preventDefault();
|
|
89
|
+
buttons.forEach((button, index) => button.tabIndex = index === next ? 0 : -1);
|
|
90
|
+
buttons[next].focus();
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
refresh() { if (this.isRendered)
|
|
94
|
+
this.update(); }
|
|
95
|
+
focus(options) { this.shadowRoot?.querySelector("button")?.focus(options); }
|
|
96
|
+
update() {
|
|
97
|
+
const choices = this.shadowRoot.querySelector("[part=choices]");
|
|
98
|
+
choices.setAttribute("role", this.variant === "menu" ? "menu" : "group");
|
|
99
|
+
choices.setAttribute("aria-label", "Choose a step type");
|
|
100
|
+
choices.replaceChildren();
|
|
101
|
+
let group = "";
|
|
102
|
+
this.catalog.forEach((kind, index) => {
|
|
103
|
+
if (kind.group && group !== kind.group) {
|
|
104
|
+
const heading = document.createElement("h3");
|
|
105
|
+
heading.textContent = kind.group;
|
|
106
|
+
heading.setAttribute("role", "presentation");
|
|
107
|
+
heading.setAttribute("part", "group-heading");
|
|
108
|
+
choices.append(heading);
|
|
109
|
+
group = kind.group;
|
|
110
|
+
}
|
|
111
|
+
const button = document.createElement("button");
|
|
112
|
+
button.type = "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);
|
|
127
|
+
if (this.variant === "menu") {
|
|
128
|
+
button.setAttribute("role", "menuitem");
|
|
129
|
+
button.tabIndex = index === 0 ? 0 : -1;
|
|
130
|
+
}
|
|
131
|
+
const description = document.createElement("small");
|
|
132
|
+
description.setAttribute("part", "choice-description");
|
|
133
|
+
description.textContent = kind.description;
|
|
134
|
+
button.append(description);
|
|
135
|
+
button.addEventListener("click", () => emit(this, "kind-pick", { kind }));
|
|
136
|
+
choices.append(button);
|
|
137
|
+
});
|
|
138
|
+
if (!this.catalog.length)
|
|
139
|
+
choices.textContent = "No step types available.";
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
export class FlowSpine extends BaseElement {
|
|
143
|
+
static tagName = "box-flow-spine";
|
|
144
|
+
nodes = [];
|
|
145
|
+
catalog = [];
|
|
146
|
+
model = defaultFlowModel;
|
|
147
|
+
selected = null;
|
|
148
|
+
invalid = null;
|
|
149
|
+
startLabel = "Flow starts";
|
|
150
|
+
endLabel = "Flow ends";
|
|
151
|
+
cards = new Map();
|
|
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>`; }
|
|
153
|
+
refresh() { if (this.isRendered)
|
|
154
|
+
this.update(); }
|
|
155
|
+
/** Re-render one card after its fields change, without rebuilding the spine. */
|
|
156
|
+
refreshNode(node) { this.cards.get(node)?.refresh(); }
|
|
157
|
+
focusNode(node) { this.cards.get(node)?.focus(); }
|
|
158
|
+
select(node, invalid = this.invalid) { this.selected = node; this.invalid = invalid; for (const [item, card] of this.cards) {
|
|
159
|
+
card.selected = item === node;
|
|
160
|
+
card.invalid = item === invalid;
|
|
161
|
+
card.refresh();
|
|
162
|
+
} }
|
|
163
|
+
update() {
|
|
164
|
+
const root = this.shadowRoot.querySelector("[part=spine]");
|
|
165
|
+
const seen = new Set();
|
|
166
|
+
const title = (node) => nodeTitle(node, this.catalog, this.model);
|
|
167
|
+
const listView = (list, where, ancestors) => {
|
|
168
|
+
const fragment = document.createDocumentFragment();
|
|
169
|
+
for (let index = 0; index <= list.length; index++) {
|
|
170
|
+
const point = document.createElement(InsertPoint.tagName);
|
|
171
|
+
point.setAttribute("exportparts", "button: insert");
|
|
172
|
+
point.detail = { list, index, label: insertLabel(list, index, where, this.catalog, this.model) };
|
|
173
|
+
fragment.append(point);
|
|
174
|
+
if (index === list.length)
|
|
175
|
+
break;
|
|
176
|
+
const node = list[index];
|
|
177
|
+
if (ancestors.has(node) || seen.has(node))
|
|
178
|
+
continue;
|
|
179
|
+
seen.add(node);
|
|
180
|
+
const card = this.cards.get(node) ?? document.createElement(FlowCard.tagName);
|
|
181
|
+
card.setAttribute("exportparts", FLOW_CARD_PARTS.join(", "));
|
|
182
|
+
this.cards.set(node, card);
|
|
183
|
+
card.node = node;
|
|
184
|
+
card.catalog = this.catalog;
|
|
185
|
+
card.model = this.model;
|
|
186
|
+
card.selected = node === this.selected;
|
|
187
|
+
card.invalid = node === this.invalid;
|
|
188
|
+
card.refresh();
|
|
189
|
+
fragment.append(card);
|
|
190
|
+
const nextAncestors = new Set([...ancestors, node]);
|
|
191
|
+
const children = this.model.children(node);
|
|
192
|
+
for (const child of children.filter(child => child.label === undefined)) {
|
|
193
|
+
const body = document.createElement("section");
|
|
194
|
+
body.setAttribute("part", "body");
|
|
195
|
+
body.setAttribute("aria-label", `${title(node)} steps`);
|
|
196
|
+
body.append(listView(child.list, `inside ${title(node)}`, nextAncestors));
|
|
197
|
+
fragment.append(body);
|
|
198
|
+
}
|
|
199
|
+
const branchLists = children.filter(child => child.label !== undefined);
|
|
200
|
+
const addLabel = this.model.addBranchLabel?.(node);
|
|
201
|
+
if (branchLists.length || addLabel) {
|
|
202
|
+
const branches = document.createElement("div");
|
|
203
|
+
branches.setAttribute("part", "branches");
|
|
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
|
+
});
|
|
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
|
+
}
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
return fragment;
|
|
240
|
+
};
|
|
241
|
+
const start = document.createElement("div");
|
|
242
|
+
start.setAttribute("part", "endpoint");
|
|
243
|
+
start.textContent = this.startLabel;
|
|
244
|
+
const end = document.createElement("div");
|
|
245
|
+
end.setAttribute("part", "endpoint");
|
|
246
|
+
end.textContent = this.endLabel;
|
|
247
|
+
root.replaceChildren(start, listView(this.nodes, "in the flow", new Set()), end);
|
|
248
|
+
for (const node of this.cards.keys())
|
|
249
|
+
if (!seen.has(node))
|
|
250
|
+
this.cards.delete(node);
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
FlowCard.register();
|
|
254
|
+
InsertPoint.register();
|
|
255
|
+
KindPicker.register();
|
|
256
|
+
FlowSpine.register();
|
|
@@ -35,6 +35,7 @@ const elementStyles = `
|
|
|
35
35
|
|
|
36
36
|
:host {
|
|
37
37
|
display: block;
|
|
38
|
+
min-inline-size: 0;
|
|
38
39
|
color: inherit;
|
|
39
40
|
font: inherit;
|
|
40
41
|
}
|
|
@@ -47,6 +48,7 @@ const elementStyles = `
|
|
|
47
48
|
|
|
48
49
|
section[part="panel"] {
|
|
49
50
|
display: grid;
|
|
51
|
+
grid-template-columns: minmax(0, 1fr);
|
|
50
52
|
gap: ${boePanel.gap};
|
|
51
53
|
padding: ${boePanel.padding};
|
|
52
54
|
border: 1px solid color-mix(in srgb, var(--boe-token-stroke-stroke, #e8e8e8) 82%, transparent);
|
|
@@ -55,6 +57,7 @@ const elementStyles = `
|
|
|
55
57
|
}
|
|
56
58
|
|
|
57
59
|
[part="title"] {
|
|
60
|
+
overflow-wrap: anywhere;
|
|
58
61
|
margin: 0;
|
|
59
62
|
font: inherit;
|
|
60
63
|
font-size: 1.1rem;
|
|
@@ -65,11 +68,24 @@ const elementStyles = `
|
|
|
65
68
|
[part="graph"] {
|
|
66
69
|
position: relative;
|
|
67
70
|
overflow-x: auto;
|
|
71
|
+
min-inline-size: 0;
|
|
72
|
+
padding-block: 3px;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
[part="graph"]:focus-visible {
|
|
76
|
+
outline: 2px solid var(--boe-token-surface-surface-brand, #0061d5);
|
|
77
|
+
outline-offset: 2px;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
[part="scroll-hint"] {
|
|
81
|
+
margin: 0;
|
|
82
|
+
font-size: 0.78rem;
|
|
83
|
+
color: var(--boe-token-text-text-secondary, #6f6f6f);
|
|
68
84
|
}
|
|
69
85
|
|
|
70
86
|
[part="edges"] {
|
|
71
87
|
position: absolute;
|
|
72
|
-
inset-block-start:
|
|
88
|
+
inset-block-start: 3px;
|
|
73
89
|
inset-inline-start: 0;
|
|
74
90
|
pointer-events: none;
|
|
75
91
|
}
|
|
@@ -104,6 +120,8 @@ const elementStyles = `
|
|
|
104
120
|
}
|
|
105
121
|
|
|
106
122
|
[part="rows"] {
|
|
123
|
+
inline-size: max-content;
|
|
124
|
+
min-inline-size: 100%;
|
|
107
125
|
list-style: none;
|
|
108
126
|
margin: 0;
|
|
109
127
|
padding: 0;
|
|
@@ -113,7 +131,8 @@ const elementStyles = `
|
|
|
113
131
|
[part="row"] {
|
|
114
132
|
position: relative;
|
|
115
133
|
display: flex;
|
|
116
|
-
flex-wrap:
|
|
134
|
+
flex-wrap: nowrap;
|
|
135
|
+
white-space: nowrap;
|
|
117
136
|
align-items: center;
|
|
118
137
|
gap: 0.45rem;
|
|
119
138
|
block-size: ${ROW_HEIGHT}px;
|
|
@@ -192,13 +211,13 @@ const elementStyles = `
|
|
|
192
211
|
}
|
|
193
212
|
|
|
194
213
|
[part="edge-chip"][data-deviation="minor"] {
|
|
195
|
-
border-color: color-mix(in srgb, var(--boe-token-surface-status-surface-
|
|
196
|
-
color:
|
|
214
|
+
border-color: color-mix(in srgb, var(--boe-token-surface-status-surface-inprogress, #f5b31b) 55%, transparent);
|
|
215
|
+
color: var(--boe-token-text-status-text-warning, #805600);
|
|
197
216
|
}
|
|
198
217
|
|
|
199
218
|
[part="edge-chip"][data-deviation="major"] {
|
|
200
219
|
border-color: color-mix(in srgb, var(--boe-token-surface-status-surface-error, #ed3757) 45%, transparent);
|
|
201
|
-
color:
|
|
220
|
+
color: var(--boe-token-text-status-text-error, #b92340);
|
|
202
221
|
}
|
|
203
222
|
|
|
204
223
|
[part="edge-chip"]:focus-visible {
|
|
@@ -481,7 +500,8 @@ export class LineageGraph extends BaseElement {
|
|
|
481
500
|
<h2 part="title">${escapeHtml(this.heading)}</h2>
|
|
482
501
|
${layout.placements.length > 0
|
|
483
502
|
? `
|
|
484
|
-
<
|
|
503
|
+
<p part="scroll-hint">Scroll horizontally to explore nodes and comparisons.</p>
|
|
504
|
+
<div part="graph" role="region" aria-label="${escapeHtml(this.heading)} — scroll horizontally to explore" tabindex="0">
|
|
485
505
|
<svg part="edges" width="${String(graphWidth)}" height="${String(graphHeight)}" viewBox="0 0 ${String(graphWidth)} ${String(graphHeight)}" aria-hidden="true"><defs><marker id="boe-graph-arrow" viewBox="0 0 8 8" refX="7" refY="4" markerWidth="4" markerHeight="4" orient="auto-start-reverse"><path part="edge-arrow" d="M 0 0 L 8 4 L 0 8 z"></path></marker><marker id="boe-graph-arrow-minor" viewBox="0 0 8 8" refX="7" refY="4" markerWidth="4" markerHeight="4" orient="auto-start-reverse"><path part="edge-arrow" data-deviation="minor" d="M 0 0 L 8 4 L 0 8 z"></path></marker><marker id="boe-graph-arrow-major" viewBox="0 0 8 8" refX="7" refY="4" markerWidth="4" markerHeight="4" orient="auto-start-reverse"><path part="edge-arrow" data-deviation="major" d="M 0 0 L 8 4 L 0 8 z"></path></marker></defs>${edgePaths}</svg>
|
|
486
506
|
<ol part="rows" role="list">${rows}</ol>
|
|
487
507
|
</div>
|
|
@@ -17,6 +17,8 @@ const elementStyles = `
|
|
|
17
17
|
|
|
18
18
|
:host {
|
|
19
19
|
display: block;
|
|
20
|
+
min-inline-size: 0;
|
|
21
|
+
container: boe-notification-inbox / inline-size;
|
|
20
22
|
color: inherit;
|
|
21
23
|
font: inherit;
|
|
22
24
|
}
|
|
@@ -29,6 +31,7 @@ const elementStyles = `
|
|
|
29
31
|
|
|
30
32
|
[part="panel"] {
|
|
31
33
|
display: grid;
|
|
34
|
+
grid-template-columns: minmax(0, 1fr);
|
|
32
35
|
grid-template-rows: auto auto 1fr;
|
|
33
36
|
gap: ${boePanel.gap};
|
|
34
37
|
padding: ${boePanel.padding};
|
|
@@ -40,11 +43,13 @@ const elementStyles = `
|
|
|
40
43
|
|
|
41
44
|
[part="header"] {
|
|
42
45
|
display: flex;
|
|
46
|
+
flex-wrap: wrap;
|
|
43
47
|
align-items: center;
|
|
44
48
|
gap: 0.5rem;
|
|
45
49
|
}
|
|
46
50
|
|
|
47
51
|
[part="title"] {
|
|
52
|
+
overflow-wrap: anywhere;
|
|
48
53
|
margin: 0;
|
|
49
54
|
font: inherit;
|
|
50
55
|
font-size: 1.05rem;
|
|
@@ -56,7 +61,7 @@ const elementStyles = `
|
|
|
56
61
|
padding: 0.1rem 0.45rem;
|
|
57
62
|
border-radius: 999px;
|
|
58
63
|
background: color-mix(in srgb, var(--boe-token-surface-status-surface-error, #ed3757) 14%, transparent);
|
|
59
|
-
color:
|
|
64
|
+
color: var(--boe-token-text-status-text-error, #b92340);
|
|
60
65
|
font-size: 0.72rem;
|
|
61
66
|
font-weight: 700;
|
|
62
67
|
}
|
|
@@ -134,7 +139,7 @@ const elementStyles = `
|
|
|
134
139
|
text-transform: none;
|
|
135
140
|
letter-spacing: normal;
|
|
136
141
|
font-size: 0.7rem;
|
|
137
|
-
color:
|
|
142
|
+
color: var(--boe-token-text-status-text-error, #b92340);
|
|
138
143
|
}
|
|
139
144
|
|
|
140
145
|
[part="items"] {
|
|
@@ -147,7 +152,7 @@ const elementStyles = `
|
|
|
147
152
|
|
|
148
153
|
[part="item"] {
|
|
149
154
|
display: grid;
|
|
150
|
-
grid-template-columns: auto 1fr auto;
|
|
155
|
+
grid-template-columns: auto minmax(0, 1fr) auto;
|
|
151
156
|
gap: 0.5rem;
|
|
152
157
|
align-items: start;
|
|
153
158
|
padding: 0.5rem 0.55rem;
|
|
@@ -174,6 +179,7 @@ const elementStyles = `
|
|
|
174
179
|
|
|
175
180
|
[part="item-body"] {
|
|
176
181
|
display: grid;
|
|
182
|
+
overflow-wrap: anywhere;
|
|
177
183
|
gap: 0.15rem;
|
|
178
184
|
min-inline-size: 0;
|
|
179
185
|
}
|
|
@@ -223,6 +229,7 @@ const elementStyles = `
|
|
|
223
229
|
|
|
224
230
|
[part="item-actions"] {
|
|
225
231
|
display: flex;
|
|
232
|
+
flex-wrap: wrap;
|
|
226
233
|
gap: 0.25rem;
|
|
227
234
|
}
|
|
228
235
|
|
|
@@ -251,6 +258,15 @@ const elementStyles = `
|
|
|
251
258
|
text-align: center;
|
|
252
259
|
}
|
|
253
260
|
|
|
261
|
+
@container boe-notification-inbox (max-width: 32rem) {
|
|
262
|
+
[part="item"] {
|
|
263
|
+
grid-template-columns: auto minmax(0, 1fr);
|
|
264
|
+
}
|
|
265
|
+
[part="item-actions"] {
|
|
266
|
+
grid-column: 2;
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
254
270
|
[part="filter"]:focus-visible,
|
|
255
271
|
[part="mark-all"]:focus-visible,
|
|
256
272
|
[part="item-title"]:focus-visible,
|
|
@@ -66,7 +66,7 @@ const elementStyles = `
|
|
|
66
66
|
padding: 0.65rem;
|
|
67
67
|
border-radius: ${boePanel.radius};
|
|
68
68
|
border: 1px solid color-mix(in srgb, var(--boe-token-stroke-stroke, #e8e8e8) 48%, transparent);
|
|
69
|
-
background:
|
|
69
|
+
background: var(--boe-token-surface-surface, #ffffff);
|
|
70
70
|
}
|
|
71
71
|
|
|
72
72
|
[part="annotation-header"] {
|
|
@@ -254,7 +254,7 @@ const elementStyles = `
|
|
|
254
254
|
[part="action"][data-tone="primary"] {
|
|
255
255
|
border-color: transparent;
|
|
256
256
|
background: var(--boe-token-surface-surface-brand, #0061d5);
|
|
257
|
-
color: #
|
|
257
|
+
color: var(--boe-token-text-text-on-brand, #ffffff);
|
|
258
258
|
}
|
|
259
259
|
`;
|
|
260
260
|
export class AnnotationInspector extends BaseElement {
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { Toast } from "../../components/feedback/toast.js";
|
|
2
|
+
/** Remove once and return an idempotent restore preserving the original object's identity. */
|
|
3
|
+
export declare function removeAt<T>(list: T[], index: number): () => void;
|
|
4
|
+
/** Never overwrite a key recreated after removal. */
|
|
5
|
+
export declare function removeKey<T>(map: Record<string, T>, key: string): () => void;
|
|
6
|
+
/** Restore into the current list, preserving intervening edits and clamping the index. */
|
|
7
|
+
export declare function removeFromMapList<T>(map: Record<string, T[]>, key: string, index: number): () => void;
|
|
8
|
+
export interface UndoOffer {
|
|
9
|
+
readonly toast: Toast | null;
|
|
10
|
+
undo(): void;
|
|
11
|
+
dispose(): void;
|
|
12
|
+
}
|
|
13
|
+
export interface UndoOptions {
|
|
14
|
+
container?: HTMLElement;
|
|
15
|
+
duration?: number;
|
|
16
|
+
onRestore?: () => void;
|
|
17
|
+
}
|
|
18
|
+
/** Offer a single document-scoped undo. Hosts call dispose on teardown. SSR-safe. */
|
|
19
|
+
export declare function offerUndo(message: string, restore: () => void, options?: UndoOptions): UndoOffer;
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { Toast } from "../../components/feedback/toast.js";
|
|
2
|
+
import { announce } from "../../foundations/a11y/index.js";
|
|
3
|
+
/** Remove once and return an idempotent restore preserving the original object's identity. */
|
|
4
|
+
export function removeAt(list, index) {
|
|
5
|
+
if (!Number.isInteger(index) || index < 0 || index >= list.length)
|
|
6
|
+
return () => { };
|
|
7
|
+
const item = list.splice(index, 1)[0];
|
|
8
|
+
let restored = false;
|
|
9
|
+
return () => {
|
|
10
|
+
if (restored)
|
|
11
|
+
return;
|
|
12
|
+
restored = true;
|
|
13
|
+
list.splice(Math.min(index, list.length), 0, item);
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
/** Never overwrite a key recreated after removal. */
|
|
17
|
+
export function removeKey(map, key) {
|
|
18
|
+
if (!Object.hasOwn(map, key))
|
|
19
|
+
return () => { };
|
|
20
|
+
const value = map[key];
|
|
21
|
+
delete map[key];
|
|
22
|
+
let restored = false;
|
|
23
|
+
return () => {
|
|
24
|
+
if (restored)
|
|
25
|
+
return;
|
|
26
|
+
restored = true;
|
|
27
|
+
if (!Object.hasOwn(map, key))
|
|
28
|
+
Object.defineProperty(map, key, { value, writable: true, enumerable: true, configurable: true });
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
/** Restore into the current list, preserving intervening edits and clamping the index. */
|
|
32
|
+
export function removeFromMapList(map, key, index) {
|
|
33
|
+
const list = Object.hasOwn(map, key) ? map[key] : undefined;
|
|
34
|
+
if (!list || !Number.isInteger(index) || index < 0 || index >= list.length)
|
|
35
|
+
return () => { };
|
|
36
|
+
const item = list.splice(index, 1)[0];
|
|
37
|
+
if (!list.length)
|
|
38
|
+
delete map[key];
|
|
39
|
+
let restored = false;
|
|
40
|
+
return () => {
|
|
41
|
+
if (restored)
|
|
42
|
+
return;
|
|
43
|
+
restored = true;
|
|
44
|
+
if (!Object.hasOwn(map, key))
|
|
45
|
+
Object.defineProperty(map, key, { value: list, writable: true, enumerable: true, configurable: true });
|
|
46
|
+
const target = map[key];
|
|
47
|
+
target.splice(Math.min(index, target.length), 0, item);
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
const offers = new WeakMap();
|
|
51
|
+
/** Offer a single document-scoped undo. Hosts call dispose on teardown. SSR-safe. */
|
|
52
|
+
export function offerUndo(message, restore, options = {}) {
|
|
53
|
+
const doc = options.container?.ownerDocument ?? globalThis.document;
|
|
54
|
+
if (!doc?.body)
|
|
55
|
+
return { toast: null, undo() { }, dispose() { } };
|
|
56
|
+
offers.get(doc)?.dispose();
|
|
57
|
+
const toast = doc.createElement(Toast.tagName);
|
|
58
|
+
const action = doc.createElement("button");
|
|
59
|
+
action.type = "button";
|
|
60
|
+
action.slot = "action";
|
|
61
|
+
action.textContent = "Undo";
|
|
62
|
+
const mac = /Mac|iPhone|iPad/.test(doc.defaultView?.navigator.platform ?? "");
|
|
63
|
+
const shortcut = mac ? "Command-Z" : "Control-Z";
|
|
64
|
+
action.setAttribute("aria-keyshortcuts", mac ? "Meta+Z" : "Control+Z");
|
|
65
|
+
toast.append(action);
|
|
66
|
+
let live = true;
|
|
67
|
+
const dispose = () => {
|
|
68
|
+
if (!live)
|
|
69
|
+
return;
|
|
70
|
+
live = false;
|
|
71
|
+
doc.removeEventListener("keydown", onKey);
|
|
72
|
+
toast.removeEventListener("open-changed", onOpen);
|
|
73
|
+
toast.hide();
|
|
74
|
+
toast.remove();
|
|
75
|
+
if (offers.get(doc) === offer)
|
|
76
|
+
offers.delete(doc);
|
|
77
|
+
};
|
|
78
|
+
const undo = () => {
|
|
79
|
+
if (!live)
|
|
80
|
+
return;
|
|
81
|
+
dispose();
|
|
82
|
+
restore();
|
|
83
|
+
options.onRestore?.();
|
|
84
|
+
announce("Restored", "polite", doc);
|
|
85
|
+
};
|
|
86
|
+
const onKey = (event) => {
|
|
87
|
+
if (event.defaultPrevented || event.isComposing || event.shiftKey || event.altKey || !(event.metaKey || event.ctrlKey) || event.key.toLowerCase() !== "z")
|
|
88
|
+
return;
|
|
89
|
+
const editing = event.composedPath().some(target => target instanceof HTMLElement && (target.matches("input, textarea, select, [role=textbox]") || target.isContentEditable));
|
|
90
|
+
if (editing)
|
|
91
|
+
return;
|
|
92
|
+
event.preventDefault();
|
|
93
|
+
undo();
|
|
94
|
+
};
|
|
95
|
+
const onOpen = (event) => { if (!event.detail.open)
|
|
96
|
+
dispose(); };
|
|
97
|
+
const offer = { toast, undo, dispose };
|
|
98
|
+
offers.set(doc, offer);
|
|
99
|
+
action.addEventListener("click", undo);
|
|
100
|
+
toast.addEventListener("open-changed", onOpen);
|
|
101
|
+
doc.addEventListener("keydown", onKey);
|
|
102
|
+
(options.container ?? doc.body).append(toast);
|
|
103
|
+
// Toast owns the one persistent announcement, avoiding duplicate live messages.
|
|
104
|
+
toast.show(`${message}. Undo available with ${shortcut}.`, { duration: options.duration ?? 8000 });
|
|
105
|
+
return offer;
|
|
106
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@unofficialbox/box-open-elements",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.22.0",
|
|
4
4
|
"description": "Box Open Elements: an open, framework-agnostic design system and web component library for Box-style experiences",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"homepage": "https://unofficialbox.github.io/box-open-elements",
|
|
@@ -28,6 +28,14 @@
|
|
|
28
28
|
"main": "dist/index.js",
|
|
29
29
|
"types": "dist/index.d.ts",
|
|
30
30
|
"exports": {
|
|
31
|
+
"./patterns/undo": {
|
|
32
|
+
"types": "./dist/patterns/undo/index.d.ts",
|
|
33
|
+
"import": "./dist/patterns/undo/index.js"
|
|
34
|
+
},
|
|
35
|
+
"./patterns/flow-builder": {
|
|
36
|
+
"types": "./dist/patterns/flow-builder/index.d.ts",
|
|
37
|
+
"import": "./dist/patterns/flow-builder/index.js"
|
|
38
|
+
},
|
|
31
39
|
".": {
|
|
32
40
|
"types": "./dist/index.d.ts",
|
|
33
41
|
"import": "./dist/index.js"
|
|
@@ -173,8 +181,9 @@
|
|
|
173
181
|
"dist"
|
|
174
182
|
],
|
|
175
183
|
"scripts": {
|
|
184
|
+
"bundles:check": "bun tools/adapters/bundle-check.ts",
|
|
176
185
|
"build": "bun run build:core && bun run build:adapters",
|
|
177
|
-
"build:core": "tsc -p tsconfig.json",
|
|
186
|
+
"build:core": "tsc -p tsconfig.json && cp src/foundations/icons/glyphs/package.json dist/foundations/icons/glyphs/package.json",
|
|
178
187
|
"build:adapters": "bun run adapters:version && bun run --cwd packages/react build && bun run --cwd packages/angular build && bun run --cwd packages/vue build && bun run --cwd packages/svelte build",
|
|
179
188
|
"build:watch": "tsc -p tsconfig.json --watch --preserveWatchOutput",
|
|
180
189
|
"prepublishOnly": "bun run build",
|