@bridge-ds/toolbar 0.0.0-stage → 0.1.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/index.js ADDED
@@ -0,0 +1,7 @@
1
+ import { B, b, u } from "./index-yvFRSnsp.js";
2
+ export {
3
+ B as BridgeToolbar,
4
+ b as buildFromConfig,
5
+ u as useDocumentScope
6
+ };
7
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":";"}
@@ -0,0 +1,41 @@
1
+ import { type RuleLike } from './diff';
2
+ export interface PropChange {
3
+ prop: string;
4
+ /** Computed value with the live tokens. */
5
+ before: string;
6
+ /** Computed value with the change's tokens. */
7
+ after: string;
8
+ }
9
+ export interface ChangedElement {
10
+ el: Element;
11
+ pseudo: string | null;
12
+ /** CSS variables of the changed tokens behind the difference. */
13
+ roots: string[];
14
+ props: PropChange[];
15
+ /** What a person calls it: "Page", "Save bar (Save changes)". */
16
+ label: string;
17
+ /** "root|property" pairs: an ancestor with all of them paints the same change. */
18
+ keys: string[];
19
+ /** Sticky or fixed: paints over other content, so it is a surface even inside a changed ancestor. */
20
+ floating: boolean;
21
+ }
22
+ export interface DiffResult {
23
+ /** Every element whose computed style differs. */
24
+ elements: ChangedElement[];
25
+ /** The outermost of them: what is outlined and counted (when visible). */
26
+ surfaces: ChangedElement[];
27
+ /** Time the comparison took, in ms (rules are read once per page load and cached). */
28
+ ms: number;
29
+ }
30
+ /** The page's own style rules (same origin), without the live tokens stylesheet the toolbar adds. Cached. */
31
+ export declare function pageRules(): RuleLike[];
32
+ /** "Page", "Content column", "Save bar (Save changes)": landmark, ARIA role or component first, its text after. */
33
+ export declare function describe(el: Element): string;
34
+ /** The part of an element inside the viewport, or null when none of it shows (hidden, transparent, off screen). */
35
+ export declare function shownRect(el: Element): DOMRect | null;
36
+ /**
37
+ * Compares the candidates under both token sets. `sheet` is the toolbar's stylesheet that switches sides (loaded):
38
+ * the live tokens over a change's preview (`sheetIs` 'before', one change), or the combination over the live product
39
+ * (`sheetIs` 'after', changes tried together). `ignore` holds the toolbar's own nodes.
40
+ */
41
+ export declare function changedElements(changedVars: string[], sheet: HTMLLinkElement | HTMLStyleElement, ignore: (el: Element) => boolean, sheetIs?: 'before' | 'after'): DiffResult;
package/dist/pin.d.ts ADDED
@@ -0,0 +1,6 @@
1
+ /** Marks an element (and what is inside it) as pinned: the toolbar root, the outline layer, their popups. */
2
+ export declare const PIN_ATTR = "data-bridge-pin";
3
+ /** Same, as a class, for popups the DS portals out (Tooltip and Popover accept a className, not attributes). */
4
+ export declare const PIN_CLASS = "bridge-pin";
5
+ /** Writes the pinned tokens for the current theme. Three-fold selectors out-rank the tokens' attribute rules. */
6
+ export declare function usePinnedBridgeTokens(url: string | null, theme: string): void;
@@ -0,0 +1,20 @@
1
+ export interface ToolbarScope {
2
+ theme: 'light' | 'dark';
3
+ brand: string;
4
+ density: string;
5
+ /** The shop (tenant) the page shows, when the product has several. */
6
+ shop?: string;
7
+ /** Moves the page to a theme, a shop or both, in one step. */
8
+ set: (next: {
9
+ theme?: 'light' | 'dark';
10
+ shop?: string;
11
+ }) => void;
12
+ }
13
+ /** A shop of a multi-brand product: its id (in `?tenant=` links), its name and its brand. */
14
+ export interface ToolbarShop {
15
+ id: string;
16
+ name: string;
17
+ brand: string;
18
+ }
19
+ /** The default scope: <html data-theme data-brand data-density>, kept in sync. */
20
+ export declare function useDocumentScope(): ToolbarScope;
@@ -0,0 +1,325 @@
1
+ const PARAM = "bridge";
2
+ const RETURN_PARAM = "bridge-return";
3
+ const SCROLL_PARAM = "bridge-scroll";
4
+ const SESSION_KEY = "bridge-admin:change";
5
+ const RETURN_KEY = "bridge-admin:return";
6
+ const DOCK_KEY = "bridge-admin:toolbar-dock";
7
+ const OPEN_KEY = "bridge-admin:toolbar-open";
8
+ const USE_PARAM = "bridge-use";
9
+ const USE_KEY = "bridge-admin:use";
10
+ const MAX_TOGETHER = 4;
11
+ const safe = (fn, fallback) => {
12
+ try {
13
+ return fn();
14
+ } catch {
15
+ return fallback;
16
+ }
17
+ };
18
+ function parseChanges(raw) {
19
+ const parts = (raw ?? "").trim().split(/,|%2C/i).map((p) => p.trim());
20
+ if (!parts.every((p) => /^\d{1,7}$/.test(p) && Number(p) > 0)) return [];
21
+ return [...new Set(parts.map(Number))].slice(0, MAX_TOGETHER);
22
+ }
23
+ function readChanges(search, storage) {
24
+ const params = new URLSearchParams(search);
25
+ if (params.has(PARAM)) {
26
+ const list = parseChanges(params.get(PARAM));
27
+ if (!list.length) {
28
+ if (storage) safe(() => (storage.removeItem(SESSION_KEY), storage.removeItem(USE_KEY)), void 0);
29
+ return [];
30
+ }
31
+ if (storage) {
32
+ safe(() => storage.setItem(SESSION_KEY, list.join(",")), void 0);
33
+ const use = params.get(USE_PARAM);
34
+ safe(() => use && list.length > 1 ? storage.setItem(USE_KEY, use) : storage.removeItem(USE_KEY), void 0);
35
+ }
36
+ return list;
37
+ }
38
+ return storage ? parseChanges(safe(() => storage.getItem(SESSION_KEY), null)) : [];
39
+ }
40
+ function readUse(search, storage) {
41
+ const params = new URLSearchParams(search);
42
+ if (params.has(PARAM)) return params.get(USE_PARAM);
43
+ return storage ? safe(() => storage.getItem(USE_KEY), null) : null;
44
+ }
45
+ function isPreviewUrl(raw, template) {
46
+ if (!raw || !template) return false;
47
+ let url;
48
+ let tpl;
49
+ try {
50
+ url = new URL(raw);
51
+ tpl = new URL(template.replace(/\{[a-z]+\}/gi, "x"));
52
+ } catch {
53
+ return false;
54
+ }
55
+ if (url.protocol !== "https:" || url.username || url.password || url.port) return false;
56
+ const tplHost = /^https?:\/\/([^/?#]+)/i.exec(template)?.[1]?.toLowerCase();
57
+ if (!tplHost || tpl.protocol !== "https:") return false;
58
+ const pattern = new RegExp(`^${tplHost.split(/\{[a-z]+\}/i).map((p) => p.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join("[a-z0-9-]+")}$`);
59
+ return pattern.test(url.hostname.toLowerCase());
60
+ }
61
+ function readReturn(search, storage, template) {
62
+ const raw = new URLSearchParams(search).get(RETURN_PARAM);
63
+ if (raw !== null) {
64
+ if (isPreviewUrl(raw, template)) {
65
+ const origin = new URL(raw).origin;
66
+ if (storage) safe(() => storage.setItem(RETURN_KEY, origin), void 0);
67
+ return origin;
68
+ }
69
+ return null;
70
+ }
71
+ const stored = storage ? safe(() => storage.getItem(RETURN_KEY), null) : null;
72
+ return isPreviewUrl(stored, template) ? new URL(stored).origin : null;
73
+ }
74
+ function whereAmI(host, build) {
75
+ const prodHost = build.production ? safe(() => new URL(build.production).host, null) : null;
76
+ if (build.deployEnv === "production" || prodHost && host.toLowerCase() === prodHost.toLowerCase()) return "production";
77
+ if (build.deployEnv === "preview") return "preview";
78
+ return "local";
79
+ }
80
+ function shouldRender(input) {
81
+ if (input.change === null) return false;
82
+ if (input.together) return true;
83
+ if (input.where === "production") return input.returnTo !== null;
84
+ return true;
85
+ }
86
+ const togetherLabel = (n) => n === 1 ? "This change" : `These ${n} changes`;
87
+ const TOGETHER_HINT = "The live product, with these changes' tokens applied in your browser only. No preview has all of them together.";
88
+ const TOGETHER_EMPTY_LINE = "These changes don't touch this screen. Try another screen.";
89
+ function toggleIncluded(all, included, n, on) {
90
+ const next = new Set(included);
91
+ if (on) next.add(n);
92
+ else next.delete(n);
93
+ return all.filter((x) => next.has(x));
94
+ }
95
+ function studioChangesUrl(build) {
96
+ return build.studio ? `${build.studio.replace(/\/$/, "")}/studio/changes/` : `https://github.com/${build.owner}/${build.repo}/pulls`;
97
+ }
98
+ function changeMode(files) {
99
+ const tokenOnly = files.length > 0 && files.every((f) => f.endsWith(".tokens.json") || /contrast\.checks\.json$/.test(f) || f.startsWith(".changeset/"));
100
+ return tokenOnly ? "tokens" : "code";
101
+ }
102
+ function liveTokensUrl(build) {
103
+ return build.studio ? safe(() => new URL("tokens.css", build.studio.replace(/\/?$/, "/")).href, null) : null;
104
+ }
105
+ function studioChangeUrl(build, change) {
106
+ if (!build.studio) return `https://github.com/${build.owner}/${build.repo}/pull/${change}`;
107
+ const studio = build.studio.replace(/\/$/, "");
108
+ const own = build.product && build.ds && build.ds.toLowerCase() !== `${build.owner}/${build.repo}`.toLowerCase();
109
+ return own ? `${studio}/studio/changes/?exploration=${change}&product=${encodeURIComponent(build.product)}` : `${studio}/studio/changes/?pr=${change}`;
110
+ }
111
+ function changedScreens(shots) {
112
+ if (!Array.isArray(shots)) return [];
113
+ const seen = /* @__PURE__ */ new Set();
114
+ return shots.filter((s) => s.kind === "route" && s.route && s.tenant && (s.status === "changed" || s.status === "added")).sort((a, b) => b.percent - a.percent).filter((s) => {
115
+ const key = `${s.route}|${s.tenant}|${s.theme}`;
116
+ if (seen.has(key)) return false;
117
+ seen.add(key);
118
+ return true;
119
+ }).map((s) => ({ label: s.label, route: s.route, tenant: s.tenant, theme: s.theme, percent: s.percent }));
120
+ }
121
+ function sameScreenOn(target, here, extra) {
122
+ const url = new URL(target);
123
+ const params = new URLSearchParams(here.search);
124
+ params.delete(RETURN_PARAM);
125
+ params.delete(SCROLL_PARAM);
126
+ params.set(PARAM, String(extra.change));
127
+ if (extra.returnTo) params.set(RETURN_PARAM, extra.returnTo);
128
+ if (extra.scroll && extra.scroll > 0) params.set(SCROLL_PARAM, String(Math.round(extra.scroll)));
129
+ url.pathname = "/";
130
+ url.search = params.toString();
131
+ url.hash = here.hash;
132
+ return url.href;
133
+ }
134
+ function readScroll(search) {
135
+ const raw = new URLSearchParams(search).get(SCROLL_PARAM);
136
+ return raw && /^\d{1,7}$/.test(raw) ? Number(raw) : null;
137
+ }
138
+ const DOCKS = ["start", "center", "end"];
139
+ function readDock(storage) {
140
+ const v = storage ? safe(() => storage.getItem(DOCK_KEY), null) : null;
141
+ return DOCKS.includes(v) ? v : "end";
142
+ }
143
+ function stepDock(dock, step) {
144
+ return DOCKS[Math.min(DOCKS.length - 1, Math.max(0, DOCKS.indexOf(dock) + step))];
145
+ }
146
+ function dockAt(x, width) {
147
+ if (width <= 0) return "center";
148
+ const r = x / width;
149
+ return r < 1 / 3 ? "start" : r > 2 / 3 ? "end" : "center";
150
+ }
151
+ function readableTitle(title) {
152
+ const t = title.replace(/^\s*[a-z]+(\([^)]*\))?!?:\s*/i, "").trim() || title.trim();
153
+ return t.charAt(0).toUpperCase() + t.slice(1);
154
+ }
155
+ function isFlipKey(e) {
156
+ if (e.altKey || e.ctrlKey || e.metaKey || e.key.toLowerCase() !== "b") return false;
157
+ const t = e.target;
158
+ if (!t) return true;
159
+ if (t.isContentEditable) return false;
160
+ const tag = (t.tagName ?? "").toLowerCase();
161
+ if (tag === "input" || tag === "textarea" || tag === "select") return false;
162
+ return !(t.closest && t.closest('[role="combobox"], [role="textbox"], [role="searchbox"]'));
163
+ }
164
+ const overlaps = (a, b, margin) => a.left < b.right + margin && a.right > b.left - margin && a.top < b.bottom + margin && a.bottom > b.top - margin;
165
+ function placeToolbar(input) {
166
+ const { left: areaLeft, right: areaRight, vh, w, h, gutter, preferred, obstacles } = input;
167
+ const margin = input.margin ?? 8;
168
+ const rectAt = (dock, lift2 = 0) => {
169
+ const left = dock === "start" ? areaLeft : dock === "end" ? areaRight - w : (areaLeft + areaRight - w) / 2;
170
+ const bottom = vh - gutter - lift2;
171
+ return { left, right: left + w, top: bottom - h, bottom };
172
+ };
173
+ const opposite = preferred === "start" ? "end" : "start";
174
+ const order = [preferred, opposite, ...DOCKS.filter((d) => d !== preferred && d !== opposite)];
175
+ for (const dock of order) {
176
+ const r = rectAt(dock);
177
+ if (!obstacles.some((o) => overlaps(r, o, margin))) return { dock, lift: 0 };
178
+ }
179
+ const base = rectAt(preferred);
180
+ const inWay = obstacles.filter((o) => o.left < base.right + margin && o.right > base.left - margin && o.bottom > base.top - margin);
181
+ const top = Math.min(...inWay.map((o) => o.top));
182
+ const lift = Math.max(0, Math.ceil(base.bottom - (top - margin)));
183
+ return { dock: preferred, lift: Math.min(lift, Math.max(0, vh - h - 2 * gutter)) };
184
+ }
185
+ function fileScope(file) {
186
+ if (file.startsWith(".changeset/")) return "none";
187
+ let m = /(?:^|\/)semantic\/([a-z0-9-]+)\.tokens\.json$/i.exec(file);
188
+ if (m && m[1] !== "base") return { theme: m[1], brand: null };
189
+ m = /(?:^|\/)brands\/([a-z0-9-]+)\.([a-z0-9-]+)\.tokens\.json$/i.exec(file);
190
+ if (m) return { theme: m[2], brand: m[1] };
191
+ m = /(?:^|\/)brands\/([a-z0-9-]+)\.tokens\.json$/i.exec(file);
192
+ if (m) return { theme: null, brand: m[1] };
193
+ return { theme: null, brand: null };
194
+ }
195
+ function noVisibleDifference(files, here) {
196
+ const scopes = files.map(fileScope).filter((s) => s !== "none");
197
+ if (!scopes.length) return null;
198
+ const hits = (theme2, brand2) => scopes.some((s) => (s.theme === null || s.theme === theme2) && (s.brand === null || s.brand === brand2));
199
+ if (hits(here.theme, here.brand)) return null;
200
+ const theme = scopes.find((s) => s.theme !== null && s.theme !== here.theme && (s.brand === null || s.brand === here.brand))?.theme;
201
+ if (theme) return { kind: "theme", here: here.theme, touched: theme };
202
+ const brand = scopes.find((s) => s.brand !== null && s.brand !== here.brand)?.brand;
203
+ return brand ? { kind: "brand", here: here.brand, touched: brand } : null;
204
+ }
205
+ function changeSummary(title) {
206
+ const t = readableTitle(title).replace(/[.\s]+$/, "");
207
+ if (!t) return "";
208
+ const lower = /^[A-Z][a-z]/.test(t) ? t.charAt(0).toLowerCase() + t.slice(1) : t;
209
+ return `${lower}.`;
210
+ }
211
+ function leaves(node, path = [], inheritedType = null, out = /* @__PURE__ */ new Map()) {
212
+ if (!node || typeof node !== "object") return out;
213
+ const n = node;
214
+ const type = typeof n.$type === "string" ? n.$type : inheritedType;
215
+ if ("$value" in n) {
216
+ out.set(path.join("."), { value: JSON.stringify(n.$value), type, description: typeof n.$description === "string" ? n.$description : null });
217
+ return out;
218
+ }
219
+ for (const [k, v] of Object.entries(n)) if (!k.startsWith("$")) leaves(v, [...path, k], type, out);
220
+ return out;
221
+ }
222
+ const PALETTE_ROLES = { primary: "Brand primary", link: "Link", neutral: "Neutral", accent: "Accent", success: "Success", warning: "Warning", danger: "Danger", info: "Info", categorical: "Chart colours" };
223
+ function roleOfToken(path, description) {
224
+ const first = description?.split(/[.:;(]/)[0]?.trim();
225
+ if (first && first.length <= 40) return first;
226
+ const m = /^palette\.([a-z-]+)\./.exec(path);
227
+ if (m) return PALETTE_ROLES[m[1]] ?? designerName(`palette.${m[1]}`);
228
+ return designerName(path);
229
+ }
230
+ function stepOfToken(path) {
231
+ return path.startsWith("palette.") ? designerName(path) : null;
232
+ }
233
+ function designerName(path, description) {
234
+ const words = path.replace(/^(color|palette)\./, "").split(".").join(" ");
235
+ return words.charAt(0).toUpperCase() + words.slice(1);
236
+ }
237
+ function tokenChanges(file, before, after) {
238
+ const scope = fileScope(file);
239
+ if (scope === "none") return [];
240
+ const a = leaves(before);
241
+ const b = leaves(after);
242
+ const out = [];
243
+ for (const path of /* @__PURE__ */ new Set([...a.keys(), ...b.keys()])) {
244
+ const x = a.get(path);
245
+ const y = b.get(path);
246
+ if (x?.value === y?.value) continue;
247
+ out.push({
248
+ path,
249
+ cssVar: `--${path.split(".").join("-")}`,
250
+ name: roleOfToken(path, y?.description ?? x?.description ?? null),
251
+ step: stepOfToken(path),
252
+ type: y?.type ?? x?.type ?? null,
253
+ theme: scope.theme,
254
+ brand: scope.brand
255
+ });
256
+ }
257
+ return out;
258
+ }
259
+ function contrastRatio(a, b) {
260
+ const lum = (c) => {
261
+ const m = /^rgba?\((\d+),\s*(\d+),\s*(\d+)/.exec(c.trim());
262
+ if (!m) return null;
263
+ const [r, g, bl] = [m[1], m[2], m[3]].map((n) => {
264
+ const v = Number(n) / 255;
265
+ return v <= 0.03928 ? v / 12.92 : ((v + 0.055) / 1.055) ** 2.4;
266
+ });
267
+ return 0.2126 * r + 0.7152 * g + 0.0722 * bl;
268
+ };
269
+ const x = lum(a);
270
+ const y = lum(b);
271
+ if (x === null || y === null) return null;
272
+ return (Math.max(x, y) + 0.05) / (Math.min(x, y) + 0.05);
273
+ }
274
+ const isSubtle = (a, b) => {
275
+ const r = contrastRatio(a, b);
276
+ return r !== null && r < 1.1;
277
+ };
278
+ function toHex(color) {
279
+ const m = /^rgba?\((\d+),\s*(\d+),\s*(\d+)(?:,\s*([\d.]+))?\)$/.exec(color.trim());
280
+ if (!m) return color;
281
+ const hex = [m[1], m[2], m[3]].map((n) => Number(n).toString(16).padStart(2, "0")).join("");
282
+ return m[4] !== void 0 && Number(m[4]) < 1 ? `#${hex}${Math.round(Number(m[4]) * 255).toString(16).padStart(2, "0")}` : `#${hex}`;
283
+ }
284
+ function fromText(from, titleOf) {
285
+ const names = from.map((n) => titleOf(n) ?? `#${n}`);
286
+ return `from ${names.length <= 2 ? names.join(" and ") : `${names.slice(0, -1).join(", ")} and ${names[names.length - 1]}`}`;
287
+ }
288
+ export {
289
+ togetherLabel as A,
290
+ changeSummary as B,
291
+ dockAt as C,
292
+ stepDock as D,
293
+ DOCK_KEY as E,
294
+ OPEN_KEY as O,
295
+ PARAM as P,
296
+ SCROLL_PARAM as S,
297
+ TOGETHER_EMPTY_LINE as T,
298
+ roleOfToken as a,
299
+ changeMode as b,
300
+ changedScreens as c,
301
+ readableTitle as d,
302
+ toggleIncluded as e,
303
+ readReturn as f,
304
+ readUse as g,
305
+ shouldRender as h,
306
+ readDock as i,
307
+ readScroll as j,
308
+ sameScreenOn as k,
309
+ liveTokensUrl as l,
310
+ studioChangesUrl as m,
311
+ noVisibleDifference as n,
312
+ studioChangeUrl as o,
313
+ contrastRatio as p,
314
+ TOGETHER_HINT as q,
315
+ readChanges as r,
316
+ stepOfToken as s,
317
+ tokenChanges as t,
318
+ isSubtle as u,
319
+ toHex as v,
320
+ whereAmI as w,
321
+ fromText as x,
322
+ isFlipKey as y,
323
+ placeToolbar as z
324
+ };
325
+ //# sourceMappingURL=session-CzlIsFDY.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session-CzlIsFDY.js","sources":["../src/session.ts"],"sourcesContent":["// The Bridge toolbar's pure logic: when it shows, which change it follows, what Before and After mean for that\n// change, and the screens it lists. A product opens it only when Bridge Studio links here with `?bridge=<change>`;\n// production renders nothing, except the Before side of a code change, which arrives with a handoff from a preview.\n\nexport interface StorageLike {\n getItem(key: string): string | null;\n setItem(key: string, value: string): void;\n removeItem(key: string): void;\n}\n\n/**\n * What the toolbar knows about the product and its design system: from bridge.config.json (the product's entry in\n * products[] and previews), read at build time (the Admin demo) or from the design system repository at run time.\n */\nexport interface BridgeBuild {\n /** The repository whose pull requests `?bridge=<n>` names: the product's own repository, else the design system's. */\n owner: string;\n repo: string;\n /** Production Studio (previews.production.docs): the link back and the shipped tokens.css. */\n studio: string | null;\n /** The product in production (products[].production, else previews.production.<key>): Before of a code change. */\n production: string | null;\n /** The product's preview template (products[].preview): only these hosts may hand a code change over to production. */\n preview: string | null;\n /** VERCEL_ENV of the build ('production', 'preview', 'development'), null when unknown. */\n deployEnv: string | null;\n /** The library branch (repo.defaultBranch) and the token files' root (tokens.root): changes tried together. */\n defaultBranch?: string;\n tokensRoot?: string;\n /** The product's key (products[].key) and the design system repository (\"owner/name\"), for the way back to Studio. */\n product?: string;\n ds?: string;\n}\n\nexport const PARAM = 'bridge';\n/** Where After lives for a code change, carried to production on Before. */\nexport const RETURN_PARAM = 'bridge-return';\n/** Scroll position carried across a Before/After navigation. */\nexport const SCROLL_PARAM = 'bridge-scroll';\nexport const SESSION_KEY = 'bridge-admin:change';\nexport const RETURN_KEY = 'bridge-admin:return';\nexport const DOCK_KEY = 'bridge-admin:toolbar-dock';\nexport const OPEN_KEY = 'bridge-admin:toolbar-open';\n/** Changes tried together: the picks of the tokens they set differently (core encodePicks), from Studio. */\nexport const USE_PARAM = 'bridge-use';\nexport const USE_KEY = 'bridge-admin:use';\n/** At most this many changes are tried together. */\nexport const MAX_TOGETHER = 4;\n\nconst safe = <T>(fn: () => T, fallback: T): T => {\n try {\n return fn();\n } catch {\n return fallback;\n }\n};\n\n/**\n * The change the toolbar follows: `?bridge=<n>` wins and is kept for the session (the admin's hash router keeps the\n * query, a reload or a new path does not need it again); `?bridge=off` forgets it. Blocked storage only loses the\n * memory, never the toolbar of the current page.\n */\nexport function readChange(search: string, storage: StorageLike | null): number | null {\n return readChanges(search, storage)[0] ?? null;\n}\n\n/**\n * The change numbers in \"101\" or \"101,118,120\" (an encoded comma too): positive, at most 7 digits, no repeats, at\n * most MAX_TOGETHER. Anything else in the list makes it unreadable (empty), never a guess.\n */\nexport function parseChanges(raw: string | null | undefined): number[] {\n const parts = (raw ?? '').trim().split(/,|%2C/i).map((p) => p.trim());\n if (!parts.every((p) => /^\\d{1,7}$/.test(p) && Number(p) > 0)) return [];\n return [...new Set(parts.map(Number))].slice(0, MAX_TOGETHER);\n}\n\n/**\n * The changes the toolbar follows: one from \"Try it in the product\", 2 to 4 from \"Try them together\". Same memory as\n * one change: `?bridge=` wins and is kept for the session, `?bridge=off` forgets it, blocked storage only loses the\n * memory. The picks (`bridge-use`) travel with the list and are kept, or forgotten, with it.\n */\nexport function readChanges(search: string, storage: StorageLike | null): number[] {\n const params = new URLSearchParams(search);\n if (params.has(PARAM)) {\n const list = parseChanges(params.get(PARAM));\n if (!list.length) {\n if (storage) safe(() => (storage.removeItem(SESSION_KEY), storage.removeItem(USE_KEY)), undefined);\n return [];\n }\n if (storage) {\n safe(() => storage.setItem(SESSION_KEY, list.join(',')), undefined);\n const use = params.get(USE_PARAM);\n safe(() => (use && list.length > 1 ? storage.setItem(USE_KEY, use) : storage.removeItem(USE_KEY)), undefined);\n }\n return list;\n }\n return storage ? parseChanges(safe(() => storage.getItem(SESSION_KEY), null)) : [];\n}\n\n/** The picks that came with the list: from the address, else the session. */\nexport function readUse(search: string, storage: StorageLike | null): string | null {\n const params = new URLSearchParams(search);\n if (params.has(PARAM)) return params.get(USE_PARAM);\n return storage ? safe(() => storage.getItem(USE_KEY), null) : null;\n}\n\n/** A host that matches the product's preview template (`bridge-admin-git-{branch}-team.vercel.app`), https only. */\nexport function isPreviewUrl(raw: string | null | undefined, template: string | null): boolean {\n if (!raw || !template) return false;\n let url: URL;\n let tpl: URL;\n try {\n url = new URL(raw);\n tpl = new URL(template.replace(/\\{[a-z]+\\}/gi, 'x'));\n } catch {\n return false;\n }\n if (url.protocol !== 'https:' || url.username || url.password || url.port) return false;\n const tplHost = /^https?:\\/\\/([^/?#]+)/i.exec(template)?.[1]?.toLowerCase();\n if (!tplHost || tpl.protocol !== 'https:') return false;\n const pattern = new RegExp(`^${tplHost.split(/\\{[a-z]+\\}/i).map((p) => p.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&')).join('[a-z0-9-]+')}$`);\n return pattern.test(url.hostname.toLowerCase());\n}\n\n/** The preview to return to from production, from `?bridge-return=` or the session. Only a preview host counts. */\nexport function readReturn(search: string, storage: StorageLike | null, template: string | null): string | null {\n const raw = new URLSearchParams(search).get(RETURN_PARAM);\n if (raw !== null) {\n if (isPreviewUrl(raw, template)) {\n const origin = new URL(raw).origin;\n if (storage) safe(() => storage.setItem(RETURN_KEY, origin), undefined);\n return origin;\n }\n return null;\n }\n const stored = storage ? safe(() => storage.getItem(RETURN_KEY), null) : null;\n return isPreviewUrl(stored, template) ? new URL(stored!).origin : null;\n}\n\nexport type Where = 'production' | 'preview' | 'local';\n\n/** Which deployment this page is: the product in production by host or by build, a preview, or a local build. */\nexport function whereAmI(host: string, build: Pick<BridgeBuild, 'production' | 'deployEnv'>): Where {\n const prodHost = build.production ? safe(() => new URL(build.production!).host, null) : null;\n if (build.deployEnv === 'production' || (prodHost && host.toLowerCase() === prodHost.toLowerCase())) return 'production';\n if (build.deployEnv === 'preview') return 'preview';\n return 'local';\n}\n\n/**\n * Whether the toolbar renders. Never without a change from Studio. In production only for the Before side of a code\n * change, that is with a preview to return to: production on its own renders nothing.\n */\nexport function shouldRender(input: { change: number | null; where: Where; returnTo: string | null; together?: boolean }): boolean {\n if (input.change === null) return false;\n // Changes tried together run on production: no preview holds all of them.\n if (input.together) return true;\n if (input.where === 'production') return input.returnTo !== null;\n return true;\n}\n\n/** \"These 3 changes\": the After side of changes tried together, counting the ones switched on. */\nexport const togetherLabel = (n: number) => (n === 1 ? 'This change' : `These ${n} changes`);\n\n/** The one hint line of changes tried together: where they run and why. */\nexport const TOGETHER_HINT = \"The live product, with these changes' tokens applied in your browser only. No preview has all of them together.\";\n\n/** Show what changed found nothing on this screen for changes tried together. */\nexport const TOGETHER_EMPTY_LINE = \"These changes don't touch this screen. Try another screen.\";\n\n/** Switches one change on or off, keeping the order Studio listed them in. */\nexport function toggleIncluded(all: number[], included: number[], n: number, on: boolean): number[] {\n const next = new Set(included);\n if (on) next.add(n);\n else next.delete(n);\n return all.filter((x) => next.has(x));\n}\n\n/** Studio's Changes list, where changes are tried together (and the way back). */\nexport function studioChangesUrl(build: Pick<BridgeBuild, 'studio' | 'owner' | 'repo'>): string {\n return build.studio ? `${build.studio.replace(/\\/$/, '')}/studio/changes/` : `https://github.com/${build.owner}/${build.repo}/pulls`;\n}\n\nexport type ChangeMode = 'tokens' | 'code';\n\n/** Token files (DTCG and the contrast gate) and changesets only: Before swaps tokens live. Anything else is code. */\nexport function changeMode(files: string[]): ChangeMode {\n const tokenOnly = files.length > 0 && files.every((f) => f.endsWith('.tokens.json') || /contrast\\.checks\\.json$/.test(f) || f.startsWith('.changeset/'));\n return tokenOnly ? 'tokens' : 'code';\n}\n\n/** The live tokens (main's build, served by production Studio at `/tokens.css`), or null when not configured. */\nexport function liveTokensUrl(build: Pick<BridgeBuild, 'studio'>): string | null {\n return build.studio ? safe(() => new URL('tokens.css', build.studio!.replace(/\\/?$/, '/')).href, null) : null;\n}\n\n/**\n * Studio's page for the change. A change in a product's own repository (an exploration there) opens as\n * `?exploration=<n>&product=<key>`; a change of the design system repository as `?pr=<n>`.\n */\nexport function studioChangeUrl(build: Pick<BridgeBuild, 'studio' | 'owner' | 'repo' | 'product' | 'ds'>, change: number): string {\n if (!build.studio) return `https://github.com/${build.owner}/${build.repo}/pull/${change}`;\n const studio = build.studio.replace(/\\/$/, '');\n const own = build.product && build.ds && build.ds.toLowerCase() !== `${build.owner}/${build.repo}`.toLowerCase();\n return own ? `${studio}/studio/changes/?exploration=${change}&product=${encodeURIComponent(build.product!)}` : `${studio}/studio/changes/?pr=${change}`;\n}\n\n/** Shape of a visual impact manifest that the toolbar reads (written by @bridge-ds/visual). */\nexport interface ManifestShot {\n kind: 'story' | 'route';\n label: string;\n route?: string;\n tenant?: string;\n theme: string;\n status: string;\n percent: number;\n}\n\nexport interface Screen {\n label: string;\n route: string;\n tenant: string;\n theme: string;\n percent: number;\n}\n\n/** The product screens the change alters, most changed first, one entry per screen, shop and theme. */\nexport function changedScreens(shots: ManifestShot[] | null | undefined): Screen[] {\n if (!Array.isArray(shots)) return [];\n const seen = new Set<string>();\n return shots\n .filter((s) => s.kind === 'route' && s.route && s.tenant && (s.status === 'changed' || s.status === 'added'))\n .sort((a, b) => b.percent - a.percent)\n .filter((s) => {\n const key = `${s.route}|${s.tenant}|${s.theme}`;\n if (seen.has(key)) return false;\n seen.add(key);\n return true;\n })\n .map((s) => ({ label: s.label, route: s.route!, tenant: s.tenant!, theme: s.theme, percent: s.percent }));\n}\n\n/**\n * The same page on another deployment (code changes: production for Before, the preview for After), with the\n * change, the way back and the scroll position in the query. The hash route (ra-core) is kept.\n */\nexport function sameScreenOn(target: string, here: { search: string; hash: string }, extra: { change: number; returnTo?: string | null; scroll?: number }): string {\n const url = new URL(target);\n const params = new URLSearchParams(here.search);\n params.delete(RETURN_PARAM);\n params.delete(SCROLL_PARAM);\n params.set(PARAM, String(extra.change));\n if (extra.returnTo) params.set(RETURN_PARAM, extra.returnTo);\n if (extra.scroll && extra.scroll > 0) params.set(SCROLL_PARAM, String(Math.round(extra.scroll)));\n url.pathname = '/';\n url.search = params.toString();\n url.hash = here.hash;\n return url.href;\n}\n\n/** The scroll position a Before/After navigation carried, once: the caller removes it from the address. */\nexport function readScroll(search: string): number | null {\n const raw = new URLSearchParams(search).get(SCROLL_PARAM);\n return raw && /^\\d{1,7}$/.test(raw) ? Number(raw) : null;\n}\n\nexport type Dock = 'start' | 'center' | 'end';\nconst DOCKS: Dock[] = ['start', 'center', 'end'];\n\n/** Bottom right by default: the corner a back office leaves free most often. */\nexport function readDock(storage: StorageLike | null): Dock {\n const v = storage ? safe(() => storage.getItem(DOCK_KEY), null) : null;\n return DOCKS.includes(v as Dock) ? (v as Dock) : 'end';\n}\n\n/** One step left (-1) or right (+1), stopping at the edges. */\nexport function stepDock(dock: Dock, step: -1 | 1): Dock {\n return DOCKS[Math.min(DOCKS.length - 1, Math.max(0, DOCKS.indexOf(dock) + step))]!;\n}\n\n/** The dock nearest to where a drag ended: thirds of the viewport width. */\nexport function dockAt(x: number, width: number): Dock {\n if (width <= 0) return 'center';\n const r = x / width;\n return r < 1 / 3 ? 'start' : r > 2 / 3 ? 'end' : 'center';\n}\n\n/** Title of a change as people read it: conventional prefixes (\"tokens: \") dropped, first letter up. */\nexport function readableTitle(title: string): string {\n const t = title.replace(/^\\s*[a-z]+(\\([^)]*\\))?!?:\\s*/i, '').trim() || title.trim();\n return t.charAt(0).toUpperCase() + t.slice(1);\n}\n\n/** The B shortcut flips Before and After, unless the person is typing or holds a modifier. */\nexport function isFlipKey(e: { key: string; altKey: boolean; ctrlKey: boolean; metaKey: boolean; target: unknown }): boolean {\n if (e.altKey || e.ctrlKey || e.metaKey || e.key.toLowerCase() !== 'b') return false;\n const t = e.target as { tagName?: string; isContentEditable?: boolean; closest?: (s: string) => unknown } | null;\n if (!t) return true;\n if (t.isContentEditable) return false;\n const tag = (t.tagName ?? '').toLowerCase();\n if (tag === 'input' || tag === 'textarea' || tag === 'select') return false;\n return !(t.closest && t.closest('[role=\"combobox\"], [role=\"textbox\"], [role=\"searchbox\"]'));\n}\n\nexport interface Rect {\n left: number;\n top: number;\n right: number;\n bottom: number;\n}\n\nconst overlaps = (a: Rect, b: Rect, margin: number) => a.left < b.right + margin && a.right > b.left - margin && a.top < b.bottom + margin && a.bottom > b.top - margin;\n\n/**\n * Where the toolbar goes so it never covers a primary or submit button of the page: the chosen dock if it is free,\n * else the opposite corner, else the centre, else the chosen dock lifted just above the buttons it would cover. The\n * docks sit inside the area [left, right] (the content column, so it never sits over the sidebar).\n */\nexport function placeToolbar(input: { left: number; right: number; vh: number; w: number; h: number; gutter: number; preferred: Dock; obstacles: Rect[]; margin?: number }): { dock: Dock; lift: number } {\n const { left: areaLeft, right: areaRight, vh, w, h, gutter, preferred, obstacles } = input;\n const margin = input.margin ?? 8;\n const rectAt = (dock: Dock, lift = 0): Rect => {\n const left = dock === 'start' ? areaLeft : dock === 'end' ? areaRight - w : (areaLeft + areaRight - w) / 2;\n const bottom = vh - gutter - lift;\n return { left, right: left + w, top: bottom - h, bottom };\n };\n const opposite: Dock = preferred === 'start' ? 'end' : 'start';\n const order: Dock[] = [preferred, opposite, ...DOCKS.filter((d) => d !== preferred && d !== opposite)];\n for (const dock of order) {\n const r = rectAt(dock);\n if (!obstacles.some((o) => overlaps(r, o, margin))) return { dock, lift: 0 };\n }\n // Every dock covers something: stay put and rise above the highest button in the way.\n const base = rectAt(preferred);\n const inWay = obstacles.filter((o) => o.left < base.right + margin && o.right > base.left - margin && o.bottom > base.top - margin);\n const top = Math.min(...inWay.map((o) => o.top));\n const lift = Math.max(0, Math.ceil(base.bottom - (top - margin)));\n return { dock: preferred, lift: Math.min(lift, Math.max(0, vh - h - 2 * gutter)) };\n}\n\n/** What one token file can change on screen: a theme and a brand, or every one of them (null). */\nfunction fileScope(file: string): { theme: string | null; brand: string | null } | 'none' {\n if (file.startsWith('.changeset/')) return 'none';\n let m = /(?:^|\\/)semantic\\/([a-z0-9-]+)\\.tokens\\.json$/i.exec(file);\n if (m && m[1] !== 'base') return { theme: m[1]!, brand: null };\n m = /(?:^|\\/)brands\\/([a-z0-9-]+)\\.([a-z0-9-]+)\\.tokens\\.json$/i.exec(file);\n if (m) return { theme: m[2]!, brand: m[1]! };\n m = /(?:^|\\/)brands\\/([a-z0-9-]+)\\.tokens\\.json$/i.exec(file);\n if (m) return { theme: null, brand: m[1]! };\n // Primitives, base, density, components and anything else: could change every theme and brand.\n return { theme: null, brand: null };\n}\n\nexport type NoDifference = { kind: 'theme'; here: string; touched: string } | { kind: 'brand'; here: string; touched: string };\n\n/**\n * Whether a token change shows nothing in this theme and brand, from the token files it edits (DTCG layers named\n * semantic/<theme>, brands/<brand>.<theme>, brands/<brand>). Null when it may show, or when that cannot be told:\n * never claims \"no difference\" on a guess.\n */\nexport function noVisibleDifference(files: string[], here: { theme: string; brand: string }): NoDifference | null {\n const scopes = files.map(fileScope).filter((s): s is { theme: string | null; brand: string | null } => s !== 'none');\n if (!scopes.length) return null;\n const hits = (theme: string, brand: string) => scopes.some((s) => (s.theme === null || s.theme === theme) && (s.brand === null || s.brand === brand));\n if (hits(here.theme, here.brand)) return null;\n const theme = scopes.find((s) => s.theme !== null && s.theme !== here.theme && (s.brand === null || s.brand === here.brand))?.theme;\n if (theme) return { kind: 'theme', here: here.theme, touched: theme };\n const brand = scopes.find((s) => s.brand !== null && s.brand !== here.brand)?.brand;\n return brand ? { kind: 'brand', here: here.brand, touched: brand } : null;\n}\n\n/** \"darker default background.\" The change title as a short phrase after \"This change:\". */\nexport function changeSummary(title: string): string {\n const t = readableTitle(title).replace(/[.\\s]+$/, '');\n if (!t) return '';\n const lower = /^[A-Z][a-z]/.test(t) ? t.charAt(0).toLowerCase() + t.slice(1) : t;\n return `${lower}.`;\n}\n\nexport interface TokenChange {\n /** Token path, \"color.bg.default\". */\n path: string;\n /** CSS variable the token builds to, \"--color-bg-default\". */\n cssVar: string;\n /** The role it plays, what designers call it: \"Page canvas\" (its description), \"Brand primary\" (a palette role). */\n name: string;\n /** The exact step, second: \"Primary 700\" for a palette step, null for a semantic token. */\n step: string | null;\n type: string | null;\n /** The theme and brand its file applies to (null: every one). */\n theme: string | null;\n brand: string | null;\n}\n\ntype TokenNode = { $value?: unknown; $type?: string; $description?: string; [k: string]: unknown };\ntype Leaf = { value: string; type: string | null; description: string | null };\n\nfunction leaves(node: unknown, path: string[] = [], inheritedType: string | null = null, out = new Map<string, Leaf>()): Map<string, Leaf> {\n if (!node || typeof node !== 'object') return out;\n const n = node as TokenNode;\n const type = typeof n.$type === 'string' ? n.$type : inheritedType;\n if ('$value' in n) {\n out.set(path.join('.'), { value: JSON.stringify(n.$value), type, description: typeof n.$description === 'string' ? n.$description : null });\n return out;\n }\n for (const [k, v] of Object.entries(n)) if (!k.startsWith('$')) leaves(v, [...path, k], type, out);\n return out;\n}\n\n/** Palette roles a brand re-points (brands/<brand>.tokens.json), in designers' words. */\nconst PALETTE_ROLES: Record<string, string> = { primary: 'Brand primary', link: 'Link', neutral: 'Neutral', accent: 'Accent', success: 'Success', warning: 'Warning', danger: 'Danger', info: 'Info', categorical: 'Chart colours' };\n\n/** The role a token plays: its description's first phrase, a palette role (\"Brand primary\"), else its path in words. */\nexport function roleOfToken(path: string, description: string | null): string {\n const first = description?.split(/[.:;(]/)[0]?.trim();\n if (first && first.length <= 40) return first;\n const m = /^palette\\.([a-z-]+)\\./.exec(path);\n if (m) return PALETTE_ROLES[m[1]!] ?? designerName(`palette.${m[1]}`, null);\n return designerName(path, null);\n}\n\n/** The palette step, shown second: \"palette.primary.700\" -> \"Primary 700\"; null for other tokens. */\nexport function stepOfToken(path: string): string | null {\n return path.startsWith('palette.') ? designerName(path, null) : null;\n}\n\n/** Designer name of a token: the first phrase of its description, else its path in words (\"Bg default\"). */\nexport function designerName(path: string, description: string | null): string {\n const first = description?.split(/[.:;(]/)[0]?.trim();\n if (first && first.length <= 40) return first;\n const words = path.replace(/^(color|palette)\\./, '').split('.').join(' ');\n return words.charAt(0).toUpperCase() + words.slice(1);\n}\n\n/**\n * The tokens one file changes between two versions of it (parsed DTCG JSON, null when the file is added or removed):\n * every leaf whose $value differs, with the theme and brand its layer applies to.\n */\nexport function tokenChanges(file: string, before: unknown, after: unknown): TokenChange[] {\n const scope = fileScope(file);\n if (scope === 'none') return [];\n const a = leaves(before);\n const b = leaves(after);\n const out: TokenChange[] = [];\n for (const path of new Set([...a.keys(), ...b.keys()])) {\n const x = a.get(path);\n const y = b.get(path);\n if (x?.value === y?.value) continue;\n out.push({\n path,\n cssVar: `--${path.split('.').join('-')}`,\n name: roleOfToken(path, y?.description ?? x?.description ?? null),\n step: stepOfToken(path),\n type: y?.type ?? x?.type ?? null,\n theme: scope.theme,\n brand: scope.brand,\n });\n }\n return out;\n}\n\n/** WCAG contrast ratio between two rgb() colours, or null when either is not rgb (a non-colour value). */\nexport function contrastRatio(a: string, b: string): number | null {\n const lum = (c: string) => {\n const m = /^rgba?\\((\\d+),\\s*(\\d+),\\s*(\\d+)/.exec(c.trim());\n if (!m) return null;\n const [r, g, bl] = [m[1], m[2], m[3]].map((n) => {\n const v = Number(n) / 255;\n return v <= 0.03928 ? v / 12.92 : ((v + 0.055) / 1.055) ** 2.4;\n }) as [number, number, number];\n return 0.2126 * r + 0.7152 * g + 0.0722 * bl;\n };\n const x = lum(a);\n const y = lum(b);\n if (x === null || y === null) return null;\n return (Math.max(x, y) + 0.05) / (Math.min(x, y) + 0.05);\n}\n\n/** Two colours under 1.1:1 read as the same at a glance: the swatch says \"Subtle change\" so it does not look broken. */\nexport const isSubtle = (a: string, b: string): boolean => {\n const r = contrastRatio(a, b);\n return r !== null && r < 1.1;\n};\n\n/** rgb(245, 247, 249) -> #f5f7f9; anything else as is. */\nexport function toHex(color: string): string {\n const m = /^rgba?\\((\\d+),\\s*(\\d+),\\s*(\\d+)(?:,\\s*([\\d.]+))?\\)$/.exec(color.trim());\n if (!m) return color;\n const hex = [m[1], m[2], m[3]].map((n) => Number(n).toString(16).padStart(2, '0')).join('');\n return m[4] !== undefined && Number(m[4]) < 1 ? `#${hex}${Math.round(Number(m[4]) * 255).toString(16).padStart(2, '0')}` : `#${hex}`;\n}\n\n/** \"from Darker default background\", \"from #101 and #118\": which change a token's value comes from (changes tried together). */\nexport function fromText(from: number[], titleOf: (n: number) => string | null): string {\n const names = from.map((n) => titleOf(n) ?? `#${n}`);\n return `from ${names.length <= 2 ? names.join(' and ') : `${names.slice(0, -1).join(', ')} and ${names[names.length - 1]}`}`;\n}\n"],"names":["lift","theme","brand"],"mappings":"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"}