@0001-labs/design-system-1 0.3.0 → 0.3.1

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.
Files changed (43) hide show
  1. package/DS1/0-face/constraint-panel.ts +27 -6
  2. package/DS1/0-face/constraint-site.ts +3 -2
  3. package/DS1/0-face/constraints.ts +27 -0
  4. package/DS1/0-face/define.ts +1 -1
  5. package/DS1/0-face/fault-lines.ts +159 -0
  6. package/DS1/1-root/cjk.css +1 -1
  7. package/DS1/1-root/fonts.css +1 -1
  8. package/DS1/1-root/one.css +3 -2
  9. package/DS1/2-core/ds-avatar.ts +47 -8
  10. package/DS1/2-core/styles/ds-button-host.css +2 -1
  11. package/DS1/2-core/styles/ds-button.css +2 -1
  12. package/DS1/2-core/styles/ds-tag.css +2 -1
  13. package/DS1/3-unit/context-trigger.ts +29 -2
  14. package/DS1/3-unit/ds-form.ts +2 -1
  15. package/DS1/3-unit/ds-table.ts +2 -1
  16. package/DS1/4-page/ds-container.ts +2 -1
  17. package/dist/0-face/constraint-panel.d.ts.map +1 -1
  18. package/dist/0-face/constraint-panel.js +28 -6
  19. package/dist/0-face/constraint-site.d.ts +1 -0
  20. package/dist/0-face/constraint-site.d.ts.map +1 -1
  21. package/dist/0-face/constraint-site.js +3 -2
  22. package/dist/0-face/constraints.d.ts.map +1 -1
  23. package/dist/0-face/constraints.js +27 -0
  24. package/dist/0-face/define.js +1 -1
  25. package/dist/0-face/fault-lines.d.ts +20 -0
  26. package/dist/0-face/fault-lines.d.ts.map +1 -0
  27. package/dist/0-face/fault-lines.js +172 -0
  28. package/dist/2-core/ds-avatar.d.ts +5 -0
  29. package/dist/2-core/ds-avatar.d.ts.map +1 -1
  30. package/dist/2-core/ds-avatar.js +53 -9
  31. package/dist/3-unit/context-trigger.d.ts.map +1 -1
  32. package/dist/3-unit/context-trigger.js +32 -2
  33. package/dist/3-unit/ds-form.d.ts.map +1 -1
  34. package/dist/3-unit/ds-form.js +2 -1
  35. package/dist/3-unit/ds-table.d.ts.map +1 -1
  36. package/dist/3-unit/ds-table.js +2 -1
  37. package/dist/4-page/ds-container.d.ts.map +1 -1
  38. package/dist/4-page/ds-container.js +2 -1
  39. package/dist/design-system-1.bundle.js +372 -32
  40. package/dist/design-system-1.bundle.js.map +1 -1
  41. package/dist/design-system-1.bundle.min.js +3380 -3128
  42. package/dist/design-system-1.bundle.min.js.map +1 -1
  43. package/package.json +6 -4
@@ -2260,6 +2260,198 @@ function initGridFit() {
2260
2260
  fit();
2261
2261
  }
2262
2262
  //#endregion
2263
+ //#region DS1/0-face/fault-lines.ts
2264
+ var tolerance = 1;
2265
+ var near$1 = (a, b) => Math.abs(a - b) <= tolerance;
2266
+ var style = (el) => el.ownerDocument.defaultView.getComputedStyle(el);
2267
+ var visible$1 = (el) => {
2268
+ const box = el.getBoundingClientRect();
2269
+ return box.width > 0 && box.height > 0 && style(el).visibility !== "hidden" && style(el).display !== "none";
2270
+ };
2271
+ /** Walk each open shadow tree once; slotted children already occur in the light tree. */
2272
+ function elements(root) {
2273
+ return Array.from(root.querySelectorAll("*")).flatMap((el) => [el, ...el.shadowRoot ? elements(el.shadowRoot) : []]);
2274
+ }
2275
+ /** Related grids opt in by name; unlabelled card grids are candidates for human review. */
2276
+ function faultLineGrids(root) {
2277
+ return elements(root).flatMap((element) => {
2278
+ const family = element.getAttribute("data-ds-fault-lines");
2279
+ if (family === "off" || !visible$1(element)) return [];
2280
+ const children = Array.from(element.children).filter(visible$1);
2281
+ if (!children.length) return [];
2282
+ if (family === null && (!/grid/.test(style(element).display) || children.length < 2 || !children.every((child) => child.matches("ds-card, .ds-card, ds-button, button")))) return [];
2283
+ const axis = element.getAttribute("data-ds-fault-axis") === "y" ? "y" : "x";
2284
+ const boxes = children.map((child) => child.getBoundingClientRect());
2285
+ const crossStart = (box) => axis === "x" ? box.top : box.left;
2286
+ const crossEnd = (box) => axis === "x" ? box.bottom : box.right;
2287
+ const start = (box) => axis === "x" ? box.left : box.top;
2288
+ const end = (box) => axis === "x" ? box.right : box.bottom;
2289
+ const bands = [];
2290
+ for (const box of boxes.sort((a, b) => crossStart(a) - crossStart(b))) {
2291
+ const band = bands.find((row) => near$1(crossStart(row[0]), crossStart(box)));
2292
+ if (band) band.push(box);
2293
+ else bands.push([box]);
2294
+ }
2295
+ const band = [...bands].sort((a, b) => b.length - a.length)[0].sort((a, b) => start(a) - start(b));
2296
+ const lines = band.flatMap((box) => [start(box), end(box)]);
2297
+ const gaps = band.slice(1).map((box, index) => start(box) - end(band[index]));
2298
+ const crossGaps = bands.slice(1).map((row, index) => Math.min(...row.map(crossStart)) - Math.max(...bands[index].map(crossEnd)));
2299
+ const internalDrift = bands.some((row) => row.length === band.length && row.slice().sort((a, b) => start(a) - start(b)).some((box, index) => !near$1(start(box), lines[index * 2]) || !near$1(end(box), lines[index * 2 + 1]))) || crossGaps.some((gap) => !near$1(gap, crossGaps[0]));
2300
+ return [{
2301
+ element,
2302
+ family,
2303
+ axis,
2304
+ lines,
2305
+ gaps,
2306
+ crossGap: crossGaps[0],
2307
+ internalDrift,
2308
+ start: lines[0],
2309
+ end: lines[lines.length - 1]
2310
+ }];
2311
+ });
2312
+ }
2313
+ function referenceFor(grids, i) {
2314
+ const current = grids[i];
2315
+ return grids.slice(0, i).find((other) => {
2316
+ if (other.axis !== current.axis || other.lines.length !== current.lines.length) return false;
2317
+ if (current.family !== null || other.family !== null) return current.family !== null && current.family === other.family;
2318
+ return near$1(current.start, other.start) && near$1(current.end, other.end);
2319
+ });
2320
+ }
2321
+ /** Fault lines compare rendered tracks across separate sections, in the current viewport. */
2322
+ function inspectFaultLines(root) {
2323
+ const grids = faultLineGrids(root);
2324
+ const findings = [];
2325
+ for (let i = 0; i < grids.length; i++) {
2326
+ const current = grids[i];
2327
+ if (current.internalDrift) findings.push({
2328
+ element: current.element,
2329
+ ...current.family === null ? { review: true } : {},
2330
+ message: `Fault lines at ${current.element.ownerDocument.documentElement.clientWidth}px: tracks or repeated gaps drift inside ${current.family || "this card grid"}. Align its complete rows and repeated spacing.`
2331
+ });
2332
+ const reference = referenceFor(grids, i);
2333
+ if (!reference) continue;
2334
+ const drift = current.lines.map((line, index) => Math.abs(line - reference.lines[index]));
2335
+ const gapDrift = current.gaps.some((gap, index) => !near$1(gap, reference.gaps[index]));
2336
+ const rowDrift = current.crossGap !== void 0 && reference.crossGap !== void 0 && !near$1(current.crossGap, reference.crossGap);
2337
+ if (!drift.some((value) => value > tolerance) && !gapDrift && !rowDrift) continue;
2338
+ const width = current.element.ownerDocument.documentElement.clientWidth;
2339
+ const review = current.family === null;
2340
+ findings.push({
2341
+ element: current.element,
2342
+ ...review ? { review: true } : {},
2343
+ message: `Fault lines at ${width}px: ${current.family || "these card grids"} ${review ? "may be related but have" : "have"} misaligned ${current.axis === "x" ? "column" : "row"} edges or unequal gaps (edge drift ${Math.round(Math.max(...drift))}px; gaps ${current.gaps.map(Math.round).join("/")}px versus ${reference.gaps.map(Math.round).join("/")}px${rowDrift ? `; cross-axis gap ${Math.round(current.crossGap)}px versus ${Math.round(reference.crossGap)}px` : ""}). ${review ? "Name the related groups with data-ds-fault-lines to enforce alignment, or mark an intentional exception off." : "Use the same tracks and gap for this family at this viewport."}`
2344
+ });
2345
+ }
2346
+ return findings;
2347
+ }
2348
+ /** The same rendered geometry as the checker, painted without affecting layout or hit testing. */
2349
+ function paintFaultLines(root, layer) {
2350
+ const doc = layer.ownerDocument;
2351
+ const width = doc.documentElement.clientWidth;
2352
+ const height = doc.defaultView.innerHeight;
2353
+ layer.setAttribute("viewBox", `0 0 ${width} ${height}`);
2354
+ layer.setAttribute("width", String(width));
2355
+ layer.setAttribute("height", String(height));
2356
+ layer.setAttribute("aria-hidden", "true");
2357
+ layer.style.cssText = "position:fixed;inset:0;pointer-events:none;overflow:hidden;z-index:2147483644";
2358
+ layer.replaceChildren();
2359
+ const grids = faultLineGrids(root);
2360
+ const failed = new Set(inspectFaultLines(root).map((finding) => finding.element));
2361
+ const drawn = /* @__PURE__ */ new Set();
2362
+ const referenceInk = "#087ba8";
2363
+ const faultInk = "#de0331";
2364
+ function mark(tag, attributes, title) {
2365
+ const node = doc.createElementNS("http://www.w3.org/2000/svg", tag);
2366
+ for (const [key, value] of Object.entries(attributes)) node.setAttribute(key, String(value));
2367
+ if (title) {
2368
+ const tip = doc.createElementNS(node.namespaceURI, "title");
2369
+ tip.textContent = title;
2370
+ node.appendChild(tip);
2371
+ }
2372
+ layer.appendChild(node);
2373
+ return node;
2374
+ }
2375
+ for (let index = 0; index < grids.length; index++) {
2376
+ const grid = grids[index];
2377
+ const reference = referenceFor(grids, index) ?? grid;
2378
+ for (const edge of reference.lines) {
2379
+ const key = `${grid.axis}:${edge}`;
2380
+ if (drawn.has(key)) continue;
2381
+ drawn.add(key);
2382
+ mark("line", {
2383
+ "data-fault-reference": "",
2384
+ x1: grid.axis === "x" ? edge : 0,
2385
+ y1: grid.axis === "y" ? edge : 0,
2386
+ x2: grid.axis === "x" ? edge : width,
2387
+ y2: grid.axis === "y" ? edge : height,
2388
+ stroke: referenceInk,
2389
+ "stroke-width": 1,
2390
+ "stroke-dasharray": "4 4"
2391
+ }, "Reference edge");
2392
+ }
2393
+ if (!failed.has(grid.element)) continue;
2394
+ for (const child of Array.from(grid.element.children).filter(visible$1)) {
2395
+ const box = child.getBoundingClientRect();
2396
+ const edges = grid.axis === "x" ? [box.left, box.right] : [box.top, box.bottom];
2397
+ for (const edge of edges) {
2398
+ if (reference.lines.some((expected) => near$1(edge, expected))) continue;
2399
+ mark("line", {
2400
+ "data-fault-actual": "",
2401
+ x1: grid.axis === "x" ? edge : box.left,
2402
+ y1: grid.axis === "y" ? edge : box.top,
2403
+ x2: grid.axis === "x" ? edge : box.right,
2404
+ y2: grid.axis === "y" ? edge : box.bottom,
2405
+ stroke: faultInk,
2406
+ "stroke-width": 2
2407
+ }, "Actual edge differs from the dashed reference");
2408
+ }
2409
+ }
2410
+ const rows = [];
2411
+ for (const child of Array.from(grid.element.children).filter(visible$1)) {
2412
+ const box = child.getBoundingClientRect();
2413
+ const start = grid.axis === "x" ? box.top : box.left;
2414
+ const row = rows.find((row) => near$1(grid.axis === "x" ? row[0].top : row[0].left, start));
2415
+ if (row) row.push(box);
2416
+ else rows.push([box]);
2417
+ }
2418
+ rows.sort((a, b) => grid.axis === "x" ? a[0].top - b[0].top : a[0].left - b[0].left);
2419
+ const bounds = grid.element.getBoundingClientRect();
2420
+ const crossGap = reference.crossGap ?? grid.crossGap;
2421
+ if (crossGap !== void 0) for (let row = 1; row < rows.length; row++) {
2422
+ const previousEnd = Math.max(...rows[row - 1].map((box) => grid.axis === "x" ? box.bottom : box.right));
2423
+ const actual = Math.min(...rows[row].map((box) => grid.axis === "x" ? box.top : box.left));
2424
+ const expected = previousEnd + crossGap;
2425
+ if (near$1(actual, expected)) continue;
2426
+ for (const [edge, kind] of [[expected, "reference"], [actual, "actual"]]) mark("line", {
2427
+ [`data-fault-${kind}`]: "",
2428
+ x1: grid.axis === "x" ? bounds.left : edge,
2429
+ y1: grid.axis === "x" ? edge : bounds.top,
2430
+ x2: grid.axis === "x" ? bounds.right : edge,
2431
+ y2: grid.axis === "x" ? edge : bounds.bottom,
2432
+ stroke: kind === "reference" ? referenceInk : faultInk,
2433
+ "stroke-width": kind === "reference" ? 1 : 2,
2434
+ "stroke-dasharray": kind === "reference" ? "4 4" : ""
2435
+ }, `Row gap ${Math.round(actual - previousEnd)}px; reference ${Math.round(crossGap)}px`);
2436
+ }
2437
+ const box = grid.element.getBoundingClientRect();
2438
+ const label = mark("text", {
2439
+ "data-fault-gap": "",
2440
+ x: Math.max(4, box.left),
2441
+ y: Math.max(16, Math.min(height - 4, box.top + 16)),
2442
+ fill: faultInk,
2443
+ stroke: "var(--ds-background-color, white)",
2444
+ "stroke-width": 3,
2445
+ "paint-order": "stroke",
2446
+ "font-family": "var(--ds-typeface-regular, sans-serif)",
2447
+ "font-size": 12
2448
+ });
2449
+ const gaps = grid.gaps.map((gap) => Math.round(gap)).join("/");
2450
+ const expected = reference.gaps.map((gap) => Math.round(gap)).join("/");
2451
+ label.textContent = `Gap ${gaps || "—"}px · reference ${expected || "—"}px` + (grid.crossGap !== void 0 && reference.crossGap !== void 0 && !near$1(grid.crossGap, reference.crossGap) ? ` · row gap ${Math.round(grid.crossGap)}px / ${Math.round(reference.crossGap)}px` : "");
2452
+ }
2453
+ }
2454
+ //#endregion
2263
2455
  //#region DS1/0-face/button-label.ts
