@ai-matrx/design-system 0.86.367 → 0.86.369

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 (63) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/dist/accents.cjs +2 -2
  3. package/dist/accents.cjs.map +1 -1
  4. package/dist/accents.js +2 -2
  5. package/dist/accents.js.map +1 -1
  6. package/dist/carousel.js +5 -5
  7. package/dist/{chunk-SOCDBDFS.js → chunk-2HB65PD6.js} +2 -2
  8. package/dist/{chunk-KOUHF4JC.js → chunk-345ESUIP.js} +4 -4
  9. package/dist/{chunk-LS3C7IUV.js → chunk-ASDPMZNP.js} +2 -2
  10. package/dist/{chunk-WLKHQGX5.js → chunk-CFFRMRWJ.js} +2 -2
  11. package/dist/{chunk-TBWKBTPM.js → chunk-F7IUBABK.js} +3 -3
  12. package/dist/{chunk-3JEAXZKX.js → chunk-FLYZSVTR.js} +2 -2
  13. package/dist/{chunk-UAIBCPMB.js → chunk-RLTTLDWU.js} +4 -4
  14. package/dist/{chunk-VG765KBC.js → chunk-U3SUPYNT.js} +2 -2
  15. package/dist/{chunk-QEQDJBNK.js → chunk-WSEZDHLA.js} +2 -2
  16. package/dist/{chunk-3YTF4MH2.js → chunk-YA3DNAAC.js} +2 -2
  17. package/dist/clipboard-fallback-dialog.js +2 -2
  18. package/dist/controls.css +7 -0
  19. package/dist/controls.js +5 -5
  20. package/dist/data-table/data-row-inspector.js +5 -5
  21. package/dist/data-table/data-row-window.js +6 -6
  22. package/dist/data-table/index.cjs +5 -1
  23. package/dist/data-table/index.cjs.map +1 -1
  24. package/dist/data-table/index.js +13 -9
  25. package/dist/data-table/index.js.map +1 -1
  26. package/dist/data-table/pagination.js +3 -3
  27. package/dist/data-table/uuid-cell.js +3 -3
  28. package/dist/desktop.js +4 -4
  29. package/dist/index.cjs +20 -12
  30. package/dist/index.cjs.map +1 -1
  31. package/dist/index.d.cts +9 -4
  32. package/dist/index.d.ts +9 -4
  33. package/dist/index.js +27 -19
  34. package/dist/index.js.map +1 -1
  35. package/dist/item.js +3 -3
  36. package/dist/lite-table.js +2 -2
  37. package/dist/record-list.js +3 -3
  38. package/dist/settings.js +3 -3
  39. package/dist/swipe-deck-tree.cjs +238 -0
  40. package/dist/swipe-deck-tree.cjs.map +1 -0
  41. package/dist/swipe-deck-tree.d.cts +202 -0
  42. package/dist/swipe-deck-tree.d.ts +202 -0
  43. package/dist/swipe-deck-tree.js +208 -0
  44. package/dist/swipe-deck-tree.js.map +1 -0
  45. package/dist/swipe-deck.cjs.map +1 -1
  46. package/dist/swipe-deck.d.cts +5 -0
  47. package/dist/swipe-deck.d.ts +5 -0
  48. package/dist/swipe-deck.js.map +1 -1
  49. package/dist/text-input-dialog.js +1 -1
  50. package/dist/tokens.css +23 -23
  51. package/dist/web-theme.css +20 -20
  52. package/dist/writing-field.js +3 -3
  53. package/package.json +12 -2
  54. /package/dist/{chunk-SOCDBDFS.js.map → chunk-2HB65PD6.js.map} +0 -0
  55. /package/dist/{chunk-KOUHF4JC.js.map → chunk-345ESUIP.js.map} +0 -0
  56. /package/dist/{chunk-LS3C7IUV.js.map → chunk-ASDPMZNP.js.map} +0 -0
  57. /package/dist/{chunk-WLKHQGX5.js.map → chunk-CFFRMRWJ.js.map} +0 -0
  58. /package/dist/{chunk-TBWKBTPM.js.map → chunk-F7IUBABK.js.map} +0 -0
  59. /package/dist/{chunk-3JEAXZKX.js.map → chunk-FLYZSVTR.js.map} +0 -0
  60. /package/dist/{chunk-UAIBCPMB.js.map → chunk-RLTTLDWU.js.map} +0 -0
  61. /package/dist/{chunk-VG765KBC.js.map → chunk-U3SUPYNT.js.map} +0 -0
  62. /package/dist/{chunk-QEQDJBNK.js.map → chunk-WSEZDHLA.js.map} +0 -0
  63. /package/dist/{chunk-3YTF4MH2.js.map → chunk-YA3DNAAC.js.map} +0 -0
package/CHANGELOG.md CHANGED
@@ -1,7 +1,21 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.86.369
4
+
5
+ - `Button variant="meta"` with an `icon`: the glyph is a fine 12px, thin-stroke icon at the heading's size (a section heading's icon-only action, e.g. Pinned's edit). Additive; no consumer action required.
6
+
7
+ ## 0.86.368
8
+
9
+ SOURCE CHANGED WITH NO RELEASE NOTE. This release shipped source changes that nobody described in an Unreleased entry,
10
+ so they may include breaking API changes. Consumer action is UNKNOWN: read the diff before upgrading.
11
+ Diff: `git diff f77bffa2f8 be7cbbcfd7 -- apps/shared/design-system/src`
12
+
13
+ Changed shipped source files (1):
14
+ - `apps/shared/design-system/src/data-table/multiline-rows.ts`
15
+
3
16
  ## 0.86.367
4
17
 
18
+ - `smsBodyHref(body, phone?)` (contact values): a new message with the words already typed and no recipient chosen (`sms:?&body=`, read by Apple and Android). Additive; no consumer action required.
5
19
  - `SwipeDeck`: the answered list's "Show me how" is its own quiet button (`matrx-sd-review-help`, 40px, one line, 16px icon) beside "Back to cards"; it borrowed the legend-chip style, which sizes no icon, so the icon drew full size and the words wrapped. No consumer action required.
6
20
  - **Contact values: "Search the web" runs in the host's own search, prefetched.** `ContactValuesHost` takes `searchWeb(query)` (open the host's search in place instead of an outbound Google link) and `prefetchWebSearch(query)` → `WebSearchPrefetch` (`get` / `subscribe`; `get` returns the same object until the status changes). The peek starts the prefetch when it opens and the row shows "Searching…", then "N results" / "No results"; the row always sits last in Actions, built by the shell from each value's `webSearch` words, and closes the peek when pressed. Phones search as `949-404-4444`. Peek rows (`PeekRow`, `PeekCardRowsItem`) gain `onSelect`, an in-place action drawn like a link that wins over `href`. Guards: `contact-values.test.tsx` "searches the web in the host's own search", "starts the host's search when the peek opens". Consumer action: pass `searchWeb` (and optionally `prefetchWebSearch`) to keep searches in-app; without them the row stays an outbound link.
7
21
 
@@ -1848,6 +1862,7 @@ Changed shipped source files (1):
1848
1862
  ## 0.86.180
1849
1863
  ### Unreleased
1850
1864
 
1865
+ - `DeckItem.parent` and the new entry `@ai-matrx/design-system/swipe-deck-tree`: `TREE_KEY` / `TREE_ANSWERS` (Just right, Too broad, Too narrow, Not useful), `treePath`, `treeBreadcrumb`, `treeChildren`, `deckTreeGraph`, and `useDeckTree(seed, answers, connection)` — Too broad asks the host to `split` a card and its children join right after the card on screen, Too narrow (two or more under one parent) asks it to `combine` them. Additive; no consumer action required.
1851
1866
  - **Swipe deck: Skip for now, Cmd+Z, and what rode with an answer (2026-10-24).** `DeckAnswer.requeue`: in a bound deck the connection sends the card to the back of the line and keeps no answer (the change is `transient`: the deck stops drawing it the moment the connection resolves, so it is never counted as answered); undo brings it back. Cmd+Z / Ctrl+Z undo (outside fields). The Answered list reads a typed `detail.value` in place of the card's text and a `detail.label` in place of the answer's label ("Competitor" for Related). The Answered corner opens with nothing answered yet (it holds the walkthrough). Guard: `src/swipe-deck/answered-list.test.tsx`. Consumer action: none.
1852
1867
  - **Swipe deck: every answer stays one tap from changing it, mid-deck (Arman, 2026-10-24).** The top-right corner is now **Answered** (with the count): the finish screen's list of every answer at any point, each row taking its answer back and returning to that card; Escape or "Back to cards" leaves it, and the card takes no answers while it is open. "Show me how" moved into that list (the `?` key still plays it). `DeckAnswer.hidden`: an answer the card's own controls give (`ctx.answer`) but never drawn as a button ("Related ▸ Partner"). Labels `answered`, `backToCards`. Undo (Z) already walked back up to 100 steps; a binding's `history` carries it past a reload. Guard: `src/swipe-deck/answered-list.test.tsx`. Consumer action: none.
1853
1868
  - **`LabeledField` (controls) — the above-label wrapper for any form input (wave 2, 2026-10-22).** Moved from matrx-frontend `components/official/Field` (label, required / optional mark, help via `InfoHint`, description, error, counter, and the EMPTY-LOOKS-EMPTY rule for a required field whose placeholder could pass for an answer). Named `LabeledField` because `Field` in this package is already the input capsule. The error's content menu comes from the existing `ErrorCardMenusProvider` slot (no host import); without a provider the error shows without a menu. Guard: `src/controls/wave2-official-units.test.tsx`. **Consumer action:** none.
package/dist/accents.cjs CHANGED
@@ -60,8 +60,8 @@ var ACCENTS = [
60
60
  ];
61
61
  var DEFAULT_ACCENT_ID = "blue";
62
62
  var ACCENT_CONTRAST_SURFACES = {
63
- light: "240 5% 96%",
64
- dark: "240 4% 16%"
63
+ light: "240 6% 99%",
64
+ dark: "240 4% 12.5%"
65
65
  };
66
66
  var DARK_TEXT = "240 10% 10%";
