gridsmith-ui 0.24.0 → 0.26.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -6
- package/dist/MentionList-CoNBRFGR.d.ts +8 -0
- package/dist/charts.d.ts +42 -1
- package/dist/charts.js +320 -89
- package/dist/charts.js.map +1 -1
- package/dist/chat.js +5 -4
- package/dist/chat.js.map +1 -1
- package/dist/{chunk-6XAU44U4.js → chunk-CYN5O567.js} +10 -23
- package/dist/chunk-CYN5O567.js.map +1 -0
- package/dist/{chunk-SBTBMAIU.js → chunk-EX64MN2U.js} +6 -6
- package/dist/{chunk-SBTBMAIU.js.map → chunk-EX64MN2U.js.map} +1 -1
- package/dist/chunk-HP7WUZPK.js +80 -0
- package/dist/chunk-HP7WUZPK.js.map +1 -0
- package/dist/{chunk-4LDYR7QP.js → chunk-HXVXJUGV.js} +53 -13
- package/dist/chunk-HXVXJUGV.js.map +1 -0
- package/dist/{chunk-MV7JXWXK.js → chunk-KWLNEH53.js} +8 -7
- package/dist/chunk-KWLNEH53.js.map +1 -0
- package/dist/{chunk-CXMV7SXJ.js → chunk-MD2EFMES.js} +5 -77
- package/dist/chunk-MD2EFMES.js.map +1 -0
- package/dist/{chunk-SVP2IG4S.js → chunk-MYIJNVTK.js} +7 -7
- package/dist/chunk-MYIJNVTK.js.map +1 -0
- package/dist/{chunk-IVCVNAXG.js → chunk-OTCEJN64.js} +3 -3
- package/dist/{chunk-IVCVNAXG.js.map → chunk-OTCEJN64.js.map} +1 -1
- package/dist/{chunk-2PWGJ4PK.js → chunk-QM4SJKWV.js} +11 -6
- package/dist/chunk-QM4SJKWV.js.map +1 -0
- package/dist/chunk-RWKCAKN3.js +209 -0
- package/dist/chunk-RWKCAKN3.js.map +1 -0
- package/dist/{chunk-2YSEME33.js → chunk-SFY2KMHG.js} +63 -6
- package/dist/chunk-SFY2KMHG.js.map +1 -0
- package/dist/{chunk-NCIZQYXQ.js → chunk-X2HEV7FB.js} +33 -3
- package/dist/chunk-X2HEV7FB.js.map +1 -0
- package/dist/{chunk-JXANCXK6.js → chunk-ZI3OQDET.js} +18 -4
- package/dist/chunk-ZI3OQDET.js.map +1 -0
- package/dist/{chunk-7TOJ4G7S.js → chunk-ZYVXV4WC.js} +14 -9
- package/dist/chunk-ZYVXV4WC.js.map +1 -0
- package/dist/data-grid.d.ts +1 -1
- package/dist/data-grid.js +13 -12
- package/dist/data-grid.js.map +1 -1
- package/dist/editor-mentions.d.ts +30 -0
- package/dist/editor-mentions.js +160 -0
- package/dist/editor-mentions.js.map +1 -0
- package/dist/editor.d.ts +23 -3
- package/dist/editor.js +58 -9
- package/dist/editor.js.map +1 -1
- package/dist/{feather-2JFSFLXN.js → feather-VFWLAILO.js} +12 -3
- package/dist/feather-VFWLAILO.js.map +1 -0
- package/dist/{heroicons-4AQ4YBVG.js → heroicons-H3Z7DFCF.js} +22 -7
- package/dist/heroicons-H3Z7DFCF.js.map +1 -0
- package/dist/icons.d.ts +11 -4
- package/dist/icons.js +1 -1
- package/dist/index.d.ts +202 -31
- package/dist/index.js +1473 -1181
- package/dist/index.js.map +1 -1
- package/dist/kanban.js +8 -8
- package/dist/kanban.js.map +1 -1
- package/dist/{lucide-RZC2FEMS.js → lucide-MR32KM4G.js} +12 -3
- package/dist/lucide-MR32KM4G.js.map +1 -0
- package/dist/{phosphor-CEN25FLM.js → phosphor-ABQQL5N5.js} +12 -3
- package/dist/phosphor-ABQQL5N5.js.map +1 -0
- package/dist/resizable.js +8 -8
- package/dist/roots.css +28 -0
- package/dist/{tabler-YXXC7Y6B.js → tabler-ANUSY4D2.js} +12 -3
- package/dist/tabler-ANUSY4D2.js.map +1 -0
- package/dist/tokens.css +6 -0
- package/dist/{use-pagination-DiJo0aW5.d.ts → use-pagination-BnKOnBdz.d.ts} +3 -0
- package/dist/virtualized.d.ts +1 -1
- package/dist/virtualized.js +7 -6
- package/dist/virtualized.js.map +1 -1
- package/dist-static/tokens.css +6 -0
- package/package.json +29 -21
- package/dist/chunk-2PWGJ4PK.js.map +0 -1
- package/dist/chunk-2YSEME33.js.map +0 -1
- package/dist/chunk-4LDYR7QP.js.map +0 -1
- package/dist/chunk-6XAU44U4.js.map +0 -1
- package/dist/chunk-7TOJ4G7S.js.map +0 -1
- package/dist/chunk-CXMV7SXJ.js.map +0 -1
- package/dist/chunk-JXANCXK6.js.map +0 -1
- package/dist/chunk-MV7JXWXK.js.map +0 -1
- package/dist/chunk-NCIZQYXQ.js.map +0 -1
- package/dist/chunk-SVP2IG4S.js.map +0 -1
- package/dist/chunk-WOVYHFUD.js +0 -50
- package/dist/chunk-WOVYHFUD.js.map +0 -1
- package/dist/feather-2JFSFLXN.js.map +0 -1
- package/dist/heroicons-4AQ4YBVG.js.map +0 -1
- package/dist/lucide-RZC2FEMS.js.map +0 -1
- package/dist/phosphor-CEN25FLM.js.map +0 -1
- package/dist/tabler-YXXC7Y6B.js.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../showcase/src/lib/table-utils.ts","../../../showcase/src/lib/card-cell.tsx","../../../showcase/src/lib/use-cards-switch.ts"],"names":[],"mappings":"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","file":"chunk-IVCVNAXG.js","sourcesContent":["/** Shared column-kind inference for Table and DataGrid. */\n\n/**\n * What a column holds, which decides how its cells lay out: numbers and dates shrink to their content and stay whole,\n * an identifier (a long run of digits: a barcode, an ID) may wrap anywhere, an email truncates, text wraps at spaces.\n */\nexport type ColumnKind = \"date\" | \"number\" | \"email\" | \"label\" | \"text\" | \"identifier\";\n\nconst DATE_ISO = /^\\d{4}-\\d{2}-\\d{2}$/;\nconst DATE_US = /^\\d{1,2}\\/\\d{1,2}\\/\\d{4}$/;\n// a month's name or its short form, with or without a full stop: \"Jan\", \"Sept\", \"Sep.\", \"September\" (Sprint 25.13 A35)\nconst MONTH = \"[A-Z][a-z]{2,8}\\\\.?\";\nconst DATE_TEXT_MDY = new RegExp(`^${MONTH}\\\\s+\\\\d{1,2},?\\\\s+\\\\d{4}$`); // \"Jan 12, 2024\" or \"Sept 12 2024\"\nconst DATE_TEXT_DMY = new RegExp(`^\\\\d{1,2}\\\\s+${MONTH}\\\\s+\\\\d{4}$`); // \"12 Jan 2024\", \"24 Sept 2026\"\n// a date this year, with no year: \"Sep 16\", \"16 Sept\" (the review of 30 September). Only a month's own name, so \"Room 12\"\n// isn't a date.\nconst MONTH_NAME = \"(?:Jan(?:uary)?|Feb(?:ruary)?|Mar(?:ch)?|Apr(?:il)?|May|June?|July?|Aug(?:ust)?|Sep(?:t(?:ember)?)?|Oct(?:ober)?|Nov(?:ember)?|Dec(?:ember)?)\\\\.?\";\nconst DATE_TEXT_MD = new RegExp(`^${MONTH_NAME}\\\\s+\\\\d{1,2}$`);\nconst DATE_TEXT_DM = new RegExp(`^\\\\d{1,2}\\\\s+${MONTH_NAME}$`);\n// a number, with a currency before or a unit after it: \"$1,200\", \"12%\", \"18 KB\", \"1.2 MB\", \"250 ms\" (Sprint 25.13 A35)\nconst NUMBER_RE = /^[$€£¥]?\\s?[\\d,]+\\.?\\d*\\s?(?:%|[KMGTP]i?B|B|bytes?|ms|s|sec|min|h|hrs?|kg|g|km|m|°[CF])?$/i;\n// a long run of digits is an identifier, never a quantity: a barcode or an account number (Sprint 25.13 A35)\nconst IDENTIFIER_RE = /^\\d{13,}$/;\nconst EMAIL_RE = /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/;\n\nfunction isDate(v: string): boolean {\n return DATE_ISO.test(v) || DATE_US.test(v) || DATE_TEXT_MDY.test(v) || DATE_TEXT_DMY.test(v) || DATE_TEXT_MD.test(v) || DATE_TEXT_DM.test(v);\n}\n\nfunction isJsx(v: unknown): boolean {\n return typeof v === \"object\" && v !== null;\n}\n\nfunction detectKind(values: unknown[]): ColumnKind | undefined {\n // Filter out null/undefined\n const present = values.filter((v) => v != null);\n if (present.length === 0) return undefined;\n\n // Any JSX value means no kind\n if (present.some(isJsx)) return undefined;\n\n // Check date (strings only)\n if (present.every((v) => typeof v === \"string\" && isDate(v))) return \"date\";\n\n // A long run of digits, as text or as a number too large to be one: an identifier\n if (present.every((v) => (typeof v === \"string\" && IDENTIFIER_RE.test(v)) || (typeof v === \"number\" && Math.abs(v) >= 1e13 && Number.isInteger(v)))) return \"identifier\";\n\n // Check number: all typeof number, or all strings matching the number pattern\n if (present.every((v) => typeof v === \"number\")) return \"number\";\n if (present.every((v) => typeof v === \"string\" && NUMBER_RE.test(v))) return \"number\";\n\n // Check email\n if (present.every((v) => typeof v === \"string\" && EMAIL_RE.test(v))) return \"email\";\n\n // Check label: strings <= 20 chars, no line breaks\n if (present.every((v) => typeof v === \"string\" && v.length <= 20 && !/[\\n\\r]/.test(v))) return \"label\";\n\n // Fallback\n return \"text\";\n}\n\n/**\n * The kind of a column from the words its cells show (Sprint 25.13 A35): a column of elements has no kind from its\n * values, so the Table reads what they render. Only a date or a number counts from rendered text, to keep it whole.\n */\nexport function kindOfRenderedText(texts: string[]): ColumnKind | undefined {\n const kind = detectKind(texts.map((t) => t.trim()).filter(Boolean));\n return kind === \"date\" || kind === \"number\" ? kind : undefined;\n}\n\n/**\n * Sample data rows and infer a ColumnKind for each key.\n * Returns a Map of key -> kind for columns that matched a pattern.\n * Columns with mixed types or JSX values get no entry.\n */\nexport function inferColumnKinds(\n data: Record<string, unknown>[],\n columnKeys: string[],\n maxSample = 50,\n): Map<string, ColumnKind> {\n const sample = data.length > maxSample ? data.slice(0, maxSample) : data;\n const result = new Map<string, ColumnKind>();\n\n for (const key of columnKeys) {\n const values = sample.map((row) => row[key]);\n const kind = detectKind(values);\n if (kind) result.set(key, kind);\n }\n\n return result;\n}\n","/**\n * The value cell of a Table or DataGrid in card mode (Sprint 25.4 C-2, C-4).\n *\n * A card row is `label | value`, the value right-aligned. Three things a value must do on a phone:\n * - wrap a long single string at sensible points first (after @, ., /, -) and mid-word only as a last resort\n * (`overflow-wrap: break-word` breaks inside a word only when the word alone cannot fit a line);\n * - clamp after two lines with the full text in a tooltip;\n * - reach the builder's own markup: a nested flex row (icon + name) keeps `min-width: auto` and overflows unless\n * every descendant is told `min-width: 0` and given the same break rule.\n *\n * A cell that holds an Avatar stacks: the avatar above, the primary text, then the secondary, aligned to the value\n * column. The rule is CSS (`:has([data-avatar])`) on the value cell's direct child, so a hand-rolled\n * `<div class=\"flex\"><Avatar/><div>…</div></div>` stacks the same way EntityCell does.\n */\nimport { Fragment, type ReactNode } from \"react\";\nimport { cn } from \"./utils\";\nimport { Tooltip } from \"../components/Tooltip\";\nimport { useTextOverflow } from \"./use-text-overflow\";\n\nexport const CARD_VALUE_CLASS = cn(\n \"min-w-0 text-right text-[var(--ds-text-primary)]\",\n \"[overflow-wrap:break-word] [&_*]:min-w-0 [&_*]:[overflow-wrap:break-word]\",\n // A hand-rolled avatar cell (div > Avatar + text) keeps its row and sits against the value column's right edge,\n // like EntityCell (round 7: the stacked cell was replaced).\n \"[&:has([data-avatar])>*]:flex [&:has([data-avatar])>*]:items-center [&:has([data-avatar])>*]:justify-end [&:has([data-avatar])>*]:text-right\",\n);\n\nconst BREAK_AFTER = /([@./-])/g;\n\n/** The string with a soft break opportunity after each @, ., / and -, so a long email or path wraps there first. */\nexport function breakable(text: string): ReactNode {\n const parts = text.split(BREAK_AFTER);\n if (parts.length === 1) return text;\n return parts.map((part, i) => (\n <Fragment key={i}>{part}{i % 2 === 1 && <wbr />}</Fragment>\n ));\n}\n\n/** A string value: sensible breaks, two lines, the full text as a tooltip once it clamps. */\nexport function CardStringValue({ text }: { text: string }) {\n const { ref, isTruncated } = useTextOverflow({ lines: 2, content: text });\n const el = <span ref={ref} className=\"block line-clamp-2\">{breakable(text)}</span>;\n return isTruncated ? <Tooltip content={text}>{el}</Tooltip> : el;\n}\n\nexport function CardValue({ value, id }: { value: ReactNode; id?: string }) {\n return (\n <span id={id} className={CARD_VALUE_CLASS}>\n {typeof value === \"string\" ? <CardStringValue text={value} /> : value}\n </span>\n );\n}\n","import { useLayoutEffect, useRef, useState } from \"react\";\n\n/**\n * Sprint 25.13 A35: below this width of its own, about a phone's, a table that doesn't fit shows cards; it goes back to\n * a table once it has the room the table needed, or this much more, so a width near the line can't flip it back and\n * forth.\n */\nexport const CARDS_BELOW = 440;\nexport const CARDS_HYSTERESIS = 40;\n\n/**\n * Whether a table (Table, DataGrid) shows cards (Sprint 25.13 A35): on a narrow screen, as before, and anywhere else\n * only when its own width is under `cardsBelow` and the table doesn't fit it — measured on the element `rootRef` holds,\n * never against the screen's breakpoint, so a table in a pane, a drawer or a dialog stays a table and scrolls sideways,\n * and a table that fits stays a table however narrow its box. 0 never switches by width. The root is whichever element\n * renders, so the measure follows the mode and `remeasure` (a titled table's root differs). A width of 0 (not laid out\n * yet, or no layout at all) decides nothing.\n *\n * The review of 30 September: the switch measured the root in both modes, and in a box that sizes to its content (a\n * flex item, an inline-block, a grid item placed at its start) the table's width said cards and the cards' width said\n * table, so it flipped on every commit until React stopped the page. It now decides each way from what the other mode\n * can't change: a table goes to cards when it overflows its frame (a box sized to its content never makes it overflow),\n * and cards go back once the root has the width the table needed then, which the cards can't make it overflow again.\n */\nexport function useCardsSwitch(narrowScreen: boolean, cardsBelow: number = CARDS_BELOW, remeasure?: unknown) {\n const rootRef = useRef<HTMLElement | null>(null);\n const [narrowBox, setNarrowBox] = useState(false);\n // the root's width at which the table fits its frame, read when it went to cards\n const needed = useRef(0);\n const cards = narrowScreen || narrowBox;\n useLayoutEffect(() => {\n const root = rootRef.current;\n if (!root || cardsBelow <= 0) { setNarrowBox(false); return; }\n const measure = () => {\n const width = root.getBoundingClientRect().width;\n if (width === 0) return;\n if (narrowBox) {\n if (width >= Math.min(needed.current, cardsBelow + CARDS_HYSTERESIS)) setNarrowBox(false);\n return;\n }\n // cards shown for a narrow screen measure nothing: the table isn't drawn to read\n if (cards) return;\n const frame = root.querySelector<HTMLElement>(\"[data-table-scroll]\") ?? root;\n const over = frame.scrollWidth - frame.clientWidth;\n if (width < cardsBelow && over > 1) {\n needed.current = width + over;\n setNarrowBox(true);\n }\n };\n measure();\n if (typeof ResizeObserver === \"undefined\") return;\n const observer = new ResizeObserver(measure);\n observer.observe(root);\n // the table's own width too: rows arriving can make it overflow a frame that keeps its size\n const table = root.querySelector(\"[data-table-scroll] > *\");\n if (table) observer.observe(table);\n return () => observer.disconnect();\n }, [cardsBelow, cards, narrowBox, remeasure]);\n return { cards, rootRef };\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../../../showcase/src/lib/table-utils.ts","../../../showcase/src/lib/card-cell.tsx","../../../showcase/src/lib/use-cards-switch.ts"],"names":[],"mappings":"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","file":"chunk-OTCEJN64.js","sourcesContent":["/** Shared column-kind inference for Table and DataGrid. */\n\n/**\n * What a column holds, which decides how its cells lay out: numbers and dates shrink to their content and stay whole,\n * an identifier (a long run of digits: a barcode, an ID) may wrap anywhere, an email truncates, text wraps at spaces.\n */\nexport type ColumnKind = \"date\" | \"number\" | \"email\" | \"label\" | \"text\" | \"identifier\";\n\nconst DATE_ISO = /^\\d{4}-\\d{2}-\\d{2}$/;\nconst DATE_US = /^\\d{1,2}\\/\\d{1,2}\\/\\d{4}$/;\n// a month's name or its short form, with or without a full stop: \"Jan\", \"Sept\", \"Sep.\", \"September\" (Sprint 25.13 A35)\nconst MONTH = \"[A-Z][a-z]{2,8}\\\\.?\";\nconst DATE_TEXT_MDY = new RegExp(`^${MONTH}\\\\s+\\\\d{1,2},?\\\\s+\\\\d{4}$`); // \"Jan 12, 2024\" or \"Sept 12 2024\"\nconst DATE_TEXT_DMY = new RegExp(`^\\\\d{1,2}\\\\s+${MONTH}\\\\s+\\\\d{4}$`); // \"12 Jan 2024\", \"24 Sept 2026\"\n// a date this year, with no year: \"Sep 16\", \"16 Sept\" (the review of 30 September). Only a month's own name, so \"Room 12\"\n// isn't a date.\nconst MONTH_NAME = \"(?:Jan(?:uary)?|Feb(?:ruary)?|Mar(?:ch)?|Apr(?:il)?|May|June?|July?|Aug(?:ust)?|Sep(?:t(?:ember)?)?|Oct(?:ober)?|Nov(?:ember)?|Dec(?:ember)?)\\\\.?\";\nconst DATE_TEXT_MD = new RegExp(`^${MONTH_NAME}\\\\s+\\\\d{1,2}$`);\nconst DATE_TEXT_DM = new RegExp(`^\\\\d{1,2}\\\\s+${MONTH_NAME}$`);\n// a number, with a currency before or a unit after it: \"$1,200\", \"12%\", \"18 KB\", \"1.2 MB\", \"250 ms\" (Sprint 25.13 A35)\nconst NUMBER_RE = /^[$€£¥]?\\s?[\\d,]+\\.?\\d*\\s?(?:%|[KMGTP]i?B|B|bytes?|ms|s|sec|min|h|hrs?|kg|g|km|m|°[CF])?$/i;\n// a long run of digits is an identifier, never a quantity: a barcode or an account number (Sprint 25.13 A35)\nconst IDENTIFIER_RE = /^\\d{13,}$/;\nconst EMAIL_RE = /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/;\n\nfunction isDate(v: string): boolean {\n return DATE_ISO.test(v) || DATE_US.test(v) || DATE_TEXT_MDY.test(v) || DATE_TEXT_DMY.test(v) || DATE_TEXT_MD.test(v) || DATE_TEXT_DM.test(v);\n}\n\nfunction isJsx(v: unknown): boolean {\n return typeof v === \"object\" && v !== null;\n}\n\nfunction detectKind(values: unknown[]): ColumnKind | undefined {\n // Filter out null/undefined\n const present = values.filter((v) => v != null);\n if (present.length === 0) return undefined;\n\n // Any JSX value means no kind\n if (present.some(isJsx)) return undefined;\n\n // Check date (strings only)\n if (present.every((v) => typeof v === \"string\" && isDate(v))) return \"date\";\n\n // A long run of digits, as text or as a number too large to be one: an identifier\n if (present.every((v) => (typeof v === \"string\" && IDENTIFIER_RE.test(v)) || (typeof v === \"number\" && Math.abs(v) >= 1e13 && Number.isInteger(v)))) return \"identifier\";\n\n // Check number: all typeof number, or all strings matching the number pattern\n if (present.every((v) => typeof v === \"number\")) return \"number\";\n if (present.every((v) => typeof v === \"string\" && NUMBER_RE.test(v))) return \"number\";\n\n // Check email\n if (present.every((v) => typeof v === \"string\" && EMAIL_RE.test(v))) return \"email\";\n\n // Check label: strings <= 20 chars, no line breaks\n if (present.every((v) => typeof v === \"string\" && v.length <= 20 && !/[\\n\\r]/.test(v))) return \"label\";\n\n // Fallback\n return \"text\";\n}\n\n/**\n * The kind of a column from the words its cells show (Sprint 25.13 A35): a column of elements has no kind from its\n * values, so the Table reads what they render. Only a date or a number counts from rendered text, to keep it whole.\n */\nexport function kindOfRenderedText(texts: string[]): ColumnKind | undefined {\n const kind = detectKind(texts.map((t) => t.trim()).filter(Boolean));\n return kind === \"date\" || kind === \"number\" ? kind : undefined;\n}\n\n/**\n * Sample data rows and infer a ColumnKind for each key.\n * Returns a Map of key -> kind for columns that matched a pattern.\n * Columns with mixed types or JSX values get no entry.\n */\nexport function inferColumnKinds(\n data: Record<string, unknown>[],\n columnKeys: string[],\n maxSample = 50,\n): Map<string, ColumnKind> {\n const sample = data.length > maxSample ? data.slice(0, maxSample) : data;\n const result = new Map<string, ColumnKind>();\n\n for (const key of columnKeys) {\n const values = sample.map((row) => row[key]);\n const kind = detectKind(values);\n if (kind) result.set(key, kind);\n }\n\n return result;\n}\n","/**\n * The value cell of a Table or DataGrid in card mode (Sprint 25.4 C-2, C-4).\n *\n * A card row is `label | value`, the value right-aligned. Three things a value must do on a phone:\n * - wrap a long single string at sensible points first (after @, ., /, -) and mid-word only as a last resort\n * (`overflow-wrap: break-word` breaks inside a word only when the word alone cannot fit a line);\n * - clamp after two lines with the full text in a tooltip;\n * - reach the builder's own markup: a nested flex row (icon + name) keeps `min-width: auto` and overflows unless\n * every descendant is told `min-width: 0` and given the same break rule.\n *\n * A cell that holds an Avatar stacks: the avatar above, the primary text, then the secondary, aligned to the value\n * column. The rule is CSS (`:has([data-avatar])`) on the value cell's direct child, so a hand-rolled\n * `<div class=\"flex\"><Avatar/><div>…</div></div>` stacks the same way EntityCell does.\n */\nimport { Fragment, type ReactNode } from \"react\";\nimport { cn } from \"./utils\";\nimport { Tooltip } from \"../components/Tooltip\";\nimport { useTextOverflow } from \"./use-text-overflow\";\n\nexport const CARD_VALUE_CLASS = cn(\n \"min-w-0 text-right text-[var(--ds-text-primary)]\",\n \"[overflow-wrap:break-word] [&_*]:min-w-0 [&_*]:[overflow-wrap:break-word]\",\n // A hand-rolled avatar cell (div > Avatar + text) keeps its row and sits against the value column's right edge,\n // like EntityCell (round 7: the stacked cell was replaced).\n \"[&:has([data-avatar])>*]:flex [&:has([data-avatar])>*]:items-center [&:has([data-avatar])>*]:justify-end [&:has([data-avatar])>*]:text-right\",\n);\n\nconst BREAK_AFTER = /([@./-])/g;\n\n/** The string with a soft break opportunity after each @, ., / and -, so a long email or path wraps there first. */\nexport function breakable(text: string): ReactNode {\n const parts = text.split(BREAK_AFTER);\n if (parts.length === 1) return text;\n return parts.map((part, i) => (\n <Fragment key={i}>{part}{i % 2 === 1 && <wbr />}</Fragment>\n ));\n}\n\n/** A string value: sensible breaks, two lines, the full text as a tooltip once it clamps. */\nexport function CardStringValue({ text }: { text: string }) {\n const { ref, isTruncated } = useTextOverflow({ lines: 2, content: text });\n const el = <span ref={ref} className=\"block line-clamp-2\">{breakable(text)}</span>;\n return isTruncated ? <Tooltip content={text}>{el}</Tooltip> : el;\n}\n\nexport function CardValue({ value, id }: { value: ReactNode; id?: string }) {\n return (\n <span id={id} className={CARD_VALUE_CLASS}>\n {typeof value === \"string\" ? <CardStringValue text={value} /> : value}\n </span>\n );\n}\n","import { useLayoutEffect, useRef, useState } from \"react\";\n\n/**\n * Sprint 25.13 A35: below this width of its own, about a phone's, a table that doesn't fit shows cards; it goes back to\n * a table once it has the room the table needed, or this much more, so a width near the line can't flip it back and\n * forth.\n */\nexport const CARDS_BELOW = 440;\nexport const CARDS_HYSTERESIS = 40;\n\n/**\n * Whether a table (Table, DataGrid) shows cards (Sprint 25.13 A35): on a narrow screen, as before, and anywhere else\n * only when its own width is under `cardsBelow` and the table doesn't fit it — measured on the element `rootRef` holds,\n * never against the screen's breakpoint, so a table in a pane, a drawer or a dialog stays a table and scrolls sideways,\n * and a table that fits stays a table however narrow its box. 0 never switches by width. The root is whichever element\n * renders, so the measure follows the mode and `remeasure` (a titled table's root differs). A width of 0 (not laid out\n * yet, or no layout at all) decides nothing.\n *\n * The review of 30 September: the switch measured the root in both modes, and in a box that sizes to its content (a\n * flex item, an inline-block, a grid item placed at its start) the table's width said cards and the cards' width said\n * table, so it flipped on every commit until React stopped the page. It now decides each way from what the other mode\n * can't change: a table goes to cards when it overflows its frame (a box sized to its content never makes it overflow),\n * and cards go back once the root has the width the table needed then, which the cards can't make it overflow again.\n */\nexport function useCardsSwitch(narrowScreen: boolean, cardsBelow: number = CARDS_BELOW, remeasure?: unknown) {\n const rootRef = useRef<HTMLElement | null>(null);\n const [narrowBox, setNarrowBox] = useState(false);\n // the root's width at which the table fits its frame, read when it went to cards\n const needed = useRef(0);\n const cards = narrowScreen || narrowBox;\n useLayoutEffect(() => {\n const root = rootRef.current;\n if (!root || cardsBelow <= 0) { setNarrowBox(false); return; }\n const measure = () => {\n const width = root.getBoundingClientRect().width;\n if (width === 0) return;\n if (narrowBox) {\n if (width >= Math.min(needed.current, cardsBelow + CARDS_HYSTERESIS)) setNarrowBox(false);\n return;\n }\n // cards shown for a narrow screen measure nothing: the table isn't drawn to read\n if (cards) return;\n const frame = root.querySelector<HTMLElement>(\"[data-table-scroll]\") ?? root;\n const over = frame.scrollWidth - frame.clientWidth;\n if (width < cardsBelow && over > 1) {\n needed.current = width + over;\n setNarrowBox(true);\n }\n };\n measure();\n if (typeof ResizeObserver === \"undefined\") return;\n const observer = new ResizeObserver(measure);\n observer.observe(root);\n // the table's own width too: rows arriving can make it overflow a frame that keeps its size\n const table = root.querySelector(\"[data-table-scroll] > *\");\n if (table) observer.observe(table);\n return () => observer.disconnect();\n }, [cardsBelow, cards, narrowBox, remeasure]);\n return { cards, rootRef };\n}\n"]}
|
|
@@ -5,6 +5,7 @@ var PageActionsCtx = createContext(null);
|
|
|
5
5
|
var CardSurfaceCtx = createContext(false);
|
|
6
6
|
var SplitPaneCtx = createContext(false);
|
|
7
7
|
var TableCardCtx = createContext(false);
|
|
8
|
+
var RowsCtx = createContext(false);
|
|
8
9
|
var SplitListFramerCtx = createContext(null);
|
|
9
10
|
function useReportToList(kind) {
|
|
10
11
|
const report = useContext(SplitListFramerCtx);
|
|
@@ -31,15 +32,19 @@ function SurfaceBoundary({ children, pane = false, framer = null }) {
|
|
|
31
32
|
TableCardCtx,
|
|
32
33
|
{ value: false },
|
|
33
34
|
createElement(
|
|
34
|
-
|
|
35
|
-
{ value:
|
|
36
|
-
createElement(
|
|
35
|
+
RowsCtx,
|
|
36
|
+
{ value: false },
|
|
37
|
+
createElement(
|
|
38
|
+
PageActionsCtx,
|
|
39
|
+
{ value: null },
|
|
40
|
+
createElement(SplitListFramerCtx, { value: framer }, children)
|
|
41
|
+
)
|
|
37
42
|
)
|
|
38
43
|
)
|
|
39
44
|
)
|
|
40
45
|
);
|
|
41
46
|
}
|
|
42
47
|
|
|
43
|
-
export { CardSurfaceCtx, PageActionsCtx, SplitPaneCtx, SurfaceBoundary, TableCardCtx, useFramesItself, useScrollsInside };
|
|
44
|
-
//# sourceMappingURL=chunk-
|
|
45
|
-
//# sourceMappingURL=chunk-
|
|
48
|
+
export { CardSurfaceCtx, PageActionsCtx, RowsCtx, SplitPaneCtx, SurfaceBoundary, TableCardCtx, useFramesItself, useScrollsInside };
|
|
49
|
+
//# sourceMappingURL=chunk-QM4SJKWV.js.map
|
|
50
|
+
//# sourceMappingURL=chunk-QM4SJKWV.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../showcase/src/lib/page-actions.ts","../../../showcase/src/lib/surface-context.ts"],"names":["createContext"],"mappings":";;;AAwBO,IAAM,cAAA,GAAiB,cAAsC,IAAI;AClBjE,IAAM,cAAA,GAAiBA,cAAc,KAAK;AAS1C,IAAM,YAAA,GAAeA,cAA6B,KAAK;AAOvD,IAAM,YAAA,GAAeA,cAAc,KAAK;AAUxC,IAAM,OAAA,GAAUA,cAAc,KAAK;AAWnC,IAAM,kBAAA,GAAqBA,cAAsC,IAAI,CAAA;AAE5E,SAAS,gBAAgB,IAAA,EAA4B;AACnD,EAAA,MAAM,MAAA,GAAS,WAAW,kBAAkB,CAAA;AAC5C,EAAA,eAAA,CAAgB,MAAM;AACpB,IAAA,IAAI,CAAC,MAAA,EAAQ;AACb,IAAA,MAAA,CAAO,MAAM,IAAI,CAAA;AACjB,IAAA,OAAO,MAAM,MAAA,CAAO,IAAA,EAAM,KAAK,CAAA;AAAA,EACjC,CAAA,EAAG,CAAC,MAAA,EAAQ,IAAI,CAAC,CAAA;AACnB;AAGO,SAAS,eAAA,GAAkB;AAChC,EAAA,eAAA,CAAgB,QAAQ,CAAA;AAC1B;AAGO,SAAS,gBAAA,GAAmB;AACjC,EAAA,eAAA,CAAgB,SAAS,CAAA;AAC3B;AASO,SAAS,gBAAgB,EAAE,QAAA,EAAU,OAAO,KAAA,EAAO,MAAA,GAAS,MAAK,EAAmF;AAGzJ,EAAA,OAAO,aAAA;AAAA,IAAc,cAAA;AAAA,IAAgB,EAAE,OAAO,KAAA,EAAM;AAAA,IAClD,aAAA;AAAA,MAAc,YAAA;AAAA,MAAc,EAAE,OAAO,IAAA,EAAK;AAAA,MACxC,aAAA;AAAA,QAAc,YAAA;AAAA,QAAc,EAAE,OAAO,KAAA,EAAM;AAAA,QACzC,aAAA;AAAA,UAAc,OAAA;AAAA,UAAS,EAAE,OAAO,KAAA,EAAM;AAAA,UACpC,aAAA;AAAA,YAAc,cAAA;AAAA,YAAgB,EAAE,OAAO,IAAA,EAAK;AAAA,YAC1C,cAAc,kBAAA,EAAoB,EAAE,KAAA,EAAO,MAAA,IAAU,QAAQ;AAAA;AAAC;AAAC;AAAC;AAAC,GAAC;AAC9E","file":"chunk-QM4SJKWV.js","sourcesContent":["import { createContext } from \"react\";\nimport type { ControlSize } from \"../components/Button\";\n\n/**\n * Sprint 25.16 A4 (R28-7): the slot in a page's own PageHeader for its actions (PageActions). PageContainer provides it,\n * its header draws it while a page asks, and the page's PageActions render into it. The review of 1 October:\n * - content that starts afresh (an overlay's body, a SplitView's pane: SurfaceBoundary) has no slot, so a header there\n * (a message's own header, a drawer's) never takes the page's actions;\n * - the page's header is the first slot in the page, so a second header that comes and goes hands nothing on;\n * - a container with no header of its own (a page that wraps itself in one under an area layout) passes its page's\n * actions to the container around it;\n * - the actions take the header's control size (a minimal or snapping header draws its own actions small).\n */\nexport interface PageActionsSlot {\n /** A page below asks for the slot: a PageActions is drawn. */\n wanted: boolean;\n /** Where the actions go: the slot of the page's header, at the size its controls are drawn. */\n target: { el: HTMLElement; size: ControlSize | null } | null;\n /** A header's slot, while drawn, with its controls' size; returns its release. */\n register: (el: HTMLElement, size: ControlSize | null) => () => void;\n /** A PageActions asks for the slot while drawn; returns its release. */\n add: () => () => void;\n}\n\nexport const PageActionsCtx = createContext<PageActionsSlot | null>(null);\n","import { createContext, createElement, useContext, useLayoutEffect, type ReactNode } from \"react\";\nimport { PageActionsCtx } from \"./page-actions\";\n\n/**\n * True inside a Card's surface. A StackedList reads it to inset its rows to the card's header (px-ds-5).\n */\nexport const CardSurfaceCtx = createContext(false);\n\n/**\n * The SplitView pane content sits in (\"list\", \"detail\", \"aside\"), false outside one. A StackedList reads it to take its\n * inset from the pane (Sprint 25.4 C-5): rows keep their padding so the selection rail sits at the pane's edge and never\n * touches content. A pane header (SectionHeading variant=\"pane\") in the detail insets at the page's gutter, as the item's\n * path above it does, and in the list at its rows' inset (the owner's review of 1 October).\n */\nexport type SplitPaneKind = false | \"list\" | \"detail\" | \"aside\";\nexport const SplitPaneCtx = createContext<SplitPaneKind>(false);\n\n/**\n * True inside a Table or DataGrid card (the phone layout below mobileBreakpoint). An EntityCell reads it to keep\n * the avatar beside the text and align to the value column (Sprint 25.4, round 7: the stacked cell made the card\n * three lines taller than it needed to be).\n */\nexport const TableCardCtx = createContext(false);\n\n/**\n * True among a collection's rows: a Table's or a DataGrid's rows and their phone cards, a StackedList's rows, a Timeline's\n * items. Something\n * with a Tab stop of its own adds a stop to every row there, which makes the collection hard to cross: a RelativeTime\n * drops its own, its full timestamp in its name and on hover (Sprint 25.17 A29). A DataList is one record's fields, not\n * rows, so it doesn't set it (the second review). Inside a control (a card or a row that is a button, a link) the time\n * finds that for itself, from the page. Overlays reset it: SurfaceBoundary, a Popover, a HoverCard.\n */\nexport const RowsCtx = createContext(false);\n\n/**\n * Sprint 25.14 A44: what a SplitView's list pane holds, as its content reports it while mounted. \"framed\": content that\n * draws its own frame or cards (a KanbanBoard, a CardGrid, a Table, a DataGrid). \"scrolls\": content with no frame of its\n * own that scrolls inside the pane (a StackedList, a Gantt, a VirtualizedList), which the view's hairline frames. The\n * view drops its frame and surface when the pane holds framed content and nothing that scrolls (P32: no frame around\n * what frames itself; a hairline around what scrolls inside). Here, not in SplitView, so a board or a card grid never\n * imports the resizable panels. Null outside a list pane: overlays and the view's other panes reset it (SurfaceBoundary).\n */\nexport type SplitListReport = (kind: \"framed\" | \"scrolls\", present: boolean) => void;\nexport const SplitListFramerCtx = createContext<SplitListReport | null>(null);\n\nfunction useReportToList(kind: \"framed\" | \"scrolls\") {\n const report = useContext(SplitListFramerCtx);\n useLayoutEffect(() => {\n if (!report) return;\n report(kind, true);\n return () => report(kind, false);\n }, [report, kind]);\n}\n\n/** Tell a SplitView whose list pane this sits in that it draws its own frame or cards (A44). */\nexport function useFramesItself() {\n useReportToList(\"framed\");\n}\n\n/** Tell a SplitView whose list pane this sits in that it scrolls inside the pane with no frame of its own (A44). */\nexport function useScrollsInside() {\n useReportToList(\"scrolls\");\n}\n\n/**\n * Where content starts afresh: an overlay's body, an opened table row's panel, a SplitView's pane. What a Card, a pane\n * or a table card told the content around it stops here (the review of 30 September: a StackedList in a Drawer opened\n * from a Card took the card's inset, and one in a Dialog declared in a list pane the pane's, so neither lined up with\n * the overlay's gutter), and so does the page's actions slot. A pane passes `pane` (which pane its content is in) and\n * the list pane its `framer`.\n */\nexport function SurfaceBoundary({ children, pane = false, framer = null }: { children: ReactNode; pane?: SplitPaneKind; framer?: SplitListReport | null }) {\n // the page's actions belong to the page's own header (Sprint 25.16's review): a header in a pane or an overlay never\n // takes them, and PageActions there renders nothing\n return createElement(CardSurfaceCtx, { value: false },\n createElement(SplitPaneCtx, { value: pane },\n createElement(TableCardCtx, { value: false },\n createElement(RowsCtx, { value: false },\n createElement(PageActionsCtx, { value: null },\n createElement(SplitListFramerCtx, { value: framer }, children))))));\n}\n"]}
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
import { usePosition } from './chunk-HP7WUZPK.js';
|
|
2
|
+
import { useLatest } from './chunk-WH2FUFAC.js';
|
|
3
|
+
import { cn } from './chunk-M2QRNBDQ.js';
|
|
4
|
+
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
5
|
+
import { useRef, useLayoutEffect, useMemo } from 'react';
|
|
6
|
+
import { createPortal } from 'react-dom';
|
|
7
|
+
|
|
8
|
+
// ../../showcase/src/lib/initials.ts
|
|
9
|
+
function initialsOf(name) {
|
|
10
|
+
return name.trim().split(/\s+/).filter(Boolean).slice(0, 2).map((word) => Array.from(word)[0] ?? "").join("").toUpperCase();
|
|
11
|
+
}
|
|
12
|
+
function toInitials(value) {
|
|
13
|
+
const s = value.trim();
|
|
14
|
+
if (!s) return "";
|
|
15
|
+
if (!/\s/.test(s) && Array.from(s).length <= 3) return s;
|
|
16
|
+
return initialsOf(s);
|
|
17
|
+
}
|
|
18
|
+
var sizeStyles = {
|
|
19
|
+
sm: "h-8 w-8 text-xs",
|
|
20
|
+
md: "h-10 w-10 text-sm",
|
|
21
|
+
lg: "h-12 w-12 text-base"
|
|
22
|
+
};
|
|
23
|
+
var paletteStyles = {
|
|
24
|
+
1: "bg-[var(--ds-palette-1-bg,var(--ds-bg-muted))] text-[var(--ds-palette-1-text,var(--ds-text-secondary))]",
|
|
25
|
+
2: "bg-[var(--ds-palette-2-bg,var(--ds-bg-muted))] text-[var(--ds-palette-2-text,var(--ds-text-secondary))]",
|
|
26
|
+
3: "bg-[var(--ds-palette-3-bg,var(--ds-bg-muted))] text-[var(--ds-palette-3-text,var(--ds-text-secondary))]",
|
|
27
|
+
4: "bg-[var(--ds-palette-4-bg,var(--ds-bg-muted))] text-[var(--ds-palette-4-text,var(--ds-text-secondary))]",
|
|
28
|
+
5: "bg-[var(--ds-palette-5-bg,var(--ds-bg-muted))] text-[var(--ds-palette-5-text,var(--ds-text-secondary))]"
|
|
29
|
+
};
|
|
30
|
+
function paletteFor(seed) {
|
|
31
|
+
let h = 0;
|
|
32
|
+
for (const ch of seed) h = h * 31 + ch.charCodeAt(0) >>> 0;
|
|
33
|
+
return h % 5 + 1;
|
|
34
|
+
}
|
|
35
|
+
function colorStyles(color, seed) {
|
|
36
|
+
if (color === "accent") return "bg-[var(--ds-accent-primary)] text-[var(--ds-text-on-primary)]";
|
|
37
|
+
if (color === "neutral") return "bg-[var(--ds-bg-muted)] text-[var(--ds-text-secondary)]";
|
|
38
|
+
return paletteStyles[color === "auto" ? paletteFor(seed) : color];
|
|
39
|
+
}
|
|
40
|
+
function PresenceMark({ status }) {
|
|
41
|
+
return /* @__PURE__ */ jsxs("svg", { viewBox: "0 0 10 10", width: "10", height: "10", "aria-hidden": "true", focusable: "false", "data-presence": status, className: "block", children: [
|
|
42
|
+
status === "online" && /* @__PURE__ */ jsx("circle", { cx: "5", cy: "5", r: "5", className: "fill-[var(--ds-status-success)]" }),
|
|
43
|
+
status === "away" && /* @__PURE__ */ jsx("circle", { cx: "5", cy: "5", r: "4", fill: "none", strokeWidth: "2", className: "stroke-[var(--ds-status-warning)]" }),
|
|
44
|
+
status === "busy" && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
45
|
+
/* @__PURE__ */ jsx("circle", { cx: "5", cy: "5", r: "5", className: "fill-[var(--ds-status-error)]" }),
|
|
46
|
+
/* @__PURE__ */ jsx("rect", { x: "2.5", y: "4.1", width: "5", height: "1.8", rx: "0.9", className: "fill-[var(--ds-bg-canvas)]" })
|
|
47
|
+
] }),
|
|
48
|
+
status === "offline" && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
49
|
+
/* @__PURE__ */ jsx("circle", { cx: "5", cy: "5", r: "4", fill: "none", strokeWidth: "2", className: "stroke-[var(--ds-text-muted)]" }),
|
|
50
|
+
/* @__PURE__ */ jsx("line", { x1: "3.1", y1: "6.9", x2: "6.9", y2: "3.1", strokeWidth: "1.5", strokeLinecap: "round", className: "stroke-[var(--ds-text-muted)]" })
|
|
51
|
+
] })
|
|
52
|
+
] });
|
|
53
|
+
}
|
|
54
|
+
function Avatar({ initials, src, alt, size = "md", status, color = "auto", className }) {
|
|
55
|
+
const who = alt ?? initials ?? "";
|
|
56
|
+
const named = status ? [who, status].filter(Boolean).join(", ") : void 0;
|
|
57
|
+
const base = cn(
|
|
58
|
+
"inline-flex items-center justify-center font-medium shrink-0",
|
|
59
|
+
"rounded-[var(--ds-radius-full)]",
|
|
60
|
+
sizeStyles[size],
|
|
61
|
+
className
|
|
62
|
+
);
|
|
63
|
+
const avatarElement = src ? /* @__PURE__ */ jsx(
|
|
64
|
+
"img",
|
|
65
|
+
{
|
|
66
|
+
"data-avatar": "",
|
|
67
|
+
src,
|
|
68
|
+
alt: named ?? alt ?? initials ?? "",
|
|
69
|
+
className: cn(base, "object-cover")
|
|
70
|
+
}
|
|
71
|
+
) : /* @__PURE__ */ jsx(
|
|
72
|
+
"div",
|
|
73
|
+
{
|
|
74
|
+
"data-avatar": "",
|
|
75
|
+
role: named ? "img" : void 0,
|
|
76
|
+
"aria-label": named,
|
|
77
|
+
className: cn(
|
|
78
|
+
base,
|
|
79
|
+
colorStyles(color, alt ?? initials ?? ""),
|
|
80
|
+
"border-[length:var(--ds-border-width)] border-transparent"
|
|
81
|
+
),
|
|
82
|
+
children: initials != null ? toInitials(initials) : null
|
|
83
|
+
}
|
|
84
|
+
);
|
|
85
|
+
if (!status) return avatarElement;
|
|
86
|
+
return /* @__PURE__ */ jsxs("div", { className: "relative inline-flex", children: [
|
|
87
|
+
avatarElement,
|
|
88
|
+
/* @__PURE__ */ jsx(
|
|
89
|
+
"span",
|
|
90
|
+
{
|
|
91
|
+
className: cn(
|
|
92
|
+
"absolute",
|
|
93
|
+
size === "sm" && "bottom-[-4px] right-[-4px]",
|
|
94
|
+
size === "md" && "bottom-[-2px] right-[-2px]",
|
|
95
|
+
size === "lg" && "bottom-[0px] right-[0px]"
|
|
96
|
+
),
|
|
97
|
+
"aria-hidden": "true",
|
|
98
|
+
children: /* @__PURE__ */ jsx("span", { className: "block rounded-[var(--ds-radius-full)] bg-[var(--ds-bg-canvas)] shadow-[0_0_0_2px_var(--ds-bg-canvas)]", children: /* @__PURE__ */ jsx(PresenceMark, { status }) })
|
|
99
|
+
}
|
|
100
|
+
)
|
|
101
|
+
] });
|
|
102
|
+
}
|
|
103
|
+
function useActiveOptionInView(list, activeId) {
|
|
104
|
+
const shown = useRef(null);
|
|
105
|
+
useLayoutEffect(() => {
|
|
106
|
+
const box = list.current;
|
|
107
|
+
if (!box || !activeId) {
|
|
108
|
+
shown.current = null;
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
const root = box.getRootNode();
|
|
112
|
+
const option = typeof root.getElementById === "function" ? root.getElementById(activeId) : null;
|
|
113
|
+
if (!option || !box.contains(option)) return;
|
|
114
|
+
const seen = `${activeId}\0${option.textContent ?? ""}`;
|
|
115
|
+
if (seen === shown.current) return;
|
|
116
|
+
shown.current = seen;
|
|
117
|
+
const top = option.getBoundingClientRect().top - box.getBoundingClientRect().top + box.scrollTop;
|
|
118
|
+
const bottom = top + option.offsetHeight;
|
|
119
|
+
if (top < box.scrollTop) box.scrollTo({ top });
|
|
120
|
+
else if (bottom > box.scrollTop + box.clientHeight) box.scrollTo({ top: bottom - box.clientHeight });
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// ../../showcase/src/lib/search-text.ts
|
|
125
|
+
function foldForSearch(text) {
|
|
126
|
+
return text.normalize("NFD").replace(/\p{M}/gu, "").replace(/_/g, " ").toLowerCase();
|
|
127
|
+
}
|
|
128
|
+
var fold = foldForSearch;
|
|
129
|
+
function matchMentions(items, query) {
|
|
130
|
+
const q = fold(query);
|
|
131
|
+
const rank = (s) => {
|
|
132
|
+
const name = fold(s.label);
|
|
133
|
+
if (name.startsWith(q)) return 0;
|
|
134
|
+
if (name.split(/\s+/).some((word) => word.startsWith(q))) return 1;
|
|
135
|
+
if (name.includes(q)) return 2;
|
|
136
|
+
return s.description && fold(s.description).includes(q) ? 3 : -1;
|
|
137
|
+
};
|
|
138
|
+
return items.map((s) => ({ s, r: rank(s) })).filter(({ r }) => r >= 0).sort((a, b) => a.r - b.r).map(({ s }) => s);
|
|
139
|
+
}
|
|
140
|
+
var mentionOptionId = (listId, index) => `${listId}-option-${index}`;
|
|
141
|
+
var MENTION_LIST_LIMIT = 10;
|
|
142
|
+
function shownMentions(found) {
|
|
143
|
+
return { items: found.slice(0, MENTION_LIST_LIMIT), more: Math.max(0, found.length - MENTION_LIST_LIMIT) };
|
|
144
|
+
}
|
|
145
|
+
function MentionList({ id, label, items, more = 0, activeIndex, onActiveIndexChange, onPick, anchorRect, listRef }) {
|
|
146
|
+
const ownRef = useRef(null);
|
|
147
|
+
const floatingRef = listRef ?? ownRef;
|
|
148
|
+
const boxRef = useRef(null);
|
|
149
|
+
const anchorRef = useLatest(anchorRect);
|
|
150
|
+
const anchor = useMemo(() => ({ current: { getBoundingClientRect: () => anchorRef.current() ?? new DOMRect() } }), [anchorRef]);
|
|
151
|
+
const pos = usePosition(anchor, floatingRef, items.length > 0, { side: "bottom", align: "start", offset: 4 });
|
|
152
|
+
useActiveOptionInView(boxRef, items.length > 0 ? mentionOptionId(id, activeIndex) : void 0);
|
|
153
|
+
if (items.length === 0) return null;
|
|
154
|
+
return createPortal(
|
|
155
|
+
/* @__PURE__ */ jsxs(
|
|
156
|
+
"div",
|
|
157
|
+
{
|
|
158
|
+
ref: floatingRef,
|
|
159
|
+
className: cn(
|
|
160
|
+
"fixed z-[var(--ds-z-popover)] min-w-[200px] flex flex-col",
|
|
161
|
+
"bg-[var(--ds-bg-surface-elevated)]",
|
|
162
|
+
"[backdrop-filter:var(--ds-glass-filter)] [-webkit-backdrop-filter:var(--ds-glass-filter)]",
|
|
163
|
+
"border-[length:var(--ds-border-width)] border-[var(--ds-border-default)]",
|
|
164
|
+
"rounded-[var(--ds-radius-md)] shadow-[var(--ds-shadow-dropdown),var(--ds-glass-inset)]"
|
|
165
|
+
),
|
|
166
|
+
style: { top: pos.top, left: pos.left },
|
|
167
|
+
children: [
|
|
168
|
+
/* @__PURE__ */ jsx("div", { ref: boxRef, id, role: "listbox", "aria-label": label, className: "max-h-[200px] overflow-y-auto p-ds-1", children: items.map((s, i) => /* @__PURE__ */ jsxs(
|
|
169
|
+
"button",
|
|
170
|
+
{
|
|
171
|
+
id: mentionOptionId(id, i),
|
|
172
|
+
type: "button",
|
|
173
|
+
role: "option",
|
|
174
|
+
tabIndex: -1,
|
|
175
|
+
"aria-selected": i === activeIndex,
|
|
176
|
+
className: cn(
|
|
177
|
+
"flex items-center gap-ds-2.5 w-full px-ds-2 py-ds-1.5 text-left text-sm cursor-pointer rounded-[var(--ds-radius-sm)]",
|
|
178
|
+
"transition-colors duration-[var(--ds-transition-speed)]",
|
|
179
|
+
i === activeIndex ? "bg-[var(--ds-bg-muted)] text-[var(--ds-text-primary)]" : "text-[var(--ds-text-secondary)]"
|
|
180
|
+
),
|
|
181
|
+
onMouseDown: (e) => e.preventDefault(),
|
|
182
|
+
onClick: () => onPick(s),
|
|
183
|
+
onMouseEnter: () => onActiveIndexChange(i),
|
|
184
|
+
children: [
|
|
185
|
+
/* @__PURE__ */ jsx("span", { "aria-hidden": "true", className: "inline-flex shrink-0", children: /* @__PURE__ */ jsx(Avatar, { src: s.avatar, initials: s.label, size: "sm", className: "!h-6 !w-6 !text-2xs" }) }),
|
|
186
|
+
/* @__PURE__ */ jsxs("span", { className: "min-w-0", children: [
|
|
187
|
+
/* @__PURE__ */ jsx("span", { className: "block font-medium text-[var(--ds-text-primary)] truncate", children: s.label }),
|
|
188
|
+
s.description && /* @__PURE__ */ jsx("span", { className: "block text-xs text-[var(--ds-text-muted)] truncate", children: s.description })
|
|
189
|
+
] })
|
|
190
|
+
]
|
|
191
|
+
},
|
|
192
|
+
s.id
|
|
193
|
+
)) }),
|
|
194
|
+
more > 0 && /* @__PURE__ */ jsxs("p", { className: "px-ds-3 py-ds-1.5 text-xs text-[var(--ds-text-muted)] border-t-[length:var(--ds-divider-width)] border-[var(--ds-border-subtle)]", children: [
|
|
195
|
+
more,
|
|
196
|
+
" more ",
|
|
197
|
+
more === 1 ? "matches" : "match",
|
|
198
|
+
": keep typing to narrow"
|
|
199
|
+
] })
|
|
200
|
+
]
|
|
201
|
+
}
|
|
202
|
+
),
|
|
203
|
+
document.body
|
|
204
|
+
);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
export { Avatar, MentionList, foldForSearch, initialsOf, matchMentions, mentionOptionId, shownMentions, useActiveOptionInView };
|
|
208
|
+
//# sourceMappingURL=chunk-RWKCAKN3.js.map
|
|
209
|
+
//# sourceMappingURL=chunk-RWKCAKN3.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../showcase/src/lib/initials.ts","../../../showcase/src/components/Avatar.tsx","../../../showcase/src/lib/use-active-option.ts","../../../showcase/src/lib/search-text.ts","../../../showcase/src/components/MentionList.tsx"],"names":["useRef","jsxs","jsx"],"mappings":"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","file":"chunk-RWKCAKN3.js","sourcesContent":["/**\n * Initials for an avatar. Round 21 (run 58): a builder passed each person's full name as `initials`, and the avatar\n * printed \"Amara Haddad\" across a 40 px circle on every row of the Team table. The avatar now reads what it is given:\n * up to three characters with no space are initials already and stay as they are (\"JB\", \"ABC\"); anything longer is a\n * name, and its first two words give the letters.\n */\n\n/** \"Sarah Chen\" → \"SC\"; \"Acme\" → \"A\"; \"Mary Jane Watson\" → \"MJ\". */\nexport function initialsOf(name: string): string {\n return name\n .trim()\n .split(/\\s+/)\n .filter(Boolean)\n .slice(0, 2)\n .map((word) => Array.from(word)[0] ?? \"\")\n .join(\"\")\n .toUpperCase();\n}\n\n/** What an avatar shows for an `initials` value: initials as given, or the initials of a name. */\nexport function toInitials(value: string): string {\n const s = value.trim();\n if (!s) return \"\";\n if (!/\\s/.test(s) && Array.from(s).length <= 3) return s;\n return initialsOf(s);\n}\n","import { cn } from \"../lib/utils\";\nimport { toInitials } from \"../lib/initials\";\n\nexport type AvatarStatus = \"online\" | \"away\" | \"busy\" | \"offline\";\nexport type AvatarColor = \"auto\" | \"accent\" | \"neutral\" | 1 | 2 | 3 | 4 | 5;\n\ninterface AvatarProps {\n /** One to three letters (\"JB\"). A name passed here shows its initials (\"Amara Haddad\" → \"AH\"), never the whole name. */\n initials?: string;\n src?: string;\n alt?: string;\n size?: \"sm\" | \"md\" | \"lg\";\n status?: AvatarStatus;\n /** Initials background. \"auto\" (default) derives a stable palette tint from alt/initials so groups vary; use \"accent\" sparingly. */\n color?: AvatarColor;\n className?: string;\n}\n\nconst sizeStyles = {\n sm: \"h-8 w-8 text-xs\",\n md: \"h-10 w-10 text-sm\",\n lg: \"h-12 w-12 text-base\",\n};\n\n// Fallbacks cover themes whose tokens CSS predates the decorative palette.\nconst paletteStyles = {\n 1: \"bg-[var(--ds-palette-1-bg,var(--ds-bg-muted))] text-[var(--ds-palette-1-text,var(--ds-text-secondary))]\",\n 2: \"bg-[var(--ds-palette-2-bg,var(--ds-bg-muted))] text-[var(--ds-palette-2-text,var(--ds-text-secondary))]\",\n 3: \"bg-[var(--ds-palette-3-bg,var(--ds-bg-muted))] text-[var(--ds-palette-3-text,var(--ds-text-secondary))]\",\n 4: \"bg-[var(--ds-palette-4-bg,var(--ds-bg-muted))] text-[var(--ds-palette-4-text,var(--ds-text-secondary))]\",\n 5: \"bg-[var(--ds-palette-5-bg,var(--ds-bg-muted))] text-[var(--ds-palette-5-text,var(--ds-text-secondary))]\",\n} as const;\n\nfunction paletteFor(seed: string): keyof typeof paletteStyles {\n let h = 0;\n for (const ch of seed) h = (h * 31 + ch.charCodeAt(0)) >>> 0;\n return ((h % 5) + 1) as keyof typeof paletteStyles;\n}\n\nfunction colorStyles(color: AvatarColor, seed: string): string {\n if (color === \"accent\") return \"bg-[var(--ds-accent-primary)] text-[var(--ds-text-on-primary)]\";\n if (color === \"neutral\") return \"bg-[var(--ds-bg-muted)] text-[var(--ds-text-secondary)]\";\n return paletteStyles[color === \"auto\" ? paletteFor(seed) : color];\n}\n\n/**\n * Presence as a shape as well as a colour (Sprint 25.12 A48, WCAG 1.4.1), as Fluent's presence badges do: online a filled\n * dot, away a ring, busy a dot with a bar, offline a ring with a slash. The words are in the avatar's name.\n */\nfunction PresenceMark({ status }: { status: AvatarStatus }) {\n return (\n <svg viewBox=\"0 0 10 10\" width=\"10\" height=\"10\" aria-hidden=\"true\" focusable=\"false\" data-presence={status} className=\"block\">\n {status === \"online\" && <circle cx=\"5\" cy=\"5\" r=\"5\" className=\"fill-[var(--ds-status-success)]\" />}\n {status === \"away\" && <circle cx=\"5\" cy=\"5\" r=\"4\" fill=\"none\" strokeWidth=\"2\" className=\"stroke-[var(--ds-status-warning)]\" />}\n {status === \"busy\" && (\n <>\n <circle cx=\"5\" cy=\"5\" r=\"5\" className=\"fill-[var(--ds-status-error)]\" />\n <rect x=\"2.5\" y=\"4.1\" width=\"5\" height=\"1.8\" rx=\"0.9\" className=\"fill-[var(--ds-bg-canvas)]\" />\n </>\n )}\n {status === \"offline\" && (\n <>\n <circle cx=\"5\" cy=\"5\" r=\"4\" fill=\"none\" strokeWidth=\"2\" className=\"stroke-[var(--ds-text-muted)]\" />\n <line x1=\"3.1\" y1=\"6.9\" x2=\"6.9\" y2=\"3.1\" strokeWidth=\"1.5\" strokeLinecap=\"round\" className=\"stroke-[var(--ds-text-muted)]\" />\n </>\n )}\n </svg>\n );\n}\n\nexport function Avatar({ initials, src, alt, size = \"md\", status, color = \"auto\", className }: AvatarProps) {\n // A48: presence can't show words beside a small avatar, so the avatar's own name carries it (\"Priya Nair, online\")\n const who = alt ?? initials ?? \"\";\n const named = status ? [who, status].filter(Boolean).join(\", \") : undefined;\n const base = cn(\n \"inline-flex items-center justify-center font-medium shrink-0\",\n \"rounded-[var(--ds-radius-full)]\",\n sizeStyles[size],\n className,\n );\n\n // data-avatar: the Table and DataGrid card mode stacks a cell that holds an avatar (Sprint 25.4 C-2).\n const avatarElement = src ? (\n <img\n data-avatar=\"\"\n src={src}\n alt={named ?? alt ?? initials ?? \"\"}\n className={cn(base, \"object-cover\")}\n />\n ) : (\n <div\n data-avatar=\"\"\n role={named ? \"img\" : undefined}\n aria-label={named}\n className={cn(\n base,\n colorStyles(color, alt ?? initials ?? \"\"),\n \"border-[length:var(--ds-border-width)] border-transparent\",\n )}\n >\n {/* round 21: a full name passed as initials shows its initials, not the name squeezed into the circle */}\n {initials != null ? toInitials(initials) : null}\n </div>\n );\n\n if (!status) return avatarElement;\n\n return (\n <div className=\"relative inline-flex\">\n {avatarElement}\n <span\n className={cn(\n \"absolute\",\n size === \"sm\" && \"bottom-[-4px] right-[-4px]\",\n size === \"md\" && \"bottom-[-2px] right-[-2px]\",\n size === \"lg\" && \"bottom-[0px] right-[0px]\",\n )}\n aria-hidden=\"true\"\n >\n {/* the canvas behind the mark fills a ring's middle and draws the 2px halo that separates it from the photo */}\n <span className=\"block rounded-[var(--ds-radius-full)] bg-[var(--ds-bg-canvas)] shadow-[0_0_0_2px_var(--ds-bg-canvas)]\">\n <PresenceMark status={status} />\n </span>\n </span>\n </div>\n );\n}\n","import { useLayoutEffect, useRef, type RefObject } from \"react\";\n\n/**\n * Keeps the active option of a list driven by aria-activedescendant in view (Sprint 25.17's review; WCAG 2.4.11): when\n * the list opens and when the active option changes, the list scrolls just enough to show it. Only the list's own box\n * scrolls, never the page: the list sits fixed over it. The option is found by its id, in the list's own document or\n * shadow root.\n *\n * Only a change brings it back into view (the second review): a render that leaves the same option active — the page\n * scrolling, the list scrolled by touch or by its scrollbar — leaves the list where the user put it.\n */\nexport function useActiveOptionInView(list: RefObject<HTMLElement | null>, activeId: string | undefined): void {\n // the option last brought into view, by its id and its text: a filtered list reuses an id for another option\n const shown = useRef<string | null>(null);\n useLayoutEffect(() => {\n const box = list.current;\n if (!box || !activeId) {\n shown.current = null;\n return;\n }\n const root = box.getRootNode() as Document | ShadowRoot;\n const option = typeof root.getElementById === \"function\" ? root.getElementById(activeId) : null;\n if (!option || !box.contains(option)) return;\n const seen = `${activeId}\\u0000${option.textContent ?? \"\"}`;\n if (seen === shown.current) return;\n shown.current = seen;\n const top = option.getBoundingClientRect().top - box.getBoundingClientRect().top + box.scrollTop;\n const bottom = top + option.offsetHeight;\n if (top < box.scrollTop) box.scrollTo({ top });\n else if (bottom > box.scrollTop + box.clientHeight) box.scrollTo({ top: bottom - box.clientHeight });\n });\n}\n","/**\n * Text as a search reads it, one way for every picker (Sprint 25.17's second review): no case, no accents, and \"_\" as a\n * space, so \"@toma\" finds Tomás, \"Sao Paulo\" and \"São Paulo\" find America/Sao_Paulo, and \"New_York\" finds New York.\n */\nexport function foldForSearch(text: string): string {\n return text.normalize(\"NFD\").replace(/\\p{M}/gu, \"\").replace(/_/g, \" \").toLowerCase();\n}\n","import { useMemo, useRef, type RefObject } from \"react\";\nimport { createPortal } from \"react-dom\";\nimport { cn } from \"../lib/utils\";\nimport { usePosition } from \"../lib/use-position\";\nimport { useLatest } from \"../lib/use-latest\";\nimport { useActiveOptionInView } from \"../lib/use-active-option\";\nimport { foldForSearch } from \"../lib/search-text\";\nimport { Avatar } from \"./Avatar\";\n\nexport interface MentionSuggestion {\n id: string;\n label: string;\n avatar?: string;\n description?: string;\n}\n\n/** Text as a search reads it: no case, no accents (\"@toma\" finds Tomás, \"@zoe\" Zoë), as every picker reads it. */\nconst fold = foldForSearch;\n\n/**\n * The suggestions a query matches, by name or by description, ignoring case and accents, best first: a name that starts with it\n * (\"@Mar\": Marcus Chen), then a name with a word that does (Tomás Martin), then a name holding it anywhere (Amara), then\n * a description that does; in their own order within each.\n */\nexport function matchMentions(items: MentionSuggestion[], query: string): MentionSuggestion[] {\n const q = fold(query);\n const rank = (s: MentionSuggestion): number => {\n const name = fold(s.label);\n if (name.startsWith(q)) return 0;\n if (name.split(/\\s+/).some((word) => word.startsWith(q))) return 1;\n if (name.includes(q)) return 2;\n return s.description && fold(s.description).includes(q) ? 3 : -1;\n };\n return items.map((s) => ({ s, r: rank(s) })).filter(({ r }) => r >= 0).sort((a, b) => a.r - b.r).map(({ s }) => s);\n}\n\n/** An option's id, for the field's aria-activedescendant. */\nexport const mentionOptionId = (listId: string, index: number) => `${listId}-option-${index}`;\n\n/**\n * The most people a list shows (Sprint 25.17's second review: two thousand drawn at once took a key a third of a second\n * to paint): the best matches, and a line saying how many more match, which typing narrows.\n */\nexport const MENTION_LIST_LIMIT = 10;\n\n/** The matches a list shows, best first, and how many more there are. */\nexport function shownMentions(found: MentionSuggestion[]): { items: MentionSuggestion[]; more: number } {\n return { items: found.slice(0, MENTION_LIST_LIMIT), more: Math.max(0, found.length - MENTION_LIST_LIMIT) };\n}\n\nexport interface MentionListProps {\n /** The listbox's id: the field's aria-controls, and the stem of its options' ids. */\n id: string;\n /** The listbox's name (\"People\", \"Suggestions\"). */\n label: string;\n /** The people shown, at most MENTION_LIST_LIMIT (shownMentions). */\n items: MentionSuggestion[];\n /** How many more match than are shown: the list says so under its people. */\n more?: number;\n activeIndex: number;\n onActiveIndexChange: (index: number) => void;\n onPick: (item: MentionSuggestion) => void;\n /** Where the mention being typed is, in the viewport: the list opens under it, above it when the screen runs out below. */\n anchorRect: () => DOMRect | null;\n /** The list's element, for the owner's checks (a click outside, focus moving into it). */\n listRef?: RefObject<HTMLDivElement | null>;\n}\n\n/**\n * The list of suggestions while a mention is typed (Sprint 25.17 A17): MentionInput's and the editor's, so a mention\n * looks and works the same in a comment box and a document. The field keeps focus and carries aria-activedescendant\n * (the field's owner wires the keys: arrows move, Enter or Tab inserts, Escape closes); the options are no Tab stops,\n * and a pointer picks one without taking focus from the field.\n */\nexport function MentionList({ id, label, items, more = 0, activeIndex, onActiveIndexChange, onPick, anchorRect, listRef }: MentionListProps) {\n const ownRef = useRef<HTMLDivElement | null>(null);\n const floatingRef = listRef ?? ownRef;\n const boxRef = useRef<HTMLDivElement | null>(null);\n const anchorRef = useLatest(anchorRect);\n // the caret as an element for usePosition, which reads only its rectangle\n const anchor = useMemo(() => ({ current: { getBoundingClientRect: () => anchorRef.current() ?? new DOMRect() } as HTMLElement }), [anchorRef]);\n const pos = usePosition(anchor, floatingRef, items.length > 0, { side: \"bottom\", align: \"start\", offset: 4 });\n // the arrows move through a list longer than its box: the active person stays in view\n useActiveOptionInView(boxRef, items.length > 0 ? mentionOptionId(id, activeIndex) : undefined);\n if (items.length === 0) return null;\n\n return createPortal(\n <div\n ref={floatingRef}\n className={cn(\n \"fixed z-[var(--ds-z-popover)] min-w-[200px] flex flex-col\",\n \"bg-[var(--ds-bg-surface-elevated)]\",\n \"[backdrop-filter:var(--ds-glass-filter)] [-webkit-backdrop-filter:var(--ds-glass-filter)]\",\n \"border-[length:var(--ds-border-width)] border-[var(--ds-border-default)]\",\n \"rounded-[var(--ds-radius-md)] shadow-[var(--ds-shadow-dropdown),var(--ds-glass-inset)]\",\n )}\n style={{ top: pos.top, left: pos.left }}\n >\n {/* rows inset in the list, rounded, as Combobox's and the menus' are (the second review: full-width square rows) */}\n <div ref={boxRef} id={id} role=\"listbox\" aria-label={label} className=\"max-h-[200px] overflow-y-auto p-ds-1\">\n {items.map((s, i) => (\n <button\n key={s.id}\n id={mentionOptionId(id, i)}\n type=\"button\"\n role=\"option\"\n tabIndex={-1}\n aria-selected={i === activeIndex}\n className={cn(\n \"flex items-center gap-ds-2.5 w-full px-ds-2 py-ds-1.5 text-left text-sm cursor-pointer rounded-[var(--ds-radius-sm)]\",\n \"transition-colors duration-[var(--ds-transition-speed)]\",\n i === activeIndex ? \"bg-[var(--ds-bg-muted)] text-[var(--ds-text-primary)]\" : \"text-[var(--ds-text-secondary)]\",\n )}\n // the field keeps focus, so the pick lands where the mention is being typed\n onMouseDown={(e) => e.preventDefault()}\n onClick={() => onPick(s)}\n onMouseEnter={() => onActiveIndexChange(i)}\n >\n {/* the name follows as text, so the picture is decoration */}\n <span aria-hidden=\"true\" className=\"inline-flex shrink-0\">\n <Avatar src={s.avatar} initials={s.label} size=\"sm\" className=\"!h-6 !w-6 !text-2xs\" />\n </span>\n <span className=\"min-w-0\">\n <span className=\"block font-medium text-[var(--ds-text-primary)] truncate\">{s.label}</span>\n {s.description && <span className=\"block text-xs text-[var(--ds-text-muted)] truncate\">{s.description}</span>}\n </span>\n </button>\n ))}\n </div>\n {more > 0 && (\n <p className=\"px-ds-3 py-ds-1.5 text-xs text-[var(--ds-text-muted)] border-t-[length:var(--ds-divider-width)] border-[var(--ds-border-subtle)]\">\n {more} more {more === 1 ? \"matches\" : \"match\"}: keep typing to narrow\n </p>\n )}\n </div>,\n document.body,\n );\n}\n"]}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { tabStops } from './chunk-ZI3OQDET.js';
|
|
2
|
+
import { createContext, useContext, useState, useLayoutEffect, useEffect } from 'react';
|
|
2
3
|
|
|
3
|
-
// ../../showcase/src/lib/use-fill-height.ts
|
|
4
4
|
function useFillHeight(ref, enabled = true) {
|
|
5
5
|
const insideFill = useContext(FillContext);
|
|
6
6
|
const active = enabled && !insideFill;
|
|
@@ -50,12 +50,26 @@ var MIN_FILL = 240;
|
|
|
50
50
|
var ROW_MAX = 56;
|
|
51
51
|
function fillHeight(el, main) {
|
|
52
52
|
const page = el.closest("[data-shell-content]") ?? main;
|
|
53
|
-
if (!leadsPage(el, page) || !isLastContent(el, page) || hasOwnHeight(el) || !followsContent(el, main)) return null;
|
|
53
|
+
if (!leadsPage(el, page) || !isLastContent(el, page) || keepScroll(el, () => hasOwnHeight(el) || !followsContent(el, main))) return null;
|
|
54
54
|
const mainBox = main.getBoundingClientRect();
|
|
55
55
|
const top = el.getBoundingClientRect().top - mainBox.top - main.clientTop + main.scrollTop;
|
|
56
56
|
const height = Math.floor(main.clientHeight - top - spaceBelow(el, main));
|
|
57
57
|
return height >= MIN_FILL ? height : null;
|
|
58
58
|
}
|
|
59
|
+
function keepScroll(el, probe) {
|
|
60
|
+
const scrolled = [];
|
|
61
|
+
for (const node of [el, ...Array.from(el.querySelectorAll("*"))]) {
|
|
62
|
+
if (node.scrollTop || node.scrollLeft) scrolled.push([node, node.scrollTop, node.scrollLeft]);
|
|
63
|
+
}
|
|
64
|
+
try {
|
|
65
|
+
return probe();
|
|
66
|
+
} finally {
|
|
67
|
+
for (const [node, top, left] of scrolled) {
|
|
68
|
+
node.scrollTop = top;
|
|
69
|
+
node.scrollLeft = left;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
59
73
|
function takesRoom(box) {
|
|
60
74
|
const style = getComputedStyle(box);
|
|
61
75
|
if (style.display === "none" || style.position === "absolute" || style.position === "fixed") return false;
|
|
@@ -112,7 +126,50 @@ function spaceBelow(el, main) {
|
|
|
112
126
|
}
|
|
113
127
|
return space;
|
|
114
128
|
}
|
|
129
|
+
function useKeyboardScroll(el, label, enabled = true, nameFromHeading = false) {
|
|
130
|
+
useEffect(() => {
|
|
131
|
+
if (!enabled || !el || typeof ResizeObserver === "undefined") return;
|
|
132
|
+
const name = () => {
|
|
133
|
+
const heading = nameFromHeading ? el.querySelector("h1, h2, h3, h4, h5, h6")?.textContent?.trim() : "";
|
|
134
|
+
return heading || label;
|
|
135
|
+
};
|
|
136
|
+
const set = (on) => {
|
|
137
|
+
if (on === el.hasAttribute("data-scroll-stop")) {
|
|
138
|
+
if (on && el.getAttribute("aria-label") !== name()) el.setAttribute("aria-label", name());
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
if (on) {
|
|
142
|
+
el.setAttribute("data-scroll-stop", "");
|
|
143
|
+
el.tabIndex = 0;
|
|
144
|
+
el.setAttribute("role", "region");
|
|
145
|
+
el.setAttribute("aria-label", name());
|
|
146
|
+
} else {
|
|
147
|
+
el.removeAttribute("data-scroll-stop");
|
|
148
|
+
el.removeAttribute("tabindex");
|
|
149
|
+
el.removeAttribute("role");
|
|
150
|
+
el.removeAttribute("aria-label");
|
|
151
|
+
}
|
|
152
|
+
};
|
|
153
|
+
const update = () => {
|
|
154
|
+
const scrolls = el.scrollHeight > el.clientHeight + 1 && tabStops(el).length === 0;
|
|
155
|
+
set(scrolls || el.hasAttribute("data-scroll-stop") && document.activeElement === el);
|
|
156
|
+
};
|
|
157
|
+
update();
|
|
158
|
+
const ro = new ResizeObserver(update);
|
|
159
|
+
ro.observe(el);
|
|
160
|
+
const mo = typeof MutationObserver === "undefined" ? null : new MutationObserver(update);
|
|
161
|
+
mo?.observe(el, { childList: true, subtree: true, characterData: true });
|
|
162
|
+
el.addEventListener("focusout", update);
|
|
163
|
+
return () => {
|
|
164
|
+
ro.disconnect();
|
|
165
|
+
mo?.disconnect();
|
|
166
|
+
el.removeEventListener("focusout", update);
|
|
167
|
+
set(false);
|
|
168
|
+
};
|
|
169
|
+
}, [el, label, enabled, nameFromHeading]);
|
|
170
|
+
}
|
|
171
|
+
var FOCUS_INSET = "outline-none focus-visible:shadow-[inset_0_0_0_2px_var(--ds-border-focus)]";
|
|
115
172
|
|
|
116
|
-
export { FillContext, useFillHeight };
|
|
117
|
-
//# sourceMappingURL=chunk-
|
|
118
|
-
//# sourceMappingURL=chunk-
|
|
173
|
+
export { FOCUS_INSET, FillContext, useFillHeight, useKeyboardScroll };
|
|
174
|
+
//# sourceMappingURL=chunk-SFY2KMHG.js.map
|
|
175
|
+
//# sourceMappingURL=chunk-SFY2KMHG.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../showcase/src/lib/use-fill-height.ts","../../../showcase/src/lib/use-keyboard-scroll.ts"],"names":[],"mappings":"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","file":"chunk-SFY2KMHG.js","sourcesContent":["import { createContext, useContext, useLayoutEffect, useState, type RefObject } from \"react\";\n\n/**\n * Sprint 25.16 A42 (P31): a page led by a chart, a board or a split view fills the height under its header, and its\n * content scrolls inside it; the page doesn't. SplitView (frame=\"page\"), Gantt and KanbanBoard use this one mechanism.\n *\n * The element takes the rest of the shell's <main> height (main's height less what sits above it and the padding below\n * it) when all of these hold:\n * - it leads the page: above it there is only the page's header, its chrome (notices, tabs, a toolbar, a search or\n * filters row, however tall) and short rows, so a board in a dashboard's last card keeps its own height;\n * - it is the page's last content: nothing after it, up to <main>, takes room, so nothing below it is cut off (the\n * shell's footer is not the page's: it sits below the fold, as it does under a SplitView frame=\"fill\");\n * - its height follows its content: neither its own class nor a box around it gives it one (a fixed height, a flex\n * column that already gives it the rest), and given more, the page grows;\n * - the rest is tall enough to use (a phone on its side keeps the natural height, and the page scrolls).\n * Outside the shell's <main> (AppShell marks it), or inside a view that fills (a Gantt in a SplitView's pane), it keeps\n * its own height. Returns the height in pixels, or null for the natural height.\n */\nexport function useFillHeight(ref: RefObject<HTMLElement | null>, enabled = true): number | null {\n const insideFill = useContext(FillContext);\n const active = enabled && !insideFill;\n const [height, setHeight] = useState<number | null>(null);\n useLayoutEffect(() => {\n const main = ref.current?.closest<HTMLElement>(\"main[data-shell-main]\");\n if (!active || !main || typeof ResizeObserver === \"undefined\") {\n setHeight(null);\n return;\n }\n let frame = 0;\n // the element is read on each measure: a view that changes its layout (stacked or side by side) changes its root\n const measure = () => {\n frame = 0;\n const el = ref.current;\n setHeight(el && main.contains(el) ? fillHeight(el, main) : null);\n };\n const schedule = () => {\n if (!frame) frame = requestAnimationFrame(measure);\n };\n measure();\n // main's height (the window, a phone's toolbar), and what sits above or after the element at the page's top level (a\n // header's actions wrapping, a banner or a note arriving): the boxes there now, and any that arrive\n const ro = new ResizeObserver(schedule);\n const watch = (box: Element) => {\n ro.observe(box);\n for (const child of Array.from(box.children)) ro.observe(child);\n };\n const page = main.querySelector<HTMLElement>(\"[data-shell-content]\");\n watch(main);\n if (page) watch(page);\n const mo = typeof MutationObserver === \"undefined\" ? null : new MutationObserver((records) => {\n for (const record of records) record.addedNodes.forEach((node) => { if (node instanceof Element) ro.observe(node); });\n schedule();\n });\n mo?.observe(main, { childList: true });\n if (page) mo?.observe(page, { childList: true });\n return () => {\n ro.disconnect();\n mo?.disconnect();\n if (frame) cancelAnimationFrame(frame);\n };\n }, [ref, active]);\n return active ? height : null;\n}\n\n/** Content inside a view that fills: it takes the view's height (h-full), never the page's. */\nexport const FillContext = createContext(false);\n\n/** Below this the rest of the screen is too short to scroll inside (about six rows of a chart). */\nconst MIN_FILL = 240;\n/** A row that may sit between the header and the view: tabs, a toolbar, a filters row (PageContainer's ROW_MAX). */\nconst ROW_MAX = 56;\n\nfunction fillHeight(el: HTMLElement, main: HTMLElement): number | null {\n // the page's own content: the shell's wrapper above its footer, else main\n const page = el.closest<HTMLElement>(\"[data-shell-content]\") ?? main;\n if (!leadsPage(el, page) || !isLastContent(el, page) || keepScroll(el, () => hasOwnHeight(el) || !followsContent(el, main))) return null;\n const mainBox = main.getBoundingClientRect();\n const top = el.getBoundingClientRect().top - mainBox.top - main.clientTop + main.scrollTop;\n const height = Math.floor(main.clientHeight - top - spaceBelow(el, main));\n return height >= MIN_FILL ? height : null;\n}\n\n/**\n * Runs a probe that changes the element's height for a moment, and puts back where everything inside it was scrolled:\n * at its natural height a region that scrolls inside it stops scrolling, and the browser drops its position (Sprint\n * 25.18: the week's hours opened at the working day, then the next measure sent them back to midnight).\n */\nfunction keepScroll<T>(el: HTMLElement, probe: () => T): T {\n const scrolled: Array<[HTMLElement, number, number]> = [];\n for (const node of [el, ...Array.from(el.querySelectorAll<HTMLElement>(\"*\"))]) {\n if (node.scrollTop || node.scrollLeft) scrolled.push([node, node.scrollTop, node.scrollLeft]);\n }\n try {\n return probe();\n } finally {\n for (const [node, top, left] of scrolled) {\n node.scrollTop = top;\n node.scrollLeft = left;\n }\n }\n}\n\n/** Whether a box takes room on the page: an overlay, a portal's anchor, a hidden or an empty node doesn't. */\nfunction takesRoom(box: Element): boolean {\n const style = getComputedStyle(box);\n if (style.display === \"none\" || style.position === \"absolute\" || style.position === \"fixed\") return false;\n const rect = box.getBoundingClientRect();\n return rect.height > 0 && rect.width > 0;\n}\n\n/**\n * The page's chrome, at any height: a notice (an Alert, a banner that wraps to two lines on a phone), tabs, a toolbar, a\n * search or filters row (the review of 1 October: a two-line banner turned the fill off). Content of the page's own (a\n * row of stats, cards, text) is not chrome.\n */\nconst CHROME = '[role=\"alert\"], [role=\"status\"], [role=\"tablist\"], [role=\"toolbar\"], [role=\"search\"]';\nconst isChrome = (box: Element) =>\n box.matches(CHROME) || (box.children.length > 0 && Array.from(box.children).every((child) => !takesRoom(child) || child.matches(CHROME)));\n\n/** Above the element, up to the page's root: only the page's header (its title), its chrome, and rows no taller than ROW_MAX. */\nfunction leadsPage(el: HTMLElement, page: HTMLElement): boolean {\n for (let node: HTMLElement | null = el; node && node !== page; node = node.parentElement) {\n for (let prev = node.previousElementSibling; prev; prev = prev.previousElementSibling) {\n if (!takesRoom(prev)) continue;\n if (prev.matches('[data-gs=\"page-header\"]') || prev.querySelector('[data-gs=\"page-header\"], h1')) continue;\n if (isChrome(prev)) continue;\n if (prev.getBoundingClientRect().height > ROW_MAX) return false;\n }\n }\n return true;\n}\n\n/** Nothing after the element, up to the page's root, takes room. */\nfunction isLastContent(el: HTMLElement, page: HTMLElement): boolean {\n for (let node: HTMLElement | null = el; node && node !== page; node = node.parentElement) {\n for (let next = node.nextElementSibling; next; next = next.nextElementSibling) {\n if (takesRoom(next)) return false;\n }\n }\n return true;\n}\n\n/**\n * The element's own CSS gives it a height that isn't its content's: a fixed height of its own (`h-[420px]`), or `h-full`\n * that resolves against a box which already has one. It keeps it. Read with the fill's own height set aside.\n */\nfunction hasOwnHeight(el: HTMLElement): boolean {\n const inline = el.style.height;\n el.style.height = \"\";\n const own = el.offsetHeight;\n el.style.height = \"auto\";\n const natural = el.offsetHeight;\n el.style.height = inline;\n return Math.abs(own - natural) > 1;\n}\n\n/**\n * Given more height, does the page grow? Probed once: the element is made a screen taller and main's top-level box\n * measured. A box of fixed height around it, or a flex column that already gives it its height, absorbs it.\n */\nfunction followsContent(el: HTMLElement, main: HTMLElement): boolean {\n let top: HTMLElement = el;\n while (top.parentElement && top.parentElement !== main) top = top.parentElement;\n const before = top.offsetHeight;\n const inline = el.style.height;\n el.style.height = `${el.offsetHeight + main.clientHeight}px`;\n const grows = top.offsetHeight > before;\n el.style.height = inline;\n return grows;\n}\n\n/** The padding, borders and margins under the element, up to main's own padding: they stay below it. */\nfunction spaceBelow(el: HTMLElement, main: HTMLElement): number {\n const px = (value: string) => parseFloat(value) || 0;\n let space = px(getComputedStyle(main).paddingBottom);\n for (let node: HTMLElement | null = el; node && node !== main; node = node.parentElement) {\n const style = getComputedStyle(node);\n space += px(style.marginBottom);\n if (node !== el) space += px(style.paddingBottom) + px(style.borderBottomWidth);\n }\n return space;\n}\n","import { useEffect } from \"react\";\nimport { tabStops } from \"./focus\";\n\n/**\n * WCAG 2.1.1: a region that scrolls, with nothing inside it to Tab to, is a Tab stop of its own, named, so a keyboard\n * can scroll it (Chrome makes such a scroller focusable by itself; Safari doesn't). Sprint 25.16 A42: what fills the\n * page scrolls inside itself, so a board's column or a split view's pane can be such a region. It stops being one when\n * it no longer scrolls or something inside it takes focus — but while it has focus it stays one, until focus leaves it,\n * so a resize that makes it fit never drops the focus to the page (as Table's scroll region does; the review of\n * 1 October). Give the element a visible focus style (FOCUS_INSET). Pass the element itself (a callback ref's), so a\n * pane drawn later (a detail that opens) is read when it appears. With `nameFromHeading`, the region takes its first\n * heading's words as its name (an item's view: its title), and `label` only when it has none.\n */\nexport function useKeyboardScroll(el: HTMLElement | null, label: string, enabled = true, nameFromHeading = false): void {\n useEffect(() => {\n if (!enabled || !el || typeof ResizeObserver === \"undefined\") return;\n const name = () => {\n const heading = nameFromHeading ? el.querySelector(\"h1, h2, h3, h4, h5, h6\")?.textContent?.trim() : \"\";\n return heading || label;\n };\n const set = (on: boolean) => {\n if (on === el.hasAttribute(\"data-scroll-stop\")) {\n if (on && el.getAttribute(\"aria-label\") !== name()) el.setAttribute(\"aria-label\", name());\n return;\n }\n if (on) {\n el.setAttribute(\"data-scroll-stop\", \"\");\n el.tabIndex = 0;\n el.setAttribute(\"role\", \"region\");\n el.setAttribute(\"aria-label\", name());\n } else {\n el.removeAttribute(\"data-scroll-stop\");\n el.removeAttribute(\"tabindex\");\n el.removeAttribute(\"role\");\n el.removeAttribute(\"aria-label\");\n }\n };\n const update = () => {\n const scrolls = el.scrollHeight > el.clientHeight + 1 && tabStops(el).length === 0;\n set(scrolls || (el.hasAttribute(\"data-scroll-stop\") && document.activeElement === el));\n };\n update();\n const ro = new ResizeObserver(update);\n ro.observe(el);\n const mo = typeof MutationObserver === \"undefined\" ? null : new MutationObserver(update);\n mo?.observe(el, { childList: true, subtree: true, characterData: true });\n // focus leaving a stop that no longer scrolls ends it\n el.addEventListener(\"focusout\", update);\n return () => {\n ro.disconnect();\n mo?.disconnect();\n el.removeEventListener(\"focusout\", update);\n set(false);\n };\n }, [el, label, enabled, nameFromHeading]);\n}\n\n/** The focus style for such a region: the theme's ring, drawn inside its edge so a neighbour can't hide it. */\nexport const FOCUS_INSET = \"outline-none focus-visible:shadow-[inset_0_0_0_2px_var(--ds-border-focus)]\";\n"]}
|
|
@@ -37,9 +37,39 @@ function Skeleton({ variant = "text", lines = 3, width, height, className }) {
|
|
|
37
37
|
return /* @__PURE__ */ jsx("div", { "data-gs": "skeleton", className: cn("space-y-ds-3", className), children: Array.from({ length: lines }, (_, i) => /* @__PURE__ */ jsx(Bone, { className: cn("h-4", widths[i % widths.length]) }, i)) });
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
+
// ../../showcase/src/lib/format-locale.ts
|
|
41
|
+
var last = null;
|
|
42
|
+
function formatLocale() {
|
|
43
|
+
if (typeof document === "undefined") return void 0;
|
|
44
|
+
const page = document.documentElement.lang?.trim();
|
|
45
|
+
if (!page) return void 0;
|
|
46
|
+
const spoken = typeof navigator === "undefined" ? [] : navigator.languages?.length ? navigator.languages : [navigator.language];
|
|
47
|
+
const key = `${page}|${spoken.join(",")}`;
|
|
48
|
+
if (last?.key !== key) last = { key, locale: choose(page, spoken) };
|
|
49
|
+
return last.locale;
|
|
50
|
+
}
|
|
51
|
+
function writtenIn(tag) {
|
|
52
|
+
try {
|
|
53
|
+
const locale = new Intl.Locale(tag).maximize();
|
|
54
|
+
return `${locale.language}-${locale.script ?? ""}`;
|
|
55
|
+
} catch {
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
function choose(page, spoken) {
|
|
60
|
+
const language = writtenIn(page);
|
|
61
|
+
if (language === null) return void 0;
|
|
62
|
+
const own = spoken.find((tag) => typeof tag === "string" && writtenIn(tag) === language);
|
|
63
|
+
try {
|
|
64
|
+
return Intl.getCanonicalLocales(own ?? page)[0];
|
|
65
|
+
} catch {
|
|
66
|
+
return void 0;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
40
70
|
// ../../showcase/src/lib/title-text.ts
|
|
41
71
|
var TITLE_TEXT = "font-[family-name:var(--ds-font-display)] text-lg font-[var(--ds-weight-heading)] text-[var(--ds-text-primary)] break-words";
|
|
42
72
|
|
|
43
|
-
export { Skeleton, TITLE_TEXT };
|
|
44
|
-
//# sourceMappingURL=chunk-
|
|
45
|
-
//# sourceMappingURL=chunk-
|
|
73
|
+
export { Skeleton, TITLE_TEXT, formatLocale };
|
|
74
|
+
//# sourceMappingURL=chunk-X2HEV7FB.js.map
|
|
75
|
+
//# sourceMappingURL=chunk-X2HEV7FB.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../showcase/src/components/Skeleton.tsx","../../../showcase/src/lib/format-locale.ts","../../../showcase/src/lib/title-text.ts"],"names":[],"mappings":"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","file":"chunk-X2HEV7FB.js","sourcesContent":["import { cn } from \"../lib/utils\";\n\ninterface SkeletonProps {\n variant?: \"text\" | \"avatar\" | \"card\" | \"table-row\" | \"custom\";\n lines?: number;\n width?: string;\n height?: string;\n className?: string;\n}\n\nfunction Bone({ className, style, \"data-gs\": marker }: { className?: string; style?: React.CSSProperties; \"data-gs\"?: string }) {\n return (\n <div\n data-gs={marker}\n className={cn(\"rounded-[var(--ds-radius-sm)] bg-[var(--ds-bg-emphasis)] animate-pulse\", className)}\n style={style}\n />\n );\n}\n\nexport function Skeleton({ variant = \"text\", lines = 3, width, height, className }: SkeletonProps) {\n if (variant === \"custom\") {\n return <Bone data-gs=\"skeleton\" className={className} style={{ width, height }} />;\n }\n\n if (variant === \"avatar\") {\n return <Bone data-gs=\"skeleton\" className={cn(\"h-10 w-10 rounded-[var(--ds-radius-full)]\", className)} />;\n }\n\n if (variant === \"card\") {\n return (\n <div data-gs=\"skeleton\" className={cn(\"space-y-ds-3\", className)}>\n <Bone className=\"h-32 w-full rounded-[var(--ds-radius-md)]\" />\n <Bone className=\"h-4 w-3/4\" />\n <Bone className=\"h-4 w-1/2\" />\n </div>\n );\n }\n\n if (variant === \"table-row\") {\n return (\n <div data-gs=\"skeleton\" className={cn(\"flex gap-ds-4\", className)}>\n <Bone className=\"h-4 flex-[3]\" />\n <Bone className=\"h-4 flex-[2]\" />\n <Bone className=\"h-4 flex-1\" />\n <Bone className=\"h-4 flex-1\" />\n </div>\n );\n }\n\n // text variant\n const widths = [\"w-3/4\", \"w-1/2\", \"w-5/6\", \"w-2/3\", \"w-4/5\"];\n return (\n <div data-gs=\"skeleton\" className={cn(\"space-y-ds-3\", className)}>\n {Array.from({ length: lines }, (_, i) => (\n <Bone key={i} className={cn(\"h-4\", widths[i % widths.length])} />\n ))}\n </div>\n );\n}\n","/** The last page language and visitor's languages read, and the locale they gave: formatting runs often, they change rarely. */\nlet last: { key: string; locale: string | undefined } | null = null;\n\n/**\n * The locale the library writes dates, times and numbers in: the app's language, the page's `lang`, in the visitor's own\n * formats where they speak it (Sprint 25.17, the owner's decision of 2 October). An English page shows \"5 minutes ago\"\n * and \"250K\" to a visitor whose browser is Japanese, where it showed \"5 分前\" and \"25万\" in a page of English (WCAG 3.1.2:\n * text in the page's language needs no `lang` of its own), and a British visitor to it keeps \"1 Oct 2026\". The visitor's\n * form counts only in the page's script: a page in traditional Chinese stays traditional for a visitor who reads\n * simplified. A page with no `lang` is written in the browser's language, as before; so is anything drawn on a server,\n * where there is no page yet.\n */\nexport function formatLocale(): string | undefined {\n if (typeof document === \"undefined\") return undefined;\n const page = document.documentElement.lang?.trim();\n if (!page) return undefined;\n const spoken = typeof navigator === \"undefined\" ? [] : navigator.languages?.length ? navigator.languages : [navigator.language];\n const key = `${page}|${spoken.join(\",\")}`;\n if (last?.key !== key) last = { key, locale: choose(page, spoken) };\n return last.locale;\n}\n\n/** The language and script a tag is written in: \"en\" and \"en-GB\" are both \"en-Latn\"; \"zh-TW\" is \"zh-Hant\", \"zh-CN\" \"zh-Hans\". */\nfunction writtenIn(tag: string): string | null {\n try {\n const locale = new Intl.Locale(tag).maximize();\n return `${locale.language}-${locale.script ?? \"\"}`;\n } catch {\n return null;\n }\n}\n\nfunction choose(page: string, spoken: readonly string[]): string | undefined {\n const language = writtenIn(page);\n // a tag Intl can't read: the browser's language\n if (language === null) return undefined;\n // the visitor's own form of the page's language (\"en-GB\" for an \"en\" page), wherever it is in their list; else the page's\n const own = spoken.find((tag) => typeof tag === \"string\" && writtenIn(tag) === language);\n try {\n return Intl.getCanonicalLocales(own ?? page)[0];\n } catch {\n return undefined;\n }\n}\n","/**\n * A title's text classes: CardTitle's, which a titled Table's band and a ChartContainer's title share (Sprint 25.9, A1),\n * one definition so they cannot drift. A word or a name longer than its line breaks at the line's edge (round 27,\n * R27-16: run 70's \"primary-logo-navy.svg\" ran out of its card); break-word, not anywhere, so a title's words still\n * set its minimum and the actions beside it wrap under it before a word is broken (HEADER_LAYOUT.floor).\n */\nexport const TITLE_TEXT = \"font-[family-name:var(--ds-font-display)] text-lg font-[var(--ds-weight-heading)] text-[var(--ds-text-primary)] break-words\";\n"]}
|