2264
2456
  function labelledButtons(root) {
2265
2457
  return [...root.querySelectorAll("ds-button:not([icon]):not([split]),button:not(ds-button button),a[role=\"button\"]")].filter((button) => button.getBoundingClientRect().width && (button.textContent?.trim() || button.getAttribute("text")));
@@ -2635,6 +2827,11 @@ function inspectWordWrap(root) {
2635
2827
  }
2636
2828
  //#endregion
2637
2829
  //#region DS1/0-face/constraints.ts
2830
+ /** The actual initial, including a component nested in another open shadow root. */
2831
+ function isAvatarInitial(element) {
2832
+ for (let parent = element; parent; parent = parent.assignedSlot ?? parent.parentElement ?? parent.getRootNode().host ?? null) if (parent.matches("ds-avatar, .ds-avatar")) return true;
2833
+ return false;
2834
+ }
2638
2835
  /** A pixel either way is the same place. */
2639
2836
  var near = (a, b) => Math.abs(a - b) <= 1;
2640
2837
  var px = (n) => `${Math.round(n)}px`;
@@ -2697,6 +2894,19 @@ function rows(found) {
2697
2894
  return groups.filter((row) => row.length > 1);
2698
2895
  }
2699
2896
  var constraints = [
2897
+ {
2898
+ code: "page.fault-lines",
2899
+ rule: "Fault lines keep related card and control grids aligned across the whole page, with matching track edges and gaps at every checked viewport width.",
2900
+ why: "Compare separate sections in the same alignment family; responsive layouts may change together, while unrelated groups and intentional exceptions remain independent.",
2901
+ components: ["ds-page"],
2902
+ check: {
2903
+ file: "tests/fault-lines.test.ts",
2904
+ test: "fault lines catch shifted tracks and unequal gaps across sections at each viewport"
2905
+ },
2906
+ measures: ["alignment group", "alignment groups"],
2907
+ count: (root) => faultLineGrids(root).length,
2908
+ inspect: inspectFaultLines
2909
+ },
2700
2910
  {
2701
2911
  code: "select.hug",
2702
2912
  rule: "Select and date-picker triggers use hug sizing, never fill sizing or growing to occupy the available row.",
@@ -2759,6 +2969,19 @@ var constraints = [
2759
2969
  count: (root) => measureTextContrast(root).length,
2760
2970
  inspect: inspectTextContrast
2761
2971
  },
2972
+ {
2973
+ code: "avatar.initial-contrast",
2974
+ rule: "Avatar initials have at least 4.5:1 contrast against their rendered fill, including after a theme or identity colour changes.",
2975
+ why: "Check each initial inside the avatar’s shadow root and in overlapping groups; decorative initials still need to be readable. Explicit ink overrides must pass too.",
2976
+ components: ["ds-avatar"],
2977
+ check: {
2978
+ file: "tests/avatar-contrast.test.ts",
2979
+ test: "avatar initials stay readable after theme and CSS-variable changes, and the avatar constraint catches overrides"
2980
+ },
2981
+ measures: ["avatar initial", "avatar initials"],
2982
+ count: (root) => measureTextContrast(root).filter((result) => isAvatarInitial(result.element)).length,
2983
+ inspect: (root) => inspectTextContrast(root).filter((finding) => isAvatarInitial(finding.element))
2984
+ },
2762
2985
  {
2763
2986
  code: "card.media-level",
2764
2987
  rule: "A card's picture stands on the card's foot, a square below the words at least, so the pictures of cards side by side stand on one level whatever the words above them.",
@@ -2925,14 +3148,16 @@ function skillBlock(list = constraints) {
2925
3148
  lines.push("", skillEnd);
2926
3149
  return lines.join("\n");
2927
3150
  }
2928
- //#endregion
2929
- //#region DS1/0-face/constraint-site.ts
2930
- /** The narrowest phone still sold, the common ones and the widest. */
2931
- var PHONE_WIDTHS = [
2932
- 320,
2933
- 375,
2934
- 393,
2935
- 430
3151
+ var VIEWPORT_WIDTHS = [
3152
+ ...[
3153
+ 320,
3154
+ 375,
3155
+ 393,
3156
+ 430
3157
+ ],
3158
+ 768,
3159
+ 1024,
3160
+ 1440
2936
3161
  ];
2937
3162
  /** An element as a person finds it: its tag, id and classes, and the start of its words. */
2938
3163
  function describeElement(element) {
@@ -2972,7 +3197,7 @@ var wait = (ms) => new Promise((done) => setTimeout(done, ms));
2972
3197
  * order of `widths`. The page reflows at each width without loading again. The address carries `ph=out`,
2973
3198
  * the analytics opt-out most trackers honour, so the check counts no visits.
2974
3199
  */
2975
- async function inspectInFrame(host, url, widths = PHONE_WIDTHS, height = 844) {
3200
+ async function inspectInFrame(host, url, widths = VIEWPORT_WIDTHS, height = 844) {
2976
3201
  const frame = document.createElement("iframe");
2977
3202
  frame.setAttribute("aria-hidden", "true");
2978
3203
  frame.tabIndex = -1;
@@ -3032,7 +3257,7 @@ function checkPage(page) {
3032
3257
  * each width as it comes. Stops starting pages when `signal` aborts. Whatever the pages store in this
3033
3258
  * browser while they are checked is put back afterwards.
3034
3259
  */
3035
- async function checkSite({ pages, widths = PHONE_WIDTHS, inspect, onResult, signal, atOnce = 3 }) {
3260
+ async function checkSite({ pages, widths = VIEWPORT_WIDTHS, inspect, onResult, signal, atOnce = 3 }) {
3036
3261
  const kept = storedNow();
3037
3262
  const results = [];
3038
3263
  const total = pages.length * widths.length;
@@ -3149,7 +3374,8 @@ function createConstraintPanel() {
3149
3374
  --danger: var(--ds-danger-color, light-dark(#de0331, #ff5f5f));
3150
3375
  }
3151
3376
  :host([hidden]) { display: none !important; }
3152
- .rings { position: absolute; inset: 0; overflow: hidden; }
3377
+ .fault-lines { z-index: 0 !important; }
3378
+ .rings { z-index: 1; position: absolute; inset: 0; overflow: hidden; }
3153
3379
  .frames { position: absolute; width: 0; height: 0; overflow: hidden; }
3154
3380
  .ring {
3155
3381
  position: absolute;
@@ -3171,6 +3397,7 @@ function createConstraintPanel() {
3171
3397
  text-align: center;
3172
3398
  }
3173
3399
  aside {
3400
+ z-index: 2;
3174
3401
  position: absolute;
3175
3402
  inset-block: 0;
3176
3403
  inset-inline-end: 0;
@@ -3217,14 +3444,18 @@ function createConstraintPanel() {
3217
3444
  .code b { color: var(--danger); font-weight: 400; }
3218
3445
  .rule { color: var(--muted); }
3219
3446
  p { margin: 0; }
3447
+ :host([data-lines-only]) aside { inset-block: 0 auto; width: auto; padding: calc(var(--u) / 2); }
3448
+ :host([data-lines-only]) aside > :not(header), :host([data-lines-only]) header > :not([data-lines], [data-close]) { display: none; }
3449
+ :host([data-lines-only]) header { margin: 0; }
3220
3450
  @media print { :host { display: none !important; } }
3221
- </style><div class="rings"></div><div class="frames"></div><aside aria-label="Constraints">
3222
- <header><strong>Constraints</strong><button type="button" data-run>Page</button><button type="button" data-site>Site</button><button type="button" data-close aria-label="Close">Close</button></header>
3223
- <p data-status aria-live="polite"></p>
3451
+ </style><svg class="fault-lines" aria-hidden="true"></svg><div class="rings"></div><div class="frames"></div><aside aria-label="Constraints">
3452
+ <header><strong>Constraints</strong><button type="button" data-run>Page</button><button type="button" data-site>Site</button><button type="button" data-lines>Lines</button><button type="button" data-close aria-label="Close">Close</button></header>
3453
+ <p data-status aria-live="polite"></p><p class="rule">Fault lines: dashed reference · red drift</p>
3224
3454
  <ol data-findings></ol>
3225
3455
  <section data-rules hidden><h3>Rules</h3><ol></ol></section>
3226
3456
  <section data-pages hidden><h3>Pages</h3><ol></ol></section>
3227
3457
  </aside>`;
3458
+ const faultLines = shadow.querySelector(".fault-lines");
3228
3459
  const rings = shadow.querySelector(".rings");
3229
3460
  const list = shadow.querySelector("[data-findings]");
3230
3461
  const rulesSection = shadow.querySelector("[data-rules]");
@@ -3234,6 +3465,12 @@ function createConstraintPanel() {
3234
3465
  const status = shadow.querySelector("[data-status]");
3235
3466
  const title = shadow.querySelector("strong");
3236
3467
  const frames = shadow.querySelector(".frames");
3468
+ const linesButton = shadow.querySelector("[data-lines]");
3469
+ const setLinesOnly = (enabled) => {
3470
+ panel.toggleAttribute("data-lines-only", enabled);
3471
+ linesButton.textContent = enabled ? "Results" : "Lines";
3472
+ };
3473
+ linesButton.addEventListener("click", () => setLinesOnly(!panel.hasAttribute("data-lines-only")));
3237
3474
  const siteButton = shadow.querySelector("[data-site]");
3238
3475
  let site = null;
3239
3476
  let findings = [];
@@ -3241,6 +3478,7 @@ function createConstraintPanel() {
3241
3478
  let frame = 0;
3242
3479
  const place = () => {
3243
3480
  frame = 0;
3481
+ paintFaultLines(document, faultLines);
3244
3482
  rings.replaceChildren(...findings.map(({ element }, index) => {
3245
3483
  const rect = element.getBoundingClientRect();
3246
3484
  const ring = document.createElement("div");
@@ -3256,6 +3494,8 @@ function createConstraintPanel() {
3256
3494
  const schedule = () => {
3257
3495
  if (!frame) frame = requestAnimationFrame(place);
3258
3496
  };
3497
+ const layoutChanges = new MutationObserver(schedule);
3498
+ const sizeChanges = typeof ResizeObserver === "undefined" ? null : new ResizeObserver(schedule);
3259
3499
  const stopSite = () => {
3260
3500
  site?.abort();
3261
3501
  site = null;
@@ -3408,7 +3648,7 @@ function createConstraintPanel() {
3408
3648
  title.textContent = "Constraints";
3409
3649
  status.textContent = "Finding the pages…";
3410
3650
  const pages = await findSitePages();
3411
- const widths = `${PHONE_WIDTHS.slice(0, -1).join(", ")} and ${PHONE_WIDTHS[PHONE_WIDTHS.length - 1]}px`;
3651
+ const widths = `${VIEWPORT_WIDTHS.slice(0, -1).join(", ")} and ${VIEWPORT_WIDTHS[VIEWPORT_WIDTHS.length - 1]}px`;
3412
3652
  let broken = 0;
3413
3653
  const results = await checkSite({
3414
3654
  pages,
@@ -3435,8 +3675,22 @@ function createConstraintPanel() {
3435
3675
  const show = (mode = "page") => {
3436
3676
  if (!panel.isConnected) document.body.appendChild(panel);
3437
3677
  panel.hidden = false;
3678
+ setLinesOnly(window.innerWidth < 640);
3438
3679
  if (mode === "site") runSite();
3439
3680
  else run();
3681
+ layoutChanges.observe(document.body, {
3682
+ subtree: true,
3683
+ childList: true,
3684
+ attributes: true,
3685
+ attributeFilter: [
3686
+ "style",
3687
+ "class",
3688
+ "hidden",
3689
+ "data-ds-fault-lines",
3690
+ "data-ds-fault-axis"
3691
+ ]
3692
+ });
3693
+ sizeChanges?.observe(document.body);
3440
3694
  window.addEventListener("scroll", schedule, {
3441
3695
  passive: true,
3442
3696
  capture: true
@@ -3446,6 +3700,11 @@ function createConstraintPanel() {
3446
3700
  const hide = () => {
3447
3701
  stopSite();
3448
3702
  panel.hidden = true;
3703
+ if (frame) cancelAnimationFrame(frame);
3704
+ frame = 0;
3705
+ faultLines.replaceChildren();
3706
+ layoutChanges.disconnect();
3707
+ sizeChanges?.disconnect();
3449
3708
  window.removeEventListener("scroll", schedule, { capture: true });
3450
3709
  window.removeEventListener("resize", schedule);
3451
3710
  };
@@ -5030,7 +5289,7 @@ function define(name, element) {
5030
5289
  const type = element;
5031
5290
  const prototype = element.prototype;
5032
5291
  const original = prototype.createRenderRoot;
5033
- if (original) {
5292
+ if (original && typeof document !== "undefined") {
5034
5293
  const definingDocument = document;
5035
5294
  prototype.createRenderRoot = function() {
5036
5295
  var _this$renderOptions;
@@ -5884,7 +6143,7 @@ _Badge.styles = r$4(ds_badge_default);
5884
6143
  define("ds-badge", Badge);
5885
6144
  //#endregion
5886
6145
  //#region DS1/2-core/styles/ds-tag.css?inline
5887
- var ds_tag_default = "/* Shape is a surface preference. Keep these base lengths unscaled: each\n * component resolves them with its own --ds-sf in component-metrics.css. */\n[data-ds-shape=\"square\"],\n:host([data-ds-shape=\"square\"]) {\n --ds-corner-size: 0px;\n --ds-corner-shape: round;\n --ds-button-inset: 0.5px;\n}\n\n[data-ds-shape=\"rounded\"],\n:host([data-ds-shape=\"rounded\"]) {\n --ds-corner-size: 4px;\n --ds-corner-shape: round;\n --ds-button-inset: 2.5px;\n}\n\n[data-ds-shape=\"circular\"],\n:host([data-ds-shape=\"circular\"]) {\n --ds-corner-size: 999px;\n --ds-corner-shape: round;\n --ds-button-inset: 5px;\n}\n\n/* A squircle needs twice an arc's radius for the same apparent corner.\n * Circular controls keep true semicircles even in a squircle browser. */\n@supports (corner-shape: squircle) {\n [data-ds-shape=\"rounded\"],\n :host([data-ds-shape=\"rounded\"]) {\n --ds-corner-size: 8px;\n --ds-corner-shape: squircle;\n }\n}\n/* Resolve dimensions where a component is drawn. Inherited calc() values have\n * already used their ancestor's scale. Base tokens stay unscaled and inherit;\n * resolved tokens can still be overridden directly on a component.\n * data-ds-surface gives ordinary layout elements the same scaled measures.\n */\n:root,\n:host,\n:where([data-ds-surface]) {\n --ds-unit: calc(var(--ds-1, 20px) * var(--ds-sf, 1));\n --ds-control-height: calc(var(--ds-control-size, var(--ds-08, 16px)) * var(--ds-sf, 1));\n --ds-row-height: calc(var(--ds-row-size, var(--ds-1, 20px)) * var(--ds-sf, 1));\n --ds-type-size-default: calc(var(--ds-text-size, 14px) * var(--ds-sf, 1));\n --ds-type-size-small: calc(var(--ds-small-text-size, 10px) * var(--ds-sf, 1));\n --ds-type-size-heading: calc(var(--ds-heading-text-size, 13px) * var(--ds-sf, 1));\n --ds-type-size-display: calc(var(--ds-display-text-size, 20px) * var(--ds-sf, 1));\n --ds-type-lineheight-default: calc(var(--ds-text-leading, 16px) * var(--ds-sf, 1));\n --ds-type-letterspacing-default: calc(var(--ds-text-tracking, -0.3px) * var(--ds-sf, 1));\n --ds-radius: calc(var(--ds-corner-size, 4px) * var(--ds-sf, 1));\n --ds-radius-card: calc(var(--ds-card-corner-size, 4px) * var(--ds-sf, 1));\n}\n:host {\n display: inline-flex;\n flex: 0 0 auto;\n width: max-content;\n max-width: 100%;\n min-width: 0;\n vertical-align: bottom;\n /* The tag's own tokens, unset by default and in unscaled px: its height, side padding,\n corners and text size; --ds-tag-accent colours it. */\n --_tag-size: var(--ds-tag-size);\n --_tag-height: calc(var(--_tag-size) * var(--ds-sf, 1));\n --_tag-padding-size: var(--ds-tag-padding-size);\n --_tag-padding: calc(var(--_tag-padding-size) * var(--ds-sf, 1));\n --_tag-corner-size: var(--ds-tag-corner-size);\n --_tag-radius: calc(var(--_tag-corner-size) * var(--ds-sf, 1));\n --_tag-text-size: var(--ds-tag-text-size);\n --_tag-font-size: calc(var(--_tag-text-size) * var(--ds-sf, 1));\n}\n:host([hidden]) { display: none !important; }\n/* The face stays one control high at the foot of its box, on the lower sixteen,\n even when a row stretches the tag to a square. */\n.frame {\n display: block;\n align-self: flex-end;\n box-sizing: border-box;\n max-width: 100%;\n padding-inline: var(--_tag-padding, calc(4px * var(--ds-sf, 1)));\n border-radius: var(--_tag-radius, var(--ds-radius));\n background: var(--ds-tag-background, light-dark(color-mix(in srgb, var(--ds-tag-accent, var(--ds-grey, #7a7a7a)) 28%, white), color-mix(in srgb, var(--ds-tag-accent, var(--ds-grey, #7a7a7a)) 20%, var(--ds-black, #2a2a2a))));\n color: var(--ds-tag-color, light-dark(color-mix(in srgb, var(--ds-tag-accent, var(--ds-grey, #7a7a7a)) 45%, black), var(--ds-tag-accent, var(--ds-grey, #7a7a7a))));\n font-family: var(--ds-typeface-regular);\n font-feature-settings: var(--ds-font-features);\n font-size: var(--_tag-font-size, var(--ds-type-size-default));\n line-height: var(--_tag-height, var(--ds-control-height));\n letter-spacing: var(--ds-type-letterspacing-default);\n overflow: hidden;\n text-overflow: clip;\n white-space: nowrap;\n}\n/* The words in a box of their own, so cut words fade over their last square\n while the fill stays whole; never an ellipsis (0-face/cut-fade.ts marks them). */\n.words {\n display: block;\n overflow: hidden;\n text-overflow: clip;\n white-space: nowrap;\n}\n.words[data-cut=\"end\"] {\n mask-image: linear-gradient(to right, #000 calc(100% - var(--ds-unit)), transparent);\n}\n:host(:dir(rtl)) .words[data-cut=\"end\"] {\n mask-image: linear-gradient(to left, #000 calc(100% - var(--ds-unit)), transparent);\n}\n";
6146
+ var ds_tag_default = "/* Shape is a surface preference. Keep these base lengths unscaled: each\n * component resolves them with its own --ds-sf in component-metrics.css. */\n[data-ds-shape=\"square\"],\n:host([data-ds-shape=\"square\"]) {\n --ds-corner-size: 0px;\n --ds-corner-shape: round;\n --ds-button-inset: 0.5px;\n}\n\n[data-ds-shape=\"rounded\"],\n:host([data-ds-shape=\"rounded\"]) {\n --ds-corner-size: 4px;\n --ds-corner-shape: round;\n --ds-button-inset: 2.5px;\n}\n\n[data-ds-shape=\"circular\"],\n:host([data-ds-shape=\"circular\"]) {\n --ds-corner-size: 999px;\n --ds-corner-shape: round;\n --ds-button-inset: 5px;\n}\n\n/* A squircle needs twice an arc's radius for the same apparent corner.\n * Circular controls keep true semicircles even in a squircle browser. */\n@supports (corner-shape: squircle) {\n [data-ds-shape=\"rounded\"],\n :host([data-ds-shape=\"rounded\"]) {\n --ds-corner-size: 8px;\n --ds-corner-shape: squircle;\n }\n}\n/* Resolve dimensions where a component is drawn. Inherited calc() values have\n * already used their ancestor's scale. Base tokens stay unscaled and inherit;\n * resolved tokens can still be overridden directly on a component.\n * data-ds-surface gives ordinary layout elements the same scaled measures.\n */\n:root,\n:host,\n:where([data-ds-surface]) {\n --ds-unit: calc(var(--ds-1, 20px) * var(--ds-sf, 1));\n --ds-control-height: calc(var(--ds-control-size, var(--ds-08, 16px)) * var(--ds-sf, 1));\n --ds-row-height: calc(var(--ds-row-size, var(--ds-1, 20px)) * var(--ds-sf, 1));\n --ds-type-size-default: calc(var(--ds-text-size, 14px) * var(--ds-sf, 1));\n --ds-type-size-small: calc(var(--ds-small-text-size, 10px) * var(--ds-sf, 1));\n --ds-type-size-heading: calc(var(--ds-heading-text-size, 13px) * var(--ds-sf, 1));\n --ds-type-size-display: calc(var(--ds-display-text-size, 20px) * var(--ds-sf, 1));\n --ds-type-lineheight-default: calc(var(--ds-text-leading, 16px) * var(--ds-sf, 1));\n --ds-type-letterspacing-default: calc(var(--ds-text-tracking, -0.3px) * var(--ds-sf, 1));\n --ds-radius: calc(var(--ds-corner-size, 4px) * var(--ds-sf, 1));\n --ds-radius-card: calc(var(--ds-card-corner-size, 4px) * var(--ds-sf, 1));\n}\n:host {\n display: inline-flex;\n flex: 0 0 auto;\n width: max-content;\n max-width: 100%;\n min-width: 0;\n vertical-align: bottom;\n /* The tag's own tokens, unset by default and in unscaled px: its height, side padding,\n corners and text size; --ds-tag-accent colours it. */\n --_tag-size: var(--ds-tag-size);\n --_tag-height: calc(var(--_tag-size) * var(--ds-sf, 1));\n --_tag-padding-size: var(--ds-tag-padding-size);\n --_tag-padding: calc(var(--_tag-padding-size) * var(--ds-sf, 1));\n --_tag-corner-size: var(--ds-tag-corner-size);\n --_tag-radius: calc(var(--_tag-corner-size) * var(--ds-sf, 1));\n --_tag-text-size: var(--ds-tag-text-size);\n --_tag-font-size: calc(var(--_tag-text-size) * var(--ds-sf, 1));\n}\n:host([hidden]) { display: none !important; }\n/* The face stays one control high at the foot of its box, on the lower sixteen,\n even when a row stretches the tag to a square. */\n.frame {\n display: block;\n align-self: flex-end;\n box-sizing: border-box;\n max-width: 100%;\n padding-inline: var(--_tag-padding, calc(4px * var(--ds-sf, 1)));\n border-radius: var(--_tag-radius, var(--ds-radius));\n background: var(--ds-tag-background, light-dark(color-mix(in srgb, var(--ds-tag-accent, var(--ds-grey, #7a7a7a)) 28%, white), color-mix(in srgb, var(--ds-tag-accent, var(--ds-grey, #7a7a7a)) 20%, var(--ds-black, #2a2a2a))));\n /* Tint the dark-mode ink: raw grey/accent colours cannot meet 4.5:1 on this fill. */\n color: var(--ds-tag-color, light-dark(color-mix(in srgb, var(--ds-tag-accent, var(--ds-grey, #7a7a7a)) 45%, black), color-mix(in srgb, var(--ds-tag-accent, var(--ds-grey, #7a7a7a)) 30%, white)));\n font-family: var(--ds-typeface-regular);\n font-feature-settings: var(--ds-font-features);\n font-size: var(--_tag-font-size, var(--ds-type-size-default));\n line-height: var(--_tag-height, var(--ds-control-height));\n letter-spacing: var(--ds-type-letterspacing-default);\n overflow: hidden;\n text-overflow: clip;\n white-space: nowrap;\n}\n/* The words in a box of their own, so cut words fade over their last square\n while the fill stays whole; never an ellipsis (0-face/cut-fade.ts marks them). */\n.words {\n display: block;\n overflow: hidden;\n text-overflow: clip;\n white-space: nowrap;\n}\n.words[data-cut=\"end\"] {\n mask-image: linear-gradient(to right, #000 calc(100% - var(--ds-unit)), transparent);\n}\n:host(:dir(rtl)) .words[data-cut=\"end\"] {\n mask-image: linear-gradient(to left, #000 calc(100% - var(--ds-unit)), transparent);\n}\n";
5888
6147
  //#endregion
5889
6148
  //#region DS1/2-core/ds-tag.ts
5890
6149
  var _Tag;
@@ -6206,10 +6465,10 @@ _Control.properties = { disabled: {
6206
6465
  } };
6207
6466
  //#endregion
6208
6467
  //#region DS1/2-core/styles/ds-button-host.css?inline
6209
- var ds_button_host_default = "/* Shape is a surface preference. Keep these base lengths unscaled: each\n * component resolves them with its own --ds-sf in component-metrics.css. */\n[data-ds-shape=\"square\"],\n:host([data-ds-shape=\"square\"]) {\n --ds-corner-size: 0px;\n --ds-corner-shape: round;\n --ds-button-inset: 0.5px;\n}\n\n[data-ds-shape=\"rounded\"],\n:host([data-ds-shape=\"rounded\"]) {\n --ds-corner-size: 4px;\n --ds-corner-shape: round;\n --ds-button-inset: 2.5px;\n}\n\n[data-ds-shape=\"circular\"],\n:host([data-ds-shape=\"circular\"]) {\n --ds-corner-size: 999px;\n --ds-corner-shape: round;\n --ds-button-inset: 5px;\n}\n\n/* A squircle needs twice an arc's radius for the same apparent corner.\n * Circular controls keep true semicircles even in a squircle browser. */\n@supports (corner-shape: squircle) {\n [data-ds-shape=\"rounded\"],\n :host([data-ds-shape=\"rounded\"]) {\n --ds-corner-size: 8px;\n --ds-corner-shape: squircle;\n }\n}\n/* Resolve dimensions where a component is drawn. Inherited calc() values have\n * already used their ancestor's scale. Base tokens stay unscaled and inherit;\n * resolved tokens can still be overridden directly on a component.\n * data-ds-surface gives ordinary layout elements the same scaled measures.\n */\n:root,\n:host,\n:where([data-ds-surface]) {\n --ds-unit: calc(var(--ds-1, 20px) * var(--ds-sf, 1));\n --ds-control-height: calc(var(--ds-control-size, var(--ds-08, 16px)) * var(--ds-sf, 1));\n --ds-row-height: calc(var(--ds-row-size, var(--ds-1, 20px)) * var(--ds-sf, 1));\n --ds-type-size-default: calc(var(--ds-text-size, 14px) * var(--ds-sf, 1));\n --ds-type-size-small: calc(var(--ds-small-text-size, 10px) * var(--ds-sf, 1));\n --ds-type-size-heading: calc(var(--ds-heading-text-size, 13px) * var(--ds-sf, 1));\n --ds-type-size-display: calc(var(--ds-display-text-size, 20px) * var(--ds-sf, 1));\n --ds-type-lineheight-default: calc(var(--ds-text-leading, 16px) * var(--ds-sf, 1));\n --ds-type-letterspacing-default: calc(var(--ds-text-tracking, -0.3px) * var(--ds-sf, 1));\n --ds-radius: calc(var(--ds-corner-size, 4px) * var(--ds-sf, 1));\n --ds-radius-card: calc(var(--ds-card-corner-size, 4px) * var(--ds-sf, 1));\n}\n/* ds-button.css */\n\n:host([control=\"host\"]),\n:host([control=\"slotted\"]) ::slotted(:is(button, a)) {\n box-sizing: border-box !important;\n display: inline-flex !important;\n align-items: center !important;\n justify-content: var(--ds-button-align, flex-start) !important;\n gap: var(--ds-button-gap, calc(4px * var(--ds-sf))) !important;\n min-width: 0 !important;\n max-width: 100% !important;\n height: var(--ds-button-height, var(--_button-height, var(--ds-control-height))) !important;\n /* Important, so a page's `* { padding: 0 }` reset cannot reach it: the\n padding is set through --ds-button-padding. */\n padding: var(--ds-button-padding, 0) !important;\n border-radius: var(--_button-radius, var(--ds-radius)) !important;\n corner-shape: var(--ds-corner-shape, round) !important;\n background: var(--ds-button-background, transparent) !important;\n color: var(--ds-button-color, var(--ds-button-text-color)) !important;\n font-family: var(--ds-typeface-regular) !important;\n font-feature-settings: var(--ds-font-features) !important;\n font-size: var(--_button-font-size, var(--ds-type-size-default)) !important;\n font-weight: var(--ds-type-weight-default) !important;\n line-height: var(--ds-type-lineheight-default) !important;\n letter-spacing: var(--ds-type-letterspacing-default) !important;\n white-space: nowrap !important;\n cursor: pointer !important;\n user-select: none !important;\n -webkit-user-select: none !important;\n -webkit-tap-highlight-color: transparent !important;\n outline: none !important;\n transition: opacity 120ms ease !important;\n}\n\n:host([control=\"host\"]) ::slotted(*) {\n flex: 0 0 auto !important;\n}\n\n:host([fit][control=\"slotted\"]) ::slotted(:is(button, a)) {\n width: max-content !important;\n min-width: var(--ds-button-min-width, 0px) !important;\n justify-content: var(--ds-button-align, flex-start) !important;\n}\n\n:host([control=\"host\"][primary]),\n:host([control=\"slotted\"][primary]) {\n --ds-button-padding: 0 var(--_button-padding, calc(var(--ds-button-inset, 4px) * var(--ds-sf)));\n --ds-button-background: var(--ds-button-primary-fill, var(--ds-button-background-color-primary));\n --ds-button-color: var(--ds-button-primary-text, var(--ds-button-text-color-on-accent));\n}\n\n:host([control=\"host\"][secondary]),\n:host([control=\"slotted\"][secondary]) {\n --ds-button-padding: 0 var(--_button-padding, calc(var(--ds-button-inset, 4px) * var(--ds-sf)));\n --ds-button-background: var(--ds-button-secondary-fill, var(--ds-button-background-color-secondary));\n}\n\n:host([control=\"host\"][secondary]:hover:not([disabled])),\n:host([control=\"slotted\"][secondary]:hover:not([disabled])) {\n --ds-button-background: var(--ds-fill-hover);\n}\n\n:host([control=\"host\"][active]),\n:host([control=\"slotted\"][active]) {\n --ds-button-background: var(--ds-fill);\n}\n\n/* A tab: muted until it is hovered or current. */\n:host([control=\"host\"][role=\"tab\"]),\n:host([control=\"host\"][choice]),\n:host([control=\"slotted\"][choice]) {\n --ds-button-padding: 0 calc(4px * var(--ds-sf));\n --ds-button-color: var(--ds-text-color-muted);\n}\n\n:host([control=\"host\"][role=\"tab\"]:hover:not([disabled])),\n:host([control=\"host\"][choice]:hover:not([disabled])),\n:host([control=\"slotted\"][choice]:hover:not([disabled])) {\n --ds-button-background: color-mix(in srgb, var(--ds-fill) 72%, transparent);\n --ds-button-color: var(--ds-text-color);\n}\n\n:host([control=\"host\"][role=\"tab\"][active]),\n:host([control=\"host\"][role=\"tab\"][active]:hover:not([disabled])),\n:host([control=\"host\"][choice][active]),\n:host([control=\"host\"][choice][active]:hover:not([disabled])),\n:host([control=\"slotted\"][choice][active]),\n:host([control=\"slotted\"][choice][active]:hover:not([disabled])) {\n --ds-button-background: var(--ds-fill);\n --ds-button-color: var(--ds-text-color);\n}\n\n:host([control=\"host\"]) ::slotted([slot=\"icon\"]) {\n display: inline-flex !important;\n align-items: center !important;\n justify-content: center !important;\n width: var(--ds-control-height) !important;\n height: var(--ds-control-height) !important;\n}\n\n:host([control=\"host\"]) ::slotted([slot=\"trailing\"]) {\n color: var(--ds-text-color-muted) !important;\n font-variant-numeric: tabular-nums !important;\n}\n\n:host([control=\"host\"][icon]),\n:host([control=\"slotted\"][icon]) {\n justify-content: center !important;\n width: var(--ds-button-height, var(--ds-control-height)) !important;\n height: var(--ds-button-height, var(--ds-control-height)) !important;\n padding: 0 !important;\n color: var(--ds-button-color, var(--ds-text-color-muted)) !important;\n}\n\n:host([control=\"host\"][icon]:hover:not([disabled])) {\n --ds-button-color: var(--ds-text-color);\n}\n\n/* A choice in a ds-menu: a full-width row, filled under the pointer or focus. */\n:host([control=\"host\"][role=\"option\"]),\n:host([control=\"host\"][role=\"menuitem\"]),\n:host([control=\"host\"][role=\"menuitemradio\"]),\n:host([control=\"host\"][role=\"menuitemcheckbox\"]) {\n --ds-button-height: auto;\n --ds-button-padding: calc(4px * var(--ds-sf)) calc(6px * var(--ds-sf));\n width: var(--ds-button-width, 100%) !important;\n min-height: var(--ds-row-height) !important;\n border-radius: var(--ds-radius) !important;\n}\n\n:host([control=\"host\"][role=\"option\"]:hover),\n:host([control=\"host\"][role=\"menuitem\"]:hover),\n:host([control=\"host\"][role=\"menuitemradio\"]:hover),\n:host([control=\"host\"][role=\"menuitemcheckbox\"]:hover),\n:host([control=\"host\"][role=\"option\"]:focus-visible),\n:host([control=\"host\"][role=\"menuitem\"]:focus-visible) {\n --ds-button-background: var(--ds-fill);\n outline: none !important;\n}\n\n:host([control=\"host\"][role=\"option\"][active]) {\n --ds-button-background: var(--ds-fill);\n}\n\n:host([control=\"host\"]:focus-visible),\n:host([control=\"slotted\"]) ::slotted(:is(button, a):focus-visible) {\n outline: 2px solid var(--ds-focus-ring) !important;\n outline-offset: 1px !important;\n}\n\n:host([control=\"host\"][disabled]),\n:host([control=\"slotted\"]) ::slotted(:is(button:disabled, a[aria-disabled=\"true\"])) {\n cursor: default !important;\n opacity: 0.5 !important;\n pointer-events: none !important;\n}\n\n/* The light-DOM button keeps native forms, submitters and fieldset semantics.\n DS1 owns its face; product layout belongs on the host. */\n:host([control=\"slotted\"]) ::slotted(:is(button, a)) {\n appearance: none !important;\n box-sizing: border-box !important;\n border: 0 !important;\n margin: 0 !important;\n width: 100% !important;\n min-height: var(--ds-button-height, var(--_button-height, var(--ds-control-height))) !important;\n max-height: var(--ds-button-height, var(--_button-height, var(--ds-control-height))) !important;\n box-shadow: none !important;\n text-align: start !important;\n text-transform: none !important;\n text-decoration: none !important;\n overflow: hidden !important;\n text-overflow: clip !important;\n}\n:host([control=\"slotted\"][icon]) ::slotted(:is(button, a)) {\n justify-content: center !important;\n}\n\n:host([control=\"slotted\"]) {\n /* The native focus ring extends beyond the face; only its text is clipped. */\n overflow: visible;\n /* The face is one control high, as the host and native faces are. Its box\n stays one square, as before, with the face at its foot, so the words take\n the lower sixteen of the square in a row or at the foot of a line and no\n row around it changes height. */\n align-items: flex-end;\n min-height: var(--ds-row-height);\n vertical-align: bottom;\n width: var(--ds-button-width, var(--_button-inline-size, max-content));\n}\n/* An icon's square: the face takes its first sixteen, the spare four above. */\n:host([control=\"slotted\"][icon]) {\n justify-content: flex-start !important;\n width: max(var(--ds-row-height), var(--ds-button-height, var(--ds-control-height))) !important;\n height: max(var(--ds-row-height), var(--ds-button-height, var(--ds-control-height))) !important;\n}\n:host([control=\"slotted\"][icon]) ::slotted(:is(button, a)) {\n width: var(--ds-button-height, var(--ds-control-height)) !important;\n}\n:host([control=\"slotted\"][fixed]) {\n width: var(--ds-button-width, calc(4 * var(--ds-unit)));\n}\n:host([control=\"slotted\"][wide]) {\n width: var(--ds-button-width, calc(8 * var(--ds-unit)));\n}\n:host([control=\"slotted\"][fit]) {\n width: max-content;\n}\n\n:host([control=\"host\"][danger]),\n:host([control=\"slotted\"][danger]) {\n --ds-button-color: var(--ds-danger-color, var(--ds-tuned-red));\n}\n\n:host([hidden]) { display: none !important; }\n\n/* Native actions in menus, including grouped and nested choices. */\n:host([menu-item]) {\n flex: 0 0 auto;\n width: 100%;\n --ds-button-height: var(--ds-row-height);\n --ds-button-align: flex-start;\n --ds-button-padding: 0 calc(4px * var(--ds-sf));\n}\n/* A row-high entry's words and icon stand on the lower sixteen, the spare four above. */\n:host([menu-item][control=\"host\"]),\n:host([menu-item][control=\"slotted\"]) ::slotted(:is(button, a)) {\n align-items: flex-end !important;\n}\n:host([menu-item]:is(:hover, :focus-within):not([disabled])) {\n --ds-button-background: var(--ds-fill);\n}\n:host([menu-item][control=\"slotted\"]) ::slotted([aria-checked=\"true\"]) {\n box-shadow: inset 0 0 0 1px var(--ds-focus-ring) !important;\n}\n\n/* A paper tab (`ds-tabs paper`): a row high with its words on the sixteen,\n rounded at the top only and square at the foot, where it joins the page. */\n:host([control=\"host\"][paper]),\n:host([control=\"slotted\"][paper]) ::slotted(:is(button, a)) {\n align-items: flex-end !important;\n border-start-start-radius: var(--ds-radius) !important;\n border-start-end-radius: var(--ds-radius) !important;\n border-end-start-radius: 0 !important;\n border-end-end-radius: 0 !important;\n}\n\n/* A tab in a joined run (`ds-tabs split`): square inside, rounded at the run's ends. */\n:host([control=\"host\"][segment]) {\n border-radius: 0 !important;\n}\n:host([control=\"host\"]:is([segment=\"first\"], [segment=\"only\"])) {\n border-start-start-radius: var(--ds-radius) !important;\n border-end-start-radius: var(--ds-radius) !important;\n}\n:host([control=\"host\"]:is([segment=\"last\"], [segment=\"only\"])) {\n border-start-end-radius: var(--ds-radius) !important;\n border-end-end-radius: var(--ds-radius) !important;\n}\n\n:host([control=\"host\"]:not([icon]):not([split]):not([menu-item])) {\n min-width: max-content !important;\n min-width: calc-size(max-content, round(up, size, var(--ds-unit))) !important;\n}\n:host([control=\"slotted\"]) ::slotted(:is(button, a)) {\n min-width: max-content !important;\n overflow: visible !important;\n}\n";
6468
+ var ds_button_host_default = "/* Shape is a surface preference. Keep these base lengths unscaled: each\n * component resolves them with its own --ds-sf in component-metrics.css. */\n[data-ds-shape=\"square\"],\n:host([data-ds-shape=\"square\"]) {\n --ds-corner-size: 0px;\n --ds-corner-shape: round;\n --ds-button-inset: 0.5px;\n}\n\n[data-ds-shape=\"rounded\"],\n:host([data-ds-shape=\"rounded\"]) {\n --ds-corner-size: 4px;\n --ds-corner-shape: round;\n --ds-button-inset: 2.5px;\n}\n\n[data-ds-shape=\"circular\"],\n:host([data-ds-shape=\"circular\"]) {\n --ds-corner-size: 999px;\n --ds-corner-shape: round;\n --ds-button-inset: 5px;\n}\n\n/* A squircle needs twice an arc's radius for the same apparent corner.\n * Circular controls keep true semicircles even in a squircle browser. */\n@supports (corner-shape: squircle) {\n [data-ds-shape=\"rounded\"],\n :host([data-ds-shape=\"rounded\"]) {\n --ds-corner-size: 8px;\n --ds-corner-shape: squircle;\n }\n}\n/* Resolve dimensions where a component is drawn. Inherited calc() values have\n * already used their ancestor's scale. Base tokens stay unscaled and inherit;\n * resolved tokens can still be overridden directly on a component.\n * data-ds-surface gives ordinary layout elements the same scaled measures.\n */\n:root,\n:host,\n:where([data-ds-surface]) {\n --ds-unit: calc(var(--ds-1, 20px) * var(--ds-sf, 1));\n --ds-control-height: calc(var(--ds-control-size, var(--ds-08, 16px)) * var(--ds-sf, 1));\n --ds-row-height: calc(var(--ds-row-size, var(--ds-1, 20px)) * var(--ds-sf, 1));\n --ds-type-size-default: calc(var(--ds-text-size, 14px) * var(--ds-sf, 1));\n --ds-type-size-small: calc(var(--ds-small-text-size, 10px) * var(--ds-sf, 1));\n --ds-type-size-heading: calc(var(--ds-heading-text-size, 13px) * var(--ds-sf, 1));\n --ds-type-size-display: calc(var(--ds-display-text-size, 20px) * var(--ds-sf, 1));\n --ds-type-lineheight-default: calc(var(--ds-text-leading, 16px) * var(--ds-sf, 1));\n --ds-type-letterspacing-default: calc(var(--ds-text-tracking, -0.3px) * var(--ds-sf, 1));\n --ds-radius: calc(var(--ds-corner-size, 4px) * var(--ds-sf, 1));\n --ds-radius-card: calc(var(--ds-card-corner-size, 4px) * var(--ds-sf, 1));\n}\n/* ds-button.css */\n\n:host([control=\"host\"]),\n:host([control=\"slotted\"]) ::slotted(:is(button, a)) {\n box-sizing: border-box !important;\n display: inline-flex !important;\n align-items: center !important;\n justify-content: var(--ds-button-align, flex-start) !important;\n gap: var(--ds-button-gap, calc(4px * var(--ds-sf))) !important;\n min-width: 0 !important;\n max-width: 100% !important;\n height: var(--ds-button-height, var(--_button-height, var(--ds-control-height))) !important;\n /* Important, so a page's `* { padding: 0 }` reset cannot reach it: the\n padding is set through --ds-button-padding. */\n padding: var(--ds-button-padding, 0) !important;\n border-radius: var(--_button-radius, var(--ds-radius)) !important;\n corner-shape: var(--ds-corner-shape, round) !important;\n background: var(--ds-button-background, transparent) !important;\n color: var(--ds-button-color, var(--ds-button-text-color)) !important;\n font-family: var(--ds-typeface-regular) !important;\n font-feature-settings: var(--ds-font-features) !important;\n font-size: var(--_button-font-size, var(--ds-type-size-default)) !important;\n font-weight: var(--ds-type-weight-default) !important;\n line-height: var(--ds-type-lineheight-default) !important;\n letter-spacing: var(--ds-type-letterspacing-default) !important;\n white-space: nowrap !important;\n cursor: pointer !important;\n user-select: none !important;\n -webkit-user-select: none !important;\n -webkit-tap-highlight-color: transparent !important;\n outline: none !important;\n transition: opacity 120ms ease !important;\n}\n\n:host([control=\"host\"]) ::slotted(*) {\n flex: 0 0 auto !important;\n}\n\n:host([fit][control=\"slotted\"]) ::slotted(:is(button, a)) {\n /* The host hugs on the grid; its visible face must occupy that same box. */\n width: 100% !important;\n min-width: var(--ds-button-min-width, 0px) !important;\n justify-content: var(--ds-button-align, flex-start) !important;\n}\n\n:host([control=\"host\"][primary]),\n:host([control=\"slotted\"][primary]) {\n --ds-button-padding: 0 var(--_button-padding, calc(var(--ds-button-inset, 4px) * var(--ds-sf)));\n --ds-button-background: var(--ds-button-primary-fill, var(--ds-button-background-color-primary));\n --ds-button-color: var(--ds-button-primary-text, var(--ds-button-text-color-on-accent));\n}\n\n:host([control=\"host\"][secondary]),\n:host([control=\"slotted\"][secondary]) {\n --ds-button-padding: 0 var(--_button-padding, calc(var(--ds-button-inset, 4px) * var(--ds-sf)));\n --ds-button-background: var(--ds-button-secondary-fill, var(--ds-button-background-color-secondary));\n}\n\n:host([control=\"host\"][secondary]:hover:not([disabled])),\n:host([control=\"slotted\"][secondary]:hover:not([disabled])) {\n --ds-button-background: var(--ds-fill-hover);\n}\n\n:host([control=\"host\"][active]),\n:host([control=\"slotted\"][active]) {\n --ds-button-background: var(--ds-fill);\n}\n\n/* A tab: muted until it is hovered or current. */\n:host([control=\"host\"][role=\"tab\"]),\n:host([control=\"host\"][choice]),\n:host([control=\"slotted\"][choice]) {\n --ds-button-padding: 0 calc(4px * var(--ds-sf));\n --ds-button-color: var(--ds-text-color-muted);\n}\n\n:host([control=\"host\"][role=\"tab\"]:hover:not([disabled])),\n:host([control=\"host\"][choice]:hover:not([disabled])),\n:host([control=\"slotted\"][choice]:hover:not([disabled])) {\n --ds-button-background: color-mix(in srgb, var(--ds-fill) 72%, transparent);\n --ds-button-color: var(--ds-text-color);\n}\n\n:host([control=\"host\"][role=\"tab\"][active]),\n:host([control=\"host\"][role=\"tab\"][active]:hover:not([disabled])),\n:host([control=\"host\"][choice][active]),\n:host([control=\"host\"][choice][active]:hover:not([disabled])),\n:host([control=\"slotted\"][choice][active]),\n:host([control=\"slotted\"][choice][active]:hover:not([disabled])) {\n --ds-button-background: var(--ds-fill);\n --ds-button-color: var(--ds-text-color);\n}\n\n:host([control=\"host\"]) ::slotted([slot=\"icon\"]) {\n display: inline-flex !important;\n align-items: center !important;\n justify-content: center !important;\n width: var(--ds-control-height) !important;\n height: var(--ds-control-height) !important;\n}\n\n:host([control=\"host\"]) ::slotted([slot=\"trailing\"]) {\n color: var(--ds-text-color-muted) !important;\n font-variant-numeric: tabular-nums !important;\n}\n\n:host([control=\"host\"][icon]),\n:host([control=\"slotted\"][icon]) {\n justify-content: center !important;\n width: var(--ds-button-height, var(--ds-control-height)) !important;\n height: var(--ds-button-height, var(--ds-control-height)) !important;\n padding: 0 !important;\n color: var(--ds-button-color, var(--ds-text-color-muted)) !important;\n}\n\n:host([control=\"host\"][icon]:hover:not([disabled])) {\n --ds-button-color: var(--ds-text-color);\n}\n\n/* A choice in a ds-menu: a full-width row, filled under the pointer or focus. */\n:host([control=\"host\"][role=\"option\"]),\n:host([control=\"host\"][role=\"menuitem\"]),\n:host([control=\"host\"][role=\"menuitemradio\"]),\n:host([control=\"host\"][role=\"menuitemcheckbox\"]) {\n --ds-button-height: auto;\n --ds-button-padding: calc(4px * var(--ds-sf)) calc(6px * var(--ds-sf));\n width: var(--ds-button-width, 100%) !important;\n min-height: var(--ds-row-height) !important;\n border-radius: var(--ds-radius) !important;\n}\n\n:host([control=\"host\"][role=\"option\"]:hover),\n:host([control=\"host\"][role=\"menuitem\"]:hover),\n:host([control=\"host\"][role=\"menuitemradio\"]:hover),\n:host([control=\"host\"][role=\"menuitemcheckbox\"]:hover),\n:host([control=\"host\"][role=\"option\"]:focus-visible),\n:host([control=\"host\"][role=\"menuitem\"]:focus-visible) {\n --ds-button-background: var(--ds-fill);\n outline: none !important;\n}\n\n:host([control=\"host\"][role=\"option\"][active]) {\n --ds-button-background: var(--ds-fill);\n}\n\n:host([control=\"host\"]:focus-visible),\n:host([control=\"slotted\"]) ::slotted(:is(button, a):focus-visible) {\n outline: 2px solid var(--ds-focus-ring) !important;\n outline-offset: 1px !important;\n}\n\n:host([control=\"host\"][disabled]),\n:host([control=\"slotted\"]) ::slotted(:is(button:disabled, a[aria-disabled=\"true\"])) {\n cursor: default !important;\n opacity: 0.5 !important;\n pointer-events: none !important;\n}\n\n/* The light-DOM button keeps native forms, submitters and fieldset semantics.\n DS1 owns its face; product layout belongs on the host. */\n:host([control=\"slotted\"]) ::slotted(:is(button, a)) {\n appearance: none !important;\n box-sizing: border-box !important;\n border: 0 !important;\n margin: 0 !important;\n width: 100% !important;\n min-height: var(--ds-button-height, var(--_button-height, var(--ds-control-height))) !important;\n max-height: var(--ds-button-height, var(--_button-height, var(--ds-control-height))) !important;\n box-shadow: none !important;\n text-align: start !important;\n text-transform: none !important;\n text-decoration: none !important;\n overflow: hidden !important;\n text-overflow: clip !important;\n}\n:host([control=\"slotted\"][icon]) ::slotted(:is(button, a)) {\n justify-content: center !important;\n}\n\n:host([control=\"slotted\"]) {\n /* The native focus ring extends beyond the face; only its text is clipped. */\n overflow: visible;\n /* The face is one control high, as the host and native faces are. Its box\n stays one square, as before, with the face at its foot, so the words take\n the lower sixteen of the square in a row or at the foot of a line and no\n row around it changes height. */\n align-items: flex-end;\n min-height: var(--ds-row-height);\n vertical-align: bottom;\n width: var(--ds-button-width, var(--_button-inline-size, max-content));\n}\n/* An icon's square: the face takes its first sixteen, the spare four above. */\n:host([control=\"slotted\"][icon]) {\n justify-content: flex-start !important;\n width: max(var(--ds-row-height), var(--ds-button-height, var(--ds-control-height))) !important;\n height: max(var(--ds-row-height), var(--ds-button-height, var(--ds-control-height))) !important;\n}\n:host([control=\"slotted\"][icon]) ::slotted(:is(button, a)) {\n width: var(--ds-button-height, var(--ds-control-height)) !important;\n}\n:host([control=\"slotted\"][fixed]) {\n width: var(--ds-button-width, calc(4 * var(--ds-unit)));\n}\n:host([control=\"slotted\"][wide]) {\n width: var(--ds-button-width, calc(8 * var(--ds-unit)));\n}\n:host([control=\"slotted\"][fit]) {\n width: max-content;\n}\n\n:host([control=\"host\"][danger]),\n:host([control=\"slotted\"][danger]) {\n --ds-button-color: var(--ds-danger-color, var(--ds-tuned-red));\n}\n\n:host([hidden]) { display: none !important; }\n\n/* Native actions in menus, including grouped and nested choices. */\n:host([menu-item]) {\n flex: 0 0 auto;\n width: 100%;\n --ds-button-height: var(--ds-row-height);\n --ds-button-align: flex-start;\n --ds-button-padding: 0 calc(4px * var(--ds-sf));\n}\n/* A row-high entry's words and icon stand on the lower sixteen, the spare four above. */\n:host([menu-item][control=\"host\"]),\n:host([menu-item][control=\"slotted\"]) ::slotted(:is(button, a)) {\n align-items: flex-end !important;\n}\n:host([menu-item]:is(:hover, :focus-within):not([disabled])) {\n --ds-button-background: var(--ds-fill);\n}\n:host([menu-item][control=\"slotted\"]) ::slotted([aria-checked=\"true\"]) {\n box-shadow: inset 0 0 0 1px var(--ds-focus-ring) !important;\n}\n\n/* A paper tab (`ds-tabs paper`): a row high with its words on the sixteen,\n rounded at the top only and square at the foot, where it joins the page. */\n:host([control=\"host\"][paper]),\n:host([control=\"slotted\"][paper]) ::slotted(:is(button, a)) {\n align-items: flex-end !important;\n border-start-start-radius: var(--ds-radius) !important;\n border-start-end-radius: var(--ds-radius) !important;\n border-end-start-radius: 0 !important;\n border-end-end-radius: 0 !important;\n}\n\n/* A tab in a joined run (`ds-tabs split`): square inside, rounded at the run's ends. */\n:host([control=\"host\"][segment]) {\n border-radius: 0 !important;\n}\n:host([control=\"host\"]:is([segment=\"first\"], [segment=\"only\"])) {\n border-start-start-radius: var(--ds-radius) !important;\n border-end-start-radius: var(--ds-radius) !important;\n}\n:host([control=\"host\"]:is([segment=\"last\"], [segment=\"only\"])) {\n border-start-end-radius: var(--ds-radius) !important;\n border-end-end-radius: var(--ds-radius) !important;\n}\n\n:host([control=\"host\"]:not([icon]):not([split]):not([menu-item])) {\n min-width: max-content !important;\n min-width: calc-size(max-content, round(up, size, var(--ds-unit))) !important;\n}\n:host([control=\"slotted\"]) ::slotted(:is(button, a)) {\n min-width: max-content !important;\n overflow: visible !important;\n}\n";
6210
6469
  //#endregion
6211
6470
  //#region DS1/2-core/styles/ds-button.css?inline
6212
- var ds_button_default = "/* ds-button.css */\n/* Core button component styles. The face is a <button>, or an <a> when the\n element has an href: both draw alike in every variant and state. */\n\n:host {\n display: inline-flex;\n /* At the foot of a line, so a 16px face takes the lower sixteen of the square. */\n vertical-align: bottom;\n min-width: 0;\n flex: 0 0 auto;\n width: var(--ds-button-width, var(--_button-inline-size, max-content));\n max-width: 100%;\n overflow: visible;\n /* The button's own tokens, unset by default and in unscaled px as --ds-control-size is,\n resolved here at this button's scale. Each face reads them first and falls back to its\n own measure, so a page that sets none draws exactly as before. --ds-button-inline-size is\n the width: unset hugs, 100% fills, a length is fixed. */\n --_button-size: var(--ds-button-size);\n --_button-height: calc(var(--_button-size) * var(--ds-sf));\n --_button-corner-size: var(--ds-button-corner-size);\n --_button-radius: calc(var(--_button-corner-size) * var(--ds-sf));\n --_button-text-size: var(--ds-button-text-size);\n --_button-font-size: calc(var(--_button-text-size) * var(--ds-sf));\n --_button-padding-size: var(--ds-button-padding-size);\n --_button-padding: calc(var(--_button-padding-size) * var(--ds-sf));\n --_button-inline-size: var(--ds-button-inline-size);\n}\n\n/* Other controls drawn on this face keep their own measures: an icon or a count's square, a\n tab, a menu entry, a split run and a paper tab. Their colours still follow the faces. */\n:host(:is([icon], [square], [split], [menu-item], [role=\"option\"], [role^=\"menuitem\"], [role=\"tab\"], [choice], [segment], [paper])) {\n --_button-height: initial;\n --_button-radius: initial;\n --_button-font-size: initial;\n --_button-padding: initial;\n --_button-inline-size: initial;\n}\n\n/* Label width is standard. Fixed grid widths are explicit layout choices. */\n:host([fixed]) { width: var(--ds-button-width, calc(4 * var(--ds-unit))); }\n:host([wide]) { width: var(--ds-button-width, calc(8 * var(--ds-unit))); }\n/* Keep fit as an explicit intrinsic-width alias for existing consumers. */\n:host([fit]) {\n flex: 0 0 auto;\n width: max-content;\n}\n:host([fit]) :is(button, a) {\n flex: 0 0 auto;\n width: max-content;\n min-width: var(--ds-button-min-width, 0px);\n justify-content: var(--ds-button-align, flex-start);\n}\n:host([fit]) :is(button, a) ds-text,\n:host([fit]) :is(button, a) ::slotted(*) {\n flex: 0 0 auto;\n width: max-content;\n}\n\n:is(button, a) {\n box-sizing: border-box;\n max-height: var(--_button-height, var(--ds-control-height));\n height: var(--_button-height, var(--ds-control-height));\n min-height: var(--_button-height, var(--ds-control-height));\n width: 100%;\n max-width: 100%;\n min-width: 0;\n display: inline-flex;\n align-items: center;\n justify-content: flex-start;\n border: none;\n border-radius: var(--_button-radius, var(--ds-radius));\n corner-shape: var(--ds-corner-shape, round);\n cursor: pointer;\n padding: 0;\n color: var(--button-color, var(--button-text-color));\n font-family: var(--ds-typeface-regular);\n font-size: var(--_button-font-size, var(--ds-type-size-default));\n line-height: var(--ds-type-lineheight-default);\n letter-spacing: var(--ds-type-letterspacing-default);\n -webkit-tap-highlight-color: transparent;\n white-space: nowrap;\n overflow-x: clip;\n overflow-y: visible;\n text-overflow: clip;\n /* Default to no-background when no variant is specified */\n background-color: transparent;\n text-decoration: none;\n}\n\n:is(button, a) ds-text,\n:is(button, a) ::slotted(*) {\n overflow-x: clip;\n overflow-y: visible;\n text-overflow: clip;\n white-space: nowrap;\n max-width: 100%;\n text-align: left;\n justify-content: flex-start;\n color: inherit;\n}\n\n/* Words drawn by a translation key take the button's text size too: unset, the expression\n ds-text resolves for itself (component-metrics.css). */\n:is(button, a) ds-text {\n --ds-type-size-default: var(--_button-font-size, calc(var(--ds-text-size, 14px) * var(--ds-sf, 1)));\n}\n\n:is(button, a)[primary] {\n background-color: var(--ds-button-primary-fill, var(--accent-color));\n color: var(--ds-button-primary-text, var(--button-text-color-on-light, var(--black)));\n text-decoration-line: none;\n font-family: var(--ds-typeface-regular);\n padding: 0 var(--_button-padding, calc(var(--ds-button-inset, 4px) * var(--ds-sf)));\n}\n\n/* The secondary face's words are the text's ink, as on `.ds-button--secondary`\n and the host and slotted faces; the grey is the face, not the words. */\n:is(button, a)[secondary] {\n background-color: var(--ds-button-secondary-fill, var(--button-background-color-secondary));\n color: var(--ds-button-color, var(--ds-button-text-color));\n font-family: var(--ds-typeface-regular);\n padding: 0 var(--_button-padding, calc(var(--ds-button-inset, 4px) * var(--ds-sf)));\n}\n\n/* An entry in a menu or a list of options is one row high on this face too,\n as on the host and slotted faces: the menu's --ds-button-height (one\n square, or a compact row), its words at the start, padded 4px. */\n:host(:is([menu-item], [role=\"option\"], [role^=\"menuitem\"])) :is(button, a) {\n /* Its words and icon on the lower sixteen of the row, the spare four above. */\n align-items: flex-end;\n height: var(--ds-button-height, var(--ds-row-height));\n min-height: var(--ds-button-height, var(--ds-row-height));\n max-height: var(--ds-button-height, var(--ds-row-height));\n justify-content: var(--ds-button-align, flex-start);\n padding: 0 calc(var(--ds-button-inset, 4px) * var(--ds-sf));\n}\n\n/* `danger`: a destructive action's words in the danger colour, on this face\n as on the host and slotted faces and `.ds-button--danger`. */\n:host([danger]) :is(button, a) {\n color: var(--ds-danger-color, var(--ds-tuned-red));\n}\n\n/* Disabled, as in the slotted and host modes. A disabled link has no href. */\n:is(button:disabled, a[aria-disabled=\"true\"]) {\n cursor: default;\n opacity: 0.5;\n}\n\n.loading {\n opacity: 0.7;\n}\n\n/* A button that holds only an icon — every close button — is a 16-unit\n square, scaled with the page, in the shape's corners: the icon fills it,\n no word-padding around it, so the glyph's lines are the same size wherever\n a page or a panel closes, on the web and in the app alike. The element\n marks itself `icon` when its only child is an ds-icon. */\n:host([icon]) {\n flex: 0 0 auto;\n width: var(--ds-control-height);\n height: var(--ds-control-height);\n}\n:host([icon]) :is(button, a) {\n width: var(--ds-control-height);\n padding: 0 !important;\n justify-content: center;\n}\n:host([icon]) ::slotted(ds-icon) {\n flex: 0 0 auto;\n width: var(--ds-control-height);\n height: var(--ds-control-height);\n}\n\n/* A button marked `square` is a 16-unit square whatever it holds, a count\n or an initial, its words in its middle with no word-padding around them. */\n:host([square]) {\n flex: 0 0 auto;\n width: var(--ds-control-height);\n}\n:host([square]) :is(button, a) {\n width: var(--ds-control-height);\n padding: 0 !important;\n justify-content: center;\n}\n\n/* Words and an icon hug them, as on every face (controls.css): this face has\n no gap, and on a filled face the icon takes the padding on its side, its\n own room around the glyph standing in for it. */\n:host(:is([primary], [secondary]):not([icon])) ::slotted(ds-icon:last-child:not(:first-child)) {\n margin-inline-end: calc(-4px * var(--ds-sf));\n}\n:host(:is([primary], [secondary]):not([icon])) ::slotted(ds-icon:first-child:not(:last-child)) {\n margin-inline-start: calc(-4px * var(--ds-sf));\n}\n\n/* A paper tab (`ds-tabs paper`) on this face: a row high with its words on\n the sixteen, half a square of room at its sides, in the run's colours,\n rounded at the top only and square at the foot, where it joins the page. */\n:host([paper]) :is(button, a) {\n height: var(--ds-row-height);\n min-height: var(--ds-row-height);\n max-height: var(--ds-row-height);\n align-items: flex-end;\n padding: 0 calc(10px * var(--ds-sf));\n border-radius: var(--ds-radius) var(--ds-radius) 0 0;\n background-color: var(--ds-button-background, transparent);\n color: var(--ds-button-color, var(--button-text-color));\n}\n\n/* A segment gets its outside corners from the enclosing split control, or\n from its place in a joined run of tabs (`ds-tabs split`). */\n:host([segment]) :is(button, a) {\n border-radius: 0 !important;\n}\n:host(:is([segment=\"first\"], [segment=\"only\"])) :is(button, a) {\n border-start-start-radius: var(--ds-radius) !important;\n border-end-start-radius: var(--ds-radius) !important;\n}\n:host(:is([segment=\"last\"], [segment=\"only\"])) :is(button, a) {\n border-start-end-radius: var(--ds-radius) !important;\n border-end-end-radius: var(--ds-radius) !important;\n}\n\n/* A label is never truncated to meet a chosen width. Round its minimum up,\n so explicit widths and flex layouts retain enough space for all the words. */\n:host(:not([icon]):not([split]):not([menu-item])) {\n min-width: max-content;\n min-width: calc-size(max-content, round(up, size, var(--ds-unit)));\n}\n/* A menu entry keeps a fixed width, so its words may still be cut and fade. */\n:host(:not([menu-item])) :is(button, a),\n:host(:not([menu-item])) :is(button, a) ds-text,\n:host(:not([menu-item])) :is(button, a) ::slotted(*) {\n overflow: visible;\n mask-image: none;\n text-overflow: clip;\n}\n";
6471
+ var ds_button_default = "/* ds-button.css */\n/* Core button component styles. The face is a <button>, or an <a> when the\n element has an href: both draw alike in every variant and state. */\n\n:host {\n display: inline-flex;\n /* At the foot of a line, so a 16px face takes the lower sixteen of the square. */\n vertical-align: bottom;\n min-width: 0;\n flex: 0 0 auto;\n width: var(--ds-button-width, var(--_button-inline-size, max-content));\n max-width: 100%;\n overflow: visible;\n /* The button's own tokens, unset by default and in unscaled px as --ds-control-size is,\n resolved here at this button's scale. Each face reads them first and falls back to its\n own measure, so a page that sets none draws exactly as before. --ds-button-inline-size is\n the width: unset hugs, 100% fills, a length is fixed. */\n --_button-size: var(--ds-button-size);\n --_button-height: calc(var(--_button-size) * var(--ds-sf));\n --_button-corner-size: var(--ds-button-corner-size);\n --_button-radius: calc(var(--_button-corner-size) * var(--ds-sf));\n --_button-text-size: var(--ds-button-text-size);\n --_button-font-size: calc(var(--_button-text-size) * var(--ds-sf));\n --_button-padding-size: var(--ds-button-padding-size);\n --_button-padding: calc(var(--_button-padding-size) * var(--ds-sf));\n --_button-inline-size: var(--ds-button-inline-size);\n}\n\n/* Other controls drawn on this face keep their own measures: an icon or a count's square, a\n tab, a menu entry, a split run and a paper tab. Their colours still follow the faces. */\n:host(:is([icon], [square], [split], [menu-item], [role=\"option\"], [role^=\"menuitem\"], [role=\"tab\"], [choice], [segment], [paper])) {\n --_button-height: initial;\n --_button-radius: initial;\n --_button-font-size: initial;\n --_button-padding: initial;\n --_button-inline-size: initial;\n}\n\n/* Label width is standard. Fixed grid widths are explicit layout choices. */\n:host([fixed]) { width: var(--ds-button-width, calc(4 * var(--ds-unit))); }\n:host([wide]) { width: var(--ds-button-width, calc(8 * var(--ds-unit))); }\n/* Keep fit as an explicit intrinsic-width alias for existing consumers. */\n:host([fit]) {\n flex: 0 0 auto;\n width: max-content;\n}\n:host([fit]) :is(button, a) {\n flex: 0 0 auto;\n /* Hug sizing belongs to the host, including its grid rounding. */\n width: 100%;\n min-width: var(--ds-button-min-width, 0px);\n justify-content: var(--ds-button-align, flex-start);\n}\n:host([fit]) :is(button, a) ds-text,\n:host([fit]) :is(button, a) ::slotted(*) {\n flex: 0 0 auto;\n width: max-content;\n}\n\n:is(button, a) {\n box-sizing: border-box;\n max-height: var(--_button-height, var(--ds-control-height));\n height: var(--_button-height, var(--ds-control-height));\n min-height: var(--_button-height, var(--ds-control-height));\n width: 100%;\n max-width: 100%;\n min-width: 0;\n display: inline-flex;\n align-items: center;\n justify-content: flex-start;\n border: none;\n border-radius: var(--_button-radius, var(--ds-radius));\n corner-shape: var(--ds-corner-shape, round);\n cursor: pointer;\n padding: 0;\n color: var(--button-color, var(--button-text-color));\n font-family: var(--ds-typeface-regular);\n font-size: var(--_button-font-size, var(--ds-type-size-default));\n line-height: var(--ds-type-lineheight-default);\n letter-spacing: var(--ds-type-letterspacing-default);\n -webkit-tap-highlight-color: transparent;\n white-space: nowrap;\n overflow-x: clip;\n overflow-y: visible;\n text-overflow: clip;\n /* Default to no-background when no variant is specified */\n background-color: transparent;\n text-decoration: none;\n}\n\n:is(button, a) ds-text,\n:is(button, a) ::slotted(*) {\n overflow-x: clip;\n overflow-y: visible;\n text-overflow: clip;\n white-space: nowrap;\n max-width: 100%;\n text-align: left;\n justify-content: flex-start;\n color: inherit;\n}\n\n/* Words drawn by a translation key take the button's text size too: unset, the expression\n ds-text resolves for itself (component-metrics.css). */\n:is(button, a) ds-text {\n --ds-type-size-default: var(--_button-font-size, calc(var(--ds-text-size, 14px) * var(--ds-sf, 1)));\n}\n\n:is(button, a)[primary] {\n background-color: var(--ds-button-primary-fill, var(--accent-color));\n color: var(--ds-button-primary-text, var(--button-text-color-on-light, var(--black)));\n text-decoration-line: none;\n font-family: var(--ds-typeface-regular);\n padding: 0 var(--_button-padding, calc(var(--ds-button-inset, 4px) * var(--ds-sf)));\n}\n\n/* The secondary face's words are the text's ink, as on `.ds-button--secondary`\n and the host and slotted faces; the grey is the face, not the words. */\n:is(button, a)[secondary] {\n background-color: var(--ds-button-secondary-fill, var(--button-background-color-secondary));\n color: var(--ds-button-color, var(--ds-button-text-color));\n font-family: var(--ds-typeface-regular);\n padding: 0 var(--_button-padding, calc(var(--ds-button-inset, 4px) * var(--ds-sf)));\n}\n\n/* An entry in a menu or a list of options is one row high on this face too,\n as on the host and slotted faces: the menu's --ds-button-height (one\n square, or a compact row), its words at the start, padded 4px. */\n:host(:is([menu-item], [role=\"option\"], [role^=\"menuitem\"])) :is(button, a) {\n /* Its words and icon on the lower sixteen of the row, the spare four above. */\n align-items: flex-end;\n height: var(--ds-button-height, var(--ds-row-height));\n min-height: var(--ds-button-height, var(--ds-row-height));\n max-height: var(--ds-button-height, var(--ds-row-height));\n justify-content: var(--ds-button-align, flex-start);\n padding: 0 calc(var(--ds-button-inset, 4px) * var(--ds-sf));\n}\n\n/* `danger`: a destructive action's words in the danger colour, on this face\n as on the host and slotted faces and `.ds-button--danger`. */\n:host([danger]) :is(button, a) {\n color: var(--ds-danger-color, var(--ds-tuned-red));\n}\n\n/* Disabled, as in the slotted and host modes. A disabled link has no href. */\n:is(button:disabled, a[aria-disabled=\"true\"]) {\n cursor: default;\n opacity: 0.5;\n}\n\n.loading {\n opacity: 0.7;\n}\n\n/* A button that holds only an icon — every close button — is a 16-unit\n square, scaled with the page, in the shape's corners: the icon fills it,\n no word-padding around it, so the glyph's lines are the same size wherever\n a page or a panel closes, on the web and in the app alike. The element\n marks itself `icon` when its only child is an ds-icon. */\n:host([icon]) {\n flex: 0 0 auto;\n width: var(--ds-control-height);\n height: var(--ds-control-height);\n}\n:host([icon]) :is(button, a) {\n width: var(--ds-control-height);\n padding: 0 !important;\n justify-content: center;\n}\n:host([icon]) ::slotted(ds-icon) {\n flex: 0 0 auto;\n width: var(--ds-control-height);\n height: var(--ds-control-height);\n}\n\n/* A button marked `square` is a 16-unit square whatever it holds, a count\n or an initial, its words in its middle with no word-padding around them. */\n:host([square]) {\n flex: 0 0 auto;\n width: var(--ds-control-height);\n}\n:host([square]) :is(button, a) {\n width: var(--ds-control-height);\n padding: 0 !important;\n justify-content: center;\n}\n\n/* Words and an icon hug them, as on every face (controls.css): this face has\n no gap, and on a filled face the icon takes the padding on its side, its\n own room around the glyph standing in for it. */\n:host(:is([primary], [secondary]):not([icon])) ::slotted(ds-icon:last-child:not(:first-child)) {\n margin-inline-end: calc(-4px * var(--ds-sf));\n}\n:host(:is([primary], [secondary]):not([icon])) ::slotted(ds-icon:first-child:not(:last-child)) {\n margin-inline-start: calc(-4px * var(--ds-sf));\n}\n\n/* A paper tab (`ds-tabs paper`) on this face: a row high with its words on\n the sixteen, half a square of room at its sides, in the run's colours,\n rounded at the top only and square at the foot, where it joins the page. */\n:host([paper]) :is(button, a) {\n height: var(--ds-row-height);\n min-height: var(--ds-row-height);\n max-height: var(--ds-row-height);\n align-items: flex-end;\n padding: 0 calc(10px * var(--ds-sf));\n border-radius: var(--ds-radius) var(--ds-radius) 0 0;\n background-color: var(--ds-button-background, transparent);\n color: var(--ds-button-color, var(--button-text-color));\n}\n\n/* A segment gets its outside corners from the enclosing split control, or\n from its place in a joined run of tabs (`ds-tabs split`). */\n:host([segment]) :is(button, a) {\n border-radius: 0 !important;\n}\n:host(:is([segment=\"first\"], [segment=\"only\"])) :is(button, a) {\n border-start-start-radius: var(--ds-radius) !important;\n border-end-start-radius: var(--ds-radius) !important;\n}\n:host(:is([segment=\"last\"], [segment=\"only\"])) :is(button, a) {\n border-start-end-radius: var(--ds-radius) !important;\n border-end-end-radius: var(--ds-radius) !important;\n}\n\n/* A label is never truncated to meet a chosen width. Round its minimum up,\n so explicit widths and flex layouts retain enough space for all the words. */\n:host(:not([icon]):not([split]):not([menu-item])) {\n min-width: max-content;\n min-width: calc-size(max-content, round(up, size, var(--ds-unit)));\n}\n/* A menu entry keeps a fixed width, so its words may still be cut and fade. */\n:host(:not([menu-item])) :is(button, a),\n:host(:not([menu-item])) :is(button, a) ds-text,\n:host(:not([menu-item])) :is(button, a) ::slotted(*) {\n overflow: visible;\n mask-image: none;\n text-overflow: clip;\n}\n";
6213
6472
  //#endregion
6214
6473
  //#region DS1/2-core/button-split.ts
6215
6474
  function choices(host) {
@@ -6903,6 +7162,7 @@ function bindContextMenuTrigger(trigger, open, { resolve = () => trigger, touch
6903
7162
  let suppressed = null;
6904
7163
  let release;
6905
7164
  let disposed = false;
7165
+ let mousePress;
6906
7166
  if (aria) trigger.setAttribute("aria-haspopup", "menu");
6907
7167
  if (selection) trigger.classList.add("ds-context-trigger");
6908
7168
  const enabled = (control) => control.isConnected && !control.matches(":disabled, [disabled], [aria-disabled=\"true\"], [data-disabled]") && !control.closest("[hidden], [inert]");
@@ -6926,6 +7186,7 @@ function bindContextMenuTrigger(trigger, open, { resolve = () => trigger, touch
6926
7186
  release = setTimeout(clearSuppression, 1e3);
6927
7187
  };
6928
7188
  const cancel = () => {
7189
+ mousePress = void 0;
6929
7190
  clearTimeout(timer);
6930
7191
  timer = void 0;
6931
7192
  point = void 0;
@@ -6939,7 +7200,16 @@ function bindContextMenuTrigger(trigger, open, { resolve = () => trigger, touch
6939
7200
  };
6940
7201
  const context = (event) => {
6941
7202
  if (event.defaultPrevented) return;
6942
- const control = resolve(event);
7203
+ const pressed = mousePress;
7204
+ mousePress = void 0;
7205
+ const gesture = pressed && event.button === 2 && Math.hypot(event.clientX - pressed.event.clientX, event.clientY - pressed.event.clientY) <= 10 ? new Proxy(pressed.event, { get(target, key) {
7206
+ if (key === "composedPath") return () => pressed.path;
7207
+ if (key === "target") return pressed.target;
7208
+ const value = Reflect.get(target, key, target);
7209
+ return typeof value === "function" ? value.bind(target) : value;
7210
+ } }) : event;
7211
+ if (gesture !== event && (typeof gesture.target?.nodeType !== "number" || !trigger.contains(gesture.target))) return;
7212
+ const control = resolve(gesture);
6943
7213
  if (!control || !enabled(control)) return;
6944
7214
  cancel();
6945
7215
  if (suppressed === control) {
@@ -6949,7 +7219,7 @@ function bindContextMenuTrigger(trigger, open, { resolve = () => trigger, touch
6949
7219
  if (open({
6950
7220
  x: event.clientX,
6951
7221
  y: event.clientY
6952
- }, control, event) === false) return;
7222
+ }, control, gesture) === false) return;
6953
7223
  event.preventDefault();
6954
7224
  if (selection && event.button === 0) arm(control);
6955
7225
  };
@@ -6992,6 +7262,15 @@ function bindContextMenuTrigger(trigger, open, { resolve = () => trigger, touch
6992
7262
  if (open(at, control, event) !== false) arm(control);
6993
7263
  }, 500);
6994
7264
  };
7265
+ const rememberMouse = (event) => {
7266
+ mousePress = !event.defaultPrevented && event.pointerType === "mouse" && event.button === 2 ? {
7267
+ event,
7268
+ path: event.composedPath(),
7269
+ target: event.target
7270
+ } : void 0;
7271
+ };
7272
+ trigger.addEventListener("pointerdown", rememberMouse, true);
7273
+ trigger.addEventListener("pointercancel", cancel, true);
6995
7274
  trigger.addEventListener("contextmenu", context);
6996
7275
  trigger.addEventListener("keydown", key);
6997
7276
  trigger.addEventListener("pointerdown", press);
@@ -7004,6 +7283,8 @@ function bindContextMenuTrigger(trigger, open, { resolve = () => trigger, touch
7004
7283
  trigger.removeEventListener("contextmenu", context);
7005
7284
  trigger.removeEventListener("keydown", key);
7006
7285
  trigger.removeEventListener("pointerdown", press);
7286
+ trigger.removeEventListener("pointerdown", rememberMouse, true);
7287
+ trigger.removeEventListener("pointercancel", cancel, true);
7007
7288
  if (aria) {
7008
7289
  if (previousAria === null) trigger.removeAttribute("aria-haspopup");
7009
7290
  else trigger.setAttribute("aria-haspopup", previousAria);
@@ -11987,8 +12268,21 @@ var _Avatar;
11987
12268
  provideIcons({ avatar: avatarIcon });
11988
12269
  /** The ink that reads on a fill: black on a light one, white on a dark one. */
11989
12270
  var inkOn = (rgb) => {
11990
- const linear = rgb.map((n) => n <= .04045 ? n / 12.92 : ((n + .055) / 1.055) ** 2.4);
11991
- return linear[0] * .2126 + linear[1] * .7152 + linear[2] * .0722 > .22 ? "var(--ds-black)" : "var(--ds-white)";
12271
+ const fill = [...rgb, 1];
12272
+ const white = [
12273
+ 1,
12274
+ 1,
12275
+ 1,
12276
+ 1
12277
+ ];
12278
+ if (contrastRatio([
12279
+ 42 / 255,
12280
+ 42 / 255,
12281
+ 42 / 255,
12282
+ 1
12283
+ ], fill) >= 4.5) return "var(--ds-black)";
12284
+ if (contrastRatio(white, fill) >= 4.5) return "var(--ds-white)";
12285
+ return "black";
11992
12286
  };
11993
12287
  /** Saved identity colours are hex; their ink must stay legible in either theme. */
11994
12288
  function avatarInk(color) {
@@ -12023,6 +12317,14 @@ function inkOnResolved(fill) {
12023
12317
  var Avatar = class extends i$4 {
12024
12318
  constructor() {
12025
12319
  super();
12320
+ this.inkFrame = 0;
12321
+ this.queueInk = () => {
12322
+ const view = this.ownerDocument.defaultView;
12323
+ if (!this.inkFrame) this.inkFrame = view.requestAnimationFrame(() => {
12324
+ this.inkFrame = 0;
12325
+ this.resolveInk();
12326
+ });
12327
+ };
12026
12328
  this.label = "";
12027
12329
  this.initial = "?";
12028
12330
  this.src = "";
@@ -12034,6 +12336,28 @@ var Avatar = class extends i$4 {
12034
12336
  this.fit = "cover";
12035
12337
  this.logo = this.mask = this.outlined = this.pending = this.plain = this.loaded = this.failed = false;
12036
12338
  }
12339
+ connectedCallback() {
12340
+ super.connectedCallback();
12341
+ const view = this.ownerDocument.defaultView;
12342
+ this.inkObserver = new view.MutationObserver(this.queueInk);
12343
+ for (let parent = this; parent; parent = parent.parentElement ?? parent.getRootNode().host ?? null) this.inkObserver.observe(parent, {
12344
+ attributes: true,
12345
+ attributeFilter: [
12346
+ "class",
12347
+ "style",
12348
+ "data-theme"
12349
+ ]
12350
+ });
12351
+ view.addEventListener("load", this.queueInk, true);
12352
+ }
12353
+ disconnectedCallback() {
12354
+ super.disconnectedCallback();
12355
+ this.inkObserver?.disconnect();
12356
+ const view = this.ownerDocument.defaultView;
12357
+ view.cancelAnimationFrame(this.inkFrame);
12358
+ this.inkFrame = 0;
12359
+ view.removeEventListener("load", this.queueInk, true);
12360
+ }
12037
12361
  willUpdate(changed) {
12038
12362
  if (changed.has("src")) {
12039
12363
  this.loaded = false;
@@ -12043,7 +12367,7 @@ var Avatar = class extends i$4 {
12043
12367
  }
12044
12368
  /** A fill named by a variable (`var(--brand-highlight)`) gets its ink once the browser has resolved it. */
12045
12369
  updated() {
12046
- if (this.ink || this.pending || this.plain || !this.color || avatarInk(this.color)) return;
12370
+ if (this.ink || this.pending || this.plain) return;
12047
12371
  this.resolveInk();
12048
12372
  }
12049
12373
  /** Reads the fill as drawn; while the variable is still undefined (its stylesheet on its way), looks again next
@@ -12051,12 +12375,28 @@ var Avatar = class extends i$4 {
12051
12375
  resolveInk(tries = 0) {
12052
12376
  const frame = this.renderRoot.querySelector(".frame");
12053
12377
  if (!frame || !this.isConnected) return;
12054
- const ink = inkOnResolved(getComputedStyle(frame).backgroundColor);
12055
- if (ink) {
12056
- frame.style.color = ink;
12378
+ if (this.ink || this.pending || this.plain) return;
12379
+ const view = this.ownerDocument.defaultView;
12380
+ const read = colorReader(this.ownerDocument);
12381
+ let fill = read(view.getComputedStyle(frame).backgroundColor);
12382
+ if (fill && fill[3] > 0) {
12383
+ for (let parent = this; parent && fill[3] < 1; parent = parent.parentElement ?? parent.getRootNode().host ?? null) {
12384
+ const background = read(view.getComputedStyle(parent).backgroundColor);
12385
+ if (background) fill = composite(fill, background);
12386
+ }
12387
+ if (fill[3] < 1) return;
12388
+ const tokens = view.getComputedStyle(this);
12389
+ const black = read(tokens.getPropertyValue("--ds-black").trim()), white = read(tokens.getPropertyValue("--ds-white").trim());
12390
+ const ink = black && contrastRatio(black, fill) >= 4.5 ? "var(--ds-black)" : white && contrastRatio(white, fill) >= 4.5 ? "var(--ds-white)" : contrastRatio([
12391
+ 0,
12392
+ 0,
12393
+ 0,
12394
+ 1
12395
+ ], fill) >= 4.5 ? "black" : "white";
12396
+ if (frame.style.color !== ink) frame.style.color = ink;
12057
12397
  return;
12058
12398
  }
12059
- if (tries < 60) requestAnimationFrame(() => this.resolveInk(tries + 1));
12399
+ if (tries < 60) this.ownerDocument.defaultView.requestAnimationFrame(() => this.resolveInk(tries + 1));
12060
12400
  if (tries === 0 && this.ownerDocument.readyState !== "complete") this.ownerDocument.defaultView?.addEventListener("load", () => this.resolveInk(60), { once: true });
12061
12401
  }
12062
12402
  render() {
@@ -12071,7 +12411,7 @@ var Avatar = class extends i$4 {
12071
12411
  class="frame"
12072
12412
  style=${o({
12073
12413
  backgroundColor: !this.pending && !this.plain ? this.color || void 0 : void 0,
12074
- color: !this.pending ? this.ink || avatarInk(this.color) : void 0
12414
+ color: !this.pending ? this.ink || (!this.plain ? avatarInk(this.color) : void 0) : void 0
12075
12415
  })}
12076
12416
  role=${this.label ? "img" : A}
12077
12417
  aria-label=${this.label || A}
@@ -14928,7 +15268,7 @@ _Form.styles = i$5`
14928
15268
  pointer-events: none;
14929
15269
  }
14930
15270
  `;
14931
- customElements.define("ds-form", Form);
15271
+ define("ds-form", Form);
14932
15272
  //#endregion
14933
15273
  //#region DS1/3-unit/styles/ds-list.css?inline
14934
15274
  var ds_list_default = "/* Shape is a surface preference. Keep these base lengths unscaled: each\n * component resolves them with its own --ds-sf in component-metrics.css. */\n[data-ds-shape=\"square\"],\n:host([data-ds-shape=\"square\"]) {\n --ds-corner-size: 0px;\n --ds-corner-shape: round;\n --ds-button-inset: 0.5px;\n}\n\n[data-ds-shape=\"rounded\"],\n:host([data-ds-shape=\"rounded\"]) {\n --ds-corner-size: 4px;\n --ds-corner-shape: round;\n --ds-button-inset: 2.5px;\n}\n\n[data-ds-shape=\"circular\"],\n:host([data-ds-shape=\"circular\"]) {\n --ds-corner-size: 999px;\n --ds-corner-shape: round;\n --ds-button-inset: 5px;\n}\n\n/* A squircle needs twice an arc's radius for the same apparent corner.\n * Circular controls keep true semicircles even in a squircle browser. */\n@supports (corner-shape: squircle) {\n [data-ds-shape=\"rounded\"],\n :host([data-ds-shape=\"rounded\"]) {\n --ds-corner-size: 8px;\n --ds-corner-shape: squircle;\n }\n}\n/* Resolve dimensions where a component is drawn. Inherited calc() values have\n * already used their ancestor's scale. Base tokens stay unscaled and inherit;\n * resolved tokens can still be overridden directly on a component.\n * data-ds-surface gives ordinary layout elements the same scaled measures.\n */\n:root,\n:host,\n:where([data-ds-surface]) {\n --ds-unit: calc(var(--ds-1, 20px) * var(--ds-sf, 1));\n --ds-control-height: calc(var(--ds-control-size, var(--ds-08, 16px)) * var(--ds-sf, 1));\n --ds-row-height: calc(var(--ds-row-size, var(--ds-1, 20px)) * var(--ds-sf, 1));\n --ds-type-size-default: calc(var(--ds-text-size, 14px) * var(--ds-sf, 1));\n --ds-type-size-small: calc(var(--ds-small-text-size, 10px) * var(--ds-sf, 1));\n --ds-type-size-heading: calc(var(--ds-heading-text-size, 13px) * var(--ds-sf, 1));\n --ds-type-size-display: calc(var(--ds-display-text-size, 20px) * var(--ds-sf, 1));\n --ds-type-lineheight-default: calc(var(--ds-text-leading, 16px) * var(--ds-sf, 1));\n --ds-type-letterspacing-default: calc(var(--ds-text-tracking, -0.3px) * var(--ds-sf, 1));\n --ds-radius: calc(var(--ds-corner-size, 4px) * var(--ds-sf, 1));\n --ds-radius-card: calc(var(--ds-card-corner-size, 4px) * var(--ds-sf, 1));\n}\n/* ds-list.css */\n\n:host {\n box-sizing: border-box;\n display: flex;\n flex-direction: column;\n gap: var(--ds-list-gap, 0);\n width: 100%;\n min-width: 0;\n}\n:host([hidden]) { display: none !important; }\n\n:host([columns]) {\n display: grid;\n grid-template-columns: repeat(var(--ds-list-columns, 2), minmax(0, 1fr));\n column-gap: var(--ds-list-column-gap, calc(var(--ds-unit) / 2));\n}\n\n:host([columns=\"2\"]) {\n --ds-list-columns: 2;\n}\n\n:host([columns=\"3\"]) {\n --ds-list-columns: 3;\n}\n\n/* The group's name on the square above its rows: words on the lower sixteen of the square with\n the spare four above, in the muted ink, as a day's heading stands over Money's payments. It\n names the list (aria-label), so the drawn words are hidden from assistive technology. */\n.heading {\n box-sizing: border-box;\n flex: none;\n grid-column: 1 / -1;\n width: 100%;\n height: var(--ds-unit);\n padding-top: calc(4px * var(--ds-sf));\n overflow: hidden;\n color: var(--ds-list-heading-color, var(--ds-text-color-muted));\n font-family: var(--ds-typeface-regular);\n font-feature-settings: var(--ds-font-features);\n font-size: var(--ds-type-size-default);\n font-weight: var(--ds-type-weight-default);\n line-height: var(--ds-type-lineheight-default);\n letter-spacing: var(--ds-type-letterspacing-default);\n white-space: nowrap;\n text-overflow: clip;\n}\n/* A cut heading fades over its last square, never an ellipsis (0-face/cut-fade.ts marks it). */\n.heading[data-cut=\"end\"] { mask-image: linear-gradient(to right, #000 calc(100% - var(--ds-unit)), transparent); }\n:host(:dir(rtl)) .heading[data-cut=\"end\"] { mask-image: linear-gradient(to left, #000 calc(100% - var(--ds-unit)), transparent); }\n";
@@ -15966,7 +16306,7 @@ function text(value) {
15966
16306
  if (blank(value)) return "";
15967
16307
  return typeof value === "number" ? value.toLocaleString() : String(value);
15968
16308
  }
15969
- customElements.define("ds-table", DsTable);
16309
+ define("ds-table", DsTable);
15970
16310
  //#endregion
15971
16311
  //#region DS1/3-unit/styles/ds-table-of-contents.css?inline
15972
16312
  var ds_table_of_contents_default = "/* Shape is a surface preference. Keep these base lengths unscaled: each\n * component resolves them with its own --ds-sf in component-metrics.css. */\n[data-ds-shape=\"square\"],\n:host([data-ds-shape=\"square\"]) {\n --ds-corner-size: 0px;\n --ds-corner-shape: round;\n --ds-button-inset: 0.5px;\n}\n\n[data-ds-shape=\"rounded\"],\n:host([data-ds-shape=\"rounded\"]) {\n --ds-corner-size: 4px;\n --ds-corner-shape: round;\n --ds-button-inset: 2.5px;\n}\n\n[data-ds-shape=\"circular\"],\n:host([data-ds-shape=\"circular\"]) {\n --ds-corner-size: 999px;\n --ds-corner-shape: round;\n --ds-button-inset: 5px;\n}\n\n/* A squircle needs twice an arc's radius for the same apparent corner.\n * Circular controls keep true semicircles even in a squircle browser. */\n@supports (corner-shape: squircle) {\n [data-ds-shape=\"rounded\"],\n :host([data-ds-shape=\"rounded\"]) {\n --ds-corner-size: 8px;\n --ds-corner-shape: squircle;\n }\n}\n/* Resolve dimensions where a component is drawn. Inherited calc() values have\n * already used their ancestor's scale. Base tokens stay unscaled and inherit;\n * resolved tokens can still be overridden directly on a component.\n * data-ds-surface gives ordinary layout elements the same scaled measures.\n */\n:root,\n:host,\n:where([data-ds-surface]) {\n --ds-unit: calc(var(--ds-1, 20px) * var(--ds-sf, 1));\n --ds-control-height: calc(var(--ds-control-size, var(--ds-08, 16px)) * var(--ds-sf, 1));\n --ds-row-height: calc(var(--ds-row-size, var(--ds-1, 20px)) * var(--ds-sf, 1));\n --ds-type-size-default: calc(var(--ds-text-size, 14px) * var(--ds-sf, 1));\n --ds-type-size-small: calc(var(--ds-small-text-size, 10px) * var(--ds-sf, 1));\n --ds-type-size-heading: calc(var(--ds-heading-text-size, 13px) * var(--ds-sf, 1));\n --ds-type-size-display: calc(var(--ds-display-text-size, 20px) * var(--ds-sf, 1));\n --ds-type-lineheight-default: calc(var(--ds-text-leading, 16px) * var(--ds-sf, 1));\n --ds-type-letterspacing-default: calc(var(--ds-text-tracking, -0.3px) * var(--ds-sf, 1));\n --ds-radius: calc(var(--ds-corner-size, 4px) * var(--ds-sf, 1));\n --ds-radius-card: calc(var(--ds-card-corner-size, 4px) * var(--ds-sf, 1));\n}\n:host {\n display: block;\n width: var(--ds-toc-width, calc(12 * var(--ds-unit)));\n max-width: 100%;\n color: var(--ds-text-color);\n font-family: var(--ds-typeface-regular);\n font-size: var(--ds-type-size-default);\n font-weight: var(--ds-type-weight-default);\n line-height: var(--ds-type-lineheight-default);\n letter-spacing: var(--ds-type-letterspacing-default);\n}\n:host([hidden]) { display: none !important; }\n.title { display: flex; align-items: flex-end; min-height: var(--ds-unit); }\n.links { margin-top: var(--ds-unit); }\na, ::slotted(a) {\n box-sizing: border-box;\n display: block;\n min-height: var(--ds-unit);\n padding-top: calc(4px * var(--ds-sf));\n padding-inline-start: calc(var(--toc-depth, 0) * var(--ds-unit));\n color: var(--ds-toc-color, var(--ds-text-color-muted));\n font: inherit;\n line-height: var(--ds-type-lineheight-default);\n letter-spacing: inherit;\n text-decoration: none;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: clip;\n}\n/* A cut label fades over its last square, never an ellipsis (0-face/cut-fade.ts marks it). */\na[data-cut=\"end\"], ::slotted(a[data-cut=\"end\"]) {\n mask-image: linear-gradient(to right, #000 calc(100% - var(--ds-unit)), transparent);\n}\n:host(:dir(rtl)) a[data-cut=\"end\"], :host(:dir(rtl)) ::slotted(a[data-cut=\"end\"]) {\n mask-image: linear-gradient(to left, #000 calc(100% - var(--ds-unit)), transparent);\n}\na:hover, ::slotted(a:hover) { color: var(--ds-text-color); text-decoration: underline; }\na[aria-current], ::slotted(a[aria-current]) { color: var(--ds-toc-current-color, var(--ds-sharp-blue)); }\na:focus-visible, ::slotted(a:focus-visible) { outline: 1px solid var(--ds-focus-ring); outline-offset: 1px; }\n";
@@ -16161,7 +16501,7 @@ var Container = class extends i$4 {
16161
16501
  };
16162
16502
  _Container = Container;
16163
16503
  _Container.styles = r$4(ds_container_default);
16164
- customElements.define("ds-container", Container);
16504
+ define("ds-container", Container);
16165
16505
  //#endregion
16166
16506
  //#region DS1/4-page/styles/ds-grid.css?inline
16167
16507
  var ds_grid_default = "/* A one-square pitch by default. Rules stay hairlines at every surface scale. */\n:host {\n --grid-scale: var(--ds-sf, var(--sf, 1));\n display: block;\n position: fixed;\n top: 0;\n left: 50%;\n transform: translateX(-50%);\n width: var(--ds-grid-width, calc(1440px * var(--grid-scale)));\n height: var(--ds-grid-height, max(100vh, calc(1280px * var(--grid-scale))));\n pointer-events: none;\n z-index: var(--ds-grid-z-index, 300);\n}\n:host([hidden]) { display: none !important; }\n:host([align=\"left\"]) { left: 0; transform: none; }\n:host([align=\"right\"]) { left: auto; right: 0; transform: none; }\n@media (max-width: 768px) {\n :host { width: calc(100% - 1px); max-width: 100vw; }\n}\n/* The containing element establishes positioning; its content stays above the paper. */\n:host([contained]) { position: absolute; inset: 0; width: 100%; height: 100%; max-width: none; transform: none; z-index: 0; }\n.paper {\n position: absolute;\n inset: 0;\n --cell: var(--ds-grid-unit, calc(var(--pitch-x) * var(--grid-scale)));\n --row: var(--ds-grid-row, calc(var(--pitch-y) * var(--grid-scale)));\n --stroke: var(--ds-grid-stroke, 1px);\n background-color: var(--paper);\n background-size: var(--cell) var(--row);\n background-position: calc(var(--ds-grid-origin-x, 0px) - var(--stroke) / 2) calc(var(--ds-grid-origin-y, 0px) - var(--stroke) / 2);\n background-repeat: repeat;\n}\n[data-pattern=\"grid\"] { background-image: linear-gradient(to right, var(--ink) var(--stroke), transparent var(--stroke)), linear-gradient(to bottom, var(--ink) var(--stroke), transparent var(--stroke)); }\n[data-pattern=\"lines\"] { background-image: linear-gradient(to bottom, var(--ink) var(--stroke), transparent var(--stroke)); }\n[data-pattern=\"columns\"] { background-image: linear-gradient(to right, var(--ink) var(--stroke), transparent var(--stroke)); }\n[data-pattern=\"dots\"] {\n background-image: radial-gradient(circle, var(--ink) var(--stroke), transparent var(--stroke));\n background-position: var(--ds-grid-origin-x, 0px) calc(var(--ds-grid-origin-y, 0px) - var(--row) / 2);\n}\n[data-pattern=\"none\"] { background-image: none; }\n@media print { :host(:not([contained])) { display: none; } }\n";