67
67
  var WHITE_TEXT = "0 0% 100%";
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/accents.ts"],"sourcesContent":["// THE ACCENT COLORS (custom-experience P4, 2026-10-20) — the curated accent set a person or an\n// organization picks from, the custom-hex path beside it, and the WCAG contrast math that WARNS about a\n// weak choice (it offers, never blocks). Pure: no React, no DOM, server-safe (`./accents` entry).\n//\n// An accent drives the design system's brand role tokens and nothing else:\n// --primary, --primary-foreground, --primary-ink (light, and their .dark twins)\n// `--accent` in this vocabulary is the quiet GRAY hover surface and is never touched; text tokens stay\n// foreground / muted / disabled. The host stamps `accentAttributes(ref)` on a root element (an attribute\n// plus six custom properties); `accent.css` maps them onto the role tokens for light and dark. With no\n// accent nothing is stamped and nothing changes.\n//\n// One rule makes both themes from ONE color (the same rule for a curated and a custom accent):\n// light primary = the color exactly as chosen;\n// dark primary = the same hue and saturation, lightened until it reads on the dark canvas (≥ 3:1);\n// foreground = white or near-black, whichever reads better on that primary;\n// ink = the primary as TEXT on its own tint: darkened (light) / lightened (dark) to ≥ 5.5:1\n// against the canvas (headroom for the 15% tint it sits on).\n\n/** An HSL color as the token vocabulary writes it: `\"210 80% 45%\"`. */\nexport type HslTriplet = string;\n\nexport interface Hsl {\n h: number;\n s: number;\n l: number;\n}\n\n/** One curated accent. `hex` is its light-theme primary; the dark twin is derived (see the header). */\nexport interface AccentDef {\n id: string;\n label: string;\n hex: string;\n}\n\n/**\n * THE CURATED SET. Every entry passes AA in both themes (guard: accents.test.ts): white or near-black\n * text on it ≥ 4.5:1, and it reads on the canvas ≥ 3:1. Ids are saved in layouts — never renamed.\n * `blue` is the built-in primary (`--primary: 210 80% 45%`), so choosing it equals choosing nothing.\n */\nexport const ACCENTS: readonly AccentDef[] = [\n { id: \"blue\", label: \"Blue\", hex: \"#1773cf\" },\n { id: \"indigo\", label: \"Indigo\", hex: \"#4f46e5\" },\n { id: \"violet\", label: \"Violet\", hex: \"#7c3aed\" },\n { id: \"purple\", label: \"Purple\", hex: \"#9333ea\" },\n { id: \"pink\", label: \"Pink\", hex: \"#db2777\" },\n { id: \"rose\", label: \"Rose\", hex: \"#e11d48\" },\n { id: \"red\", label: \"Red\", hex: \"#dc2626\" },\n { id: \"orange\", label: \"Orange\", hex: \"#c2410c\" },\n { id: \"amber\", label: \"Amber\", hex: \"#b45309\" },\n { id: \"green\", label: \"Green\", hex: \"#15803d\" },\n { id: \"emerald\", label: \"Emerald\", hex: \"#047857\" },\n { id: \"teal\", label: \"Teal\", hex: \"#0f766e\" },\n { id: \"cyan\", label: \"Cyan\", hex: \"#0e7490\" },\n { id: \"slate\", label: \"Slate\", hex: \"#475569\" },\n];\n\nexport const DEFAULT_ACCENT_ID = \"blue\";\n\n/**\n * The canvases a primary is judged against: the design system's `--background` in light and dark\n * (tokens.css; accents.test.ts fails if they drift apart).\n */\nexport const ACCENT_CONTRAST_SURFACES: Readonly<Record<AccentTheme, HslTriplet>> = {\n light: \"240 5% 96%\",\n dark: \"240 4% 16%\",\n};\n\n/** The dark text a light primary carries (tokens.css `--secondary-foreground`). */\nconst DARK_TEXT: HslTriplet = \"240 10% 10%\";\nconst WHITE_TEXT: HslTriplet = \"0 0% 100%\";\n\n/** WCAG 2.2 AA: 4.5:1 for text, 3:1 for a control's color against what is around it (1.4.11). */\nexport const AA_TEXT = 4.5;\nexport const AA_NON_TEXT = 3;\n/** The ink is judged against the canvas with headroom, because it sits on a 15% tint of itself. */\nconst INK_TARGET = 5.5;\n\nexport type AccentTheme = \"light\" | \"dark\";\n\n/** A saved accent: a curated id, or a custom `#rrggbb`. */\nexport type AccentRef = { id: string } | { hex: string };\n\n// ── color math ─────────────────────────────────────────────────────────────────\n\n/** `#rgb` / `#rrggbb` (any case, `#` optional) → `#rrggbb` lowercase; anything else → null. */\nexport function normalizeHex(value: unknown): string | null {\n if (typeof value !== \"string\") return null;\n const raw = value.trim().replace(/^#/, \"\");\n if (/^[0-9a-f]{3}$/i.test(raw)) return `#${raw.split(\"\").map((c) => c + c).join(\"\").toLowerCase()}`;\n if (/^[0-9a-f]{6}$/i.test(raw)) return `#${raw.toLowerCase()}`;\n return null;\n}\n\nfunction hexToRgb(hex: string): [number, number, number] {\n const n = Number.parseInt(hex.slice(1), 16);\n return [(n >> 16) & 255, (n >> 8) & 255, n & 255];\n}\n\nexport function hexToHsl(hex: string): Hsl {\n const [r, g, b] = hexToRgb(hex).map((v) => v / 255) as [number, number, number];\n const max = Math.max(r, g, b);\n const min = Math.min(r, g, b);\n const l = (max + min) / 2;\n const d = max - min;\n let h = 0;\n let s = 0;\n if (d !== 0) {\n s = d / (1 - Math.abs(2 * l - 1));\n if (max === r) h = ((g - b) / d) % 6;\n else if (max === g) h = (b - r) / d + 2;\n else h = (r - g) / d + 4;\n h *= 60;\n if (h < 0) h += 360;\n }\n return { h: Math.round(h), s: Math.round(s * 100), l: Math.round(l * 100) };\n}\n\nfunction hslToRgb({ h, s, l }: Hsl): [number, number, number] {\n const sat = s / 100;\n const lig = l / 100;\n const c = (1 - Math.abs(2 * lig - 1)) * sat;\n const x = c * (1 - Math.abs(((h / 60) % 2) - 1));\n const m = lig - c / 2;\n const [r, g, b] =\n h < 60 ? [c, x, 0] : h < 120 ? [x, c, 0] : h < 180 ? [0, c, x] : h < 240 ? [0, x, c] : h < 300 ? [x, 0, c] : [c, 0, x];\n return [(r + m) * 255, (g + m) * 255, (b + m) * 255];\n}\n\nexport function parseTriplet(triplet: HslTriplet): Hsl {\n const [h, s, l] = triplet.replace(/%/g, \"\").trim().split(/\\s+/).map(Number);\n return { h: h ?? 0, s: s ?? 0, l: l ?? 0 };\n}\n\nexport function toTriplet({ h, s, l }: Hsl): HslTriplet {\n return `${h} ${s}% ${l}%`;\n}\n\n/** WCAG relative luminance of an HSL color. */\nexport function relativeLuminance(color: Hsl): number {\n const [r, g, b] = hslToRgb(color).map((v) => {\n const c = v / 255;\n return c <= 0.03928 ? c / 12.92 : ((c + 0.055) / 1.055) ** 2.4;\n }) as [number, number, number];\n return 0.2126 * r + 0.7152 * g + 0.0722 * b;\n}\n\n/** WCAG contrast ratio of two colors (1 to 21). */\nexport function contrastRatio(a: Hsl | HslTriplet, b: Hsl | HslTriplet): number {\n const la = relativeLuminance(typeof a === \"string\" ? parseTriplet(a) : a);\n const lb = relativeLuminance(typeof b === \"string\" ? parseTriplet(b) : b);\n const [hi, lo] = la > lb ? [la, lb] : [lb, la];\n return (hi + 0.05) / (lo + 0.05);\n}\n\n/** Moves lightness one point at a time (darker or lighter) until `ok`, within 0–100. */\nfunction walkLightness(start: Hsl, step: 1 | -1, ok: (c: Hsl) => boolean, limit: number): Hsl {\n let c = start;\n while (!ok(c) && (step > 0 ? c.l < limit : c.l > limit)) c = { ...c, l: c.l + step };\n return c;\n}\n\n// ── tokens ─────────────────────────────────────────────────────────────────────\n\nexport interface AccentTokens {\n primary: HslTriplet;\n primaryForeground: HslTriplet;\n primaryInk: HslTriplet;\n}\n\n/** The role tokens one color makes in one theme (the rule in the header of this file). */\nexport function accentTokens(hex: string, theme: AccentTheme): AccentTokens {\n const normalized = normalizeHex(hex) ?? ACCENTS[0]!.hex;\n const chosen = hexToHsl(normalized);\n const canvas = parseTriplet(ACCENT_CONTRAST_SURFACES[theme]);\n const primary =\n theme === \"light\" ? chosen : walkLightness(chosen, 1, (c) => contrastRatio(c, canvas) >= AA_NON_TEXT, 80);\n const onWhite = contrastRatio(primary, WHITE_TEXT);\n const onDark = contrastRatio(primary, DARK_TEXT);\n const ink =\n theme === \"light\"\n ? walkLightness(primary, -1, (c) => contrastRatio(c, canvas) >= INK_TARGET, 10)\n : walkLightness(primary, 1, (c) => contrastRatio(c, canvas) >= INK_TARGET, 92);\n return {\n primary: toTriplet(primary),\n primaryForeground: onWhite >= onDark ? WHITE_TEXT : DARK_TEXT,\n primaryInk: toTriplet(ink),\n };\n}\n\nexport function accentDef(id: unknown): AccentDef | null {\n return ACCENTS.find((a) => a.id === id) ?? null;\n}\n\n/** The color of a saved accent; null when it names no curated id and holds no valid hex. */\nexport function accentHex(ref: AccentRef | null | undefined): string | null {\n if (!ref) return null;\n if (\"id\" in ref) return accentDef(ref.id)?.hex ?? null;\n return normalizeHex(ref.hex);\n}\n\n/** Validates a saved accent: a curated id is kept, a hex is normalized, anything else → null. */\nexport function parseAccentRef(raw: unknown): AccentRef | null {\n if (typeof raw !== \"object\" || raw === null || Array.isArray(raw)) return null;\n const record = raw as Record<string, unknown>;\n if (typeof record.id === \"string\") return accentDef(record.id) ? { id: record.id } : null;\n const hex = normalizeHex(record.hex);\n return hex ? { hex } : null;\n}\n\n// ── the contrast warning (offers, never blocks) ───────────────────────────────\n\nexport interface AccentContrastCheck {\n theme: AccentTheme;\n /** Text on the accent (a primary button's label). AA: ≥ 4.5. */\n text: number;\n /** The accent against the canvas (a link, a ring, a selected tab). AA: ≥ 3. */\n canvas: number;\n ok: boolean;\n}\n\n/** WCAG AA of a color as an accent, in light and dark. A warning to show, never a refusal. */\nexport function accentContrast(hex: string): AccentContrastCheck[] {\n return ([\"light\", \"dark\"] as const).map((theme) => {\n const tokens = accentTokens(hex, theme);\n const text = contrastRatio(tokens.primary, tokens.primaryForeground);\n const canvas = contrastRatio(tokens.primary, ACCENT_CONTRAST_SURFACES[theme]);\n return { theme, text, canvas, ok: text >= AA_TEXT && canvas >= AA_NON_TEXT };\n });\n}\n\n/** One line (≤ 60 characters) for the first failing check; null when AA holds in both themes. */\nexport function accentContrastWarning(hex: string): string | null {\n const failing = accentContrast(hex).find((c) => !c.ok);\n if (!failing) return null;\n const mode = failing.theme === \"light\" ? \"light\" : \"dark\";\n if (failing.text < AA_TEXT) return `Hard to read in ${mode} mode: text ${failing.text.toFixed(1)}:1, AA is 4.5:1`;\n return `Faint in ${mode} mode: ${failing.canvas.toFixed(1)}:1, AA is 3:1`;\n}\n\n// ── what a host stamps ─────────────────────────────────────────────────────────\n\n/** The attribute `accent.css` reads; its value is the curated id or `custom`. */\nexport const ACCENT_ATTRIBUTE = \"data-matrx-accent\" as const;\n\n/** The six custom properties `accent.css` maps onto the role tokens. */\nexport const ACCENT_PROPERTIES = {\n light: { primary: \"--matrx-accent-primary\", primaryForeground: \"--matrx-accent-primary-foreground\", primaryInk: \"--matrx-accent-primary-ink\" },\n dark: { primary: \"--matrx-accent-dark-primary\", primaryForeground: \"--matrx-accent-dark-primary-foreground\", primaryInk: \"--matrx-accent-dark-primary-ink\" },\n} as const;\n\nexport interface AccentAttributes {\n [ACCENT_ATTRIBUTE]?: string;\n style?: Record<string, string>;\n}\n\n/**\n * What a host puts on its root element for an accent: the attribute and the six properties (light and\n * dark), so the server's first frame is already in the accent in either theme. No accent (or the\n * built-in blue) = nothing at all.\n */\nexport function accentAttributes(ref: AccentRef | null | undefined): AccentAttributes {\n const hex = accentHex(ref);\n if (!ref || !hex || (\"id\" in ref && ref.id === DEFAULT_ACCENT_ID)) return {};\n const style: Record<string, string> = {};\n for (const theme of [\"light\", \"dark\"] as const) {\n const tokens = accentTokens(hex, theme);\n const names = ACCENT_PROPERTIES[theme];\n style[names.primary] = tokens.primary;\n style[names.primaryForeground] = tokens.primaryForeground;\n style[names.primaryInk] = tokens.primaryInk;\n }\n return { [ACCENT_ATTRIBUTE]: \"id\" in ref ? ref.id : \"custom\", style };\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/accents.ts"],"sourcesContent":["// THE ACCENT COLORS (custom-experience P4, 2026-10-20) — the curated accent set a person or an\n// organization picks from, the custom-hex path beside it, and the WCAG contrast math that WARNS about a\n// weak choice (it offers, never blocks). Pure: no React, no DOM, server-safe (`./accents` entry).\n//\n// An accent drives the design system's brand role tokens and nothing else:\n// --primary, --primary-foreground, --primary-ink (light, and their .dark twins)\n// `--accent` in this vocabulary is the quiet GRAY hover surface and is never touched; text tokens stay\n// foreground / muted / disabled. The host stamps `accentAttributes(ref)` on a root element (an attribute\n// plus six custom properties); `accent.css` maps them onto the role tokens for light and dark. With no\n// accent nothing is stamped and nothing changes.\n//\n// One rule makes both themes from ONE color (the same rule for a curated and a custom accent):\n// light primary = the color exactly as chosen;\n// dark primary = the same hue and saturation, lightened until it reads on the dark canvas (≥ 3:1);\n// foreground = white or near-black, whichever reads better on that primary;\n// ink = the primary as TEXT on its own tint: darkened (light) / lightened (dark) to ≥ 5.5:1\n// against the canvas (headroom for the 15% tint it sits on).\n\n/** An HSL color as the token vocabulary writes it: `\"210 80% 45%\"`. */\nexport type HslTriplet = string;\n\nexport interface Hsl {\n h: number;\n s: number;\n l: number;\n}\n\n/** One curated accent. `hex` is its light-theme primary; the dark twin is derived (see the header). */\nexport interface AccentDef {\n id: string;\n label: string;\n hex: string;\n}\n\n/**\n * THE CURATED SET. Every entry passes AA in both themes (guard: accents.test.ts): white or near-black\n * text on it ≥ 4.5:1, and it reads on the canvas ≥ 3:1. Ids are saved in layouts — never renamed.\n * `blue` is the built-in primary (`--primary: 210 80% 45%`), so choosing it equals choosing nothing.\n */\nexport const ACCENTS: readonly AccentDef[] = [\n { id: \"blue\", label: \"Blue\", hex: \"#1773cf\" },\n { id: \"indigo\", label: \"Indigo\", hex: \"#4f46e5\" },\n { id: \"violet\", label: \"Violet\", hex: \"#7c3aed\" },\n { id: \"purple\", label: \"Purple\", hex: \"#9333ea\" },\n { id: \"pink\", label: \"Pink\", hex: \"#db2777\" },\n { id: \"rose\", label: \"Rose\", hex: \"#e11d48\" },\n { id: \"red\", label: \"Red\", hex: \"#dc2626\" },\n { id: \"orange\", label: \"Orange\", hex: \"#c2410c\" },\n { id: \"amber\", label: \"Amber\", hex: \"#b45309\" },\n { id: \"green\", label: \"Green\", hex: \"#15803d\" },\n { id: \"emerald\", label: \"Emerald\", hex: \"#047857\" },\n { id: \"teal\", label: \"Teal\", hex: \"#0f766e\" },\n { id: \"cyan\", label: \"Cyan\", hex: \"#0e7490\" },\n { id: \"slate\", label: \"Slate\", hex: \"#475569\" },\n];\n\nexport const DEFAULT_ACCENT_ID = \"blue\";\n\n/**\n * The canvases a primary is judged against: the design system's `--background` in light and dark\n * (tokens.css; accents.test.ts fails if they drift apart).\n */\nexport const ACCENT_CONTRAST_SURFACES: Readonly<Record<AccentTheme, HslTriplet>> = {\n light: \"240 6% 99%\",\n dark: \"240 4% 12.5%\",\n};\n\n/** The dark text a light primary carries (tokens.css `--secondary-foreground`). */\nconst DARK_TEXT: HslTriplet = \"240 10% 10%\";\nconst WHITE_TEXT: HslTriplet = \"0 0% 100%\";\n\n/** WCAG 2.2 AA: 4.5:1 for text, 3:1 for a control's color against what is around it (1.4.11). */\nexport const AA_TEXT = 4.5;\nexport const AA_NON_TEXT = 3;\n/** The ink is judged against the canvas with headroom, because it sits on a 15% tint of itself. */\nconst INK_TARGET = 5.5;\n\nexport type AccentTheme = \"light\" | \"dark\";\n\n/** A saved accent: a curated id, or a custom `#rrggbb`. */\nexport type AccentRef = { id: string } | { hex: string };\n\n// ── color math ─────────────────────────────────────────────────────────────────\n\n/** `#rgb` / `#rrggbb` (any case, `#` optional) → `#rrggbb` lowercase; anything else → null. */\nexport function normalizeHex(value: unknown): string | null {\n if (typeof value !== \"string\") return null;\n const raw = value.trim().replace(/^#/, \"\");\n if (/^[0-9a-f]{3}$/i.test(raw)) return `#${raw.split(\"\").map((c) => c + c).join(\"\").toLowerCase()}`;\n if (/^[0-9a-f]{6}$/i.test(raw)) return `#${raw.toLowerCase()}`;\n return null;\n}\n\nfunction hexToRgb(hex: string): [number, number, number] {\n const n = Number.parseInt(hex.slice(1), 16);\n return [(n >> 16) & 255, (n >> 8) & 255, n & 255];\n}\n\nexport function hexToHsl(hex: string): Hsl {\n const [r, g, b] = hexToRgb(hex).map((v) => v / 255) as [number, number, number];\n const max = Math.max(r, g, b);\n const min = Math.min(r, g, b);\n const l = (max + min) / 2;\n const d = max - min;\n let h = 0;\n let s = 0;\n if (d !== 0) {\n s = d / (1 - Math.abs(2 * l - 1));\n if (max === r) h = ((g - b) / d) % 6;\n else if (max === g) h = (b - r) / d + 2;\n else h = (r - g) / d + 4;\n h *= 60;\n if (h < 0) h += 360;\n }\n return { h: Math.round(h), s: Math.round(s * 100), l: Math.round(l * 100) };\n}\n\nfunction hslToRgb({ h, s, l }: Hsl): [number, number, number] {\n const sat = s / 100;\n const lig = l / 100;\n const c = (1 - Math.abs(2 * lig - 1)) * sat;\n const x = c * (1 - Math.abs(((h / 60) % 2) - 1));\n const m = lig - c / 2;\n const [r, g, b] =\n h < 60 ? [c, x, 0] : h < 120 ? [x, c, 0] : h < 180 ? [0, c, x] : h < 240 ? [0, x, c] : h < 300 ? [x, 0, c] : [c, 0, x];\n return [(r + m) * 255, (g + m) * 255, (b + m) * 255];\n}\n\nexport function parseTriplet(triplet: HslTriplet): Hsl {\n const [h, s, l] = triplet.replace(/%/g, \"\").trim().split(/\\s+/).map(Number);\n return { h: h ?? 0, s: s ?? 0, l: l ?? 0 };\n}\n\nexport function toTriplet({ h, s, l }: Hsl): HslTriplet {\n return `${h} ${s}% ${l}%`;\n}\n\n/** WCAG relative luminance of an HSL color. */\nexport function relativeLuminance(color: Hsl): number {\n const [r, g, b] = hslToRgb(color).map((v) => {\n const c = v / 255;\n return c <= 0.03928 ? c / 12.92 : ((c + 0.055) / 1.055) ** 2.4;\n }) as [number, number, number];\n return 0.2126 * r + 0.7152 * g + 0.0722 * b;\n}\n\n/** WCAG contrast ratio of two colors (1 to 21). */\nexport function contrastRatio(a: Hsl | HslTriplet, b: Hsl | HslTriplet): number {\n const la = relativeLuminance(typeof a === \"string\" ? parseTriplet(a) : a);\n const lb = relativeLuminance(typeof b === \"string\" ? parseTriplet(b) : b);\n const [hi, lo] = la > lb ? [la, lb] : [lb, la];\n return (hi + 0.05) / (lo + 0.05);\n}\n\n/** Moves lightness one point at a time (darker or lighter) until `ok`, within 0–100. */\nfunction walkLightness(start: Hsl, step: 1 | -1, ok: (c: Hsl) => boolean, limit: number): Hsl {\n let c = start;\n while (!ok(c) && (step > 0 ? c.l < limit : c.l > limit)) c = { ...c, l: c.l + step };\n return c;\n}\n\n// ── tokens ─────────────────────────────────────────────────────────────────────\n\nexport interface AccentTokens {\n primary: HslTriplet;\n primaryForeground: HslTriplet;\n primaryInk: HslTriplet;\n}\n\n/** The role tokens one color makes in one theme (the rule in the header of this file). */\nexport function accentTokens(hex: string, theme: AccentTheme): AccentTokens {\n const normalized = normalizeHex(hex) ?? ACCENTS[0]!.hex;\n const chosen = hexToHsl(normalized);\n const canvas = parseTriplet(ACCENT_CONTRAST_SURFACES[theme]);\n const primary =\n theme === \"light\" ? chosen : walkLightness(chosen, 1, (c) => contrastRatio(c, canvas) >= AA_NON_TEXT, 80);\n const onWhite = contrastRatio(primary, WHITE_TEXT);\n const onDark = contrastRatio(primary, DARK_TEXT);\n const ink =\n theme === \"light\"\n ? walkLightness(primary, -1, (c) => contrastRatio(c, canvas) >= INK_TARGET, 10)\n : walkLightness(primary, 1, (c) => contrastRatio(c, canvas) >= INK_TARGET, 92);\n return {\n primary: toTriplet(primary),\n primaryForeground: onWhite >= onDark ? WHITE_TEXT : DARK_TEXT,\n primaryInk: toTriplet(ink),\n };\n}\n\nexport function accentDef(id: unknown): AccentDef | null {\n return ACCENTS.find((a) => a.id === id) ?? null;\n}\n\n/** The color of a saved accent; null when it names no curated id and holds no valid hex. */\nexport function accentHex(ref: AccentRef | null | undefined): string | null {\n if (!ref) return null;\n if (\"id\" in ref) return accentDef(ref.id)?.hex ?? null;\n return normalizeHex(ref.hex);\n}\n\n/** Validates a saved accent: a curated id is kept, a hex is normalized, anything else → null. */\nexport function parseAccentRef(raw: unknown): AccentRef | null {\n if (typeof raw !== \"object\" || raw === null || Array.isArray(raw)) return null;\n const record = raw as Record<string, unknown>;\n if (typeof record.id === \"string\") return accentDef(record.id) ? { id: record.id } : null;\n const hex = normalizeHex(record.hex);\n return hex ? { hex } : null;\n}\n\n// ── the contrast warning (offers, never blocks) ───────────────────────────────\n\nexport interface AccentContrastCheck {\n theme: AccentTheme;\n /** Text on the accent (a primary button's label). AA: ≥ 4.5. */\n text: number;\n /** The accent against the canvas (a link, a ring, a selected tab). AA: ≥ 3. */\n canvas: number;\n ok: boolean;\n}\n\n/** WCAG AA of a color as an accent, in light and dark. A warning to show, never a refusal. */\nexport function accentContrast(hex: string): AccentContrastCheck[] {\n return ([\"light\", \"dark\"] as const).map((theme) => {\n const tokens = accentTokens(hex, theme);\n const text = contrastRatio(tokens.primary, tokens.primaryForeground);\n const canvas = contrastRatio(tokens.primary, ACCENT_CONTRAST_SURFACES[theme]);\n return { theme, text, canvas, ok: text >= AA_TEXT && canvas >= AA_NON_TEXT };\n });\n}\n\n/** One line (≤ 60 characters) for the first failing check; null when AA holds in both themes. */\nexport function accentContrastWarning(hex: string): string | null {\n const failing = accentContrast(hex).find((c) => !c.ok);\n if (!failing) return null;\n const mode = failing.theme === \"light\" ? \"light\" : \"dark\";\n if (failing.text < AA_TEXT) return `Hard to read in ${mode} mode: text ${failing.text.toFixed(1)}:1, AA is 4.5:1`;\n return `Faint in ${mode} mode: ${failing.canvas.toFixed(1)}:1, AA is 3:1`;\n}\n\n// ── what a host stamps ─────────────────────────────────────────────────────────\n\n/** The attribute `accent.css` reads; its value is the curated id or `custom`. */\nexport const ACCENT_ATTRIBUTE = \"data-matrx-accent\" as const;\n\n/** The six custom properties `accent.css` maps onto the role tokens. */\nexport const ACCENT_PROPERTIES = {\n light: { primary: \"--matrx-accent-primary\", primaryForeground: \"--matrx-accent-primary-foreground\", primaryInk: \"--matrx-accent-primary-ink\" },\n dark: { primary: \"--matrx-accent-dark-primary\", primaryForeground: \"--matrx-accent-dark-primary-foreground\", primaryInk: \"--matrx-accent-dark-primary-ink\" },\n} as const;\n\nexport interface AccentAttributes {\n [ACCENT_ATTRIBUTE]?: string;\n style?: Record<string, string>;\n}\n\n/**\n * What a host puts on its root element for an accent: the attribute and the six properties (light and\n * dark), so the server's first frame is already in the accent in either theme. No accent (or the\n * built-in blue) = nothing at all.\n */\nexport function accentAttributes(ref: AccentRef | null | undefined): AccentAttributes {\n const hex = accentHex(ref);\n if (!ref || !hex || (\"id\" in ref && ref.id === DEFAULT_ACCENT_ID)) return {};\n const style: Record<string, string> = {};\n for (const theme of [\"light\", \"dark\"] as const) {\n const tokens = accentTokens(hex, theme);\n const names = ACCENT_PROPERTIES[theme];\n style[names.primary] = tokens.primary;\n style[names.primaryForeground] = tokens.primaryForeground;\n style[names.primaryInk] = tokens.primaryInk;\n }\n return { [ACCENT_ATTRIBUTE]: \"id\" in ref ? ref.id : \"custom\", style };\n}\n"],"mappings":"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","names":[]}
package/dist/accents.js CHANGED
@@ -17,8 +17,8 @@ var ACCENTS = [
17
17
  ];
