@bridge-ds/toolbar 0.0.0-stage → 0.2.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.
@@ -0,0 +1,1486 @@
1
+ if (typeof document !== 'undefined' && !document.querySelector('style[data-bridge-toolbar-css="' + "Toolbar" + '"]')) {
2
+ const style = document.createElement('style');
3
+ style.setAttribute('data-bridge-toolbar-css', "Toolbar");
4
+ style.textContent = "/* The Bridge toolbar floats over the admin's content column: bottom right by default, docked left or centre on demand,\n lifted above the page's primary buttons and the outlined surfaces when every dock would cover one. One row, 44px,\n on desktop; compact density so every control is 36px. The border stays neutral. Text: 14 and 12, plus the wordmark.\n Tokens only. */\n._anchor_nhlur_5 {\n position: fixed;\n inset-inline: var(--space-4);\n bottom: var(--space-4);\n z-index: var(--layer-sticky);\n display: flex;\n justify-content: flex-end;\n pointer-events: none;\n}\n\n._anchor_nhlur_5[data-dock=\"start\"] {\n justify-content: flex-start;\n}\n\n._anchor_nhlur_5[data-dock=\"center\"] {\n justify-content: center;\n}\n\n/* Never over the sidebar: the docks live in the content column (the shell's sidebar is size-60 wide above 1024px). */\n@media (min-width: 1025px) {\n ._anchor_nhlur_5 {\n inset-inline-start: calc(var(--size-60) + var(--space-4));\n }\n}\n\n._panel_nhlur_30,\n._pill_nhlur_31 {\n box-sizing: border-box;\n border: var(--border-width-default) solid var(--color-border-default);\n background-color: var(--color-surface-raised);\n box-shadow: var(--elevation-overlay);\n color: var(--color-text-default);\n font-family: var(--typography-ui-family);\n font-size: var(--typography-ui-size);\n line-height: var(--typography-ui-line-height);\n pointer-events: auto;\n}\n\n._panel_nhlur_30[data-dragging],\n._pill_nhlur_31[data-dragging] {\n box-shadow: var(--elevation-modal);\n}\n\n/* Desktop: one row, 44px tall with its border (36px controls); a note, when there is one, takes a second row. */\n._panel_nhlur_30 {\n display: grid;\n grid-template-areas:\n \"grip title actions collapse\"\n \"note note note note\";\n grid-template-rows: calc(var(--size-11) - 2 * var(--border-width-default)) auto;\n grid-template-columns: auto auto minmax(0, 1fr) auto;\n align-items: center;\n column-gap: var(--space-2);\n max-width: min(100%, var(--size-300));\n padding: 0 var(--space-1);\n border-radius: var(--radius-overlay);\n}\n\n._grip_nhlur_63 {\n grid-area: grip;\n cursor: grab;\n touch-action: none;\n}\n\n._panel_nhlur_30[data-dragging] ._grip_nhlur_63 {\n cursor: grabbing;\n}\n\n._title_nhlur_73 {\n display: flex;\n grid-area: title;\n align-items: baseline;\n gap: var(--space-2);\n min-width: 0;\n margin: 0;\n}\n\n._brand_nhlur_82 {\n flex: none;\n font-family: var(--font-family-display);\n font-size: var(--typography-ui-size);\n font-weight: var(--font-weight-semibold);\n}\n\n/* Desktop: the wordmark only; the change's name is in the pill and in the This change tooltip. */\n._panel_nhlur_30 ._name_nhlur_90 {\n display: none;\n}\n\n._name_nhlur_90,\n._pillTitle_nhlur_95 {\n overflow: hidden;\n font-weight: var(--font-weight-medium);\n white-space: nowrap;\n text-overflow: ellipsis;\n}\n\n._actions_nhlur_102 {\n display: flex;\n grid-area: actions;\n align-items: center;\n justify-content: flex-end;\n gap: var(--space-2);\n min-width: 0;\n}\n\n._sides_nhlur_111 {\n flex: none;\n}\n\n._collapse_nhlur_115 {\n grid-area: collapse;\n}\n\n._phoneOnly_nhlur_119 {\n display: none;\n}\n\n._desktopOnly_nhlur_123 {\n display: contents;\n}\n\n._count_nhlur_127 {\n margin-inline-start: var(--space-1-5);\n color: var(--color-text-muted);\n font-variant-numeric: tabular-nums;\n}\n\n/* The colour pair of each changed token: a 24px split swatch, before half and after half. Values in the tooltip. */\n._swatches_nhlur_134 {\n display: inline-flex;\n align-items: center;\n gap: var(--space-1);\n border-radius: var(--radius-inset);\n}\n\n._swatches_nhlur_134:focus-visible {\n outline: var(--focus-ring-width) solid var(--focus-ring-color);\n outline-offset: var(--focus-ring-offset);\n}\n\n._split_nhlur_146 {\n box-sizing: border-box;\n display: inline-flex;\n flex: none;\n width: var(--size-6);\n height: var(--size-6);\n overflow: hidden;\n border: var(--border-width-default) solid var(--color-border-strong);\n border-radius: var(--radius-pill);\n}\n\n._split_nhlur_146 > span {\n flex: 1 1 0;\n}\n\n/* Under 1.1:1 the two halves look alike: say so, so a near-white change reads as intended. */\n._subtle_nhlur_162 {\n color: var(--color-text-muted);\n font-size: var(--typography-caption-size);\n line-height: var(--typography-caption-line-height);\n white-space: nowrap;\n}\n\n._more_nhlur_169 {\n white-space: nowrap;\n color: var(--color-text-muted);\n font-size: var(--typography-caption-size);\n font-variant-numeric: tabular-nums;\n}\n\n._noteRow_nhlur_176 {\n grid-area: note;\n min-width: 0;\n padding-block-end: var(--space-1);\n}\n\n._note_nhlur_176 {\n margin: 0;\n color: var(--color-text-muted);\n font-size: var(--typography-caption-size);\n line-height: var(--typography-caption-line-height);\n}\n\n._noteRow_nhlur_176 ._note_nhlur_176 {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n gap: 0 var(--space-2);\n padding-inline-start: var(--space-2);\n color: var(--color-text-default);\n}\n\n._noteAction_nhlur_198 {\n flex: none;\n}\n\n._sheet_nhlur_202 {\n width: min(90vw, var(--size-80));\n}\n\n._list_nhlur_206 {\n display: grid;\n max-height: var(--size-60);\n margin: 0;\n padding: 0;\n overflow-y: auto;\n list-style: none;\n}\n\n._item_nhlur_215 {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: var(--space-3);\n min-height: var(--control-height-md);\n}\n\n._screenLink_nhlur_223,\n._here_nhlur_224 {\n display: inline-flex;\n align-items: center;\n gap: var(--space-1-5);\n min-height: var(--control-height-md);\n}\n\n._here_nhlur_224 {\n font-weight: var(--font-weight-semibold);\n}\n\n._meta_nhlur_235 {\n flex: none;\n color: var(--color-text-muted);\n font-size: var(--typography-caption-size);\n line-height: var(--typography-caption-line-height);\n}\n\n/* Changed here: one focusable row per surface. Line 1 its role, line 2 its tokens (muted, 12). Nothing truncates. */\n._rows_nhlur_243 {\n display: grid;\n max-height: var(--size-60);\n margin: 0;\n padding: 0;\n overflow-y: auto;\n list-style: none;\n}\n\n._row_nhlur_243 {\n height: auto;\n min-height: var(--control-height-md);\n padding-block: var(--space-1);\n justify-content: flex-start;\n text-align: start;\n}\n\n._rowText_nhlur_260 {\n display: grid;\n min-width: 0;\n font-weight: var(--font-weight-regular);\n white-space: normal;\n}\n\n._rowName_nhlur_267 {\n color: var(--color-text-default);\n font-size: var(--typography-ui-size);\n line-height: var(--typography-ui-line-height);\n}\n\n._rowMeta_nhlur_273 {\n color: var(--color-text-muted);\n font-size: var(--typography-caption-size);\n line-height: var(--typography-caption-line-height);\n}\n\n._tipText_nhlur_279 {\n display: grid;\n}\n\n._tipStep_nhlur_283 {\n opacity: 0.8;\n}\n\n._sheetFoot_nhlur_287 {\n margin-top: var(--space-2);\n}\n\n._sheetSection_nhlur_291 {\n margin-top: var(--space-2);\n padding-top: var(--space-2);\n border-top: var(--border-width-default) solid var(--color-border-default);\n}\n\n._sheetSection_nhlur_291:first-child {\n margin-top: 0;\n padding-top: 0;\n border-top: 0;\n}\n\n._sectionTitle_nhlur_303 {\n margin: 0;\n font-weight: var(--font-weight-semibold);\n}\n\n/* A separate notice, not part of a list. */\n._notice_nhlur_309 {\n display: flex;\n align-items: flex-start;\n gap: var(--space-1-5);\n margin: var(--space-2) 0 0;\n padding-top: var(--space-2);\n border-top: var(--border-width-default) solid var(--color-border-default);\n color: var(--color-text-muted);\n font-size: var(--typography-caption-size);\n line-height: var(--typography-caption-line-height);\n}\n\n/* In the sheet on phones only: the token pairs and the way back. */\n._sheetPhone_nhlur_322 {\n display: none;\n}\n\n._back_nhlur_326 {\n display: inline-flex;\n align-items: center;\n min-height: var(--control-height-md);\n}\n\n._pill_nhlur_31 {\n display: grid;\n grid-template-columns: auto minmax(0, 1fr) auto auto;\n align-items: center;\n gap: var(--space-2);\n max-width: min(100%, var(--size-120));\n padding: var(--space-1) var(--space-1) var(--space-1) var(--space-4);\n border-radius: var(--radius-pill);\n}\n\n/* With a note under it, the pill becomes a small card. */\n._pill_nhlur_31[data-note] {\n border-radius: var(--radius-overlay);\n}\n\n._pill_nhlur_31 ._noteRow_nhlur_176 {\n grid-column: 1 / -1;\n padding-inline-end: var(--space-3);\n}\n\n._pill_nhlur_31 ._noteRow_nhlur_176 ._note_nhlur_176 {\n padding-inline-start: 0;\n}\n\n/* Show what changed: a dashed Deep Harbor frame (Bridge's tool colour, not the product's) on each changed surface,\n with a faint Deep Harbor tint and a role chip. Fixed layer: no pointer events, no focus, no layout. */\n._overlay_nhlur_358 {\n position: fixed;\n inset: 0;\n z-index: var(--layer-sticky);\n pointer-events: none;\n}\n\n._box_nhlur_365 {\n position: absolute;\n box-sizing: border-box;\n outline: var(--border-width-strong) dashed var(--color-action-primary-bg);\n outline-offset: var(--border-width-strong);\n}\n\n/* A page-sized surface: an inset frame, so it reads as a selection, not as the window edge. */\n._box_nhlur_365[data-page] {\n outline-offset: 0;\n border-radius: var(--radius-container);\n}\n\n/* No tint on the page frame: it would tint the very colour being compared. */\n._box_nhlur_365[data-page] ._tint_nhlur_379 {\n display: none;\n}\n\n/* No room above (top of the viewport): under the surface instead. */\n/* No room above (top of the viewport): inside the surface's top-left corner instead. */\n._box_nhlur_365[data-inside] ._chip_nhlur_385 {\n top: 0;\n bottom: auto;\n left: 0;\n}\n\n/* The surface a list row points at: solid frame, stronger tint. */\n._box_nhlur_365[data-emphasis] {\n outline-style: solid;\n}\n\n._box_nhlur_365[data-emphasis] ._tint_nhlur_379 {\n opacity: 0.1;\n}\n\n._box_nhlur_365[data-page] ._chip_nhlur_385 {\n top: var(--space-1);\n bottom: auto;\n left: var(--space-1);\n}\n\n._tint_nhlur_379 {\n position: absolute;\n inset: 0;\n border-radius: inherit;\n background-color: var(--color-action-primary-bg);\n opacity: 0.04;\n}\n\n/* Above the surface's top-left corner, so it never hides the surface's own content. */\n/* Hidden by default: shown on hover, from its list row, and briefly when outlines turn on. */\n._chip_nhlur_385 {\n position: absolute;\n visibility: hidden;\n bottom: calc(100% + var(--border-width-strong) * 2);\n left: calc(var(--border-width-strong) * -2);\n max-width: var(--size-60);\n overflow: hidden;\n padding: 0 var(--space-1-5);\n border-radius: var(--radius-inset);\n background-color: var(--color-action-primary-bg);\n color: var(--color-action-primary-text);\n font-family: var(--typography-ui-family);\n font-size: var(--typography-caption-size);\n font-weight: var(--font-weight-medium);\n line-height: var(--typography-caption-line-height);\n white-space: nowrap;\n text-overflow: ellipsis;\n}\n\n._chip_nhlur_385[data-shown] {\n visibility: visible;\n}\n\n._tip_nhlur_279 {\n display: inline-flex;\n align-items: center;\n gap: var(--space-2);\n}\n\n@media (max-width: 760px) {\n ._anchor_nhlur_5 {\n inset-inline: var(--space-2);\n bottom: var(--space-2);\n }\n\n /* Phone, open: docked to the bottom edge as one 44px row (sides, Changes, outline toggle, collapse). */\n ._anchor_nhlur_5[data-open] {\n inset-inline: 0;\n bottom: 0;\n }\n\n ._panel_nhlur_30 {\n grid-template-areas:\n \"actions toggle collapse\"\n \"note note note\";\n grid-template-rows: calc(var(--size-11) - var(--border-width-default)) auto;\n grid-template-columns: minmax(0, 1fr) auto auto;\n column-gap: var(--space-2);\n width: 100%;\n padding: 0 var(--space-1);\n border-width: var(--border-width-default) 0 0;\n border-radius: 0;\n }\n\n ._title_nhlur_73 {\n display: none;\n }\n\n ._phoneOnly_nhlur_119 {\n display: inline-flex;\n grid-area: toggle;\n }\n\n ._grip_nhlur_63,\n ._brand_nhlur_82,\n ._studio_nhlur_481,\n ._desktopOnly_nhlur_123 {\n display: none;\n }\n\n ._actions_nhlur_102 {\n justify-content: space-between;\n }\n\n ._noteRow_nhlur_176 ._note_nhlur_176 {\n padding-inline-start: 0;\n }\n\n ._pill_nhlur_31 {\n grid-template-columns: minmax(0, 1fr) auto auto;\n }\n\n ._sheetPhone_nhlur_322 {\n display: block;\n }\n}\n\n/* Below 480px the note's action takes its own line. */\n@media (max-width: 480px) {\n ._noteAction_nhlur_198 {\n flex-basis: 100%;\n justify-content: flex-start;\n }\n}\n";
5
+ document.head.appendChild(style);
6
+ }
7
+ import { jsx, jsxs, Fragment } from "react/jsx-runtime";
8
+ import { useState, useEffect, useLayoutEffect, useMemo, useRef, useCallback } from "react";
9
+ import { ThemeProvider, Tooltip, Button, ButtonGroup, IconButton, Popover, PopoverTrigger, PopoverContent, Checkbox, Icon, Link, Badge } from "@bridge-ds/react";
10
+ import { GripVertical, ListChecks, ChevronUp, Check, Info, Minimize2, ScanSearch } from "lucide-react";
11
+ import { g as githubReads, D as DEFAULT_PROXY, l as loadBuild, u as useDocumentScope } from "./index-3f0I3tAL.js";
12
+ import { t as tokenChanges, c as changedScreens, b as changeMode, d as readableTitle, e as toggleIncluded, r as readChanges, w as whereAmI, f as readReturn, g as readUse, h as shouldRender, O as OPEN_KEY, i as readDock, l as liveTokensUrl, n as noVisibleDifference, j as readScroll, S as SCROLL_PARAM, k as sameScreenOn, m as studioChangesUrl, o as studioChangeUrl, T as TOGETHER_EMPTY_LINE, p as contrastRatio, q as TOGETHER_HINT, P as PARAM, u as isSubtle, v as toHex, x as fromText, y as isFlipKey, z as placeToolbar, A as togetherLabel, B as changeSummary, C as dockAt, D as stepDock, E as DOCK_KEY } from "./session-CzlIsFDY.js";
13
+ const VISUAL_BRANCH = "bridge-visual";
14
+ const cacheKey = (n) => `bridge-admin:facts:v3:${n}`;
15
+ function session$1() {
16
+ try {
17
+ return window.sessionStorage;
18
+ } catch {
19
+ return null;
20
+ }
21
+ }
22
+ function readsOf(build) {
23
+ return githubReads({ team: build.ds ?? `${build.owner}/${build.repo}`, proxy: build.proxy === void 0 ? DEFAULT_PROXY : build.proxy });
24
+ }
25
+ async function readManifest(gh, build, R, n, sha) {
26
+ const root = await gh.api(`${R}/git/trees/${VISUAL_BRANCH}`).catch(() => null);
27
+ const pr = root?.tree.find((e) => e.path === `pr-${n}` && e.type === "tree");
28
+ if (!pr) return null;
29
+ const commits = await gh.api(`${R}/git/trees/${pr.sha}`).catch(() => null);
30
+ if (!commits?.tree.some((e) => e.path === sha && e.type === "tree")) return null;
31
+ return gh.file(build.owner, build.repo, VISUAL_BRANCH, `pr-${n}/${sha}/manifest.json`).then((t) => JSON.parse(t)).catch(() => null);
32
+ }
33
+ async function loadChange(build, n, gh = readsOf(build)) {
34
+ const store = session$1();
35
+ try {
36
+ const cached = store?.getItem(cacheKey(n));
37
+ if (cached) return JSON.parse(cached);
38
+ } catch {
39
+ }
40
+ const R = `/repos/${build.owner}/${build.repo}`;
41
+ const [pull, files] = await Promise.all([
42
+ gh.api(`${R}/pulls/${n}`),
43
+ gh.api(`${R}/pulls/${n}/files?per_page=100`)
44
+ ]);
45
+ const tokenFiles = files.filter((f) => f.filename.endsWith(".tokens.json")).slice(0, 8);
46
+ const version = (sha, path) => gh.file(build.owner, build.repo, sha, path).then((t) => JSON.parse(t)).catch(() => null);
47
+ const tokenReads = Promise.all(
48
+ tokenFiles.map(
49
+ async (f) => tokenChanges(
50
+ f.filename,
51
+ f.status === "added" ? null : await version(pull.base.sha, f.previous_filename ?? f.filename),
52
+ f.status === "removed" ? null : await version(pull.head.sha, f.filename)
53
+ )
54
+ )
55
+ );
56
+ const [compare, manifest, tokens] = await Promise.all([
57
+ gh.api(`${R}/compare/${encodeURIComponent(pull.base.ref)}...${pull.head.sha}`).catch(() => null),
58
+ readManifest(gh, build, R, n, pull.head.sha),
59
+ tokenReads
60
+ ]);
61
+ const names = files.map((f) => f.filename);
62
+ const facts = {
63
+ number: pull.number,
64
+ title: readableTitle(pull.title),
65
+ url: pull.html_url,
66
+ sha: pull.head.sha,
67
+ mode: changeMode(names),
68
+ files: names,
69
+ tokens: tokens.flat(),
70
+ behind: typeof compare?.behind_by === "number" ? compare.behind_by : null,
71
+ screens: manifest ? changedScreens(manifest.shots) : null
72
+ };
73
+ try {
74
+ store?.setItem(cacheKey(n), JSON.stringify(facts));
75
+ } catch {
76
+ }
77
+ return facts;
78
+ }
79
+ const togetherKey = (n) => `bridge-admin:together:v1:${n}`;
80
+ async function loadTogetherChange(build, n, gh = readsOf(build)) {
81
+ const store = session$1();
82
+ try {
83
+ const cached = store?.getItem(togetherKey(n));
84
+ if (cached) return JSON.parse(cached);
85
+ } catch {
86
+ }
87
+ const R = `/repos/${build.owner}/${build.repo}`;
88
+ const pull = await gh.api(`${R}/pulls/${n}`);
89
+ const compare = await gh.api(
90
+ `${R}/compare/${encodeURIComponent(build.defaultBranch ?? pull.base.ref)}...${pull.head.sha}`
91
+ );
92
+ const files = compare.files ?? [];
93
+ const names = files.flatMap((f) => f.previous_filename ? [f.filename, f.previous_filename] : [f.filename]);
94
+ const root = `${(build.tokensRoot ?? "packages/tokens/src").replace(/^\/+|\/+$/g, "")}/`;
95
+ const tokenOnly = files.length > 0 && files.length < 300 && names.every((f) => f.startsWith(root) && f.endsWith(".tokens.json") || /contrast\.checks\.json$/.test(f) || f.startsWith(".changeset/"));
96
+ const start = compare.merge_base_commit?.sha;
97
+ let source = null;
98
+ if (tokenOnly && start) {
99
+ const text = (sha, path) => gh.file(build.owner, build.repo, sha, path);
100
+ const mine = files.filter((f) => f.filename.startsWith(root) && f.filename.endsWith(".tokens.json"));
101
+ const src = { number: n, base: {}, head: {}, files: mine.map((f) => f.filename.slice(root.length)) };
102
+ await Promise.all(
103
+ mine.map(async (f) => {
104
+ const rel = f.filename.slice(root.length);
105
+ src.base[rel] = f.status === "added" ? void 0 : await text(start, f.previous_filename ?? f.filename);
106
+ src.head[rel] = f.status === "removed" ? void 0 : await text(pull.head.sha, f.filename);
107
+ })
108
+ );
109
+ source = src;
110
+ }
111
+ const facts = { number: pull.number, title: readableTitle(pull.title), url: pull.html_url, sha: pull.head.sha, mode: tokenOnly ? "tokens" : "code", source };
112
+ try {
113
+ store?.setItem(togetherKey(n), JSON.stringify(facts));
114
+ } catch {
115
+ }
116
+ return facts;
117
+ }
118
+ const VAR = /var\(\s*(--[\w-]+)/g;
119
+ function varsIn(value) {
120
+ return [...value.matchAll(VAR)].map((m) => m[1]);
121
+ }
122
+ function dependents(rules, changed) {
123
+ const deps = /* @__PURE__ */ new Map();
124
+ for (const r of rules) {
125
+ for (const d of r.decls) {
126
+ if (!d.prop.startsWith("--")) continue;
127
+ for (const v of varsIn(d.value)) {
128
+ if (!deps.has(d.prop)) deps.set(d.prop, /* @__PURE__ */ new Set());
129
+ deps.get(d.prop).add(v);
130
+ }
131
+ }
132
+ }
133
+ const roots = new Map(changed.map((c) => [c, /* @__PURE__ */ new Set([c])]));
134
+ let grew = true;
135
+ while (grew) {
136
+ grew = false;
137
+ for (const [name2, uses] of deps) {
138
+ for (const u of uses) {
139
+ const from = roots.get(u);
140
+ if (!from) continue;
141
+ if (!roots.has(name2)) roots.set(name2, /* @__PURE__ */ new Set());
142
+ const mine = roots.get(name2);
143
+ for (const f of from) {
144
+ if (!mine.has(f)) {
145
+ mine.add(f);
146
+ grew = true;
147
+ }
148
+ }
149
+ }
150
+ }
151
+ }
152
+ return roots;
153
+ }
154
+ const SIDES = ["top", "right", "bottom", "left"];
155
+ const SHORTHANDS = {
156
+ background: ["background-color", "background-image"],
157
+ border: [...SIDES.map((s) => `border-${s}-color`), ...SIDES.map((s) => `border-${s}-width`)],
158
+ "border-color": SIDES.map((s) => `border-${s}-color`),
159
+ "border-width": SIDES.map((s) => `border-${s}-width`),
160
+ "border-block": ["border-top-color", "border-bottom-color", "border-top-width", "border-bottom-width"],
161
+ "border-inline": ["border-left-color", "border-right-color", "border-left-width", "border-right-width"],
162
+ "border-radius": ["border-top-left-radius", "border-top-right-radius", "border-bottom-right-radius", "border-bottom-left-radius"],
163
+ outline: ["outline-color", "outline-width"],
164
+ padding: SIDES.map((s) => `padding-${s}`),
165
+ margin: SIDES.map((s) => `margin-${s}`),
166
+ inset: SIDES,
167
+ gap: ["row-gap", "column-gap"],
168
+ font: ["font-family", "font-size", "font-weight", "line-height"],
169
+ "text-decoration": ["text-decoration-color", "text-decoration-line"],
170
+ transition: ["transition-duration", "transition-timing-function"]
171
+ };
172
+ for (const s of SIDES) SHORTHANDS[`border-${s}`] = [`border-${s}-color`, `border-${s}-width`];
173
+ for (const [logical, physical] of [
174
+ ["border-block-start", "top"],
175
+ ["border-block-end", "bottom"],
176
+ ["border-inline-start", "left"],
177
+ ["border-inline-end", "right"]
178
+ ]) {
179
+ SHORTHANDS[logical] = [`border-${physical}-color`, `border-${physical}-width`];
180
+ SHORTHANDS[`${logical}-color`] = [`border-${physical}-color`];
181
+ SHORTHANDS[`${logical}-width`] = [`border-${physical}-width`];
182
+ }
183
+ SHORTHANDS["padding-inline"] = ["padding-left", "padding-right"];
184
+ SHORTHANDS["padding-block"] = ["padding-top", "padding-bottom"];
185
+ SHORTHANDS["margin-inline"] = ["margin-left", "margin-right"];
186
+ SHORTHANDS["margin-block"] = ["margin-top", "margin-bottom"];
187
+ function longhands(prop) {
188
+ return SHORTHANDS[prop] ?? [prop];
189
+ }
190
+ function splitSelectors(selector) {
191
+ const out = [];
192
+ let depth = 0;
193
+ let cur = "";
194
+ for (const ch of selector) {
195
+ if (ch === "(") depth++;
196
+ if (ch === ")") depth--;
197
+ if (ch === "," && depth === 0) {
198
+ out.push(cur.trim());
199
+ cur = "";
200
+ } else cur += ch;
201
+ }
202
+ if (cur.trim()) out.push(cur.trim());
203
+ return out;
204
+ }
205
+ const NON_PAINT = /* @__PURE__ */ new Set(["accent-color", "caret-color"]);
206
+ function usages(rules, roots) {
207
+ const out = /* @__PURE__ */ new Map();
208
+ for (const r of rules) {
209
+ const hits = r.decls.filter((d) => !d.prop.startsWith("--") && !NON_PAINT.has(d.prop)).map((d) => ({ d, from: varsIn(d.value).filter((v) => roots.has(v)) })).filter((x) => x.from.length);
210
+ if (!hits.length) continue;
211
+ for (const one of splitSelectors(r.selector)) {
212
+ const m = /::?(before|after|placeholder|marker|selection|backdrop)\b/i.exec(one);
213
+ const pseudo = m ? `::${m[1].toLowerCase()}` : null;
214
+ if (pseudo === "::selection") continue;
215
+ const selector = (m ? one.slice(0, m.index) + one.slice(m.index + m[0].length) : one).trim() || "*";
216
+ const key = `${selector}|${pseudo ?? ""}`;
217
+ const u = out.get(key) ?? { selector, pseudo, props: [], roots: [] };
218
+ for (const { d, from } of hits) {
219
+ for (const p of longhands(d.prop)) if (!u.props.includes(p)) u.props.push(p);
220
+ for (const v of from) for (const root of roots.get(v)) if (!u.roots.includes(root)) u.roots.push(root);
221
+ }
222
+ out.set(key, u);
223
+ }
224
+ }
225
+ return [...out.values()];
226
+ }
227
+ function outermost(items, contains) {
228
+ return items.filter((b) => b.floating || !items.some((a) => a !== b && contains(a, b) && b.keys.every((k) => a.keys.includes(k))));
229
+ }
230
+ function surfaceLine(tokenNames, surfaces) {
231
+ const names = [...new Set(tokenNames)];
232
+ const tokens = `${names.length} ${names.length === 1 ? "token" : "tokens"} changed: ${names.slice(0, 3).join(", ")}${names.length > 3 ? ` and ${names.length - 3} more` : ""}`;
233
+ return `${tokens}, on ${surfaces} ${surfaces === 1 ? "surface" : "surfaces"}`;
234
+ }
235
+ function outOfView(n) {
236
+ return n === 1 ? "Nothing changed in view. 1 surface changes further down: scroll to see it." : `Nothing changed in view. ${n} surfaces change further down: scroll to see them.`;
237
+ }
238
+ const EMPTY_LINE = "This change doesn't touch this screen. Open Screens to see where it does.";
239
+ const LANDMARKS = { main: "Content", header: "Header", nav: "Navigation", aside: "Sidebar", footer: "Footer", form: "Form", dialog: "Dialog" };
240
+ const ROLES = { main: "Content", banner: "Header", navigation: "Navigation", complementary: "Sidebar", contentinfo: "Footer", dialog: "Dialog", toolbar: "Toolbar", tablist: "Tabs", table: "Table", grid: "Table", menu: "Menu", listbox: "List" };
241
+ const MODULES = { mark: "Shop logo", shell: "App shell", column: "Content column", content: "Content", sticky: "Save bar", toolbar: "Save bar", header: "Header", sidebar: "Sidebar", card: "Card", panel: "Panel" };
242
+ const MODULE_CLASS = /^_([a-zA-Z][a-zA-Z0-9]*)_[a-z0-9]+_\d+$/;
243
+ const words = (s) => {
244
+ const w = s.replace(/([a-z])([A-Z])/g, "$1 $2").replace(/[-_]+/g, " ").trim().toLowerCase();
245
+ return w.charAt(0).toUpperCase() + w.slice(1);
246
+ };
247
+ function roleName(info) {
248
+ const tag = info.tag.toLowerCase();
249
+ if (tag === "html" || tag === "body") return "Page";
250
+ if (["svg", "path", "line", "rect", "circle", "g", "polyline", "stop"].includes(tag)) return "Chart";
251
+ const locals = info.classes.map((c) => MODULE_CLASS.exec(c)?.[1]).filter((l) => Boolean(l));
252
+ const local = locals.find((l) => MODULES[l]) ?? locals[0];
253
+ const role = info.ariaLabel || info.role && ROLES[info.role] || LANDMARKS[tag] || (local ? MODULES[local] ?? words(local) : null);
254
+ const text = local === "mark" ? null : info.text && info.text.length <= 32 && info.text !== role ? info.text : null;
255
+ if (role) return text ? `${role} (${text})` : role;
256
+ return text ?? tag;
257
+ }
258
+ function isKey(e, key) {
259
+ if (e.altKey || e.ctrlKey || e.metaKey || e.key.toLowerCase() !== key) return false;
260
+ const t = e.target;
261
+ if (!t) return true;
262
+ if (t.isContentEditable) return false;
263
+ const tag = (t.tagName ?? "").toLowerCase();
264
+ if (tag === "input" || tag === "textarea" || tag === "select") return false;
265
+ return !(t.closest && t.closest('[role="combobox"], [role="textbox"], [role="searchbox"]'));
266
+ }
267
+ let ruleCache = null;
268
+ function declsOf(style) {
269
+ return style.cssText.split(";").map((d) => {
270
+ const i = d.indexOf(":");
271
+ return i < 0 ? null : { prop: d.slice(0, i).trim().toLowerCase(), value: d.slice(i + 1).trim() };
272
+ }).filter((d) => Boolean(d && d.prop && d.value));
273
+ }
274
+ function walk(list2, out) {
275
+ for (const rule of Array.from(list2)) {
276
+ if (rule instanceof CSSStyleRule) {
277
+ out.push({ selector: rule.selectorText, decls: declsOf(rule.style) });
278
+ if (rule.cssRules?.length) walk(rule.cssRules, out);
279
+ } else if ("cssRules" in rule && rule.cssRules) {
280
+ walk(rule.cssRules, out);
281
+ }
282
+ }
283
+ }
284
+ function pageRules() {
285
+ const sheets = Array.from(document.styleSheets).filter((s) => !(s.ownerNode instanceof Element && s.ownerNode.hasAttribute("data-bridge-live")));
286
+ const key = sheets.map((s) => s.href ?? `inline:${s.ownerNode?.textContent?.length ?? 0}`).join("|");
287
+ if (ruleCache?.key === key) return ruleCache.rules;
288
+ const rules = [];
289
+ for (const s of sheets) {
290
+ try {
291
+ walk(s.cssRules, rules);
292
+ } catch {
293
+ }
294
+ }
295
+ ruleCache = { key, rules };
296
+ return rules;
297
+ }
298
+ function describe(el) {
299
+ const heading = el.querySelector("h1, h2, h3")?.textContent?.replace(/\s+/g, " ").trim();
300
+ const own = el.textContent?.replace(/\s+/g, " ").trim();
301
+ return roleName({
302
+ tag: el.tagName,
303
+ role: el.getAttribute("role"),
304
+ ariaLabel: el.getAttribute("aria-label"),
305
+ classes: Array.from(el.classList),
306
+ text: heading || (own && own.length <= 32 ? own : null)
307
+ });
308
+ }
309
+ function shownRect(el) {
310
+ const check = el.checkVisibility;
311
+ if (check && !check.call(el, { opacityProperty: true, visibilityProperty: true })) return null;
312
+ const r = el.getBoundingClientRect();
313
+ const w = Math.min(r.right, window.innerWidth) - Math.max(r.left, 0);
314
+ const h = Math.min(r.bottom, window.innerHeight) - Math.max(r.top, 0);
315
+ return w > 0 && h > 0 ? r : null;
316
+ }
317
+ const visible = (el) => el.getClientRects().length > 0;
318
+ function changedElements(changedVars, sheet2, ignore, sheetIs = "before") {
319
+ const live = sheet2;
320
+ const t0 = performance.now();
321
+ const rules = pageRules();
322
+ const roots = dependents(rules, changedVars);
323
+ const uses = usages(rules, roots);
324
+ const candidates = /* @__PURE__ */ new Map();
325
+ const ids = /* @__PURE__ */ new WeakMap();
326
+ let next = 0;
327
+ for (const u of uses) {
328
+ let found;
329
+ try {
330
+ found = document.querySelectorAll(u.selector);
331
+ } catch {
332
+ continue;
333
+ }
334
+ for (const el of Array.from(found)) {
335
+ if (ignore(el) || !visible(el)) continue;
336
+ if (!ids.has(el)) ids.set(el, next++);
337
+ const key = `${ids.get(el)}|${u.pseudo ?? ""}`;
338
+ const c = candidates.get(key) ?? { el, pseudo: u.pseudo, props: /* @__PURE__ */ new Set(), roots: /* @__PURE__ */ new Set() };
339
+ u.props.forEach((p) => c.props.add(p));
340
+ u.roots.forEach((r) => c.roots.add(r));
341
+ candidates.set(key, c);
342
+ }
343
+ }
344
+ for (const el of Array.from(document.querySelectorAll('[style*="var(--"], [stroke*="var(--"], [fill*="var(--"], [stop-color*="var(--"]'))) {
345
+ if (ignore(el) || !visible(el)) continue;
346
+ const props = /* @__PURE__ */ new Set();
347
+ const hit = /* @__PURE__ */ new Set();
348
+ const take = (prop, value) => {
349
+ if (!value) return;
350
+ const from = varsIn(value).filter((v) => roots.has(v));
351
+ if (!from.length) return;
352
+ longhands(prop).forEach((p) => props.add(p));
353
+ from.forEach((v) => roots.get(v).forEach((r) => hit.add(r)));
354
+ };
355
+ for (const d of declsOf(el.style ?? { cssText: "" })) take(d.prop, d.value);
356
+ for (const attr of ["stroke", "fill", "stop-color"]) take(attr, el.getAttribute(attr));
357
+ if (!props.size) continue;
358
+ if (!ids.has(el)) ids.set(el, next++);
359
+ const key = `${ids.get(el)}|`;
360
+ const c = candidates.get(key) ?? { el, pseudo: null, props: /* @__PURE__ */ new Set(), roots: /* @__PURE__ */ new Set() };
361
+ props.forEach((p) => c.props.add(p));
362
+ hit.forEach((r) => c.roots.add(r));
363
+ candidates.set(key, c);
364
+ }
365
+ const list2 = [...candidates.values()].map((c) => ({ ...c, propList: [...c.props] }));
366
+ const read = () => list2.map((c) => {
367
+ const cs = getComputedStyle(c.el, c.pseudo);
368
+ return c.propList.map((p) => cs.getPropertyValue(p));
369
+ });
370
+ const still = document.createElement("style");
371
+ still.setAttribute("data-bridge-still", "");
372
+ still.textContent = "*, *::before, *::after { transition: none !important; }";
373
+ document.head.appendChild(still);
374
+ const media = live.media;
375
+ let withLive = [];
376
+ let withChange = [];
377
+ try {
378
+ getComputedStyle(document.documentElement).color;
379
+ live.media = "all";
380
+ withLive = read();
381
+ live.media = "not all";
382
+ withChange = read();
383
+ } finally {
384
+ live.media = media;
385
+ read();
386
+ still.remove();
387
+ }
388
+ if (sheetIs === "after") [withLive, withChange] = [withChange, withLive];
389
+ const elements = [];
390
+ list2.forEach((c, i) => {
391
+ const props = c.propList.map((prop, j) => ({ prop, before: withLive[i][j], after: withChange[i][j] })).filter((p) => p.before !== p.after);
392
+ if (!props.length) return;
393
+ const roots2 = [...c.roots];
394
+ const position = getComputedStyle(c.el).position;
395
+ elements.push({
396
+ el: c.el,
397
+ pseudo: c.pseudo,
398
+ roots: roots2,
399
+ props,
400
+ label: describe(c.el),
401
+ keys: roots2.flatMap((r) => props.map((p) => `${r}|${p.prop}`)),
402
+ floating: position === "sticky" || position === "fixed"
403
+ });
404
+ });
405
+ const charts = /* @__PURE__ */ new Map();
406
+ const plain = [];
407
+ for (const e of elements) {
408
+ let svg = e.el.closest("svg");
409
+ while (svg?.parentElement?.closest("svg")) svg = svg.parentElement.closest("svg");
410
+ if (!svg) {
411
+ plain.push(e);
412
+ continue;
413
+ }
414
+ const c = charts.get(svg) ?? { el: svg, pseudo: null, roots: [], props: [], label: "Chart", keys: [], floating: false };
415
+ for (const r of e.roots) if (!c.roots.includes(r)) c.roots.push(r);
416
+ for (const p of e.props) if (!c.props.some((x) => x.prop === p.prop)) c.props.push(p);
417
+ for (const k of e.keys) if (!c.keys.includes(k)) c.keys.push(k);
418
+ charts.set(svg, c);
419
+ }
420
+ const surfaces = outermost([...plain, ...charts.values()], (a, b) => a.el !== b.el && a.el.contains(b.el));
421
+ return { elements, surfaces, ms: Math.round((performance.now() - t0) * 10) / 10 };
422
+ }
423
+ const IGNORE = "[data-bridge-toolbar], [data-bridge-overlay]";
424
+ function useChangedElements(on, live, vars, screen, sheetIs = "before") {
425
+ const [result, setResult] = useState(null);
426
+ const key = vars.join(",");
427
+ useEffect(() => {
428
+ if (!on || !live || !key) {
429
+ setResult(null);
430
+ return;
431
+ }
432
+ const run = () => setResult(changedElements(key.split(","), live, (el) => Boolean(el.closest(IGNORE)), sheetIs));
433
+ const ids = [window.setTimeout(run, 0), window.setTimeout(run, 400), window.setTimeout(run, 1200)];
434
+ return () => ids.forEach((id) => window.clearTimeout(id));
435
+ }, [on, live, key, screen, sheetIs]);
436
+ return result;
437
+ }
438
+ const PIN_ATTR = "data-bridge-pin";
439
+ const PIN_CLASS = "bridge-pin";
440
+ const STYLE_ATTR = "data-bridge-pinned";
441
+ const cssCache = /* @__PURE__ */ new Map();
442
+ const valueCache = /* @__PURE__ */ new Map();
443
+ function liveCss(url) {
444
+ if (!cssCache.has(url)) cssCache.set(url, fetch(url).then((r) => r.ok ? r.text() : Promise.reject(new Error(String(r.status)))));
445
+ return cssCache.get(url);
446
+ }
447
+ function resolve(css, theme) {
448
+ const key = `${theme}|${css.length}`;
449
+ const hit = valueCache.get(key);
450
+ if (hit) return hit;
451
+ const names = [...new Set([...css.matchAll(/(--[\w-]+)\s*:/g)].map((m) => m[1]))];
452
+ const sheet2 = new CSSStyleSheet();
453
+ sheet2.replaceSync(css.replace(/:root/g, ":host"));
454
+ const host = document.createElement("div");
455
+ host.style.display = "none";
456
+ document.body.appendChild(host);
457
+ const shadow = host.attachShadow({ mode: "open" });
458
+ shadow.adoptedStyleSheets = [sheet2];
459
+ const scope = document.createElement("div");
460
+ scope.setAttribute("data-theme", theme);
461
+ scope.setAttribute("data-brand", "bridge");
462
+ scope.setAttribute("data-density", "compact");
463
+ shadow.appendChild(scope);
464
+ const cs = getComputedStyle(scope);
465
+ const decls = names.map((n) => [n, cs.getPropertyValue(n).trim()]).filter(([, v]) => v !== "").map(([n, v]) => `${n}: ${v};`).join("\n ");
466
+ host.remove();
467
+ valueCache.set(key, decls);
468
+ return decls;
469
+ }
470
+ function usePinnedBridgeTokens(url, theme) {
471
+ useEffect(() => {
472
+ if (!url) return;
473
+ let live = true;
474
+ liveCss(url).then((css) => {
475
+ if (!live) return;
476
+ const decls = resolve(css, theme);
477
+ let el = document.head.querySelector(`style[${STYLE_ATTR}]`);
478
+ if (!el) {
479
+ el = document.createElement("style");
480
+ el.setAttribute(STYLE_ATTR, "");
481
+ }
482
+ el.textContent = `[${PIN_ATTR}][${PIN_ATTR}][${PIN_ATTR}], .${PIN_CLASS}.${PIN_CLASS}.${PIN_CLASS} {
483
+ ${decls}
484
+ }`;
485
+ document.head.appendChild(el);
486
+ }).catch(() => {
487
+ });
488
+ return () => {
489
+ live = false;
490
+ };
491
+ }, [url, theme]);
492
+ }
493
+ const anchor = "_anchor_nhlur_5";
494
+ const panel = "_panel_nhlur_30";
495
+ const pill = "_pill_nhlur_31";
496
+ const grip = "_grip_nhlur_63";
497
+ const title = "_title_nhlur_73";
498
+ const brand = "_brand_nhlur_82";
499
+ const name = "_name_nhlur_90";
500
+ const pillTitle = "_pillTitle_nhlur_95";
501
+ const actions = "_actions_nhlur_102";
502
+ const sides = "_sides_nhlur_111";
503
+ const collapse = "_collapse_nhlur_115";
504
+ const phoneOnly = "_phoneOnly_nhlur_119";
505
+ const desktopOnly = "_desktopOnly_nhlur_123";
506
+ const count = "_count_nhlur_127";
507
+ const swatches = "_swatches_nhlur_134";
508
+ const split = "_split_nhlur_146";
509
+ const subtle = "_subtle_nhlur_162";
510
+ const more = "_more_nhlur_169";
511
+ const noteRow = "_noteRow_nhlur_176";
512
+ const note = "_note_nhlur_176";
513
+ const noteAction = "_noteAction_nhlur_198";
514
+ const sheet = "_sheet_nhlur_202";
515
+ const list = "_list_nhlur_206";
516
+ const item = "_item_nhlur_215";
517
+ const screenLink = "_screenLink_nhlur_223";
518
+ const here = "_here_nhlur_224";
519
+ const meta = "_meta_nhlur_235";
520
+ const rows = "_rows_nhlur_243";
521
+ const row = "_row_nhlur_243";
522
+ const rowText = "_rowText_nhlur_260";
523
+ const rowName = "_rowName_nhlur_267";
524
+ const rowMeta = "_rowMeta_nhlur_273";
525
+ const tipText = "_tipText_nhlur_279";
526
+ const tipStep = "_tipStep_nhlur_283";
527
+ const sheetFoot = "_sheetFoot_nhlur_287";
528
+ const sheetSection = "_sheetSection_nhlur_291";
529
+ const sectionTitle = "_sectionTitle_nhlur_303";
530
+ const notice = "_notice_nhlur_309";
531
+ const sheetPhone = "_sheetPhone_nhlur_322";
532
+ const back = "_back_nhlur_326";
533
+ const overlay = "_overlay_nhlur_358";
534
+ const box = "_box_nhlur_365";
535
+ const tint = "_tint_nhlur_379";
536
+ const chip = "_chip_nhlur_385";
537
+ const tip = "_tip_nhlur_279";
538
+ const studio = "_studio_nhlur_481";
539
+ const styles = {
540
+ anchor,
541
+ panel,
542
+ pill,
543
+ grip,
544
+ title,
545
+ brand,
546
+ name,
547
+ pillTitle,
548
+ actions,
549
+ sides,
550
+ collapse,
551
+ phoneOnly,
552
+ desktopOnly,
553
+ count,
554
+ swatches,
555
+ split,
556
+ subtle,
557
+ more,
558
+ noteRow,
559
+ note,
560
+ noteAction,
561
+ sheet,
562
+ list,
563
+ item,
564
+ screenLink,
565
+ here,
566
+ meta,
567
+ rows,
568
+ row,
569
+ rowText,
570
+ rowName,
571
+ rowMeta,
572
+ tipText,
573
+ tipStep,
574
+ sheetFoot,
575
+ sheetSection,
576
+ sectionTitle,
577
+ notice,
578
+ sheetPhone,
579
+ back,
580
+ overlay,
581
+ box,
582
+ tint,
583
+ chip,
584
+ tip,
585
+ studio
586
+ };
587
+ const INSET = 8;
588
+ function boxOf(el) {
589
+ const r = shownRect(el);
590
+ if (!r) return null;
591
+ const vw = window.innerWidth;
592
+ const vh = window.innerHeight;
593
+ const left = Math.max(r.left, 0);
594
+ const top = Math.max(r.top, 0);
595
+ const right = Math.min(r.right, vw);
596
+ const bottom = Math.min(r.bottom, vh);
597
+ const page = (right - left) * (bottom - top) > 0.85 * vw * vh;
598
+ if (page) return { left: INSET, top: INSET, width: vw - 2 * INSET, height: vh - 2 * INSET, radius: "", page };
599
+ return { left, top, width: right - left, height: bottom - top, radius: getComputedStyle(el).borderRadius, page };
600
+ }
601
+ function ChangeOutlines({
602
+ surfaces,
603
+ theme,
604
+ tip: tip2,
605
+ onShown,
606
+ emphasis,
607
+ flash
608
+ }) {
609
+ const [tick, setTick] = useState(0);
610
+ const [active, setActive] = useState(null);
611
+ useEffect(() => {
612
+ let frame = 0;
613
+ const on = () => {
614
+ cancelAnimationFrame(frame);
615
+ frame = requestAnimationFrame(() => setTick((t) => t + 1));
616
+ };
617
+ window.addEventListener("scroll", on, { passive: true, capture: true });
618
+ window.addEventListener("resize", on);
619
+ return () => {
620
+ cancelAnimationFrame(frame);
621
+ window.removeEventListener("scroll", on, { capture: true });
622
+ window.removeEventListener("resize", on);
623
+ };
624
+ }, []);
625
+ const boxes = surfaces.map((s) => boxOf(s.el));
626
+ const shownKey = boxes.map((b) => b ? `${Math.round(b.left)},${Math.round(b.top)},${Math.round(b.width)},${Math.round(b.height)}` : "-").join("|");
627
+ useLayoutEffect(() => {
628
+ onShown({
629
+ count: boxes.filter(Boolean).length,
630
+ obstacles: boxes.filter((b) => Boolean(b && !b.page)).map((b) => ({ left: b.left, top: b.top, right: b.left + b.width, bottom: b.top + b.height }))
631
+ });
632
+ }, [shownKey, tick]);
633
+ useEffect(() => {
634
+ const pick = (match) => {
635
+ let best = null;
636
+ let area = Infinity;
637
+ surfaces.forEach((s, i) => {
638
+ const b = boxOf(s.el);
639
+ if (!b || b.page || !match(s.el, i)) return;
640
+ if (b.width * b.height < area) {
641
+ area = b.width * b.height;
642
+ best = i;
643
+ }
644
+ });
645
+ return best;
646
+ };
647
+ const move = (e) => setActive(
648
+ pick((el) => {
649
+ const r = el.getBoundingClientRect();
650
+ return e.clientX >= r.left && e.clientX <= r.right && e.clientY >= r.top && e.clientY <= r.bottom;
651
+ })
652
+ );
653
+ const focus = (e) => setActive(e.target instanceof Node ? pick((el) => el.contains(e.target)) : null);
654
+ document.addEventListener("pointermove", move, { passive: true });
655
+ document.addEventListener("focusin", focus);
656
+ return () => {
657
+ document.removeEventListener("pointermove", move);
658
+ document.removeEventListener("focusin", focus);
659
+ };
660
+ }, [surfaces]);
661
+ return /* @__PURE__ */ jsx(ThemeProvider, { theme, brand: "bridge", density: "compact", className: styles.overlay, "data-bridge-overlay": "", ...{ [PIN_ATTR]: "" }, "aria-hidden": "true", children: boxes.map((b, i) => {
662
+ if (!b) return null;
663
+ const s = surfaces[i];
664
+ const showChip = flash || active === i || emphasis === i;
665
+ const chip2 = /* @__PURE__ */ jsx("span", { className: styles.chip, "data-page": b.page || void 0, "data-shown": showChip || void 0, children: s.label });
666
+ return /* @__PURE__ */ jsxs(
667
+ "span",
668
+ {
669
+ className: styles.box,
670
+ "data-page": b.page || void 0,
671
+ "data-inside": !b.page && b.top < 24 || void 0,
672
+ "data-emphasis": emphasis === i || void 0,
673
+ style: { left: b.left, top: b.top, width: b.width, height: b.height, borderRadius: b.radius },
674
+ children: [
675
+ /* @__PURE__ */ jsx("span", { className: styles.tint }),
676
+ active === i || emphasis === i ? (
677
+ // Above the chip when there is room, else below it: never over the toolbar, which keeps clear of boxes.
678
+ /* @__PURE__ */ jsx(Tooltip, { open: true, content: tip2(s), side: b.top < 64 ? "bottom" : "top", align: "start", className: PIN_CLASS, children: chip2 })
679
+ ) : chip2
680
+ ]
681
+ },
682
+ i
683
+ );
684
+ }) });
685
+ }
686
+ function useSheet(css, applied) {
687
+ const [el, setEl] = useState(null);
688
+ const ref = useRef(null);
689
+ useEffect(() => {
690
+ if (css === null) return;
691
+ if (!ref.current) {
692
+ const s = document.createElement("style");
693
+ s.setAttribute("data-bridge-live", "");
694
+ s.setAttribute("data-bridge-together", "");
695
+ const pinned = document.head.querySelector("style[data-bridge-pinned]");
696
+ document.head.insertBefore(s, pinned);
697
+ ref.current = s;
698
+ setEl(s);
699
+ }
700
+ ref.current.textContent = css;
701
+ ref.current.media = applied ? "all" : "not all";
702
+ }, [css, applied]);
703
+ useEffect(() => () => ref.current?.remove(), []);
704
+ return el;
705
+ }
706
+ function useTogether(build, latest, changes, use, here2, applied) {
707
+ const [mod, setMod] = useState(null);
708
+ const [list2, setList] = useState(null);
709
+ const [error, setError] = useState(false);
710
+ const [included, setIncluded] = useState([]);
711
+ const key = changes?.join(",") ?? "";
712
+ useEffect(() => {
713
+ if (!key) return;
714
+ let live = true;
715
+ const numbers = key.split(",").map(Number);
716
+ const base = latest ? latest() : Promise.reject(new Error("This product does not try changes together."));
717
+ Promise.all([import("./together.js"), base, Promise.all(numbers.map((n) => loadTogetherChange(build, n)))]).then(
718
+ ([m, b, facts]) => {
719
+ if (!live) return;
720
+ setMod({ m, base: b });
721
+ setList(facts);
722
+ setIncluded(facts.filter((f) => f.mode === "tokens" && f.source).map((f) => f.number));
723
+ },
724
+ () => live && setError(true)
725
+ );
726
+ return () => {
727
+ live = false;
728
+ };
729
+ }, [build, key, latest]);
730
+ const edits = useMemo(() => mod && list2 ? list2.filter((f) => f.source).map((f) => mod.m.changeEdits(f.source)) : [], [mod, list2]);
731
+ const result = useMemo(() => mod && list2 ? mod.m.together(mod.base, edits, mod.m.decodePicks(use), included) : null, [mod, list2, edits, use, included]);
732
+ const { theme, brand: brand2, density } = here2;
733
+ const pairs = useMemo(() => mod && result ? mod.m.togetherPairs(mod.base, result, { theme, brand: brand2, density }) : [], [mod, result, theme, brand2, density]);
734
+ const sheet2 = useSheet(key ? result?.css ?? null : null, applied);
735
+ const noDiff = useMemo(() => {
736
+ if (!mod || !result || !result.tokens.length || pairs.length) return null;
737
+ const other = mod.base.modes.themes.find((t) => t !== theme && mod.m.togetherPairs(mod.base, result, { theme: t, brand: brand2, density }).length);
738
+ if (other) return { kind: "theme", here: theme, touched: other };
739
+ const brandElse = mod.base.modes.brands.find((b) => b !== brand2 && mod.m.togetherPairs(mod.base, result, { theme, brand: b, density }).length);
740
+ return brandElse ? { kind: "brand", here: brand2, touched: brandElse } : null;
741
+ }, [mod, result, pairs.length, theme, brand2, density]);
742
+ if (!key) return null;
743
+ return {
744
+ list: list2,
745
+ error,
746
+ included,
747
+ toggle: (n, on) => setIncluded((cur) => toggleIncluded(list2?.map((f) => f.number) ?? [], cur, n, on)),
748
+ result,
749
+ editedBy: new Map(edits.map((e) => [e.number, [...new Set(e.edits.map((x) => x.path))]])),
750
+ pairs,
751
+ noDiff,
752
+ sheet: sheet2
753
+ };
754
+ }
755
+ const SIDE_LABEL = { before: "Live product", after: "This change" };
756
+ const MAX_PAIRS = 3;
757
+ function session() {
758
+ try {
759
+ return window.sessionStorage;
760
+ } catch {
761
+ return null;
762
+ }
763
+ }
764
+ function remember(key, value) {
765
+ try {
766
+ window.sessionStorage.setItem(key, value);
767
+ } catch {
768
+ }
769
+ }
770
+ function ToolbarRoot(props) {
771
+ const { ds, product, studio: studio2, production, preview, deployEnv, proxy } = props;
772
+ const given = useMemo(() => props.build && proxy !== void 0 ? { ...props.build, proxy } : props.build, [props.build, proxy]);
773
+ const [build, setBuild] = useState(given ?? null);
774
+ useEffect(() => {
775
+ if (given || !ds || !product) return;
776
+ let live = true;
777
+ loadBuild(ds, product, { studio: studio2, production, preview, deployEnv, ...proxy !== void 0 ? { proxy } : {} }).then(
778
+ (b) => live && setBuild(b),
779
+ (err) => console.warn(`Bridge toolbar: ${err.message}`)
780
+ );
781
+ return () => {
782
+ live = false;
783
+ };
784
+ }, [given, ds, product, studio2, production, preview, deployEnv, proxy]);
785
+ if (!build) return null;
786
+ return /* @__PURE__ */ jsx(Decide, { build, useScope: props.scope ?? useDocumentScope, shops: props.shops ?? [], together: props.together ?? null });
787
+ }
788
+ function Decide({ build: given, useScope, shops, together: latest }) {
789
+ const [setup] = useState(() => {
790
+ const build = given;
791
+ const store = session();
792
+ const changes = readChanges(window.location.search, store);
793
+ const where = whereAmI(window.location.host, build);
794
+ const returnTo = where === "production" ? readReturn(window.location.search, store, build.preview) : null;
795
+ const together = changes.length > 1 ? { changes, use: readUse(window.location.search, store) } : null;
796
+ return { build, change: changes[0] ?? null, where, returnTo, together };
797
+ });
798
+ if (!shouldRender({ ...setup, together: Boolean(setup.together) })) return null;
799
+ return /* @__PURE__ */ jsx(Toolbar, { build: setup.build, change: setup.change, onProduction: setup.where === "production", returnTo: setup.returnTo, together: setup.together, useScope, shops, latest });
800
+ }
801
+ function useLiveSheet(url, need, applied) {
802
+ const [state, setState] = useState("idle");
803
+ const link = useRef(null);
804
+ useEffect(() => {
805
+ if (!url) return;
806
+ if (link.current) {
807
+ link.current.media = applied ? "all" : "not all";
808
+ return;
809
+ }
810
+ if (!need && !applied) return;
811
+ const el = document.createElement("link");
812
+ el.rel = "stylesheet";
813
+ el.href = url;
814
+ el.media = applied ? "all" : "not all";
815
+ el.referrerPolicy = "no-referrer";
816
+ el.setAttribute("data-bridge-live", "");
817
+ el.onload = () => setState("ready");
818
+ el.onerror = () => {
819
+ el.remove();
820
+ link.current = null;
821
+ setState("error");
822
+ };
823
+ link.current = el;
824
+ setState("loading");
825
+ document.head.appendChild(el);
826
+ }, [url, need, applied]);
827
+ useEffect(() => () => link.current?.remove(), []);
828
+ return { state, link: state === "ready" ? link.current : null };
829
+ }
830
+ function probe(root, tokens, scope) {
831
+ const host = document.createElement("div");
832
+ host.setAttribute("data-theme", scope.theme);
833
+ host.setAttribute("data-brand", scope.brand);
834
+ host.setAttribute("data-density", "comfortable");
835
+ host.style.display = "none";
836
+ const spans = tokens.map((t) => {
837
+ const s = document.createElement("span");
838
+ s.style.backgroundColor = `var(${t.cssVar})`;
839
+ host.appendChild(s);
840
+ return s;
841
+ });
842
+ root.appendChild(host);
843
+ const out = tokens.map((t, i) => t.type === "color" ? getComputedStyle(spans[i]).backgroundColor : getComputedStyle(host).getPropertyValue(t.cssVar).trim());
844
+ host.remove();
845
+ return out;
846
+ }
847
+ function usePairs(facts, liveUrl, brand2, liveOn) {
848
+ const [pairs, setPairs] = useState([]);
849
+ const afterCache = useRef(/* @__PURE__ */ new Map());
850
+ useEffect(() => {
851
+ const tokens = facts?.tokens ?? [];
852
+ if (!tokens.length || !liveUrl) return;
853
+ let live = true;
854
+ const scopeOf = (t) => ({ theme: t.theme ?? "light", brand: t.brand ?? brand2 });
855
+ const key = `${brand2}`;
856
+ if (!afterCache.current.has(key) && !liveOn) afterCache.current.set(key, tokens.map((t) => probe(document.body, [t], scopeOf(t))[0]));
857
+ const after = afterCache.current.get(key);
858
+ if (!after) return;
859
+ fetch(liveUrl).then((r) => r.ok ? r.text() : Promise.reject(new Error(String(r.status)))).then((css) => {
860
+ if (!live) return;
861
+ const sheet2 = new CSSStyleSheet();
862
+ sheet2.replaceSync(css.replace(/:root/g, ":host"));
863
+ const host = document.createElement("div");
864
+ host.style.display = "none";
865
+ document.body.appendChild(host);
866
+ const shadow = host.attachShadow({ mode: "open" });
867
+ shadow.adoptedStyleSheets = [sheet2];
868
+ const before = tokens.map((t) => probe(shadow, [t], scopeOf(t))[0]);
869
+ host.remove();
870
+ setPairs(tokens.map((t, i) => ({ token: t, before: before[i], after: after[i], color: t.type === "color" })).filter((p) => p.before !== p.after));
871
+ }).catch(() => live && setPairs([]));
872
+ return () => {
873
+ live = false;
874
+ };
875
+ }, [facts, liveUrl, brand2, liveOn]);
876
+ return pairs;
877
+ }
878
+ function modeDetail(facts, side, tokens) {
879
+ if (!facts) return "Reading the change.";
880
+ return changeSummary(facts.title);
881
+ }
882
+ const brandName = (shops, brand2) => shops.find((t) => t.brand === brand2)?.name ?? brand2;
883
+ function noDifferenceText(d, shops) {
884
+ const cap = (t) => t.charAt(0).toUpperCase() + t.slice(1);
885
+ return d.kind === "theme" ? `${cap(d.touched)} mode only. Nothing changes in ${d.here}.` : `${brandName(shops, d.touched)} only. Nothing changes in ${brandName(shops, d.here)}.`;
886
+ }
887
+ function pageObstacles(own) {
888
+ const probeEl = document.createElement("span");
889
+ probeEl.style.backgroundColor = "var(--color-action-primary-bg)";
890
+ probeEl.hidden = true;
891
+ document.body.appendChild(probeEl);
892
+ const primary = getComputedStyle(probeEl).backgroundColor;
893
+ probeEl.remove();
894
+ const out = [];
895
+ for (const el of document.querySelectorAll('button, a[href], [role="button"]')) {
896
+ if (own?.contains(el) || el.closest('[data-bridge-toolbar], [role="dialog"], [role="menu"], [role="listbox"]')) continue;
897
+ const isSubmit = el instanceof HTMLButtonElement && el.type === "submit";
898
+ if (!isSubmit && getComputedStyle(el).backgroundColor !== primary) continue;
899
+ const r = el.getBoundingClientRect();
900
+ if (!r.width || !r.height || r.bottom <= 0 || r.top >= window.innerHeight) continue;
901
+ out.push({ left: r.left, top: r.top, right: r.right, bottom: r.bottom });
902
+ }
903
+ return out;
904
+ }
905
+ function Toolbar({
906
+ build,
907
+ change,
908
+ onProduction,
909
+ returnTo,
910
+ together: tg,
911
+ useScope,
912
+ shops,
913
+ latest
914
+ }) {
915
+ const scope = useScope();
916
+ const { theme, density, brand: brand2 } = scope;
917
+ const tenantId = scope.shop ?? "";
918
+ const setPrefs = scope.set;
919
+ const isTenantId = (id) => shops.some((t) => t.id === id);
920
+ const [oneFacts, setFacts] = useState(null);
921
+ const [oneError, setError] = useState(false);
922
+ const [side, setSide] = useState(onProduction && !tg ? "before" : "after");
923
+ const T = useTogether(build, latest, tg?.changes ?? null, tg?.use ?? null, { theme, brand: brand2, density }, side === "after");
924
+ const titleOf = (n) => T?.list?.find((f) => f.number === n)?.title ?? null;
925
+ const togetherFacts = useMemo(() => {
926
+ if (!T?.list || !T.result) return null;
927
+ const on = T.list.filter((f) => T.included.includes(f.number));
928
+ const names = on.map((f) => f.title);
929
+ return {
930
+ number: change,
931
+ title: names.length ? names.length <= 2 ? names.join(" and ") : `${names.slice(0, 2).join(", ")} and ${names.length - 2} more` : "No change switched on",
932
+ url: "",
933
+ sha: "",
934
+ mode: "tokens",
935
+ files: [...new Set(on.flatMap((f) => f.source?.files ?? []))],
936
+ behind: null,
937
+ tokens: T.result.tokens,
938
+ screens: null
939
+ };
940
+ }, [T?.list, T?.result, T?.included, change]);
941
+ const facts = tg ? togetherFacts : oneFacts;
942
+ const error = tg ? Boolean(T?.error) : oneError;
943
+ const [open, setOpen] = useState(() => {
944
+ try {
945
+ return window.sessionStorage.getItem(OPEN_KEY) === "1";
946
+ } catch {
947
+ return false;
948
+ }
949
+ });
950
+ const [dock, setDock] = useState(() => readDock(session()));
951
+ const [place, setPlace] = useState({ dock, lift: 0 });
952
+ const [drag, setDrag] = useState(null);
953
+ const [shownIn, setShownIn] = useState(null);
954
+ const dragStart = useRef(0);
955
+ const root = useRef(null);
956
+ const hash = useHash();
957
+ useEffect(() => {
958
+ if (tg) return;
959
+ let live2 = true;
960
+ loadChange(build, change).then(
961
+ (f) => live2 && setFacts(f),
962
+ () => live2 && setError(true)
963
+ );
964
+ return () => {
965
+ live2 = false;
966
+ };
967
+ }, [build, change, tg]);
968
+ const liveUrl = !tg && facts?.mode === "tokens" ? liveTokensUrl(build) : null;
969
+ const [outlines, setOutlines] = useState(false);
970
+ const canOutline = facts?.mode === "tokens" && (facts.tokens.length ?? 0) > 0;
971
+ const live = useLiveSheet(liveUrl, outlines && canOutline, side === "before" && facts?.mode === "tokens");
972
+ const onePairs = usePairs(tg ? null : facts, liveUrl, brand2, side === "before");
973
+ const pairs = tg ? T?.pairs ?? [] : onePairs;
974
+ const noDiff = tg ? T?.noDiff ?? null : facts?.mode === "tokens" ? noVisibleDifference(facts.files, { theme, brand: brand2 }) : null;
975
+ const outlinesOn = outlines && canOutline && !noDiff;
976
+ const diff = useChangedElements(
977
+ outlinesOn,
978
+ tg ? T?.sheet ?? null : live.link,
979
+ (facts?.tokens ?? []).map((t) => t.cssVar),
980
+ // Switching a change on or off repaints the combination: the outlines are measured again.
981
+ `${hash}|${theme}|${tenantId}|${tg ? T?.included.join(",") ?? "" : ""}`,
982
+ tg ? "after" : "before"
983
+ );
984
+ const nameOf = (v) => facts?.tokens.find((t) => t.cssVar === v)?.name ?? v;
985
+ const [shown, setShown] = useState({ count: 0, obstacles: [] });
986
+ const setShownStable = useCallback((next) => setShown((p) => JSON.stringify(p) === JSON.stringify(next) ? p : next), []);
987
+ const outlineBoxes = useRef([]);
988
+ outlineBoxes.current = outlinesOn ? shown.obstacles : [];
989
+ const [emphasis, setEmphasis] = useState(null);
990
+ const [flash, setFlash] = useState(false);
991
+ const [sheetOpen, setSheetOpen] = useState(false);
992
+ const phone = useMedia("(max-width: 760px)");
993
+ const reduced = useMedia("(prefers-reduced-motion: reduce)");
994
+ usePinnedBridgeTokens(liveTokensUrl(build), theme);
995
+ useEffect(() => {
996
+ if (!outlinesOn || reduced) {
997
+ setFlash(false);
998
+ return;
999
+ }
1000
+ setFlash(true);
1001
+ const id = window.setTimeout(() => setFlash(false), 1500);
1002
+ return () => window.clearTimeout(id);
1003
+ }, [outlinesOn, reduced]);
1004
+ useEffect(() => {
1005
+ if (!sheetOpen) setEmphasis(null);
1006
+ }, [sheetOpen]);
1007
+ const autoTheme = useRef(false);
1008
+ useEffect(() => {
1009
+ if (autoTheme.current || !facts) return;
1010
+ autoTheme.current = true;
1011
+ if (noDiff?.kind === "theme" && (noDiff.touched === "light" || noDiff.touched === "dark")) {
1012
+ setPrefs({ theme: noDiff.touched });
1013
+ setShownIn(noDiff.touched);
1014
+ }
1015
+ }, [facts, noDiff, setPrefs]);
1016
+ useEffect(() => {
1017
+ if (shownIn && theme !== shownIn) setShownIn(null);
1018
+ }, [theme, shownIn]);
1019
+ useEffect(() => {
1020
+ const y = readScroll(window.location.search);
1021
+ if (y === null) return;
1022
+ const url = new URL(window.location.href);
1023
+ url.searchParams.delete(SCROLL_PARAM);
1024
+ window.history.replaceState(window.history.state, "", url);
1025
+ let tries = 0;
1026
+ const id = window.setInterval(() => {
1027
+ tries += 1;
1028
+ if (document.documentElement.scrollHeight - window.innerHeight >= y || tries > 20) {
1029
+ window.scrollTo({ top: y });
1030
+ window.clearInterval(id);
1031
+ }
1032
+ }, 150);
1033
+ return () => window.clearInterval(id);
1034
+ }, []);
1035
+ const choose = useCallback(
1036
+ (next) => {
1037
+ if (next === side || !facts) return;
1038
+ if (facts.mode === "tokens") {
1039
+ setSide(next);
1040
+ return;
1041
+ }
1042
+ const here2 = { search: window.location.search, hash: window.location.hash };
1043
+ if (next === "before" && build.production) {
1044
+ window.location.assign(sameScreenOn(build.production, here2, { change, returnTo: window.location.origin, scroll: window.scrollY }));
1045
+ } else if (next === "after" && returnTo) {
1046
+ window.location.assign(sameScreenOn(returnTo, here2, { change, scroll: window.scrollY }));
1047
+ }
1048
+ },
1049
+ [side, facts, build, change, returnTo]
1050
+ );
1051
+ useEffect(() => {
1052
+ const onKey = (e) => {
1053
+ if (canOutline && !noDiff && isKey(e, "c")) {
1054
+ e.preventDefault();
1055
+ setOutlines((o) => !o);
1056
+ return;
1057
+ }
1058
+ if (!isFlipKey(e)) return;
1059
+ e.preventDefault();
1060
+ choose(side === "after" ? "before" : "after");
1061
+ };
1062
+ window.addEventListener("keydown", onKey);
1063
+ return () => window.removeEventListener("keydown", onKey);
1064
+ }, [choose, side, canOutline, noDiff]);
1065
+ useLayoutEffect(() => {
1066
+ const el = root.current;
1067
+ const anchor2 = el?.parentElement;
1068
+ if (!el || !anchor2) return;
1069
+ let frame = 0;
1070
+ const measure = () => {
1071
+ cancelAnimationFrame(frame);
1072
+ frame = requestAnimationFrame(() => {
1073
+ const box2 = el.getBoundingClientRect();
1074
+ const area = anchor2.getBoundingClientRect();
1075
+ const gutter = window.innerWidth <= 760 ? open ? 0 : 8 : 16;
1076
+ const next = placeToolbar({ left: area.left, right: area.right, vh: window.innerHeight, w: box2.width, h: box2.height, gutter, preferred: dock, obstacles: [...pageObstacles(el), ...outlineBoxes.current] });
1077
+ setPlace((p) => p.dock === next.dock && p.lift === next.lift ? p : next);
1078
+ document.body.style.paddingBottom = open ? `${box2.height + 2 * gutter}px` : "";
1079
+ });
1080
+ };
1081
+ measure();
1082
+ const later = window.setTimeout(measure, 600);
1083
+ const ro = typeof ResizeObserver === "undefined" ? null : new ResizeObserver(measure);
1084
+ ro?.observe(el);
1085
+ ro?.observe(document.body);
1086
+ window.addEventListener("resize", measure);
1087
+ window.addEventListener("scroll", measure, { passive: true });
1088
+ window.addEventListener("hashchange", measure);
1089
+ return () => {
1090
+ cancelAnimationFrame(frame);
1091
+ window.clearTimeout(later);
1092
+ ro?.disconnect();
1093
+ window.removeEventListener("resize", measure);
1094
+ window.removeEventListener("scroll", measure);
1095
+ window.removeEventListener("hashchange", measure);
1096
+ document.body.style.paddingBottom = "";
1097
+ };
1098
+ }, [dock, open, facts, shown, outlinesOn]);
1099
+ const toggleOpen = (next) => {
1100
+ setOpen(next);
1101
+ remember(OPEN_KEY, next ? "1" : "0");
1102
+ };
1103
+ const moveTo = (next) => {
1104
+ setDock(next);
1105
+ remember(DOCK_KEY, next);
1106
+ };
1107
+ const onGripKey = (e) => {
1108
+ if (e.key === "ArrowLeft" || e.key === "ArrowRight") {
1109
+ e.preventDefault();
1110
+ moveTo(stepDock(dock, e.key === "ArrowLeft" ? -1 : 1));
1111
+ }
1112
+ };
1113
+ const onGripDown = (e) => {
1114
+ e.currentTarget.setPointerCapture(e.pointerId);
1115
+ dragStart.current = e.clientX;
1116
+ setDrag(0);
1117
+ };
1118
+ const onGripMove = (e) => {
1119
+ if (drag !== null) setDrag(e.clientX - dragStart.current);
1120
+ };
1121
+ const onGripUp = (e) => {
1122
+ if (drag === null) return;
1123
+ const box2 = root.current?.getBoundingClientRect();
1124
+ setDrag(null);
1125
+ if (box2 && Math.abs(e.clientX - dragStart.current) > 4) moveTo(dockAt(box2.left + box2.width / 2, window.innerWidth));
1126
+ };
1127
+ const goTo = (s) => (e) => {
1128
+ e.preventDefault();
1129
+ toggleOpen(true);
1130
+ if (isTenantId(s.tenant) && (s.tenant !== tenantId || s.theme !== theme)) setPrefs({ shop: s.tenant, theme: s.theme === "dark" ? "dark" : "light" });
1131
+ window.location.hash = s.route;
1132
+ };
1133
+ const fixNoDiff = () => {
1134
+ if (!noDiff) return;
1135
+ toggleOpen(true);
1136
+ if (noDiff.kind === "theme") setPrefs({ theme: noDiff.touched === "dark" ? "dark" : "light" });
1137
+ else {
1138
+ const t = shops.find((x) => x.brand === noDiff.touched);
1139
+ if (t) setPrefs({ shop: t.id });
1140
+ }
1141
+ };
1142
+ const sideLabel = (x) => x === "after" && tg ? togetherLabel(T?.included.length ?? tg.changes.length) : SIDE_LABEL[x];
1143
+ const label = sideLabel(side);
1144
+ const title2 = facts?.title ?? (tg ? `${tg.changes.length} changes together` : `Change ${change}`);
1145
+ const screens = facts?.screens ?? null;
1146
+ const canFlip = Boolean(facts) && (facts.mode === "tokens" || (side === "after" ? Boolean(build.production) : Boolean(returnTo)));
1147
+ const studio2 = tg ? studioChangesUrl(build) : studioChangeUrl(build, change);
1148
+ const behind = facts?.behind ? "Live has new changes since this change started." : null;
1149
+ const shared = screens?.length && screens.every((s) => s.tenant === screens[0].tenant && s.theme === screens[0].theme) ? screens[0] : null;
1150
+ const shopOf = (t) => shops.find((x) => x.id === t)?.name ?? t;
1151
+ const surfaces = outlinesOn ? diff?.surfaces ?? [] : [];
1152
+ const shownCount = outlinesOn && diff ? shown.count : null;
1153
+ const activeHere = new Set((T?.pairs ?? []).flatMap((p) => p.token.from)).size;
1154
+ const note2 = shownIn ? /* @__PURE__ */ jsxs("p", { className: styles.note, role: "status", children: [
1155
+ "Shown in ",
1156
+ shownIn,
1157
+ ". ",
1158
+ tg ? "These changes only affect" : "This change only affects",
1159
+ " ",
1160
+ shownIn,
1161
+ "."
1162
+ ] }) : noDiff ? /* @__PURE__ */ jsxs("p", { className: styles.note, role: "status", children: [
1163
+ /* @__PURE__ */ jsx("span", { children: noDifferenceText(noDiff, shops) }),
1164
+ /* @__PURE__ */ jsx(Button, { variant: "ghost", size: "md", onClick: fixNoDiff, className: styles.noteAction, children: noDiff.kind === "theme" ? `Switch to ${noDiff.touched}` : `Switch to ${brandName(shops, noDiff.touched)}` })
1165
+ ] }) : (outlinesOn || side === "before") && live.state === "error" ? /* @__PURE__ */ jsx("p", { className: styles.note, role: "status", children: "Could not load the live product. Check your connection, then try again." }) : shownCount === 0 ? /* @__PURE__ */ jsx("p", { className: styles.note, role: "status", children: diff?.surfaces.length ? outOfView(diff.surfaces.length) : tg ? TOGETHER_EMPTY_LINE : EMPTY_LINE }) : tg && T && side === "after" && activeHere < T.included.length ? /* @__PURE__ */ jsxs("p", { className: styles.note, role: "status", children: [
1166
+ activeHere,
1167
+ " of these ",
1168
+ T.included.length,
1169
+ " changes ",
1170
+ activeHere === 1 ? "shows" : "show",
1171
+ " in ",
1172
+ theme,
1173
+ ". Changes lists what each one does here."
1174
+ ] }) : null;
1175
+ const visiblePairs = (noDiff ? [] : pairs).slice().sort((a, b) => (contrastRatio(b.before, b.after) ?? 0) - (contrastRatio(a.before, a.after) ?? 0));
1176
+ const hexPair = (before, after) => `${toHex(before).toUpperCase()} → ${toHex(after).toUpperCase()}`;
1177
+ const fromOf = (p) => {
1178
+ const from = p?.token?.from;
1179
+ return tg && from?.length ? fromText(from, titleOf) : null;
1180
+ };
1181
+ const pairTip = (p) => `${p.token.name}${p.token.step ? ` (${p.token.step})` : ""}${fromOf(p) ? `, ${fromOf(p)}` : ""}: ${p.color ? hexPair(p.before, p.after) : `${p.before} → ${p.after}`}`;
1182
+ const splitOf = (before, after) => /* @__PURE__ */ jsxs("span", { className: styles.split, "aria-hidden": "true", children: [
1183
+ /* @__PURE__ */ jsx("span", { style: { backgroundColor: before } }),
1184
+ /* @__PURE__ */ jsx("span", { style: { backgroundColor: after } })
1185
+ ] });
1186
+ const subtleOf = (before, after) => isSubtle(before, after) ? /* @__PURE__ */ jsx("span", { className: styles.subtle, children: "Subtle change" }) : null;
1187
+ const shownPairs = visiblePairs.filter((p) => p.color).slice(0, MAX_PAIRS);
1188
+ const roleGroups = [...visiblePairs.reduce((m, p) => m.set(p.token.name, [...m.get(p.token.name) ?? [], p]), /* @__PURE__ */ new Map())].map(([name2, list2]) => ({ name: name2, pairs: list2 }));
1189
+ const swatches2 = visiblePairs.length ? /* @__PURE__ */ jsx(Tooltip, { className: PIN_CLASS, content: visiblePairs.slice(0, 6).map(pairTip).join(". ") + (visiblePairs.length > 6 ? `. And ${visiblePairs.length - 6} more.` : ""), children: /* @__PURE__ */ jsxs("span", { className: styles.swatches, tabIndex: 0, "aria-label": `Tokens this change edits: ${visiblePairs.map(pairTip).join(". ")}`, children: [
1190
+ shownPairs.map((p) => /* @__PURE__ */ jsx("span", { children: splitOf(p.before, p.after) }, p.token.path)),
1191
+ visiblePairs.length > shownPairs.length && /* @__PURE__ */ jsxs("span", { className: styles.more, children: [
1192
+ "+",
1193
+ visiblePairs.length - shownPairs.length,
1194
+ " more ",
1195
+ visiblePairs.length - shownPairs.length === 1 ? "token" : "tokens"
1196
+ ] }),
1197
+ shownPairs[0] ? subtleOf(shownPairs[0].before, shownPairs[0].after) : null
1198
+ ] }) }) : null;
1199
+ const tokenNames = (facts?.tokens ?? []).map((t) => t.name);
1200
+ const summary = shownCount === null ? null : surfaceLine(diff.surfaces.flatMap((s) => s.roots.map(nameOf)), shownCount);
1201
+ const toggleTip = `${summary ?? `Outline what ${tokenNames.length === 1 ? tokenNames[0] : "this change"} changes on this screen.`} Press C.`;
1202
+ const toggleLabel = shownCount === null ? "Show what changed" : `Show what changed · ${shownCount}`;
1203
+ const outlineToggle = canOutline ? /* @__PURE__ */ jsx(Tooltip, { className: PIN_CLASS, content: toggleTip, children: /* @__PURE__ */ jsx(Button, { variant: "secondary", size: "md", pressed: outlinesOn, "aria-keyshortcuts": "C", disabled: Boolean(noDiff), onClick: () => setOutlines((o) => !o), children: toggleLabel }) }) : null;
1204
+ const outlineIcon = canOutline ? /* @__PURE__ */ jsx(
1205
+ IconButton,
1206
+ {
1207
+ icon: ScanSearch,
1208
+ size: "md",
1209
+ variant: "ghost",
1210
+ pressed: outlinesOn,
1211
+ disabled: Boolean(noDiff),
1212
+ label: shownCount === null ? "Show what changed" : `Show what changed, ${shownCount} ${shownCount === 1 ? "surface" : "surfaces"}`,
1213
+ className: styles.phoneOnly,
1214
+ onClick: () => setOutlines((o) => !o)
1215
+ }
1216
+ ) : null;
1217
+ const shownSurfaces = surfaces.map((s, i) => ({ s, i })).filter(({ s }) => shownRect(s.el));
1218
+ const focusSurface = (i) => {
1219
+ const el = surfaces[i]?.el;
1220
+ if (!el) return;
1221
+ el.scrollIntoView({ block: "center", behavior: reduced ? "auto" : "smooth" });
1222
+ setEmphasis(i);
1223
+ };
1224
+ const tokensOf = (x) => [...new Set(x.roots.map(nameOf))].join(", ");
1225
+ const sides2 = /* @__PURE__ */ jsx(ButtonGroup, { appearance: "attached", "aria-label": "Compare", className: styles.sides, children: ["before", "after"].map((s) => /* @__PURE__ */ jsx(Tooltip, { className: PIN_CLASS, content: `${sideLabel(s)}: ${s === "before" ? "what customers see today." : modeDetail(facts)} Press B to flip.`, children: /* @__PURE__ */ jsx(
1226
+ Button,
1227
+ {
1228
+ variant: "secondary",
1229
+ size: "md",
1230
+ pressed: side === s,
1231
+ "aria-keyshortcuts": "B",
1232
+ disabled: !canFlip && side !== s,
1233
+ onClick: () => choose(s),
1234
+ children: sideLabel(s)
1235
+ }
1236
+ ) }, s)) });
1237
+ return /* @__PURE__ */ jsxs(
1238
+ ThemeProvider,
1239
+ {
1240
+ theme,
1241
+ brand: "bridge",
1242
+ density: "compact",
1243
+ className: styles.anchor,
1244
+ "data-bridge-toolbar": "",
1245
+ ...{ [PIN_ATTR]: "" },
1246
+ "data-open": open || void 0,
1247
+ "data-dock": place.dock,
1248
+ style: place.lift ? { paddingBottom: `${place.lift}px` } : void 0,
1249
+ children: [
1250
+ /* @__PURE__ */ jsxs(
1251
+ "div",
1252
+ {
1253
+ ref: root,
1254
+ className: open ? styles.panel : styles.pill,
1255
+ role: "region",
1256
+ "aria-label": `Bridge: ${label}, ${title2}`,
1257
+ "data-side": side,
1258
+ "data-diff-ms": outlinesOn ? diff?.ms : void 0,
1259
+ "data-diff-count": shownCount ?? void 0,
1260
+ "data-diff-elements": outlinesOn ? diff?.elements.length : void 0,
1261
+ "data-note": note2 ? "" : void 0,
1262
+ style: drag ? { transform: `translateX(${drag}px)` } : void 0,
1263
+ "data-dragging": drag !== null || void 0,
1264
+ children: [
1265
+ open ? /* @__PURE__ */ jsxs(Fragment, { children: [
1266
+ /* @__PURE__ */ jsx(Tooltip, { className: PIN_CLASS, content: "Drag, or use the arrow keys, to move the toolbar", children: /* @__PURE__ */ jsx(
1267
+ IconButton,
1268
+ {
1269
+ icon: GripVertical,
1270
+ size: "md",
1271
+ label: `Move the toolbar, now ${dock === "start" ? "left" : dock === "end" ? "right" : "centred"}. Use the left and right arrow keys.`,
1272
+ className: styles.grip,
1273
+ onKeyDown: onGripKey,
1274
+ onPointerDown: onGripDown,
1275
+ onPointerMove: onGripMove,
1276
+ onPointerUp: onGripUp,
1277
+ onPointerCancel: () => setDrag(null)
1278
+ }
1279
+ ) }),
1280
+ /* @__PURE__ */ jsxs("p", { className: styles.title, children: [
1281
+ /* @__PURE__ */ jsx("span", { className: styles.brand, children: "Bridge" }),
1282
+ /* @__PURE__ */ jsx("span", { className: styles.name, title: title2, children: title2 })
1283
+ ] }),
1284
+ outlineIcon,
1285
+ /* @__PURE__ */ jsxs("div", { className: styles.actions, children: [
1286
+ sides2,
1287
+ /* @__PURE__ */ jsx("span", { className: styles.desktopOnly, children: swatches2 }),
1288
+ /* @__PURE__ */ jsxs(Popover, { open: sheetOpen, onOpenChange: setSheetOpen, children: [
1289
+ /* @__PURE__ */ jsx(
1290
+ PopoverTrigger,
1291
+ {
1292
+ render: phone ? /* @__PURE__ */ jsx(IconButton, { icon: ListChecks, size: "md", variant: "ghost", label: shownCount ? `Changes, ${shownCount} changed here` : "Changes" }) : /* @__PURE__ */ jsx(Button, { variant: "ghost", size: "md", iconEnd: ChevronUp }),
1293
+ children: phone ? null : /* @__PURE__ */ jsxs(Fragment, { children: [
1294
+ "Changes",
1295
+ shownCount ? /* @__PURE__ */ jsx("span", { className: styles.count, children: shownCount }) : null
1296
+ ] })
1297
+ }
1298
+ ),
1299
+ /* @__PURE__ */ jsxs(PopoverContent, { side: "top", align: "center", className: `${styles.sheet} ${PIN_CLASS}`, "aria-label": "Changes", children: [
1300
+ tg && T?.list ? /* @__PURE__ */ jsxs("section", { className: styles.sheetSection, "aria-label": "Changes tried together", children: [
1301
+ /* @__PURE__ */ jsx("p", { className: styles.sectionTitle, children: "Changes tried together" }),
1302
+ /* @__PURE__ */ jsx("p", { className: styles.note, children: TOGETHER_HINT }),
1303
+ /* @__PURE__ */ jsx("ul", { className: styles.list, children: T.list.map((f) => {
1304
+ const can = f.mode === "tokens" && Boolean(f.source);
1305
+ const on = T.included.includes(f.number);
1306
+ const paths = T.editedBy.get(f.number) ?? [];
1307
+ const byOther = [...new Set(paths.flatMap((p) => T.result?.tokens.find((t) => t.path === p && !t.from.includes(f.number))?.from ?? []))];
1308
+ const used = paths.length - paths.filter((p) => T.result?.tokens.some((t) => t.path === p && !t.from.includes(f.number))).length;
1309
+ const showsHere = T.pairs.some((p) => p.token.from.includes(f.number));
1310
+ const what = !on ? `#${f.number}, switched off` : used === 0 && byOther.length ? `#${f.number}, ${paths.length === 1 ? "its token is" : `its ${paths.length} tokens are`} set by ${byOther.map((x) => `#${x}`).join(" and ")} here (your pick)` : `#${f.number}, ${used} of ${paths.length} ${paths.length === 1 ? "token" : "tokens"} used${byOther.length ? `, the rest set by ${byOther.map((x) => `#${x}`).join(" and ")} (your pick)` : ""}${showsHere ? "" : `. Nothing of it shows in ${theme}`}`;
1311
+ return /* @__PURE__ */ jsx("li", { className: styles.item, children: /* @__PURE__ */ jsx(
1312
+ Checkbox,
1313
+ {
1314
+ label: f.title,
1315
+ description: can ? what : `#${f.number}. It changes code, so it can't be applied here.`,
1316
+ checked: T.included.includes(f.number),
1317
+ disabled: !can,
1318
+ onCheckedChange: (on2) => T.toggle(f.number, on2)
1319
+ }
1320
+ ) }, f.number);
1321
+ }) }),
1322
+ T.result?.unpicked.length ? /* @__PURE__ */ jsxs("p", { className: styles.note, children: [
1323
+ T.result.unpicked.join(", "),
1324
+ ": the change you picked in Studio is off, so the first one listed sets ",
1325
+ T.result.unpicked.length === 1 ? "it" : "them",
1326
+ "."
1327
+ ] }) : null
1328
+ ] }) : null,
1329
+ outlinesOn && shownSurfaces.length ? /* @__PURE__ */ jsxs("section", { className: styles.sheetSection, "aria-label": `Changed here, ${shownSurfaces.length}`, children: [
1330
+ /* @__PURE__ */ jsxs("p", { className: styles.sectionTitle, children: [
1331
+ "Changed here · ",
1332
+ shownSurfaces.length
1333
+ ] }),
1334
+ /* @__PURE__ */ jsx("ul", { className: styles.rows, children: shownSurfaces.map(({ s: x, i }) => /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsx(
1335
+ Button,
1336
+ {
1337
+ variant: "ghost",
1338
+ size: "md",
1339
+ fullWidth: true,
1340
+ className: styles.row,
1341
+ "data-emphasis": emphasis === i || void 0,
1342
+ onMouseEnter: () => setEmphasis(i),
1343
+ onMouseLeave: () => setEmphasis((e) => e === i ? null : e),
1344
+ onFocus: () => setEmphasis(i),
1345
+ onBlur: () => setEmphasis((e) => e === i ? null : e),
1346
+ onClick: () => focusSurface(i),
1347
+ children: /* @__PURE__ */ jsxs("span", { className: styles.rowText, children: [
1348
+ /* @__PURE__ */ jsx("span", { className: styles.rowName, children: x.label }),
1349
+ /* @__PURE__ */ jsx("span", { className: styles.rowMeta, children: tokensOf(x) })
1350
+ ] })
1351
+ }
1352
+ ) }, i)) })
1353
+ ] }) : null,
1354
+ screens && screens.length ? /* @__PURE__ */ jsxs("section", { className: styles.sheetSection, "aria-label": "Screens this change affects", children: [
1355
+ /* @__PURE__ */ jsx("p", { className: styles.sectionTitle, children: "Screens this change affects" }),
1356
+ shared ? /* @__PURE__ */ jsxs("p", { className: styles.note, children: [
1357
+ shopOf(shared.tenant),
1358
+ ", ",
1359
+ shared.theme
1360
+ ] }) : null,
1361
+ /* @__PURE__ */ jsx("ul", { className: styles.list, children: screens.map((sc) => {
1362
+ const current = sc.route === hash && sc.tenant === tenantId && sc.theme === theme;
1363
+ const where = shared ? null : /* @__PURE__ */ jsxs("span", { className: styles.meta, children: [
1364
+ " ",
1365
+ shopOf(sc.tenant),
1366
+ ", ",
1367
+ sc.theme
1368
+ ] });
1369
+ return /* @__PURE__ */ jsxs("li", { className: styles.item, "aria-current": current ? "page" : void 0, children: [
1370
+ current ? /* @__PURE__ */ jsxs("span", { className: styles.here, children: [
1371
+ /* @__PURE__ */ jsx(Icon, { icon: Check, size: "sm" }),
1372
+ sc.label,
1373
+ where
1374
+ ] }) : /* @__PURE__ */ jsxs(
1375
+ Link,
1376
+ {
1377
+ variant: "standalone",
1378
+ href: `?${PARAM}=${change}&tenant=${encodeURIComponent(sc.tenant)}&theme=${encodeURIComponent(sc.theme)}#${sc.route}`,
1379
+ onClick: goTo(sc),
1380
+ className: styles.screenLink,
1381
+ children: [
1382
+ sc.label,
1383
+ where
1384
+ ]
1385
+ }
1386
+ ),
1387
+ /* @__PURE__ */ jsx("span", { className: styles.meta, children: current ? "You are here" : `${Math.max(1, Math.round(sc.percent))}% changed` })
1388
+ ] }, `${sc.route}|${sc.tenant}|${sc.theme}`);
1389
+ }) })
1390
+ ] }) : null,
1391
+ visiblePairs.length ? /* @__PURE__ */ jsxs("section", { className: `${styles.sheetSection} ${styles.sheetPhone}`, "aria-label": "Tokens this change edits", children: [
1392
+ /* @__PURE__ */ jsx("p", { className: styles.sectionTitle, children: "Tokens" }),
1393
+ /* @__PURE__ */ jsx("ul", { className: styles.list, children: roleGroups.map((g) => /* @__PURE__ */ jsxs("li", { className: styles.item, children: [
1394
+ /* @__PURE__ */ jsxs("span", { className: styles.rowText, children: [
1395
+ /* @__PURE__ */ jsx("span", { children: g.name }),
1396
+ /* @__PURE__ */ jsxs("span", { className: styles.rowMeta, children: [
1397
+ g.pairs.length === 1 ? g.pairs[0].token.step ?? "" : `${g.pairs.length} steps`,
1398
+ isSubtle(g.pairs[0].before, g.pairs[0].after) ? ", subtle change" : ""
1399
+ ] })
1400
+ ] }),
1401
+ g.pairs[0].color ? splitOf(g.pairs[0].before, g.pairs[0].after) : null
1402
+ ] }, g.name)) })
1403
+ ] }) : null,
1404
+ behind ? /* @__PURE__ */ jsxs("p", { className: styles.notice, role: "note", children: [
1405
+ /* @__PURE__ */ jsx(Icon, { icon: Info, size: "sm" }),
1406
+ behind
1407
+ ] }) : null,
1408
+ /* @__PURE__ */ jsx("div", { className: styles.sheetPhone, children: /* @__PURE__ */ jsx(Link, { variant: "standalone", href: studio2, className: styles.back, children: "Back to Studio" }) }),
1409
+ screens === null ? /* @__PURE__ */ jsx("p", { className: `${styles.note} ${styles.sheetFoot}`, children: tg ? facts ? "Screens are measured per change, not for this mix. Show what changed outlines this screen." : "Reading the changes." : facts ? "The screens this change affects are not measured yet." : "Reading the change." }) : screens.length === 0 ? /* @__PURE__ */ jsx("p", { className: `${styles.note} ${styles.sheetFoot}`, children: "No admin screen changes. Component examples may still change: Studio lists them." }) : null
1410
+ ] })
1411
+ ] }),
1412
+ /* @__PURE__ */ jsx("span", { className: styles.desktopOnly, children: outlineToggle }),
1413
+ /* @__PURE__ */ jsx(Button, { variant: "ghost", size: "md", render: /* @__PURE__ */ jsx("a", { href: studio2 }), className: styles.studio, children: "Back to Studio" })
1414
+ ] }),
1415
+ /* @__PURE__ */ jsx(Tooltip, { className: PIN_CLASS, content: "Collapse", children: /* @__PURE__ */ jsx(IconButton, { icon: Minimize2, size: "md", label: "Collapse the Bridge toolbar", className: styles.collapse, onClick: () => toggleOpen(false) }) }),
1416
+ error ? /* @__PURE__ */ jsx("p", { className: styles.noteRow, role: "status", children: tg ? "Could not read these changes. Reload the page to try again." : "Could not read this change. Reload the page to try again." }) : null
1417
+ ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
1418
+ /* @__PURE__ */ jsx("span", { className: styles.brand, children: "Bridge" }),
1419
+ /* @__PURE__ */ jsx("span", { className: styles.pillTitle, title: title2, children: title2 }),
1420
+ /* @__PURE__ */ jsx(Badge, { size: "sm", children: label }),
1421
+ /* @__PURE__ */ jsx(Tooltip, { className: PIN_CLASS, content: "Open the toolbar", children: /* @__PURE__ */ jsx(IconButton, { icon: ChevronUp, size: "md", label: `Open the Bridge toolbar. Showing ${label.toLowerCase()}. Press B to flip.`, onClick: () => toggleOpen(true) }) })
1422
+ ] }),
1423
+ note2 && /* @__PURE__ */ jsx("div", { className: styles.noteRow, children: note2 })
1424
+ ]
1425
+ }
1426
+ ),
1427
+ outlinesOn && surfaces.length ? /* @__PURE__ */ jsx(
1428
+ ChangeOutlines,
1429
+ {
1430
+ surfaces,
1431
+ theme,
1432
+ emphasis,
1433
+ flash,
1434
+ onShown: setShownStable,
1435
+ tip: (s) => {
1436
+ const prop = s.props.find((p) => /^rgb/.test(p.before) && /^rgb/.test(p.after));
1437
+ const token = prop ? visiblePairs.find((p) => p.color && toHex(p.after) === toHex(prop.after) && toHex(p.before) === toHex(prop.before)) : null;
1438
+ const name2 = token?.token.name ?? [...new Set(s.roots.map(nameOf))].slice(0, 2).join(", ");
1439
+ const rootsFrom = [...new Set(s.roots.flatMap((r) => facts?.tokens.find((t) => t.cssVar === r)?.from ?? []))];
1440
+ const fromLine = fromOf(token) ?? (tg && rootsFrom.length ? fromText(rootsFrom, titleOf) : null);
1441
+ return /* @__PURE__ */ jsxs("span", { className: styles.tip, children: [
1442
+ prop ? splitOf(prop.before, prop.after) : null,
1443
+ /* @__PURE__ */ jsxs("span", { className: styles.tipText, children: [
1444
+ /* @__PURE__ */ jsxs("span", { children: [
1445
+ s.label,
1446
+ ": ",
1447
+ name2,
1448
+ prop ? `, ${hexPair(prop.before, prop.after)}` : ""
1449
+ ] }),
1450
+ token?.token.step ? /* @__PURE__ */ jsx("span", { className: styles.tipStep, children: token.token.step }) : null,
1451
+ fromLine ? /* @__PURE__ */ jsx("span", { className: styles.tipStep, children: fromLine.charAt(0).toUpperCase() + fromLine.slice(1) }) : null
1452
+ ] }),
1453
+ prop ? subtleOf(prop.before, prop.after) : null
1454
+ ] });
1455
+ }
1456
+ }
1457
+ ) : null
1458
+ ]
1459
+ }
1460
+ );
1461
+ }
1462
+ function useMedia(query) {
1463
+ const get = () => typeof window !== "undefined" && window.matchMedia ? window.matchMedia(query).matches : false;
1464
+ const [match, setMatch] = useState(get);
1465
+ useEffect(() => {
1466
+ const m = window.matchMedia(query);
1467
+ const on = () => setMatch(m.matches);
1468
+ m.addEventListener("change", on);
1469
+ return () => m.removeEventListener("change", on);
1470
+ }, [query]);
1471
+ return match;
1472
+ }
1473
+ function useHash() {
1474
+ const read = () => window.location.hash.replace(/^#/, "").split("?")[0] || "/";
1475
+ const [hash, setHash] = useState(read);
1476
+ useEffect(() => {
1477
+ const on = () => setHash(read());
1478
+ window.addEventListener("hashchange", on);
1479
+ return () => window.removeEventListener("hashchange", on);
1480
+ }, []);
1481
+ return hash;
1482
+ }
1483
+ export {
1484
+ ToolbarRoot as default
1485
+ };
1486
+ //# sourceMappingURL=Toolbar-CzR1TM9x.js.map