@unofficialbox/box-open-elements 0.20.0 → 0.21.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 +48 -0
- package/dist/patterns/flow-builder/flow-builder.js +200 -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 +33 -0
- package/dist/patterns/flow-builder/model.js +28 -0
- package/dist/patterns/flow-builder/primitives.d.ts +47 -0
- package/dist/patterns/flow-builder/primitives.js +192 -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
|
@@ -3,6 +3,8 @@ export declare class MenuItem extends BaseElement {
|
|
|
3
3
|
static readonly tagName: string;
|
|
4
4
|
static get observedAttributes(): string[];
|
|
5
5
|
private itemEl;
|
|
6
|
+
/** Let composed menus move focus without reaching into the shadow root. */
|
|
7
|
+
focus(options?: FocusOptions): void;
|
|
6
8
|
get disabled(): boolean;
|
|
7
9
|
set disabled(value: boolean);
|
|
8
10
|
get label(): string;
|
|
@@ -67,6 +67,10 @@ export class MenuItem extends BaseElement {
|
|
|
67
67
|
return ["disabled", "label", "selected", "value"];
|
|
68
68
|
}
|
|
69
69
|
itemEl;
|
|
70
|
+
/** Let composed menus move focus without reaching into the shadow root. */
|
|
71
|
+
focus(options) {
|
|
72
|
+
this.itemEl?.focus(options);
|
|
73
|
+
}
|
|
70
74
|
get disabled() {
|
|
71
75
|
return this.hasAttribute("disabled");
|
|
72
76
|
}
|
|
@@ -25,6 +25,11 @@ const cardStyles = `
|
|
|
25
25
|
0 14px 28px rgba(15, 23, 42, 0.04);
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
+
[part="header"],
|
|
29
|
+
[part="body"] {
|
|
30
|
+
min-width: 0;
|
|
31
|
+
}
|
|
32
|
+
|
|
28
33
|
[part="header"] {
|
|
29
34
|
display: grid;
|
|
30
35
|
gap: 0.35rem;
|
|
@@ -48,6 +53,7 @@ const cardStyles = `
|
|
|
48
53
|
}
|
|
49
54
|
|
|
50
55
|
[part="body"] {
|
|
56
|
+
overflow-x: auto;
|
|
51
57
|
color: var(--boe-token-text-text-secondary, #6f6f6f);
|
|
52
58
|
line-height: 1.55;
|
|
53
59
|
}
|
|
@@ -25,12 +25,15 @@ export const resultBlockStyles = `
|
|
|
25
25
|
:host([hidden]){display:none !important} *{box-sizing:border-box} h3{font-size:1em;margin:0 0 .5rem} dl,ul{padding:0;margin:0;list-style:none}
|
|
26
26
|
[part="row"]{border-bottom:1px solid var(--boe-token-stroke-stroke,#ddd);padding:.7rem 0;display:flex;gap:.6rem;align-items:baseline}
|
|
27
27
|
dt{color:var(--boe-token-text-text-secondary,#666)} dd{margin:0 0 0 auto;font-variant-numeric:tabular-nums}
|
|
28
|
+
[part="facts"] [part="row"]{display:grid;grid-template-columns:minmax(0,1fr) minmax(0,1fr);overflow-wrap:anywhere}
|
|
29
|
+
[part="facts"] dd{margin:0;text-align:end}
|
|
30
|
+
@container boe-result-block (max-width:28rem){[part="facts"] [part="row"]{grid-template-columns:minmax(0,1fr);gap:.25rem}[part="facts"] dd{text-align:start}}
|
|
28
31
|
p{margin:.25rem 0;color:var(--boe-token-text-text-secondary,#666)} small{display:block;font-weight:normal;color:var(--boe-token-text-text-secondary,#666)}
|
|
29
32
|
[part="verdict"]{margin-left:auto;text-align:right} [data-status="warn"]{color:var(--boe-token-text-status-text-warning,#946400)} [data-status="fail"]{color:var(--boe-token-text-status-text-error,#c52a46)}
|
|
30
33
|
[part="document"]{display:flex;gap:.6rem;align-items:center;width:100%;text-align:left;color:inherit;font:inherit;background:none;border:0;text-decoration:none;cursor:pointer}
|
|
31
34
|
[part="table-frame"]{overflow:auto;max-width:100%} table{border-collapse:collapse;min-width:100%;font-variant-numeric:tabular-nums} td,th{padding:.7rem;text-align:left;border-bottom:1px solid var(--boe-token-stroke-stroke,#ddd)} td:last-child{font-weight:600;background:var(--boe-token-surface-surface-secondary,#f7f7f7)}
|
|
32
35
|
.sr{position:absolute;width:1px;height:1px;overflow:hidden;clip-path:inset(50%)}
|
|
33
|
-
[part="block"]{${boeEntrance()}${boeStaggerStyles};margin-block:1rem;min-width:0}
|
|
36
|
+
[part="block"]{${boeEntrance()}${boeStaggerStyles};margin-block:1rem;min-width:0;container:boe-result-block / inline-size}
|
|
34
37
|
${boeEntranceKeyframes}${boeStatusStyles}${boeReducedMotionPolicy}`;
|
|
35
38
|
const DEFAULT_TAG_NAME = "box-result-blocks";
|
|
36
39
|
export class ResultBlocks extends BaseElement {
|
|
@@ -22,6 +22,8 @@ export declare class Alert extends BaseElement {
|
|
|
22
22
|
get tone(): string;
|
|
23
23
|
set tone(value: string);
|
|
24
24
|
connectedCallback(): void;
|
|
25
|
+
private announcement;
|
|
26
|
+
disconnectedCallback(): void;
|
|
25
27
|
attributeChangedCallback(name: string, oldValue: string | null, newValue: string | null): void;
|
|
26
28
|
dismiss(): void;
|
|
27
29
|
protected renderTemplate(): void;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { BaseElement } from "../../core/index.js";
|
|
2
|
+
import { FeedbackAnnouncement } from "../../foundations/a11y/announcer.js";
|
|
2
3
|
import { toneAccessibleLabel, toneIcon } from "./tone.js";
|
|
3
4
|
import { boeRadius, boeSpace } from "../../foundations/geometry/index.js";
|
|
4
5
|
import { boeNeutralInteractiveStyles } from "../../foundations/tokens/index.js";
|
|
@@ -15,10 +16,8 @@ const alertStyles = `
|
|
|
15
16
|
display: none !important;
|
|
16
17
|
}
|
|
17
18
|
|
|
18
|
-
/*
|
|
19
|
-
|
|
20
|
-
deliberately not derived from it; upstream has no glyph, so adding one
|
|
21
|
-
costs no conformance. */
|
|
19
|
+
/* Preserve upstream light tints, but mix against the active theme surface.
|
|
20
|
+
Glyphs use readable status ink, not decorative status fill colours. */
|
|
22
21
|
[part="alert"] {
|
|
23
22
|
--alert-accent: var(--boe-token-text-text-secondary, #6f6f6f);
|
|
24
23
|
|
|
@@ -36,23 +35,23 @@ const alertStyles = `
|
|
|
36
35
|
|
|
37
36
|
[part="alert"][data-tone="info"] {
|
|
38
37
|
--alert-accent: var(--boe-token-surface-surface-brand, #0061d5);
|
|
39
|
-
background: color-mix(in srgb, var(--boe-token-surface-surface-brand, #0061d5) 10%, #
|
|
38
|
+
background: color-mix(in srgb, var(--boe-token-surface-surface-brand, #0061d5) 10%, var(--boe-token-surface-surface, #ffffff));
|
|
40
39
|
}
|
|
41
40
|
|
|
42
41
|
[part="alert"][data-tone="success"] {
|
|
43
|
-
--alert-accent: var(--boe-token-
|
|
44
|
-
background: color-mix(in srgb, var(--boe-token-surface-status-surface-success, #26c281) 10%, #
|
|
42
|
+
--alert-accent: var(--boe-token-text-status-text-success, #187657);
|
|
43
|
+
background: color-mix(in srgb, var(--boe-token-surface-status-surface-success, #26c281) 10%, var(--boe-token-surface-surface, #ffffff));
|
|
45
44
|
}
|
|
46
45
|
|
|
47
46
|
[part="alert"][data-tone="error"] {
|
|
48
|
-
--alert-accent: var(--boe-token-
|
|
49
|
-
background: color-mix(in srgb, var(--boe-token-surface-status-surface-error, #ed3757) 10%, #
|
|
47
|
+
--alert-accent: var(--boe-token-text-status-text-error, #b92340);
|
|
48
|
+
background: color-mix(in srgb, var(--boe-token-surface-status-surface-error, #ed3757) 10%, var(--boe-token-surface-surface, #ffffff));
|
|
50
49
|
}
|
|
51
50
|
|
|
52
51
|
[part="alert"][data-tone="warning"],
|
|
53
52
|
[part="alert"][data-tone="inprogress"] {
|
|
54
|
-
--alert-accent: var(--boe-token-
|
|
55
|
-
background: color-mix(in srgb, var(--boe-token-surface-status-surface-inprogress, #f5b31b) 10%, #
|
|
53
|
+
--alert-accent: var(--boe-token-text-status-text-warning, #805600);
|
|
54
|
+
background: color-mix(in srgb, var(--boe-token-surface-status-surface-inprogress, #f5b31b) 10%, var(--boe-token-surface-surface, #ffffff));
|
|
56
55
|
}
|
|
57
56
|
|
|
58
57
|
[part="icon"] {
|
|
@@ -202,9 +201,12 @@ export class Alert extends BaseElement {
|
|
|
202
201
|
this.setAttribute("tone", value);
|
|
203
202
|
}
|
|
204
203
|
connectedCallback() {
|
|
205
|
-
this.openValue = this.hasAttribute("open") || !this.hasAttribute("open");
|
|
206
204
|
super.connectedCallback();
|
|
207
205
|
}
|
|
206
|
+
announcement = new FeedbackAnnouncement();
|
|
207
|
+
disconnectedCallback() {
|
|
208
|
+
this.announcement.reset();
|
|
209
|
+
}
|
|
208
210
|
attributeChangedCallback(name, oldValue, newValue) {
|
|
209
211
|
if (name === "open") {
|
|
210
212
|
this.openValue = this.hasAttribute("open");
|
|
@@ -224,7 +226,7 @@ export class Alert extends BaseElement {
|
|
|
224
226
|
}
|
|
225
227
|
this.shadowRoot.innerHTML = `
|
|
226
228
|
<style>${alertStyles}</style>
|
|
227
|
-
<div part="alert" role="
|
|
229
|
+
<div part="alert" role="group">
|
|
228
230
|
<span part="icon" aria-hidden="true"></span>
|
|
229
231
|
<div part="content">
|
|
230
232
|
<span part="tone-label" class="sr-only"></span>
|
|
@@ -263,6 +265,8 @@ export class Alert extends BaseElement {
|
|
|
263
265
|
const hasRich = this.hasRichContent();
|
|
264
266
|
const visible = this.openValue && Boolean(this.heading || this.message || hasRich);
|
|
265
267
|
this.hidden = !visible;
|
|
268
|
+
const richText = this.richSlot.assignedNodes({ flatten: true }).map(node => node.textContent ?? "").join(" ").trim();
|
|
269
|
+
this.announcement.update(this, visible ? [toneAccessibleLabel(this.tone), this.heading, this.message, richText].filter(Boolean).join(": ") : "", this.tone === "error" ? "assertive" : "polite");
|
|
266
270
|
this.richSlot.classList.toggle("has-content", hasRich);
|
|
267
271
|
if (!visible) {
|
|
268
272
|
return;
|
|
@@ -2,7 +2,8 @@ import { BaseElement } from "../../core/index.js";
|
|
|
2
2
|
/**
|
|
3
3
|
* Whether the toast clears itself.
|
|
4
4
|
*
|
|
5
|
-
* `dismissible` auto-dismisses after `duration
|
|
5
|
+
* `dismissible` auto-dismisses non-error feedback after `duration`;
|
|
6
|
+
* errors always persist until explicitly dismissed or replaced.
|
|
6
7
|
* `sticky` stays until the reader closes it, whatever `duration` says. Both
|
|
7
8
|
* carry the close control — a toast the reader cannot get rid of is a trap.
|
|
8
9
|
*/
|
|
@@ -14,6 +15,10 @@ export declare class Toast extends BaseElement {
|
|
|
14
15
|
static get observedAttributes(): string[];
|
|
15
16
|
private openValue;
|
|
16
17
|
private timeoutId;
|
|
18
|
+
private remaining;
|
|
19
|
+
private deadline;
|
|
20
|
+
private holds;
|
|
21
|
+
private announcement;
|
|
17
22
|
private toastEl;
|
|
18
23
|
private iconEl;
|
|
19
24
|
private headingEl;
|
|
@@ -28,6 +33,9 @@ export declare class Toast extends BaseElement {
|
|
|
28
33
|
get open(): boolean;
|
|
29
34
|
set open(value: boolean);
|
|
30
35
|
private scheduleAutoDismiss;
|
|
36
|
+
private resumeTimer;
|
|
37
|
+
private hold;
|
|
38
|
+
private dismiss;
|
|
31
39
|
get message(): string;
|
|
32
40
|
set message(value: string);
|
|
33
41
|
/**
|
|
@@ -56,6 +64,7 @@ export declare class Toast extends BaseElement {
|
|
|
56
64
|
get tone(): string;
|
|
57
65
|
set tone(value: string);
|
|
58
66
|
disconnectedCallback(): void;
|
|
67
|
+
connectedCallback(): void;
|
|
59
68
|
attributeChangedCallback(name: string, oldValue: string | null, newValue: string | null): void;
|
|
60
69
|
show(message?: string, options?: {
|
|
61
70
|
duration?: number;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { BaseElement } from "../../core/index.js";
|
|
2
|
+
import { FeedbackAnnouncement } from "../../foundations/a11y/announcer.js";
|
|
2
3
|
import { toneAccessibleLabel, toneIcon } from "./tone.js";
|
|
3
4
|
import { boeRadius, boeSpace } from "../../foundations/geometry/index.js";
|
|
4
5
|
import { boeFocusVisibleStyles } from "../../foundations/tokens/index.js";
|
|
@@ -21,18 +22,8 @@ const toastStyles = `
|
|
|
21
22
|
display: none !important;
|
|
22
23
|
}
|
|
23
24
|
|
|
24
|
-
/*
|
|
25
|
-
|
|
26
|
-
derived from the tone accent. The --toast-accent property exists only to
|
|
27
|
-
colour the status glyph, which upstream has no equivalent for, so adding it
|
|
28
|
-
costs no conformance.
|
|
29
|
-
|
|
30
|
-
There is no border. Upstream .notification has one, and box-open-elements
|
|
31
|
-
matched it until the outline was judged too heavy; the claim that pinned it
|
|
32
|
-
was retired rather than left describing something no longer painted. Tone
|
|
33
|
-
survives that in three places — the tinted fill, the full-strength glyph,
|
|
34
|
-
and the visually-hidden tone label — so it was never carried by the border
|
|
35
|
-
alone. */
|
|
25
|
+
/* Preserve upstream light tints while deriving dark fills from the active
|
|
26
|
+
surface. Status ink, tinted fill and a text label communicate the tone. */
|
|
36
27
|
[part="toast"] {
|
|
37
28
|
--toast-accent: var(--boe-token-text-text, #222222);
|
|
38
29
|
|
|
@@ -57,19 +48,19 @@ const toastStyles = `
|
|
|
57
48
|
}
|
|
58
49
|
|
|
59
50
|
[part="toast"][data-tone="success"] {
|
|
60
|
-
--toast-accent: var(--boe-token-
|
|
61
|
-
background: color-mix(in srgb, var(--boe-token-surface-status-surface-success, #26c281) 20%, #
|
|
51
|
+
--toast-accent: var(--boe-token-text-status-text-success, #187657);
|
|
52
|
+
background: color-mix(in srgb, var(--boe-token-surface-status-surface-success, #26c281) 20%, var(--boe-token-surface-surface, #ffffff));
|
|
62
53
|
}
|
|
63
54
|
|
|
64
55
|
[part="toast"][data-tone="error"] {
|
|
65
|
-
--toast-accent: var(--boe-token-
|
|
66
|
-
background: color-mix(in srgb, var(--boe-token-surface-status-surface-error, #ed3757) 20%, #
|
|
56
|
+
--toast-accent: var(--boe-token-text-status-text-error, #b92340);
|
|
57
|
+
background: color-mix(in srgb, var(--boe-token-surface-status-surface-error, #ed3757) 20%, var(--boe-token-surface-surface, #ffffff));
|
|
67
58
|
}
|
|
68
59
|
|
|
69
60
|
[part="toast"][data-tone="warning"],
|
|
70
61
|
[part="toast"][data-tone="inprogress"] {
|
|
71
|
-
--toast-accent: var(--boe-token-
|
|
72
|
-
background: color-mix(in srgb, var(--boe-token-surface-status-surface-inprogress, #f5b31b) 20%, #
|
|
62
|
+
--toast-accent: var(--boe-token-text-status-text-warning, #805600);
|
|
63
|
+
background: color-mix(in srgb, var(--boe-token-surface-status-surface-inprogress, #f5b31b) 20%, var(--boe-token-surface-surface, #ffffff));
|
|
73
64
|
}
|
|
74
65
|
|
|
75
66
|
.sr-only {
|
|
@@ -88,8 +79,7 @@ const toastStyles = `
|
|
|
88
79
|
flex: 0 0 auto;
|
|
89
80
|
inline-size: 20px;
|
|
90
81
|
block-size: 20px;
|
|
91
|
-
/*
|
|
92
|
-
read as the status colour rather than a darkened version of it. */
|
|
82
|
+
/* Readable status ink is separate from the decorative fill colour. */
|
|
93
83
|
color: var(--toast-accent);
|
|
94
84
|
}
|
|
95
85
|
|
|
@@ -205,6 +195,10 @@ export class Toast extends BaseElement {
|
|
|
205
195
|
}
|
|
206
196
|
openValue = false;
|
|
207
197
|
timeoutId = null;
|
|
198
|
+
remaining = 0;
|
|
199
|
+
deadline = 0;
|
|
200
|
+
holds = new Set();
|
|
201
|
+
announcement = new FeedbackAnnouncement();
|
|
208
202
|
toastEl;
|
|
209
203
|
iconEl;
|
|
210
204
|
headingEl;
|
|
@@ -255,11 +249,36 @@ export class Toast extends BaseElement {
|
|
|
255
249
|
clearTimeout(this.timeoutId);
|
|
256
250
|
this.timeoutId = null;
|
|
257
251
|
}
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
252
|
+
this.remaining = Number.isFinite(duration) ? Math.max(0, duration) : 0;
|
|
253
|
+
this.resumeTimer();
|
|
254
|
+
}
|
|
255
|
+
resumeTimer() {
|
|
256
|
+
if (!this.isConnected || !this.open || this.holds.size || this.remaining <= 0 || this.mode === "sticky" || this.tone === "error")
|
|
257
|
+
return;
|
|
258
|
+
this.deadline = Date.now() + this.remaining;
|
|
259
|
+
this.timeoutId = setTimeout(() => {
|
|
260
|
+
this.timeoutId = null;
|
|
261
|
+
this.remaining = 0;
|
|
262
|
+
this.dismiss("timeout");
|
|
263
|
+
}, this.remaining);
|
|
264
|
+
}
|
|
265
|
+
hold(reason, active) {
|
|
266
|
+
if (active) {
|
|
267
|
+
this.holds.add(reason);
|
|
268
|
+
if (this.timeoutId !== null) {
|
|
269
|
+
this.remaining = Math.max(0, this.deadline - Date.now());
|
|
270
|
+
clearTimeout(this.timeoutId);
|
|
271
|
+
this.timeoutId = null;
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
else if (this.holds.delete(reason) && !this.holds.size) {
|
|
275
|
+
this.resumeTimer();
|
|
261
276
|
}
|
|
262
277
|
}
|
|
278
|
+
dismiss(source) {
|
|
279
|
+
this.hide();
|
|
280
|
+
this.dispatchEvent(new CustomEvent("dismiss", { bubbles: true, composed: true, detail: { source } }));
|
|
281
|
+
}
|
|
263
282
|
get message() {
|
|
264
283
|
return this.getAttribute("message") ?? "";
|
|
265
284
|
}
|
|
@@ -308,16 +327,26 @@ export class Toast extends BaseElement {
|
|
|
308
327
|
this.setAttribute("tone", value);
|
|
309
328
|
}
|
|
310
329
|
disconnectedCallback() {
|
|
330
|
+
this.announcement.reset();
|
|
331
|
+
this.holds.clear();
|
|
311
332
|
if (this.timeoutId) {
|
|
333
|
+
this.remaining = Math.max(0, this.deadline - Date.now());
|
|
312
334
|
clearTimeout(this.timeoutId);
|
|
313
335
|
this.timeoutId = null;
|
|
314
336
|
}
|
|
315
337
|
}
|
|
338
|
+
connectedCallback() {
|
|
339
|
+
super.connectedCallback();
|
|
340
|
+
this.resumeTimer();
|
|
341
|
+
}
|
|
316
342
|
attributeChangedCallback(name, oldValue, newValue) {
|
|
317
343
|
if (name === "open") {
|
|
318
344
|
this.openValue = this.hasAttribute("open");
|
|
319
345
|
}
|
|
320
346
|
super.attributeChangedCallback(name, oldValue, newValue);
|
|
347
|
+
if (oldValue !== newValue && ["open", "duration", "mode", "tone"].includes(name)) {
|
|
348
|
+
this.scheduleAutoDismiss(this.open ? this.duration : 0);
|
|
349
|
+
}
|
|
321
350
|
}
|
|
322
351
|
show(message, options) {
|
|
323
352
|
if (typeof message === "string") {
|
|
@@ -328,8 +357,12 @@ export class Toast extends BaseElement {
|
|
|
328
357
|
}
|
|
329
358
|
this.open = true;
|
|
330
359
|
// options.duration wins; else a declarative `duration` attribute; else 2500.
|
|
331
|
-
const duration = options?.duration ?? (this.duration
|
|
360
|
+
const duration = options?.duration ?? (this.hasAttribute("duration") ? this.duration : 2500);
|
|
332
361
|
this.scheduleAutoDismiss(duration);
|
|
362
|
+
// Re-showing the same text is a new feedback event, even when already open.
|
|
363
|
+
this.announcement.reset();
|
|
364
|
+
if (this.isRendered)
|
|
365
|
+
this.update();
|
|
333
366
|
}
|
|
334
367
|
hide() {
|
|
335
368
|
this.open = false;
|
|
@@ -340,7 +373,7 @@ export class Toast extends BaseElement {
|
|
|
340
373
|
}
|
|
341
374
|
this.shadowRoot.innerHTML = `
|
|
342
375
|
<style>${toastStyles}</style>
|
|
343
|
-
<div part="toast" role="
|
|
376
|
+
<div part="toast" role="group" aria-label="Notification">
|
|
344
377
|
<span part="icon" aria-hidden="true"></span>
|
|
345
378
|
<div part="content">
|
|
346
379
|
<span part="tone-label" class="sr-only"></span>
|
|
@@ -360,9 +393,17 @@ export class Toast extends BaseElement {
|
|
|
360
393
|
this.dismissEl = this.shadowRoot.querySelector('[part="dismiss"]');
|
|
361
394
|
}
|
|
362
395
|
setupListeners() {
|
|
396
|
+
this.addEventListener("pointerenter", () => this.hold("pointer", true));
|
|
397
|
+
this.addEventListener("pointerleave", () => this.hold("pointer", false));
|
|
398
|
+
this.addEventListener("focusin", () => this.hold("focus", true));
|
|
399
|
+
this.addEventListener("focusout", event => {
|
|
400
|
+
const target = event.relatedTarget;
|
|
401
|
+
if (target instanceof Node && (this.contains(target) || this.shadowRoot?.contains(target)))
|
|
402
|
+
return;
|
|
403
|
+
this.hold("focus", false);
|
|
404
|
+
});
|
|
363
405
|
this.dismissEl.addEventListener("click", () => {
|
|
364
|
-
this.
|
|
365
|
-
this.hide();
|
|
406
|
+
this.dismiss("close-button");
|
|
366
407
|
});
|
|
367
408
|
this.actionSlot.addEventListener("slotchange", () => {
|
|
368
409
|
const hasContent = this.actionSlot.assignedNodes({ flatten: true }).length > 0;
|
|
@@ -375,6 +416,7 @@ export class Toast extends BaseElement {
|
|
|
375
416
|
}
|
|
376
417
|
const visible = this.openValue && Boolean(this.message);
|
|
377
418
|
this.hidden = !visible;
|
|
419
|
+
this.announcement.update(this, visible ? [toneAccessibleLabel(this.tone), this.heading, this.message].filter(Boolean).join(": ") : "", this.tone === "error" ? "assertive" : "polite");
|
|
378
420
|
if (!visible) {
|
|
379
421
|
return;
|
|
380
422
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { FormAssociatedElement, boeFormFieldErrorStyles, formErrorMessageMarkup, } from "../../core/index.js";
|
|
1
|
+
import { FormAssociatedElement, boeFormFieldErrorStyles, boeFormFieldSupportStyles, formDescriptionMarkup, formErrorMessageMarkup, } from "../../core/index.js";
|
|
2
2
|
import { boeNeutralInteractiveStyles } from "../../foundations/tokens/index.js";
|
|
3
3
|
import { boeMotionDuration, boeMotionEasing } from "../../foundations/motion/index.js";
|
|
4
|
-
import { boeRadius } from "../../foundations/geometry/index.js";
|
|
4
|
+
import { boeControl, boeRadius } from "../../foundations/geometry/index.js";
|
|
5
5
|
const DEFAULT_TAG_NAME = "box-number-input";
|
|
6
6
|
const parseNumber = (value, fallback) => {
|
|
7
7
|
if (value == null || value === "") {
|
|
@@ -29,11 +29,9 @@ const numberInputStyles = `
|
|
|
29
29
|
}
|
|
30
30
|
|
|
31
31
|
[part="label"] {
|
|
32
|
-
font-size:
|
|
32
|
+
font-size: ${boeControl.fontSize};
|
|
33
33
|
font-weight: 700;
|
|
34
|
-
|
|
35
|
-
text-transform: uppercase;
|
|
36
|
-
color: var(--boe-token-text-text-secondary, #6f6f6f);
|
|
34
|
+
color: var(--boe-token-text-text, #222222);
|
|
37
35
|
}
|
|
38
36
|
|
|
39
37
|
[part="input"] {
|
|
@@ -60,6 +58,7 @@ const numberInputStyles = `
|
|
|
60
58
|
}
|
|
61
59
|
|
|
62
60
|
${boeFormFieldErrorStyles}
|
|
61
|
+
${boeFormFieldSupportStyles}
|
|
63
62
|
`;
|
|
64
63
|
export class NumberInput extends FormAssociatedElement {
|
|
65
64
|
static tagName = DEFAULT_TAG_NAME;
|
|
@@ -79,6 +78,7 @@ export class NumberInput extends FormAssociatedElement {
|
|
|
79
78
|
inputEl;
|
|
80
79
|
labelEl;
|
|
81
80
|
errorEl;
|
|
81
|
+
descriptionEl;
|
|
82
82
|
get disabled() {
|
|
83
83
|
return this.hasAttribute("disabled");
|
|
84
84
|
}
|
|
@@ -194,6 +194,7 @@ export class NumberInput extends FormAssociatedElement {
|
|
|
194
194
|
<style>${numberInputStyles}</style>
|
|
195
195
|
<label part="field">
|
|
196
196
|
<span part="label"></span>
|
|
197
|
+
${formDescriptionMarkup()}
|
|
197
198
|
<input type="number" part="input" />
|
|
198
199
|
${formErrorMessageMarkup()}
|
|
199
200
|
</label>
|
|
@@ -201,6 +202,7 @@ export class NumberInput extends FormAssociatedElement {
|
|
|
201
202
|
this.labelEl = this.shadowRoot.querySelector('[part="label"]');
|
|
202
203
|
this.inputEl = this.shadowRoot.querySelector('[part="input"]');
|
|
203
204
|
this.errorEl = this.shadowRoot.querySelector('[part="error-message"]');
|
|
205
|
+
this.descriptionEl = this.shadowRoot.querySelector('[part="description"]');
|
|
204
206
|
}
|
|
205
207
|
setupListeners() {
|
|
206
208
|
this.inputEl.addEventListener("input", event => {
|
|
@@ -239,6 +241,7 @@ export class NumberInput extends FormAssociatedElement {
|
|
|
239
241
|
else {
|
|
240
242
|
this.inputEl.removeAttribute("disabled");
|
|
241
243
|
}
|
|
244
|
+
this.applyFieldSupport(this.labelEl, this.inputEl, this.descriptionEl);
|
|
242
245
|
this.applyInvalidState(this.inputEl, this.errorEl);
|
|
243
246
|
}
|
|
244
247
|
}
|
|
@@ -6,6 +6,11 @@ const searchSpinnerMarkup = '<span part="spinner" aria-hidden="true"></span>';
|
|
|
6
6
|
const searchFieldStyles = `
|
|
7
7
|
:host {
|
|
8
8
|
display: block;
|
|
9
|
+
min-inline-size: 0;
|
|
10
|
+
container: boe-search-field / inline-size;
|
|
11
|
+
/* Inline-size containment otherwise contributes zero intrinsic width in
|
|
12
|
+
content-sized flex/grid rows. Explicit parent widths still take priority. */
|
|
13
|
+
contain-intrinsic-inline-size: 20rem;
|
|
9
14
|
color: inherit;
|
|
10
15
|
font: inherit;
|
|
11
16
|
}
|
|
@@ -18,6 +23,7 @@ const searchFieldStyles = `
|
|
|
18
23
|
|
|
19
24
|
[part="field"] {
|
|
20
25
|
display: grid;
|
|
26
|
+
grid-template-columns: minmax(0, 1fr);
|
|
21
27
|
gap: 0.45rem;
|
|
22
28
|
}
|
|
23
29
|
|
|
@@ -116,6 +122,16 @@ const searchFieldStyles = `
|
|
|
116
122
|
background: var(--boe-token-surface-surface-hover, #f4f4f4);
|
|
117
123
|
}
|
|
118
124
|
|
|
125
|
+
@container boe-search-field (max-width: 22rem) {
|
|
126
|
+
[part="input-shell"] {
|
|
127
|
+
flex-wrap: wrap;
|
|
128
|
+
border-radius: ${boeRadius.control};
|
|
129
|
+
}
|
|
130
|
+
[part="input"] {
|
|
131
|
+
flex-basis: 100%;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
119
135
|
[part="submit"]:focus-visible,
|
|
120
136
|
[part="clear"]:focus-visible {
|
|
121
137
|
outline: none;
|
|
@@ -27,6 +27,8 @@ export declare class ContextMenu extends BaseElement {
|
|
|
27
27
|
get disabled(): boolean;
|
|
28
28
|
set disabled(value: boolean);
|
|
29
29
|
get open(): boolean;
|
|
30
|
+
/** Open from an explicit keyboard/touch trigger without a synthetic event. */
|
|
31
|
+
show(): void;
|
|
30
32
|
disconnectedCallback(): void;
|
|
31
33
|
protected renderTemplate(): void;
|
|
32
34
|
protected setupListeners(): void;
|
|
@@ -139,6 +139,13 @@ export class ContextMenu extends BaseElement {
|
|
|
139
139
|
get open() {
|
|
140
140
|
return this.openValue;
|
|
141
141
|
}
|
|
142
|
+
/** Open from an explicit keyboard/touch trigger without a synthetic event. */
|
|
143
|
+
show() {
|
|
144
|
+
if (!this.isConnected || !this.surfaceEl || this.disabled || !this.items.length || this.open)
|
|
145
|
+
return;
|
|
146
|
+
const rect = this.getBoundingClientRect();
|
|
147
|
+
this.openAt(rect.left, rect.bottom);
|
|
148
|
+
}
|
|
142
149
|
disconnectedCallback() {
|
|
143
150
|
this.unbindDocument();
|
|
144
151
|
}
|
|
@@ -17,11 +17,13 @@ const dialogStyles = `
|
|
|
17
17
|
max-* are reset because this element is the full-viewport scrim rather
|
|
18
18
|
than a centred card. */
|
|
19
19
|
[part="backdrop"] {
|
|
20
|
+
box-sizing: border-box;
|
|
20
21
|
position: fixed;
|
|
21
22
|
inset: 0;
|
|
22
23
|
z-index: 1200;
|
|
23
24
|
background: ${boeOverlay.modalBackdrop};
|
|
24
25
|
display: grid;
|
|
26
|
+
grid-template-columns: minmax(0, 1fr);
|
|
25
27
|
place-items: center;
|
|
26
28
|
padding: ${boeOverlay.modalPadding};
|
|
27
29
|
border: 0;
|
|
@@ -45,13 +47,18 @@ const dialogStyles = `
|
|
|
45
47
|
}
|
|
46
48
|
|
|
47
49
|
[part="dialog"] {
|
|
50
|
+
box-sizing: border-box;
|
|
51
|
+
min-width: 0;
|
|
52
|
+
max-width: 100%;
|
|
53
|
+
max-height: calc(100dvh - ${boeOverlay.modalPadding} - ${boeOverlay.modalPadding});
|
|
48
54
|
width: min(${boeOverlay.modalWidth}, calc(100vw - 3rem));
|
|
49
55
|
background: var(--boe-token-surface-surface, #ffffff);
|
|
50
56
|
border: 0;
|
|
51
57
|
border-radius: ${boeOverlay.modalRadius};
|
|
52
58
|
box-shadow: ${boeOverlay.modalShadow};
|
|
53
59
|
padding: ${boeOverlay.modalPadding};
|
|
54
|
-
display:
|
|
60
|
+
display: flex;
|
|
61
|
+
flex-direction: column;
|
|
55
62
|
gap: ${boeSpace[5]};
|
|
56
63
|
color: var(--boe-token-text-text, #222222);
|
|
57
64
|
}
|
|
@@ -62,7 +69,7 @@ const dialogStyles = `
|
|
|
62
69
|
[part="dialog"][data-size="fullscreen"] {
|
|
63
70
|
width: calc(100vw - 2rem);
|
|
64
71
|
height: calc(100vh - 2rem);
|
|
65
|
-
max-width:
|
|
72
|
+
max-width: 100%;
|
|
66
73
|
border-radius: ${boeRadius.large};
|
|
67
74
|
}
|
|
68
75
|
|
|
@@ -86,6 +93,9 @@ const dialogStyles = `
|
|
|
86
93
|
}
|
|
87
94
|
|
|
88
95
|
[part="body"] {
|
|
96
|
+
min-height: 0;
|
|
97
|
+
overflow: auto;
|
|
98
|
+
overflow-wrap: anywhere;
|
|
89
99
|
color: var(--boe-token-text-text, #222222);
|
|
90
100
|
font-size: 14px;
|
|
91
101
|
line-height: 20px;
|
|
@@ -93,6 +103,8 @@ const dialogStyles = `
|
|
|
93
103
|
|
|
94
104
|
[part="footer"] {
|
|
95
105
|
display: flex;
|
|
106
|
+
flex-shrink: 0;
|
|
107
|
+
flex-wrap: wrap;
|
|
96
108
|
justify-content: end;
|
|
97
109
|
gap: ${boeSpace[2]};
|
|
98
110
|
margin-top: ${boeSpace[2]};
|
|
@@ -246,7 +258,11 @@ export class Dialog extends BaseElement {
|
|
|
246
258
|
});
|
|
247
259
|
this.hostEl.addEventListener("keydown", event => {
|
|
248
260
|
const keyboardEvent = event;
|
|
249
|
-
|
|
261
|
+
if (keyboardEvent.defaultPrevented)
|
|
262
|
+
return;
|
|
263
|
+
// Slotted controls are not DOM descendants of the surface; their event
|
|
264
|
+
// path still crosses it. closest() misses keyboard events from slots.
|
|
265
|
+
const dialog = keyboardEvent.composedPath().find(node => node instanceof HTMLElement && node.getAttribute("part") === "dialog");
|
|
250
266
|
if (!dialog || !this.hostEl.contains(dialog)) {
|
|
251
267
|
return;
|
|
252
268
|
}
|
|
@@ -64,13 +64,18 @@ const popoverStyles = `
|
|
|
64
64
|
unavailable or fails, the surface still renders exactly as before and only
|
|
65
65
|
the top-layer promotion is lost — see foundations/overlay/top-layer.ts. */
|
|
66
66
|
[part="surface"] {
|
|
67
|
+
box-sizing: border-box;
|
|
68
|
+
margin: 0;
|
|
69
|
+
inset: auto;
|
|
67
70
|
display: block;
|
|
68
71
|
position: fixed;
|
|
69
72
|
z-index: 30;
|
|
70
73
|
inset-block-start: 0;
|
|
71
74
|
inset-inline-start: 0;
|
|
72
75
|
width: min(360px, calc(100vw - 5rem));
|
|
73
|
-
min-width: 200px;
|
|
76
|
+
min-width: min(200px, calc(100vw - 1rem));
|
|
77
|
+
max-height: calc(100dvh - 1rem);
|
|
78
|
+
overflow: auto;
|
|
74
79
|
padding: ${boeOverlay.padding};
|
|
75
80
|
border: ${boeOverlay.border};
|
|
76
81
|
border-radius: ${boeOverlay.radius};
|