18
18
  var DEFAULT_ACCENT_ID = "blue";
19
19
  var ACCENT_CONTRAST_SURFACES = {
20
- light: "240 5% 96%",
21
- dark: "240 4% 16%"
20
+ light: "240 6% 99%",
21
+ dark: "240 4% 12.5%"
22
22
  };
23
23
  var DARK_TEXT = "240 10% 10%";
24
24
  var WHITE_TEXT = "0 0% 100%";
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/accents.ts"],"sourcesContent":["// THE ACCENT COLORS (custom-experience P4, 2026-10-20) — the curated accent set a person or an\n// organization picks from, the custom-hex path beside it, and the WCAG contrast math that WARNS about a\n// weak choice (it offers, never blocks). Pure: no React, no DOM, server-safe (`./accents` entry).\n//\n// An accent drives the design system's brand role tokens and nothing else:\n// --primary, --primary-foreground, --primary-ink (light, and their .dark twins)\n// `--accent` in this vocabulary is the quiet GRAY hover surface and is never touched; text tokens stay\n// foreground / muted / disabled. The host stamps `accentAttributes(ref)` on a root element (an attribute\n// plus six custom properties); `accent.css` maps them onto the role tokens for light and dark. With no\n// accent nothing is stamped and nothing changes.\n//\n// One rule makes both themes from ONE color (the same rule for a curated and a custom accent):\n// light primary = the color exactly as chosen;\n// dark primary = the same hue and saturation, lightened until it reads on the dark canvas (≥ 3:1);\n// foreground = white or near-black, whichever reads better on that primary;\n// ink = the primary as TEXT on its own tint: darkened (light) / lightened (dark) to ≥ 5.5:1\n// against the canvas (headroom for the 15% tint it sits on).\n\n/** An HSL color as the token vocabulary writes it: `\"210 80% 45%\"`. */\nexport type HslTriplet = string;\n\nexport interface Hsl {\n h: number;\n s: number;\n l: number;\n}\n\n/** One curated accent. `hex` is its light-theme primary; the dark twin is derived (see the header). */\nexport interface AccentDef {\n id: string;\n label: string;\n hex: string;\n}\n\n/**\n * THE CURATED SET. Every entry passes AA in both themes (guard: accents.test.ts): white or near-black\n * text on it ≥ 4.5:1, and it reads on the canvas ≥ 3:1. Ids are saved in layouts — never renamed.\n * `blue` is the built-in primary (`--primary: 210 80% 45%`), so choosing it equals choosing nothing.\n */\nexport const ACCENTS: readonly AccentDef[] = [\n { id: \"blue\", label: \"Blue\", hex: \"#1773cf\" },\n { id: \"indigo\", label: \"Indigo\", hex: \"#4f46e5\" },\n { id: \"violet\", label: \"Violet\", hex: \"#7c3aed\" },\n { id: \"purple\", label: \"Purple\", hex: \"#9333ea\" },\n { id: \"pink\", label: \"Pink\", hex: \"#db2777\" },\n { id: \"rose\", label: \"Rose\", hex: \"#e11d48\" },\n { id: \"red\", label: \"Red\", hex: \"#dc2626\" },\n { id: \"orange\", label: \"Orange\", hex: \"#c2410c\" },\n { id: \"amber\", label: \"Amber\", hex: \"#b45309\" },\n { id: \"green\", label: \"Green\", hex: \"#15803d\" },\n { id: \"emerald\", label: \"Emerald\", hex: \"#047857\" },\n { id: \"teal\", label: \"Teal\", hex: \"#0f766e\" },\n { id: \"cyan\", label: \"Cyan\", hex: \"#0e7490\" },\n { id: \"slate\", label: \"Slate\", hex: \"#475569\" },\n];\n\nexport const DEFAULT_ACCENT_ID = \"blue\";\n\n/**\n * The canvases a primary is judged against: the design system's `--background` in light and dark\n * (tokens.css; accents.test.ts fails if they drift apart).\n */\nexport const ACCENT_CONTRAST_SURFACES: Readonly<Record<AccentTheme, HslTriplet>> = {\n light: \"240 5% 96%\",\n dark: \"240 4% 16%\",\n};\n\n/** The dark text a light primary carries (tokens.css `--secondary-foreground`). */\nconst DARK_TEXT: HslTriplet = \"240 10% 10%\";\nconst WHITE_TEXT: HslTriplet = \"0 0% 100%\";\n\n/** WCAG 2.2 AA: 4.5:1 for text, 3:1 for a control's color against what is around it (1.4.11). */\nexport const AA_TEXT = 4.5;\nexport const AA_NON_TEXT = 3;\n/** The ink is judged against the canvas with headroom, because it sits on a 15% tint of itself. */\nconst INK_TARGET = 5.5;\n\nexport type AccentTheme = \"light\" | \"dark\";\n\n/** A saved accent: a curated id, or a custom `#rrggbb`. */\nexport type AccentRef = { id: string } | { hex: string };\n\n// ── color math ─────────────────────────────────────────────────────────────────\n\n/** `#rgb` / `#rrggbb` (any case, `#` optional) → `#rrggbb` lowercase; anything else → null. */\nexport function normalizeHex(value: unknown): string | null {\n if (typeof value !== \"string\") return null;\n const raw = value.trim().replace(/^#/, \"\");\n if (/^[0-9a-f]{3}$/i.test(raw)) return `#${raw.split(\"\").map((c) => c + c).join(\"\").toLowerCase()}`;\n if (/^[0-9a-f]{6}$/i.test(raw)) return `#${raw.toLowerCase()}`;\n return null;\n}\n\nfunction hexToRgb(hex: string): [number, number, number] {\n const n = Number.parseInt(hex.slice(1), 16);\n return [(n >> 16) & 255, (n >> 8) & 255, n & 255];\n}\n\nexport function hexToHsl(hex: string): Hsl {\n const [r, g, b] = hexToRgb(hex).map((v) => v / 255) as [number, number, number];\n const max = Math.max(r, g, b);\n const min = Math.min(r, g, b);\n const l = (max + min) / 2;\n const d = max - min;\n let h = 0;\n let s = 0;\n if (d !== 0) {\n s = d / (1 - Math.abs(2 * l - 1));\n if (max === r) h = ((g - b) / d) % 6;\n else if (max === g) h = (b - r) / d + 2;\n else h = (r - g) / d + 4;\n h *= 60;\n if (h < 0) h += 360;\n }\n return { h: Math.round(h), s: Math.round(s * 100), l: Math.round(l * 100) };\n}\n\nfunction hslToRgb({ h, s, l }: Hsl): [number, number, number] {\n const sat = s / 100;\n const lig = l / 100;\n const c = (1 - Math.abs(2 * lig - 1)) * sat;\n const x = c * (1 - Math.abs(((h / 60) % 2) - 1));\n const m = lig - c / 2;\n const [r, g, b] =\n h < 60 ? [c, x, 0] : h < 120 ? [x, c, 0] : h < 180 ? [0, c, x] : h < 240 ? [0, x, c] : h < 300 ? [x, 0, c] : [c, 0, x];\n return [(r + m) * 255, (g + m) * 255, (b + m) * 255];\n}\n\nexport function parseTriplet(triplet: HslTriplet): Hsl {\n const [h, s, l] = triplet.replace(/%/g, \"\").trim().split(/\\s+/).map(Number);\n return { h: h ?? 0, s: s ?? 0, l: l ?? 0 };\n}\n\nexport function toTriplet({ h, s, l }: Hsl): HslTriplet {\n return `${h} ${s}% ${l}%`;\n}\n\n/** WCAG relative luminance of an HSL color. */\nexport function relativeLuminance(color: Hsl): number {\n const [r, g, b] = hslToRgb(color).map((v) => {\n const c = v / 255;\n return c <= 0.03928 ? c / 12.92 : ((c + 0.055) / 1.055) ** 2.4;\n }) as [number, number, number];\n return 0.2126 * r + 0.7152 * g + 0.0722 * b;\n}\n\n/** WCAG contrast ratio of two colors (1 to 21). */\nexport function contrastRatio(a: Hsl | HslTriplet, b: Hsl | HslTriplet): number {\n const la = relativeLuminance(typeof a === \"string\" ? parseTriplet(a) : a);\n const lb = relativeLuminance(typeof b === \"string\" ? parseTriplet(b) : b);\n const [hi, lo] = la > lb ? [la, lb] : [lb, la];\n return (hi + 0.05) / (lo + 0.05);\n}\n\n/** Moves lightness one point at a time (darker or lighter) until `ok`, within 0–100. */\nfunction walkLightness(start: Hsl, step: 1 | -1, ok: (c: Hsl) => boolean, limit: number): Hsl {\n let c = start;\n while (!ok(c) && (step > 0 ? c.l < limit : c.l > limit)) c = { ...c, l: c.l + step };\n return c;\n}\n\n// ── tokens ─────────────────────────────────────────────────────────────────────\n\nexport interface AccentTokens {\n primary: HslTriplet;\n primaryForeground: HslTriplet;\n primaryInk: HslTriplet;\n}\n\n/** The role tokens one color makes in one theme (the rule in the header of this file). */\nexport function accentTokens(hex: string, theme: AccentTheme): AccentTokens {\n const normalized = normalizeHex(hex) ?? ACCENTS[0]!.hex;\n const chosen = hexToHsl(normalized);\n const canvas = parseTriplet(ACCENT_CONTRAST_SURFACES[theme]);\n const primary =\n theme === \"light\" ? chosen : walkLightness(chosen, 1, (c) => contrastRatio(c, canvas) >= AA_NON_TEXT, 80);\n const onWhite = contrastRatio(primary, WHITE_TEXT);\n const onDark = contrastRatio(primary, DARK_TEXT);\n const ink =\n theme === \"light\"\n ? walkLightness(primary, -1, (c) => contrastRatio(c, canvas) >= INK_TARGET, 10)\n : walkLightness(primary, 1, (c) => contrastRatio(c, canvas) >= INK_TARGET, 92);\n return {\n primary: toTriplet(primary),\n primaryForeground: onWhite >= onDark ? WHITE_TEXT : DARK_TEXT,\n primaryInk: toTriplet(ink),\n };\n}\n\nexport function accentDef(id: unknown): AccentDef | null {\n return ACCENTS.find((a) => a.id === id) ?? null;\n}\n\n/** The color of a saved accent; null when it names no curated id and holds no valid hex. */\nexport function accentHex(ref: AccentRef | null | undefined): string | null {\n if (!ref) return null;\n if (\"id\" in ref) return accentDef(ref.id)?.hex ?? null;\n return normalizeHex(ref.hex);\n}\n\n/** Validates a saved accent: a curated id is kept, a hex is normalized, anything else → null. */\nexport function parseAccentRef(raw: unknown): AccentRef | null {\n if (typeof raw !== \"object\" || raw === null || Array.isArray(raw)) return null;\n const record = raw as Record<string, unknown>;\n if (typeof record.id === \"string\") return accentDef(record.id) ? { id: record.id } : null;\n const hex = normalizeHex(record.hex);\n return hex ? { hex } : null;\n}\n\n// ── the contrast warning (offers, never blocks) ───────────────────────────────\n\nexport interface AccentContrastCheck {\n theme: AccentTheme;\n /** Text on the accent (a primary button's label). AA: ≥ 4.5. */\n text: number;\n /** The accent against the canvas (a link, a ring, a selected tab). AA: ≥ 3. */\n canvas: number;\n ok: boolean;\n}\n\n/** WCAG AA of a color as an accent, in light and dark. A warning to show, never a refusal. */\nexport function accentContrast(hex: string): AccentContrastCheck[] {\n return ([\"light\", \"dark\"] as const).map((theme) => {\n const tokens = accentTokens(hex, theme);\n const text = contrastRatio(tokens.primary, tokens.primaryForeground);\n const canvas = contrastRatio(tokens.primary, ACCENT_CONTRAST_SURFACES[theme]);\n return { theme, text, canvas, ok: text >= AA_TEXT && canvas >= AA_NON_TEXT };\n });\n}\n\n/** One line (≤ 60 characters) for the first failing check; null when AA holds in both themes. */\nexport function accentContrastWarning(hex: string): string | null {\n const failing = accentContrast(hex).find((c) => !c.ok);\n if (!failing) return null;\n const mode = failing.theme === \"light\" ? \"light\" : \"dark\";\n if (failing.text < AA_TEXT) return `Hard to read in ${mode} mode: text ${failing.text.toFixed(1)}:1, AA is 4.5:1`;\n return `Faint in ${mode} mode: ${failing.canvas.toFixed(1)}:1, AA is 3:1`;\n}\n\n// ── what a host stamps ─────────────────────────────────────────────────────────\n\n/** The attribute `accent.css` reads; its value is the curated id or `custom`. */\nexport const ACCENT_ATTRIBUTE = \"data-matrx-accent\" as const;\n\n/** The six custom properties `accent.css` maps onto the role tokens. */\nexport const ACCENT_PROPERTIES = {\n light: { primary: \"--matrx-accent-primary\", primaryForeground: \"--matrx-accent-primary-foreground\", primaryInk: \"--matrx-accent-primary-ink\" },\n dark: { primary: \"--matrx-accent-dark-primary\", primaryForeground: \"--matrx-accent-dark-primary-foreground\", primaryInk: \"--matrx-accent-dark-primary-ink\" },\n} as const;\n\nexport interface AccentAttributes {\n [ACCENT_ATTRIBUTE]?: string;\n style?: Record<string, string>;\n}\n\n/**\n * What a host puts on its root element for an accent: the attribute and the six properties (light and\n * dark), so the server's first frame is already in the accent in either theme. No accent (or the\n * built-in blue) = nothing at all.\n */\nexport function accentAttributes(ref: AccentRef | null | undefined): AccentAttributes {\n const hex = accentHex(ref);\n if (!ref || !hex || (\"id\" in ref && ref.id === DEFAULT_ACCENT_ID)) return {};\n const style: Record<string, string> = {};\n for (const theme of [\"light\", \"dark\"] as const) {\n const tokens = accentTokens(hex, theme);\n const names = ACCENT_PROPERTIES[theme];\n style[names.primary] = tokens.primary;\n style[names.primaryForeground] = tokens.primaryForeground;\n style[names.primaryInk] = tokens.primaryInk;\n }\n return { [ACCENT_ATTRIBUTE]: \"id\" in ref ? ref.id : \"custom\", style };\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/accents.ts"],"sourcesContent":["// THE ACCENT COLORS (custom-experience P4, 2026-10-20) — the curated accent set a person or an\n// organization picks from, the custom-hex path beside it, and the WCAG contrast math that WARNS about a\n// weak choice (it offers, never blocks). Pure: no React, no DOM, server-safe (`./accents` entry).\n//\n// An accent drives the design system's brand role tokens and nothing else:\n// --primary, --primary-foreground, --primary-ink (light, and their .dark twins)\n// `--accent` in this vocabulary is the quiet GRAY hover surface and is never touched; text tokens stay\n// foreground / muted / disabled. The host stamps `accentAttributes(ref)` on a root element (an attribute\n// plus six custom properties); `accent.css` maps them onto the role tokens for light and dark. With no\n// accent nothing is stamped and nothing changes.\n//\n// One rule makes both themes from ONE color (the same rule for a curated and a custom accent):\n// light primary = the color exactly as chosen;\n// dark primary = the same hue and saturation, lightened until it reads on the dark canvas (≥ 3:1);\n// foreground = white or near-black, whichever reads better on that primary;\n// ink = the primary as TEXT on its own tint: darkened (light) / lightened (dark) to ≥ 5.5:1\n// against the canvas (headroom for the 15% tint it sits on).\n\n/** An HSL color as the token vocabulary writes it: `\"210 80% 45%\"`. */\nexport type HslTriplet = string;\n\nexport interface Hsl {\n h: number;\n s: number;\n l: number;\n}\n\n/** One curated accent. `hex` is its light-theme primary; the dark twin is derived (see the header). */\nexport interface AccentDef {\n id: string;\n label: string;\n hex: string;\n}\n\n/**\n * THE CURATED SET. Every entry passes AA in both themes (guard: accents.test.ts): white or near-black\n * text on it ≥ 4.5:1, and it reads on the canvas ≥ 3:1. Ids are saved in layouts — never renamed.\n * `blue` is the built-in primary (`--primary: 210 80% 45%`), so choosing it equals choosing nothing.\n */\nexport const ACCENTS: readonly AccentDef[] = [\n { id: \"blue\", label: \"Blue\", hex: \"#1773cf\" },\n { id: \"indigo\", label: \"Indigo\", hex: \"#4f46e5\" },\n { id: \"violet\", label: \"Violet\", hex: \"#7c3aed\" },\n { id: \"purple\", label: \"Purple\", hex: \"#9333ea\" },\n { id: \"pink\", label: \"Pink\", hex: \"#db2777\" },\n { id: \"rose\", label: \"Rose\", hex: \"#e11d48\" },\n { id: \"red\", label: \"Red\", hex: \"#dc2626\" },\n { id: \"orange\", label: \"Orange\", hex: \"#c2410c\" },\n { id: \"amber\", label: \"Amber\", hex: \"#b45309\" },\n { id: \"green\", label: \"Green\", hex: \"#15803d\" },\n { id: \"emerald\", label: \"Emerald\", hex: \"#047857\" },\n { id: \"teal\", label: \"Teal\", hex: \"#0f766e\" },\n { id: \"cyan\", label: \"Cyan\", hex: \"#0e7490\" },\n { id: \"slate\", label: \"Slate\", hex: \"#475569\" },\n];\n\nexport const DEFAULT_ACCENT_ID = \"blue\";\n\n/**\n * The canvases a primary is judged against: the design system's `--background` in light and dark\n * (tokens.css; accents.test.ts fails if they drift apart).\n */\nexport const ACCENT_CONTRAST_SURFACES: Readonly<Record<AccentTheme, HslTriplet>> = {\n light: \"240 6% 99%\",\n dark: \"240 4% 12.5%\",\n};\n\n/** The dark text a light primary carries (tokens.css `--secondary-foreground`). */\nconst DARK_TEXT: HslTriplet = \"240 10% 10%\";\nconst WHITE_TEXT: HslTriplet = \"0 0% 100%\";\n\n/** WCAG 2.2 AA: 4.5:1 for text, 3:1 for a control's color against what is around it (1.4.11). */\nexport const AA_TEXT = 4.5;\nexport const AA_NON_TEXT = 3;\n/** The ink is judged against the canvas with headroom, because it sits on a 15% tint of itself. */\nconst INK_TARGET = 5.5;\n\nexport type AccentTheme = \"light\" | \"dark\";\n\n/** A saved accent: a curated id, or a custom `#rrggbb`. */\nexport type AccentRef = { id: string } | { hex: string };\n\n// ── color math ─────────────────────────────────────────────────────────────────\n\n/** `#rgb` / `#rrggbb` (any case, `#` optional) → `#rrggbb` lowercase; anything else → null. */\nexport function normalizeHex(value: unknown): string | null {\n if (typeof value !== \"string\") return null;\n const raw = value.trim().replace(/^#/, \"\");\n if (/^[0-9a-f]{3}$/i.test(raw)) return `#${raw.split(\"\").map((c) => c + c).join(\"\").toLowerCase()}`;\n if (/^[0-9a-f]{6}$/i.test(raw)) return `#${raw.toLowerCase()}`;\n return null;\n}\n\nfunction hexToRgb(hex: string): [number, number, number] {\n const n = Number.parseInt(hex.slice(1), 16);\n return [(n >> 16) & 255, (n >> 8) & 255, n & 255];\n}\n\nexport function hexToHsl(hex: string): Hsl {\n const [r, g, b] = hexToRgb(hex).map((v) => v / 255) as [number, number, number];\n const max = Math.max(r, g, b);\n const min = Math.min(r, g, b);\n const l = (max + min) / 2;\n const d = max - min;\n let h = 0;\n let s = 0;\n if (d !== 0) {\n s = d / (1 - Math.abs(2 * l - 1));\n if (max === r) h = ((g - b) / d) % 6;\n else if (max === g) h = (b - r) / d + 2;\n else h = (r - g) / d + 4;\n h *= 60;\n if (h < 0) h += 360;\n }\n return { h: Math.round(h), s: Math.round(s * 100), l: Math.round(l * 100) };\n}\n\nfunction hslToRgb({ h, s, l }: Hsl): [number, number, number] {\n const sat = s / 100;\n const lig = l / 100;\n const c = (1 - Math.abs(2 * lig - 1)) * sat;\n const x = c * (1 - Math.abs(((h / 60) % 2) - 1));\n const m = lig - c / 2;\n const [r, g, b] =\n h < 60 ? [c, x, 0] : h < 120 ? [x, c, 0] : h < 180 ? [0, c, x] : h < 240 ? [0, x, c] : h < 300 ? [x, 0, c] : [c, 0, x];\n return [(r + m) * 255, (g + m) * 255, (b + m) * 255];\n}\n\nexport function parseTriplet(triplet: HslTriplet): Hsl {\n const [h, s, l] = triplet.replace(/%/g, \"\").trim().split(/\\s+/).map(Number);\n return { h: h ?? 0, s: s ?? 0, l: l ?? 0 };\n}\n\nexport function toTriplet({ h, s, l }: Hsl): HslTriplet {\n return `${h} ${s}% ${l}%`;\n}\n\n/** WCAG relative luminance of an HSL color. */\nexport function relativeLuminance(color: Hsl): number {\n const [r, g, b] = hslToRgb(color).map((v) => {\n const c = v / 255;\n return c <= 0.03928 ? c / 12.92 : ((c + 0.055) / 1.055) ** 2.4;\n }) as [number, number, number];\n return 0.2126 * r + 0.7152 * g + 0.0722 * b;\n}\n\n/** WCAG contrast ratio of two colors (1 to 21). */\nexport function contrastRatio(a: Hsl | HslTriplet, b: Hsl | HslTriplet): number {\n const la = relativeLuminance(typeof a === \"string\" ? parseTriplet(a) : a);\n const lb = relativeLuminance(typeof b === \"string\" ? parseTriplet(b) : b);\n const [hi, lo] = la > lb ? [la, lb] : [lb, la];\n return (hi + 0.05) / (lo + 0.05);\n}\n\n/** Moves lightness one point at a time (darker or lighter) until `ok`, within 0–100. */\nfunction walkLightness(start: Hsl, step: 1 | -1, ok: (c: Hsl) => boolean, limit: number): Hsl {\n let c = start;\n while (!ok(c) && (step > 0 ? c.l < limit : c.l > limit)) c = { ...c, l: c.l + step };\n return c;\n}\n\n// ── tokens ─────────────────────────────────────────────────────────────────────\n\nexport interface AccentTokens {\n primary: HslTriplet;\n primaryForeground: HslTriplet;\n primaryInk: HslTriplet;\n}\n\n/** The role tokens one color makes in one theme (the rule in the header of this file). */\nexport function accentTokens(hex: string, theme: AccentTheme): AccentTokens {\n const normalized = normalizeHex(hex) ?? ACCENTS[0]!.hex;\n const chosen = hexToHsl(normalized);\n const canvas = parseTriplet(ACCENT_CONTRAST_SURFACES[theme]);\n const primary =\n theme === \"light\" ? chosen : walkLightness(chosen, 1, (c) => contrastRatio(c, canvas) >= AA_NON_TEXT, 80);\n const onWhite = contrastRatio(primary, WHITE_TEXT);\n const onDark = contrastRatio(primary, DARK_TEXT);\n const ink =\n theme === \"light\"\n ? walkLightness(primary, -1, (c) => contrastRatio(c, canvas) >= INK_TARGET, 10)\n : walkLightness(primary, 1, (c) => contrastRatio(c, canvas) >= INK_TARGET, 92);\n return {\n primary: toTriplet(primary),\n primaryForeground: onWhite >= onDark ? WHITE_TEXT : DARK_TEXT,\n primaryInk: toTriplet(ink),\n };\n}\n\nexport function accentDef(id: unknown): AccentDef | null {\n return ACCENTS.find((a) => a.id === id) ?? null;\n}\n\n/** The color of a saved accent; null when it names no curated id and holds no valid hex. */\nexport function accentHex(ref: AccentRef | null | undefined): string | null {\n if (!ref) return null;\n if (\"id\" in ref) return accentDef(ref.id)?.hex ?? null;\n return normalizeHex(ref.hex);\n}\n\n/** Validates a saved accent: a curated id is kept, a hex is normalized, anything else → null. */\nexport function parseAccentRef(raw: unknown): AccentRef | null {\n if (typeof raw !== \"object\" || raw === null || Array.isArray(raw)) return null;\n const record = raw as Record<string, unknown>;\n if (typeof record.id === \"string\") return accentDef(record.id) ? { id: record.id } : null;\n const hex = normalizeHex(record.hex);\n return hex ? { hex } : null;\n}\n\n// ── the contrast warning (offers, never blocks) ───────────────────────────────\n\nexport interface AccentContrastCheck {\n theme: AccentTheme;\n /** Text on the accent (a primary button's label). AA: ≥ 4.5. */\n text: number;\n /** The accent against the canvas (a link, a ring, a selected tab). AA: ≥ 3. */\n canvas: number;\n ok: boolean;\n}\n\n/** WCAG AA of a color as an accent, in light and dark. A warning to show, never a refusal. */\nexport function accentContrast(hex: string): AccentContrastCheck[] {\n return ([\"light\", \"dark\"] as const).map((theme) => {\n const tokens = accentTokens(hex, theme);\n const text = contrastRatio(tokens.primary, tokens.primaryForeground);\n const canvas = contrastRatio(tokens.primary, ACCENT_CONTRAST_SURFACES[theme]);\n return { theme, text, canvas, ok: text >= AA_TEXT && canvas >= AA_NON_TEXT };\n });\n}\n\n/** One line (≤ 60 characters) for the first failing check; null when AA holds in both themes. */\nexport function accentContrastWarning(hex: string): string | null {\n const failing = accentContrast(hex).find((c) => !c.ok);\n if (!failing) return null;\n const mode = failing.theme === \"light\" ? \"light\" : \"dark\";\n if (failing.text < AA_TEXT) return `Hard to read in ${mode} mode: text ${failing.text.toFixed(1)}:1, AA is 4.5:1`;\n return `Faint in ${mode} mode: ${failing.canvas.toFixed(1)}:1, AA is 3:1`;\n}\n\n// ── what a host stamps ─────────────────────────────────────────────────────────\n\n/** The attribute `accent.css` reads; its value is the curated id or `custom`. */\nexport const ACCENT_ATTRIBUTE = \"data-matrx-accent\" as const;\n\n/** The six custom properties `accent.css` maps onto the role tokens. */\nexport const ACCENT_PROPERTIES = {\n light: { primary: \"--matrx-accent-primary\", primaryForeground: \"--matrx-accent-primary-foreground\", primaryInk: \"--matrx-accent-primary-ink\" },\n dark: { primary: \"--matrx-accent-dark-primary\", primaryForeground: \"--matrx-accent-dark-primary-foreground\", primaryInk: \"--matrx-accent-dark-primary-ink\" },\n} as const;\n\nexport interface AccentAttributes {\n [ACCENT_ATTRIBUTE]?: string;\n style?: Record<string, string>;\n}\n\n/**\n * What a host puts on its root element for an accent: the attribute and the six properties (light and\n * dark), so the server's first frame is already in the accent in either theme. No accent (or the\n * built-in blue) = nothing at all.\n */\nexport function accentAttributes(ref: AccentRef | null | undefined): AccentAttributes {\n const hex = accentHex(ref);\n if (!ref || !hex || (\"id\" in ref && ref.id === DEFAULT_ACCENT_ID)) return {};\n const style: Record<string, string> = {};\n for (const theme of [\"light\", \"dark\"] as const) {\n const tokens = accentTokens(hex, theme);\n const names = ACCENT_PROPERTIES[theme];\n style[names.primary] = tokens.primary;\n style[names.primaryForeground] = tokens.primaryForeground;\n style[names.primaryInk] = tokens.primaryInk;\n }\n return { [ACCENT_ATTRIBUTE]: \"id\" in ref ? ref.id : \"custom\", style };\n}\n"],"mappings":"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","names":[]}
package/dist/carousel.js CHANGED
@@ -1,14 +1,14 @@
1
1
  "use client";
2
2
  import {
3
3
  IconButton
4
- } from "./chunk-VG765KBC.js";
4
+ } from "./chunk-U3SUPYNT.js";
5
5
  import "./chunk-6NOLVA4E.js";
6
6
  import "./chunk-5IR3BZOS.js";
7
7
  import "./chunk-AV5C4YLL.js";
8
8
  import "./chunk-XERGQT2M.js";
9
9
  import "./chunk-WQ2QCMMA.js";
10
10
  import "./chunk-YRF22M5S.js";
11
- import "./chunk-WLKHQGX5.js";
11
+ import "./chunk-CFFRMRWJ.js";
12
12
  import "./chunk-K4XCB576.js";
13
13
  import "./chunk-FEITX4L3.js";
14
14
  import "./chunk-DRSBLHOF.js";
@@ -20,16 +20,16 @@ import {
20
20
  import "./chunk-IMQ3RX35.js";
21
21
  import "./chunk-U24MOJWK.js";
22
22
  import "./chunk-NDTDBBMM.js";
23
- import "./chunk-3YTF4MH2.js";
23
+ import "./chunk-YA3DNAAC.js";
24
24
  import "./chunk-WFBPCMPF.js";
25
25
  import {
26
26
  Popover,
27
27
  PopoverContent,
28
28
  PopoverTrigger
29
29
  } from "./chunk-OPF265PU.js";
30
- import "./chunk-KOUHF4JC.js";
31
- import "./chunk-BD77DHUO.js";
30
+ import "./chunk-345ESUIP.js";
32
31
  import "./chunk-BKCJUFD3.js";
32
+ import "./chunk-BD77DHUO.js";
33
33
  import {
34
34
  TooltipProvider
35
35
  } from "./chunk-PIQJULQB.js";
@@ -37,7 +37,7 @@ import {
37
37
  } from "./chunk-WFBPCMPF.js";
38
38
  import {
39
39
  Button
40
- } from "./chunk-KOUHF4JC.js";
40
+ } from "./chunk-345ESUIP.js";
41
41
  import {
42
42
  DropdownMenu,
43
43
  DropdownMenuTrigger
@@ -967,4 +967,4 @@ export {
967
967
  ItemContextMenu,
968
968
  ItemRow
969
969
  };
970
- //# sourceMappingURL=chunk-SOCDBDFS.js.map
970
+ //# sourceMappingURL=chunk-2HB65PD6.js.map
@@ -1,12 +1,12 @@
1
1
  "use client";
2
+ import {
3
+ isIconOnly
4
+ } from "./chunk-BKCJUFD3.js";
2
5
  import {
3
6
  boxStyle,
4
7
  useAutoGrow,
5
8
  useMergedTextareaRef
6
9
  } from "./chunk-BD77DHUO.js";
7
- import {
8
- isIconOnly
9
- } from "./chunk-BKCJUFD3.js";
10
10
  import {
11
11
  DropdownMenu,
12
12
  DropdownMenuContent,
@@ -463,4 +463,4 @@ export {
463
463
  Switch,
464
464
  Textarea
465
465
  };
466
- //# sourceMappingURL=chunk-KOUHF4JC.js.map
466
+ //# sourceMappingURL=chunk-345ESUIP.js.map
@@ -2,7 +2,7 @@
2
2
  import {
3
3
  Button,
4
4
  Select
5
- } from "./chunk-KOUHF4JC.js";
5
+ } from "./chunk-345ESUIP.js";
6
6
  import {
7
7
  Tooltip,
8
8
  TooltipContent,
@@ -367,4 +367,4 @@ export {
367
367
  DEFAULT_TABLE_PAGE_SIZES,
368
368
  GenericTablePagination
369
369
  };
370
- //# sourceMappingURL=chunk-LS3C7IUV.js.map
370
+ //# sourceMappingURL=chunk-ASDPMZNP.js.map
@@ -8,7 +8,7 @@ import {
8
8
  import {
9
9
  Button,
10
10
  ControlRow
11
- } from "./chunk-KOUHF4JC.js";
11
+ } from "./chunk-345ESUIP.js";
12
12
  import {
13
13
  buttonVariants,
14
14
  swallowClick
@@ -482,4 +482,4 @@ export {
482
482
  DangerZone,
483
483
  PromoBanner
484
484
  };
485
- //# sourceMappingURL=chunk-WLKHQGX5.js.map
485
+ //# sourceMappingURL=chunk-CFFRMRWJ.js.map
@@ -9,7 +9,7 @@ import {
9
9
  } from "./chunk-VQQ3O7J5.js";
10
10
  import {
11
11
  ConfirmDialog
12
- } from "./chunk-WLKHQGX5.js";
12
+ } from "./chunk-CFFRMRWJ.js";
13
13
  import {
14
14
  ErrorBox
15
15
  } from "./chunk-FEITX4L3.js";
@@ -21,7 +21,7 @@ import {
21
21
  } from "./chunk-TDMVH77G.js";
22
22
  import {
23
23
  Button
24
- } from "./chunk-KOUHF4JC.js";
24
+ } from "./chunk-345ESUIP.js";
25
25
  import {
26
26
  Tooltip,
27
27
  TooltipContent,
@@ -778,4 +778,4 @@ export {
778
778
  CanvasLayout,
779
779
  ModeSwitch
780
780
  };
781
- //# sourceMappingURL=chunk-TBWKBTPM.js.map
781
+ //# sourceMappingURL=chunk-F7IUBABK.js.map
@@ -1,7 +1,7 @@
1
1
  "use client";
2
2
  import {
3
3
  Button
4
- } from "./chunk-KOUHF4JC.js";
4
+ } from "./chunk-345ESUIP.js";
5
5
  import {
6
6
  Tooltip,
7
7
  TooltipContent,
@@ -295,4 +295,4 @@ export {
295
295
  displayId,
296
296
  RecordId
297
297
  };
298
- //# sourceMappingURL=chunk-3JEAXZKX.js.map
298
+ //# sourceMappingURL=chunk-FLYZSVTR.js.map
@@ -10,17 +10,17 @@ import {
10
10
  } from "./chunk-CZGZV35G.js";
11
11
  import {
12
12
  PropertyRow
13
- } from "./chunk-3YTF4MH2.js";
13
+ } from "./chunk-YA3DNAAC.js";
14
14
  import {
15
15
  completeLayeredFilterRules,
16
16
  layeredFilterMatchesValue
17
17
  } from "./chunk-7ZCD4AZS.js";
18
18
  import {
19
19
  RecordId
20
- } from "./chunk-3JEAXZKX.js";
20
+ } from "./chunk-FLYZSVTR.js";
21
21
  import {
22
22
  Button
23
- } from "./chunk-KOUHF4JC.js";
23
+ } from "./chunk-345ESUIP.js";
24
24
  import {
25
25
  useMatrxDataTableHost
26
26
  } from "./chunk-B5D3VIMC.js";
@@ -671,4 +671,4 @@ export {
671
671
  isUuidValue,
672
672
  DataRowInspector
673
673
  };
674
- //# sourceMappingURL=chunk-UAIBCPMB.js.map
674
+ //# sourceMappingURL=chunk-RLTTLDWU.js.map
@@ -49,7 +49,7 @@ import {
49
49
  import {
50
50
  Button as Button2,
51
51
  Chip
52
- } from "./chunk-KOUHF4JC.js";
52
+ } from "./chunk-345ESUIP.js";
53
53
  import {
54
54
  Tooltip,
55
55
  TooltipContent,
@@ -4007,4 +4007,4 @@ export {
4007
4007
  TileCard,
4008
4008
  TileCardGrid
4009
4009
  };
4010
- //# sourceMappingURL=chunk-VG765KBC.js.map
4010
+ //# sourceMappingURL=chunk-U3SUPYNT.js.map
@@ -21,7 +21,7 @@ import {
21
21
  import {
22
22
  DataRowInspector,
23
23
  columnId
24
- } from "./chunk-UAIBCPMB.js";
24
+ } from "./chunk-RLTTLDWU.js";
25
25
  import {
26
26
  useMatrxDataTableHost
27
27
  } from "./chunk-B5D3VIMC.js";
@@ -305,4 +305,4 @@ export {
305
305
  withMacLookDefaults,
306
306
  DataRowWindow
307
307
  };
308
- //# sourceMappingURL=chunk-QEQDJBNK.js.map
308
+ //# sourceMappingURL=chunk-WSEZDHLA.js.map
@@ -6,7 +6,7 @@ import {
6
6
  } from "./chunk-OPF265PU.js";
7
7
  import {
8
8
  Button
9
- } from "./chunk-KOUHF4JC.js";
9
+ } from "./chunk-345ESUIP.js";
10
10
  import {
11
11
  AlertTriangleIcon,
12
12
  ChevronDownIcon,
@@ -605,4 +605,4 @@ export {
605
605
  ConfigurationTable,
606
606
  ConfigurationTableRow
607
607
  };
608
- //# sourceMappingURL=chunk-3YTF4MH2.js.map
608
+ //# sourceMappingURL=chunk-YA3DNAAC.js.map
@@ -15,9 +15,9 @@ import "./chunk-U24MOJWK.js";
15
15
  import "./chunk-NDTDBBMM.js";
16
16
  import {
17
17
  Textarea
18
- } from "./chunk-KOUHF4JC.js";
19
- import "./chunk-BD77DHUO.js";
18
+ } from "./chunk-345ESUIP.js";
20
19
  import "./chunk-BKCJUFD3.js";
20
+ import "./chunk-BD77DHUO.js";
21
21
  import "./chunk-MOZRIHJF.js";
22
22
  import "./chunk-AKSKVT2M.js";
23
23
  import "./chunk-BNXNIN2S.js";
package/dist/controls.css CHANGED
@@ -306,6 +306,13 @@
306
306
  background-color: hsl(var(--muted)) !important;
307
307
  color: hsl(var(--foreground)) !important;
308
308
  }
309
+ /* meta with a glyph: a FINE icon at the heading's size (12px, thin stroke) — never the
310
+ 16px control glyph beside 11px text (owner, 2026-10-24: "just use a fine icon"). */
311
+ .matrx-control-button[data-variant="meta"] > svg {
312
+ width: 0.75rem !important;
313
+ height: 0.75rem !important;
314
+ stroke-width: 1.75 !important;
315
+ }
309
316
  /* PRESSED (Button pressed): a toggle in a set. On = a primary tint with a primary
310
317
  hairline (the old accent gray, rgb 239,239,240, vanished on tinted cards; pinned in
311
318
  pressed-contrast.test.ts), full ink; off = no fill, quiet ink. Out-ranks the
package/dist/controls.js CHANGED
@@ -73,7 +73,7 @@ import {
73
73
  spansOf,
74
74
  splitForMiddleTruncate,
75
75
  stageStates
76
- } from "./chunk-VG765KBC.js";
76
+ } from "./chunk-U3SUPYNT.js";
77
77
  import "./chunk-6NOLVA4E.js";
78
78
  import {
79
79
  Input,
@@ -98,7 +98,7 @@ import {
98
98
  RowGroup,
99
99
  SettingRow,
100
100
  Tile
101
- } from "./chunk-WLKHQGX5.js";
101
+ } from "./chunk-CFFRMRWJ.js";
102
102
  import "./chunk-K4XCB576.js";
103
103
  import "./chunk-FEITX4L3.js";
104
104
  import "./chunk-DRSBLHOF.js";
@@ -119,7 +119,7 @@ import {
119
119
  StatusToken,
120
120
  isCollectedValue,
121
121
  valueSummary
122
- } from "./chunk-3YTF4MH2.js";
122
+ } from "./chunk-YA3DNAAC.js";
123
123
  import "./chunk-WFBPCMPF.js";
124
124
  import "./chunk-OPF265PU.js";
125
125
  import {
@@ -141,9 +141,9 @@ import {
141
141
  Switch,
142
142
  Tabs,
143
143
  Textarea
144
- } from "./chunk-KOUHF4JC.js";
145
- import "./chunk-BD77DHUO.js";
144
+ } from "./chunk-345ESUIP.js";
146
145
  import "./chunk-BKCJUFD3.js";
146
+ import "./chunk-BD77DHUO.js";
147
147
  import "./chunk-PIQJULQB.js";
148
148
  import "./chunk-MOZRIHJF.js";
149
149
  import "./chunk-AKSKVT2M.js";
@@ -1,7 +1,7 @@
1
1
  "use client";
2
2
  import {
3
3
  DataRowInspector
4
- } from "../chunk-UAIBCPMB.js";
4
+ } from "../chunk-RLTTLDWU.js";
5
5
  import "../chunk-UEQK6PFD.js";
6
6
  import "../chunk-PTYKILZE.js";
7
7
  import "../chunk-CZGZV35G.js";
@@ -9,16 +9,16 @@ import "../chunk-KPZX2M2C.js";
9
9
  import "../chunk-GLZJ7WZK.js";
10
10
  import "../chunk-B3LDW3IQ.js";
11
11
  import "../chunk-FXT7PZAF.js";
12
- import "../chunk-3YTF4MH2.js";
12
+ import "../chunk-YA3DNAAC.js";
13
13
  import "../chunk-WFBPCMPF.js";
14
14
  import "../chunk-KGW373MK.js";
15
15
  import "../chunk-7ZCD4AZS.js";
16
16
  import "../chunk-OPF265PU.js";
17
17
  import "../chunk-QFP4BUKZ.js";
18
- import "../chunk-3JEAXZKX.js";
19
- import "../chunk-KOUHF4JC.js";
20
- import "../chunk-BD77DHUO.js";
18
+ import "../chunk-FLYZSVTR.js";
19
+ import "../chunk-345ESUIP.js";
21
20
  import "../chunk-BKCJUFD3.js";
21
+ import "../chunk-BD77DHUO.js";
22
22
  import "../chunk-PIQJULQB.js";
23
23
  import "../chunk-MOZRIHJF.js";
24
24
  import "../chunk-AKSKVT2M.js";
@@ -1,7 +1,7 @@
1
1
  "use client";
2
2
  import {
3
3
  DataRowWindow
4
- } from "../chunk-QEQDJBNK.js";
4
+ } from "../chunk-WSEZDHLA.js";
5
5
  import "../chunk-YC5GAO35.js";
6
6
  import "../chunk-VLLUUOYJ.js";
7
7
  import "../chunk-TH6DDCWF.js";
@@ -10,7 +10,7 @@ import "../chunk-LEPD6TV7.js";
10
10
  import "../chunk-IMQ3RX35.js";
11
11
  import "../chunk-U24MOJWK.js";
12
12
  import "../chunk-NDTDBBMM.js";
13
- import "../chunk-UAIBCPMB.js";
13
+ import "../chunk-RLTTLDWU.js";
14
14
  import "../chunk-UEQK6PFD.js";
15
15
  import "../chunk-PTYKILZE.js";
16
16
  import "../chunk-CZGZV35G.js";
@@ -18,16 +18,16 @@ import "../chunk-KPZX2M2C.js";
18
18
  import "../chunk-GLZJ7WZK.js";
19
19
  import "../chunk-B3LDW3IQ.js";
20
20
  import "../chunk-FXT7PZAF.js";
21
- import "../chunk-3YTF4MH2.js";
21
+ import "../chunk-YA3DNAAC.js";
22
22
  import "../chunk-WFBPCMPF.js";
23
23
  import "../chunk-KGW373MK.js";
24
24
  import "../chunk-7ZCD4AZS.js";
25
25
  import "../chunk-OPF265PU.js";
26
26
  import "../chunk-QFP4BUKZ.js";
27
- import "../chunk-3JEAXZKX.js";
28
- import "../chunk-KOUHF4JC.js";
29
- import "../chunk-BD77DHUO.js";
27
+ import "../chunk-FLYZSVTR.js";
28
+ import "../chunk-345ESUIP.js";
30
29
  import "../chunk-BKCJUFD3.js";
30
+ import "../chunk-BD77DHUO.js";
31
31
  import "../chunk-PIQJULQB.js";
32
32
  import "../chunk-MOZRIHJF.js";
33
33
  import "../chunk-AKSKVT2M.js";
@@ -20375,7 +20375,11 @@ var MULTI = "multi";
20375
20375
  function cellTextIsMultiline(cell) {
20376
20376
  const lineHeight = Number.parseFloat(getComputedStyle(cell).lineHeight);
20377
20377
  const minStep = Number.isFinite(lineHeight) ? lineHeight * 0.75 : 10;
20378
- const walker = cell.ownerDocument.createTreeWalker(cell, NodeFilter.SHOW_TEXT);
20378
+ const walker = cell.ownerDocument.createTreeWalker(
20379
+ cell,
20380
+ 4
20381
+ /* NodeFilter.SHOW_TEXT: the global is gone once a test env tears down */
20382
+ );
20379
20383
  const range = cell.ownerDocument.createRange();
20380
20384
  if (typeof range.getClientRects !== "function") return false;
20381
20385
  let firstTop = null;