gridsmith-ui 0.30.2 → 0.31.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +71 -1
- package/dist/charts.d.ts +114 -1
- package/dist/charts.js +1113 -206
- package/dist/charts.js.map +1 -1
- package/dist/chat.js +20 -12
- package/dist/chat.js.map +1 -1
- package/dist/{chunk-HVGHGBKV.js → chunk-2TCCSKL3.js} +6 -7
- package/dist/chunk-2TCCSKL3.js.map +1 -0
- package/dist/{chunk-L6HWACK7.js → chunk-53F7GVBQ.js} +6 -3
- package/dist/chunk-53F7GVBQ.js.map +1 -0
- package/dist/chunk-BHTAVJIG.js +285 -0
- package/dist/chunk-BHTAVJIG.js.map +1 -0
- package/dist/{chunk-WWN6CQKI.js → chunk-DR6WEI3A.js} +60 -7
- package/dist/chunk-DR6WEI3A.js.map +1 -0
- package/dist/{chunk-4HJDOZ2M.js → chunk-EZ3ZGFGA.js} +203 -34
- package/dist/chunk-EZ3ZGFGA.js.map +1 -0
- package/dist/{chunk-MIUSSYVE.js → chunk-HQFTP7Y5.js} +27 -52
- package/dist/chunk-HQFTP7Y5.js.map +1 -0
- package/dist/{chunk-SIWERRP2.js → chunk-I6JJ7KKP.js} +14 -15
- package/dist/chunk-I6JJ7KKP.js.map +1 -0
- package/dist/chunk-IOHGE4EW.js +755 -0
- package/dist/chunk-IOHGE4EW.js.map +1 -0
- package/dist/{chunk-USHW523V.js → chunk-IZW5XHKA.js} +73 -699
- package/dist/chunk-IZW5XHKA.js.map +1 -0
- package/dist/{chunk-ZASGFL5D.js → chunk-K3NAFQ4S.js} +3 -4
- package/dist/chunk-K3NAFQ4S.js.map +1 -0
- package/dist/{chunk-ACLKDE5W.js → chunk-KSJTMSYA.js} +23 -11
- package/dist/chunk-KSJTMSYA.js.map +1 -0
- package/dist/{chunk-SQHKC4HA.js → chunk-NDL2JGQ7.js} +8 -7
- package/dist/chunk-NDL2JGQ7.js.map +1 -0
- package/dist/{chunk-TYNDXMBV.js → chunk-QOMP4D6V.js} +3 -3
- package/dist/{chunk-TYNDXMBV.js.map → chunk-QOMP4D6V.js.map} +1 -1
- package/dist/{chunk-BZRZENBS.js → chunk-SLGGZ3MU.js} +6 -6
- package/dist/{chunk-BZRZENBS.js.map → chunk-SLGGZ3MU.js.map} +1 -1
- package/dist/{chunk-K6IDOCGE.js → chunk-TH272DIC.js} +10 -10
- package/dist/{chunk-K6IDOCGE.js.map → chunk-TH272DIC.js.map} +1 -1
- package/dist/chunk-V73LHNRL.js +51 -0
- package/dist/chunk-V73LHNRL.js.map +1 -0
- package/dist/{chunk-ZCDA74RQ.js → chunk-W3GY5ROH.js} +3 -3
- package/dist/{chunk-ZCDA74RQ.js.map → chunk-W3GY5ROH.js.map} +1 -1
- package/dist/data-grid.js +11 -12
- package/dist/data-grid.js.map +1 -1
- package/dist/editor-mentions.js +2 -3
- package/dist/editor-mentions.js.map +1 -1
- package/dist/editor.js +4 -6
- package/dist/editor.js.map +1 -1
- package/dist/icons.js +1 -1
- package/dist/index.d.ts +100 -16
- package/dist/index.js +1177 -660
- package/dist/index.js.map +1 -1
- package/dist/kanban.js +10 -11
- package/dist/kanban.js.map +1 -1
- package/dist/resizable.js +10 -11
- package/dist/resizable.js.map +1 -1
- package/dist/virtualized.js +11 -12
- package/dist/virtualized.js.map +1 -1
- package/package.json +1 -1
- package/dist/chunk-4HJDOZ2M.js.map +0 -1
- package/dist/chunk-6C2IUMY7.js +0 -46
- package/dist/chunk-6C2IUMY7.js.map +0 -1
- package/dist/chunk-7Q46JXSK.js +0 -51
- package/dist/chunk-7Q46JXSK.js.map +0 -1
- package/dist/chunk-ACLKDE5W.js.map +0 -1
- package/dist/chunk-HVGHGBKV.js.map +0 -1
- package/dist/chunk-L6HWACK7.js.map +0 -1
- package/dist/chunk-MIUSSYVE.js.map +0 -1
- package/dist/chunk-NKUCS67T.js +0 -13
- package/dist/chunk-NKUCS67T.js.map +0 -1
- package/dist/chunk-SIWERRP2.js.map +0 -1
- package/dist/chunk-SQHKC4HA.js.map +0 -1
- package/dist/chunk-USHW523V.js.map +0 -1
- package/dist/chunk-WWN6CQKI.js.map +0 -1
- package/dist/chunk-YL5KD5RC.js +0 -49
- package/dist/chunk-YL5KD5RC.js.map +0 -1
- package/dist/chunk-ZASGFL5D.js.map +0 -1
package/dist/chat.js
CHANGED
|
@@ -1,13 +1,11 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import { Divider, CodeBlock, Link } from './chunk-
|
|
3
|
-
import './chunk-
|
|
4
|
-
import { useKeyboardScroll } from './chunk-
|
|
5
|
-
import './chunk-
|
|
6
|
-
import './chunk-
|
|
7
|
-
import './chunk-
|
|
8
|
-
import './chunk-
|
|
9
|
-
import './chunk-4HJDOZ2M.js';
|
|
10
|
-
import { cn, FOCUS_INSET } from './chunk-YL5KD5RC.js';
|
|
2
|
+
import { Divider, CodeBlock, Link } from './chunk-NDL2JGQ7.js';
|
|
3
|
+
import './chunk-SLGGZ3MU.js';
|
|
4
|
+
import { useKeyboardScroll } from './chunk-KSJTMSYA.js';
|
|
5
|
+
import './chunk-W3GY5ROH.js';
|
|
6
|
+
import './chunk-DR6WEI3A.js';
|
|
7
|
+
import './chunk-53F7GVBQ.js';
|
|
8
|
+
import { cn, FOCUS_INSET } from './chunk-EZ3ZGFGA.js';
|
|
11
9
|
import { memo, useState, Children, isValidElement } from 'react';
|
|
12
10
|
import Markdown from 'react-markdown';
|
|
13
11
|
import remarkGfm from 'remark-gfm';
|
|
@@ -22,9 +20,19 @@ function codeFromPre(children) {
|
|
|
22
20
|
const code = Children.toArray(raw).map((c) => typeof c === "string" || typeof c === "number" ? String(c) : "").join("").replace(/\n$/, "");
|
|
23
21
|
return { code, language };
|
|
24
22
|
}
|
|
25
|
-
|
|
23
|
+
var words = (n) => n.type === "text" ? n.value ?? "" : (n.children ?? []).map(words).join("");
|
|
24
|
+
var childrenTagged = (n, tag) => (n?.children ?? []).filter((c) => c.type === "element" && c.tagName === tag);
|
|
25
|
+
function tableName(node) {
|
|
26
|
+
const table = node;
|
|
27
|
+
const head = childrenTagged(childrenTagged(table, "thead")[0], "tr")[0];
|
|
28
|
+
const headings = childrenTagged(head, "th").map((th) => words(th).trim()).filter(Boolean);
|
|
29
|
+
if (!headings.length) return "Table";
|
|
30
|
+
const more = headings.length > 2 ? ` and ${headings.length - 2} more` : "";
|
|
31
|
+
return `Table: ${headings.slice(0, 2).join(", ")}${more}`;
|
|
32
|
+
}
|
|
33
|
+
function TableFrame({ node, ...props }) {
|
|
26
34
|
const [frame, setFrame] = useState(null);
|
|
27
|
-
useKeyboardScroll(frame,
|
|
35
|
+
useKeyboardScroll(frame, tableName(node), true, false, "x");
|
|
28
36
|
return /* @__PURE__ */ jsx("div", { ref: setFrame, className: cn("overflow-x-auto rounded-[var(--ds-radius-md)] border-[length:var(--ds-border-width)] border-[var(--ds-border-default)]", FOCUS_INSET), children: /* @__PURE__ */ jsx("table", { className: "w-full border-collapse text-sm", ...props }) });
|
|
29
37
|
}
|
|
30
38
|
var components = {
|
|
@@ -73,7 +81,7 @@ var components = {
|
|
|
73
81
|
hr: () => /* @__PURE__ */ jsx(Divider, {}),
|
|
74
82
|
strong: ({ node: _n, ...props }) => /* @__PURE__ */ jsx("strong", { className: "font-semibold", ...props }),
|
|
75
83
|
// Wide content scrolls inside its own container; the page never scrolls sideways.
|
|
76
|
-
table: ({ node
|
|
84
|
+
table: ({ node, ...props }) => /* @__PURE__ */ jsx(TableFrame, { node, ...props }),
|
|
77
85
|
thead: ({ node: _n, ...props }) => /* @__PURE__ */ jsx("thead", { className: "bg-[var(--ds-bg-muted)]", ...props }),
|
|
78
86
|
th: ({ node: _n, ...props }) => /* @__PURE__ */ jsx("th", { className: "px-ds-3 py-ds-2 text-left text-xs font-semibold uppercase tracking-wide text-[var(--ds-text-secondary)]", ...props }),
|
|
79
87
|
td: ({ node: _n, ...props }) => /* @__PURE__ */ jsx("td", { className: "border-t-[length:var(--ds-border-width)] border-[var(--ds-border-subtle)] px-ds-3 py-ds-2 align-top", ...props }),
|
package/dist/chat.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../showcase/src/components/MarkdownContent.tsx"],"names":[],"mappings":"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","file":"chat.js","sourcesContent":["import { memo, Children, isValidElement, useState, type ComponentProps, type ReactElement, type ReactNode } from \"react\";\nimport Markdown, { type Components } from \"react-markdown\";\nimport remarkGfm from \"remark-gfm\";\nimport { cn, FOCUS_INSET } from \"../lib/utils\";\nimport { useKeyboardScroll } from \"../lib/use-keyboard-scroll\";\nimport { CodeBlock } from \"./CodeBlock\";\nimport { Link } from \"./Link\";\nimport { Divider } from \"./Divider\";\n\nexport interface MarkdownContentProps {\n /** The markdown source — a model's reply, an API's description. Raw HTML in it is never rendered. */\n children: string;\n /** Show a caret at the end of the last block while the source is still arriving. Pass a message's status === \"streaming\" from ChatMessageList's renderContent. */\n streaming?: boolean;\n className?: string;\n}\n\nconst isExternal = (href: string) => /^(https?:)?\\/\\//i.test(href);\n\n/** Pull the code string and language out of the single <code> child react-markdown gives a <pre>. */\nfunction codeFromPre(children: ReactNode): { code: string; language?: string } {\n const only = Children.toArray(children).find(isValidElement) as ReactElement<{ className?: string; children?: ReactNode }> | undefined;\n const cls = only?.props.className ?? \"\";\n const language = /language-([\\w+-]+)/.exec(cls)?.[1];\n const raw = only ? only.props.children : children;\n const code = Children.toArray(raw)\n .map((c) => (typeof c === \"string\" || typeof c === \"number\" ? String(c) : \"\"))\n .join(\"\")\n .replace(/\\n$/, \"\");\n return { code, language };\n}\n\n/**\n * A table in its own frame, which scrolls sideways when the table is wider; with nothing in it to Tab to, the frame is\n * a Tab stop while it scrolls, so a keyboard can scroll it too (Sprint 25.30 item 2).\n */\nfunction TableFrame(props: ComponentProps<\"table\">) {\n const [frame, setFrame] = useState<HTMLDivElement | null>(null);\n useKeyboardScroll(frame, \"Table\", true, false, \"x\");\n return (\n <div ref={setFrame} className={cn(\"overflow-x-auto rounded-[var(--ds-radius-md)] border-[length:var(--ds-border-width)] border-[var(--ds-border-default)]\", FOCUS_INSET)}>\n <table className=\"w-full border-collapse text-sm\" {...props} />\n </div>\n );\n}\n\ntype P<T extends keyof React.JSX.IntrinsicElements> = ComponentProps<T> & { node?: unknown };\n\nconst components: Components = {\n p: ({ node: _n, ...props }: P<\"p\">) => <p {...props} />,\n a: ({ node: _n, href = \"#\", children, ...props }: P<\"a\">) => (\n <Link\n href={href}\n underline=\"always\"\n {...(isExternal(href) ? { target: \"_blank\", rel: \"noopener noreferrer\" } : {})}\n {...(props as Record<string, unknown>)}\n >\n {children}\n </Link>\n ),\n pre: ({ node: _n, children }: P<\"pre\">) => {\n const { code, language } = codeFromPre(children);\n return <CodeBlock code={code} language={language} />;\n },\n code: ({ node: _n, className, children, ...props }: P<\"code\">) => (\n <code\n className={cn(\n \"rounded-[var(--ds-radius-sm)] bg-[var(--ds-bg-muted)] px-ds-1 py-[1px] font-[family-name:var(--ds-font-mono)] text-[0.9em]\",\n className,\n )}\n {...props}\n >\n {children}\n </code>\n ),\n ul: ({ node: _n, ...props }: P<\"ul\">) => <ul className=\"list-disc space-y-ds-1 pl-ds-5\" {...props} />,\n ol: ({ node: _n, ...props }: P<\"ol\">) => <ol className=\"list-decimal space-y-ds-1 pl-ds-5\" {...props} />,\n li: ({ node: _n, ...props }: P<\"li\">) => <li {...props} />,\n h1: ({ node: _n, ...props }: P<\"h1\">) => <h3 className=\"text-base font-semibold text-[var(--ds-text-primary)]\" {...props} />,\n h2: ({ node: _n, ...props }: P<\"h2\">) => <h3 className=\"text-base font-semibold text-[var(--ds-text-primary)]\" {...props} />,\n h3: ({ node: _n, ...props }: P<\"h3\">) => <h4 className=\"text-sm font-semibold text-[var(--ds-text-primary)]\" {...props} />,\n h4: ({ node: _n, ...props }: P<\"h4\">) => <h4 className=\"text-sm font-semibold text-[var(--ds-text-primary)]\" {...props} />,\n h5: ({ node: _n, ...props }: P<\"h5\">) => <h5 className=\"text-sm font-medium text-[var(--ds-text-primary)]\" {...props} />,\n h6: ({ node: _n, ...props }: P<\"h6\">) => <h6 className=\"text-sm font-medium text-[var(--ds-text-secondary)]\" {...props} />,\n blockquote: ({ node: _n, ...props }: P<\"blockquote\">) => (\n <blockquote\n className=\"border-l-[length:calc(var(--ds-border-width)*2)] border-[var(--ds-border-default)] pl-ds-3 text-[var(--ds-text-secondary)]\"\n {...props}\n />\n ),\n hr: () => <Divider />,\n strong: ({ node: _n, ...props }: P<\"strong\">) => <strong className=\"font-semibold\" {...props} />,\n // Wide content scrolls inside its own container; the page never scrolls sideways.\n table: ({ node: _n, ...props }: P<\"table\">) => <TableFrame {...props} />,\n thead: ({ node: _n, ...props }: P<\"thead\">) => <thead className=\"bg-[var(--ds-bg-muted)]\" {...props} />,\n th: ({ node: _n, ...props }: P<\"th\">) => (\n <th className=\"px-ds-3 py-ds-2 text-left text-xs font-semibold uppercase tracking-wide text-[var(--ds-text-secondary)]\" {...props} />\n ),\n td: ({ node: _n, ...props }: P<\"td\">) => (\n <td className=\"border-t-[length:var(--ds-border-width)] border-[var(--ds-border-subtle)] px-ds-3 py-ds-2 align-top\" {...props} />\n ),\n img: ({ node: _n, alt = \"\", ...props }: P<\"img\">) => <img alt={alt} className=\"max-w-full rounded-[var(--ds-radius-md)]\" {...props} />,\n};\n\n/**\n * Renders markdown (GitHub flavour: tables, task lists, strikethrough) with\n * Gridsmith primitives: fenced code becomes CodeBlock, links become Link,\n * a rule becomes Divider, everything else is token-styled markup. Raw HTML is\n * not rendered — the source is untrusted by default. Memoised: an unchanged\n * message never re-parses; a streaming message re-parses only its own string.\n *\n * Lives on the `gridsmith-ui/chat` entry point (react-markdown and remark-gfm\n * are optional peers), so apps without a chat never load a markdown parser.\n */\n// The caret is a pseudo-element on the last block, so it sits at the end of the\n// last line of text instead of on a line of its own below a block element.\nconst STREAMING_CARET = cn(\n \"[&>:last-child]:after:content-['▍'] [&>:last-child]:after:ml-ds-0.5\",\n \"[&>:last-child]:after:text-[var(--ds-accent-ink)] [&>:last-child]:after:animate-pulse\",\n);\n\nconst Memoised = memo(function MarkdownContentInner({ children, streaming, className }: MarkdownContentProps) {\n return (\n <div\n className={cn(\"space-y-ds-4 text-sm leading-relaxed text-[var(--ds-text-primary)]\", streaming && STREAMING_CARET, className)}\n data-markdown=\"\"\n data-streaming={streaming || undefined}\n >\n <Markdown remarkPlugins={[remarkGfm]} components={components} skipHtml>\n {children}\n </Markdown>\n </div>\n );\n});\n\n// A plain function export so the registry extractor (which reads `export function`)\n// documents it; the memo above still skips re-parsing unchanged sources.\nexport function MarkdownContent(props: MarkdownContentProps) {\n return <Memoised {...props} />;\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../../../showcase/src/components/MarkdownContent.tsx"],"names":[],"mappings":"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","file":"chat.js","sourcesContent":["import { memo, Children, isValidElement, useState, type ComponentProps, type ReactElement, type ReactNode } from \"react\";\nimport Markdown, { type Components } from \"react-markdown\";\nimport remarkGfm from \"remark-gfm\";\nimport { cn, FOCUS_INSET } from \"../lib/utils\";\nimport { useKeyboardScroll } from \"../lib/use-keyboard-scroll\";\nimport { CodeBlock } from \"./CodeBlock\";\nimport { Link } from \"./Link\";\nimport { Divider } from \"./Divider\";\n\nexport interface MarkdownContentProps {\n /** The markdown source — a model's reply, an API's description. Raw HTML in it is never rendered. */\n children: string;\n /** Show a caret at the end of the last block while the source is still arriving. Pass a message's status === \"streaming\" from ChatMessageList's renderContent. */\n streaming?: boolean;\n className?: string;\n}\n\nconst isExternal = (href: string) => /^(https?:)?\\/\\//i.test(href);\n\n/** Pull the code string and language out of the single <code> child react-markdown gives a <pre>. */\nfunction codeFromPre(children: ReactNode): { code: string; language?: string } {\n const only = Children.toArray(children).find(isValidElement) as ReactElement<{ className?: string; children?: ReactNode }> | undefined;\n const cls = only?.props.className ?? \"\";\n const language = /language-([\\w+-]+)/.exec(cls)?.[1];\n const raw = only ? only.props.children : children;\n const code = Children.toArray(raw)\n .map((c) => (typeof c === \"string\" || typeof c === \"number\" ? String(c) : \"\"))\n .join(\"\")\n .replace(/\\n$/, \"\");\n return { code, language };\n}\n\n/** The parts of a parsed node (hast) a table's name is read from. */\ninterface ParsedNode { type: string; tagName?: string; value?: string; children?: ParsedNode[] }\n/** A parsed node's words, as its text reads. */\nconst words = (n: ParsedNode): string => (n.type === \"text\" ? n.value ?? \"\" : (n.children ?? []).map(words).join(\"\"));\nconst childrenTagged = (n: ParsedNode | undefined, tag: string) => (n?.children ?? []).filter((c) => c.type === \"element\" && c.tagName === tag);\n\n/**\n * A markdown table's name, for its frame while that's a Tab stop (Sprint 25.31 item 8): its column headings, \"Table:\n * Plan, Seats\", so a page of several reads them apart; past two, the first two and how many more, \"Table: Name, Email\n * and 8 more\", so the name stays short (the review of 16 October, 7); \"Table\" with none. A caption would name it better,\n * but markdown has no caption, and raw HTML is never drawn here, so the headings are what a table brings.\n */\nfunction tableName(node: unknown): string {\n const table = node as ParsedNode | undefined;\n const head = childrenTagged(childrenTagged(table, \"thead\")[0], \"tr\")[0];\n const headings = childrenTagged(head, \"th\").map((th) => words(th).trim()).filter(Boolean);\n if (!headings.length) return \"Table\";\n const more = headings.length > 2 ? ` and ${headings.length - 2} more` : \"\";\n return `Table: ${headings.slice(0, 2).join(\", \")}${more}`;\n}\n\n/**\n * A table in its own frame, which scrolls sideways when the table is wider; with nothing in it to Tab to, the frame is\n * a Tab stop while it scrolls, so a keyboard can scroll it too (Sprint 25.30 item 2).\n */\nfunction TableFrame({ node, ...props }: ComponentProps<\"table\"> & { node?: unknown }) {\n const [frame, setFrame] = useState<HTMLDivElement | null>(null);\n useKeyboardScroll(frame, tableName(node), true, false, \"x\");\n return (\n <div ref={setFrame} className={cn(\"overflow-x-auto rounded-[var(--ds-radius-md)] border-[length:var(--ds-border-width)] border-[var(--ds-border-default)]\", FOCUS_INSET)}>\n <table className=\"w-full border-collapse text-sm\" {...props} />\n </div>\n );\n}\n\ntype P<T extends keyof React.JSX.IntrinsicElements> = ComponentProps<T> & { node?: unknown };\n\nconst components: Components = {\n p: ({ node: _n, ...props }: P<\"p\">) => <p {...props} />,\n a: ({ node: _n, href = \"#\", children, ...props }: P<\"a\">) => (\n <Link\n href={href}\n underline=\"always\"\n {...(isExternal(href) ? { target: \"_blank\", rel: \"noopener noreferrer\" } : {})}\n {...(props as Record<string, unknown>)}\n >\n {children}\n </Link>\n ),\n pre: ({ node: _n, children }: P<\"pre\">) => {\n const { code, language } = codeFromPre(children);\n return <CodeBlock code={code} language={language} />;\n },\n code: ({ node: _n, className, children, ...props }: P<\"code\">) => (\n <code\n className={cn(\n \"rounded-[var(--ds-radius-sm)] bg-[var(--ds-bg-muted)] px-ds-1 py-[1px] font-[family-name:var(--ds-font-mono)] text-[0.9em]\",\n className,\n )}\n {...props}\n >\n {children}\n </code>\n ),\n ul: ({ node: _n, ...props }: P<\"ul\">) => <ul className=\"list-disc space-y-ds-1 pl-ds-5\" {...props} />,\n ol: ({ node: _n, ...props }: P<\"ol\">) => <ol className=\"list-decimal space-y-ds-1 pl-ds-5\" {...props} />,\n li: ({ node: _n, ...props }: P<\"li\">) => <li {...props} />,\n h1: ({ node: _n, ...props }: P<\"h1\">) => <h3 className=\"text-base font-semibold text-[var(--ds-text-primary)]\" {...props} />,\n h2: ({ node: _n, ...props }: P<\"h2\">) => <h3 className=\"text-base font-semibold text-[var(--ds-text-primary)]\" {...props} />,\n h3: ({ node: _n, ...props }: P<\"h3\">) => <h4 className=\"text-sm font-semibold text-[var(--ds-text-primary)]\" {...props} />,\n h4: ({ node: _n, ...props }: P<\"h4\">) => <h4 className=\"text-sm font-semibold text-[var(--ds-text-primary)]\" {...props} />,\n h5: ({ node: _n, ...props }: P<\"h5\">) => <h5 className=\"text-sm font-medium text-[var(--ds-text-primary)]\" {...props} />,\n h6: ({ node: _n, ...props }: P<\"h6\">) => <h6 className=\"text-sm font-medium text-[var(--ds-text-secondary)]\" {...props} />,\n blockquote: ({ node: _n, ...props }: P<\"blockquote\">) => (\n <blockquote\n className=\"border-l-[length:calc(var(--ds-border-width)*2)] border-[var(--ds-border-default)] pl-ds-3 text-[var(--ds-text-secondary)]\"\n {...props}\n />\n ),\n hr: () => <Divider />,\n strong: ({ node: _n, ...props }: P<\"strong\">) => <strong className=\"font-semibold\" {...props} />,\n // Wide content scrolls inside its own container; the page never scrolls sideways.\n table: ({ node, ...props }: P<\"table\">) => <TableFrame node={node} {...props} />,\n thead: ({ node: _n, ...props }: P<\"thead\">) => <thead className=\"bg-[var(--ds-bg-muted)]\" {...props} />,\n th: ({ node: _n, ...props }: P<\"th\">) => (\n <th className=\"px-ds-3 py-ds-2 text-left text-xs font-semibold uppercase tracking-wide text-[var(--ds-text-secondary)]\" {...props} />\n ),\n td: ({ node: _n, ...props }: P<\"td\">) => (\n <td className=\"border-t-[length:var(--ds-border-width)] border-[var(--ds-border-subtle)] px-ds-3 py-ds-2 align-top\" {...props} />\n ),\n img: ({ node: _n, alt = \"\", ...props }: P<\"img\">) => <img alt={alt} className=\"max-w-full rounded-[var(--ds-radius-md)]\" {...props} />,\n};\n\n/**\n * Renders markdown (GitHub flavour: tables, task lists, strikethrough) with\n * Gridsmith primitives: fenced code becomes CodeBlock, links become Link,\n * a rule becomes Divider, everything else is token-styled markup. Raw HTML is\n * not rendered — the source is untrusted by default. Memoised: an unchanged\n * message never re-parses; a streaming message re-parses only its own string.\n *\n * Lives on the `gridsmith-ui/chat` entry point (react-markdown and remark-gfm\n * are optional peers), so apps without a chat never load a markdown parser.\n */\n// The caret is a pseudo-element on the last block, so it sits at the end of the\n// last line of text instead of on a line of its own below a block element.\nconst STREAMING_CARET = cn(\n \"[&>:last-child]:after:content-['▍'] [&>:last-child]:after:ml-ds-0.5\",\n \"[&>:last-child]:after:text-[var(--ds-accent-ink)] [&>:last-child]:after:animate-pulse\",\n);\n\nconst Memoised = memo(function MarkdownContentInner({ children, streaming, className }: MarkdownContentProps) {\n return (\n <div\n className={cn(\"space-y-ds-4 text-sm leading-relaxed text-[var(--ds-text-primary)]\", streaming && STREAMING_CARET, className)}\n data-markdown=\"\"\n data-streaming={streaming || undefined}\n >\n <Markdown remarkPlugins={[remarkGfm]} components={components} skipHtml>\n {children}\n </Markdown>\n </div>\n );\n});\n\n// A plain function export so the registry extractor (which reads `export function`)\n// documents it; the memo above still skips re-parsing unchanged sources.\nexport function MarkdownContent(props: MarkdownContentProps) {\n return <Memoised {...props} />;\n}\n"]}
|
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import { triggerType } from './chunk-
|
|
2
|
+
import { triggerType } from './chunk-HQFTP7Y5.js';
|
|
3
3
|
import { useBreakpoint } from './chunk-ZHUVLBCC.js';
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import { usePosition, usePopupFocus } from './chunk-
|
|
7
|
-
import { cn, GLASS_DROPDOWN, FOCUS_RING } from './chunk-YL5KD5RC.js';
|
|
4
|
+
import { Slot } from './chunk-DR6WEI3A.js';
|
|
5
|
+
import { Icon } from './chunk-53F7GVBQ.js';
|
|
6
|
+
import { cn, GLASS_DROPDOWN, usePosition, usePopupFocus, FOCUS_RING } from './chunk-EZ3ZGFGA.js';
|
|
8
7
|
import { useState, useRef, useEffect, useLayoutEffect } from 'react';
|
|
9
8
|
import { createPortal } from 'react-dom';
|
|
10
9
|
import { jsxs, Fragment, jsx } from 'react/jsx-runtime';
|
|
@@ -311,5 +310,5 @@ function DropdownMenu({ trigger, items, align = "end", className, container }) {
|
|
|
311
310
|
}
|
|
312
311
|
|
|
313
312
|
export { DropdownMenu };
|
|
314
|
-
//# sourceMappingURL=chunk-
|
|
315
|
-
//# sourceMappingURL=chunk-
|
|
313
|
+
//# sourceMappingURL=chunk-2TCCSKL3.js.map
|
|
314
|
+
//# sourceMappingURL=chunk-2TCCSKL3.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../showcase/src/components/DropdownMenu.tsx"],"names":[],"mappings":"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","file":"chunk-2TCCSKL3.js","sourcesContent":["import { useRef, useEffect, useLayoutEffect, useState, type ReactNode, type KeyboardEvent as ReactKeyboardEvent } from \"react\";\nimport { useBreakpoint } from \"../lib/use-breakpoint\";\nimport { createPortal } from \"react-dom\";\nimport { cn, FOCUS_RING, GLASS_DROPDOWN } from \"../lib/utils\";\nimport { Icon, type IconName } from \"../lib/icons\";\nimport { Slot } from \"../lib/slot\";\nimport { triggerType } from \"./Button\";\nimport { usePosition, type Align } from \"../lib/use-position\";\nimport { usePopupFocus } from \"../lib/focus\";\n\nexport interface DropdownMenuItem {\n label: string;\n icon?: IconName;\n onClick: () => void;\n variant?: \"default\" | \"destructive\";\n disabled?: boolean;\n}\n\nexport interface DropdownMenuDivider {\n type: \"divider\";\n}\n\n/**\n * An entry that opens a submenu of its own (Sprint 25.8.7's look check: a card's Move among its other actions), one\n * level deep. On a desktop it opens beside the menu: → or Enter opens it, its first item focused; ← or Escape closes it,\n * focus back on this entry; a hover opens it too. On a phone its items take the menu's place, under a Back item.\n */\nexport interface DropdownMenuSubmenu {\n label: string;\n icon?: IconName;\n items: (DropdownMenuItem | DropdownMenuDivider)[];\n disabled?: boolean;\n}\n\nexport type DropdownMenuEntry = DropdownMenuItem | DropdownMenuDivider | DropdownMenuSubmenu;\n\nfunction isDivider(entry: DropdownMenuEntry): entry is DropdownMenuDivider {\n return \"type\" in entry && entry.type === \"divider\";\n}\nfunction isSubmenu(entry: DropdownMenuEntry): entry is DropdownMenuSubmenu {\n return \"items\" in entry;\n}\n/** The indices of a list's items the arrows stop at: no divider, nothing unavailable. */\nconst stopsOf = (entries: DropdownMenuEntry[]): number[] => entries.reduce<number[]>((acc, e, i) => {\n if (!isDivider(e) && !isUnavailable(e)) acc.push(i);\n return acc;\n}, []);\n/** An entry that can't be chosen: disabled, or a submenu with nothing in it to choose, which never opens empty. */\nfunction isUnavailable(entry: DropdownMenuItem | DropdownMenuSubmenu): boolean {\n return !!entry.disabled || (isSubmenu(entry) && stopsOf(entry.items).length === 0);\n}\n/**\n * How long a hover on another entry waits before it closes the open submenu (or opens the other's): a pointer on its way\n * to the submenu crosses the entries below its own, and reaching the submenu in that time keeps it open.\n */\nconst SUBMENU_GRACE_MS = 300;\n\nconst ITEM = \"flex items-center gap-ds-2 w-full text-left px-ds-3 py-ds-2 text-sm cursor-pointer transition-colors rounded-[var(--ds-radius-sm)]\";\nconst itemLook = (entry: DropdownMenuItem | DropdownMenuSubmenu, open = false) => cn(\n ITEM,\n FOCUS_RING,\n isUnavailable(entry)\n ? \"opacity-disabled cursor-not-allowed\"\n : \"variant\" in entry && entry.variant === \"destructive\"\n ? \"text-[var(--ds-status-error-text)] hover:bg-[var(--ds-status-error-bg)] focus-visible:bg-[var(--ds-status-error-bg)]\"\n : cn(\"text-[var(--ds-text-primary)] hover:bg-[var(--ds-bg-emphasis)] focus-visible:bg-[var(--ds-bg-emphasis)]\", open && \"bg-[var(--ds-bg-emphasis)]\"),\n);\nconst PANEL = cn(\n // the items' outline has its 4 px of room at any density (Sprint 30, P3)\n \"fixed z-[var(--ds-z-popover)] min-w-[180px] p-[max(var(--ds-space-1),4px)]\",\n \"bg-[var(--ds-bg-surface-elevated)]\",\n GLASS_DROPDOWN,\n \"border-[length:var(--ds-border-width)] border-[var(--ds-border-default)]\",\n \"rounded-[var(--ds-radius-md)] overflow-hidden\",\n);\nconst Divider = () => <div role=\"separator\" className=\"my-ds-1 h-px bg-[var(--ds-border-default)]\" />;\n\ninterface DropdownMenuProps {\n trigger: ReactNode;\n items: DropdownMenuEntry[];\n align?: Align;\n className?: string;\n container?: Element | DocumentFragment;\n}\n\nexport function DropdownMenu({ trigger, items, align = \"end\", className, container }: DropdownMenuProps) {\n const [open, setOpen] = useState(false);\n const [focusedIdx, setFocusedIdx] = useState(-1);\n // the submenu open, by its entry's index, and the focus inside it\n const [sub, setSub] = useState<number | null>(null);\n const [subFocusedIdx, setSubFocusedIdx] = useState(-1);\n const triggerRef = useRef<HTMLElement>(null);\n // both panels, the menu and its submenu, sit in one box (display: contents) that is the popup's for focus and presses\n const panelsRef = useRef<HTMLDivElement>(null);\n const menuRef = useRef<HTMLDivElement>(null);\n const subRef = useRef<HTMLDivElement>(null);\n const subItemRef = useRef<HTMLElement>(null);\n const isPhone = useBreakpoint(\"sm\");\n // a hover elsewhere, waiting before it closes the open submenu (SUBMENU_GRACE_MS)\n const graceTimer = useRef<ReturnType<typeof setTimeout> | null>(null);\n const cancelGrace = () => {\n if (graceTimer.current) { clearTimeout(graceTimer.current); graceTimer.current = null; }\n };\n const afterGrace = (then: () => void) => {\n cancelGrace();\n graceTimer.current = setTimeout(() => { graceTimer.current = null; then(); }, SUBMENU_GRACE_MS);\n };\n // closing the menu, or leaving the page, drops a hover's wait\n useEffect(() => { if (!open) cancelGrace(); }, [open]);\n useEffect(() => cancelGrace, []);\n\n const pos = usePosition(triggerRef, menuRef, open, { side: \"bottom\", align, offset: 4, flip: true });\n // beside the menu, 4 px from its edge, its first item level with its entry; on the other side where the screen has no\n // room, and inside the screen (measured against the menu, not the entry: the menu's padding and edge sit between them)\n const [subPos, setSubPos] = useState({ top: 0, left: 0 });\n useLayoutEffect(() => {\n if (!open || sub === null || isPhone) return;\n const place = () => {\n const menu = menuRef.current;\n const entry = subItemRef.current;\n const panel = subRef.current;\n if (!menu || !entry || !panel) return;\n const m = menu.getBoundingClientRect();\n const e = entry.getBoundingClientRect();\n const p = panel.getBoundingClientRect();\n const first = panel.querySelector(\"[data-sub-index]\")?.getBoundingClientRect();\n let left = m.right + 4;\n if (left + p.width > window.innerWidth - 8) left = Math.max(8, m.left - 4 - p.width);\n const top = Math.max(8, Math.min(e.top - (first ? first.top - p.top : 0), window.innerHeight - p.height - 8));\n setSubPos((cur) => (cur.top === top && cur.left === left ? cur : { top, left }));\n };\n place();\n window.addEventListener(\"resize\", place);\n window.addEventListener(\"scroll\", place, true);\n return () => {\n window.removeEventListener(\"resize\", place);\n window.removeEventListener(\"scroll\", place, true);\n };\n }, [open, sub, isPhone]);\n\n const enabledIndices = stopsOf(items);\n const subEntry = sub !== null && isSubmenu(items[sub]) ? (items[sub] as DropdownMenuSubmenu) : null;\n const subIndices = subEntry ? stopsOf(subEntry.items) : [];\n\n // Closing clears the keyboard focus index and the submenu (render-time state adjustment).\n const [prevOpen, setPrevOpen] = useState(open);\n if (open !== prevOpen) {\n setPrevOpen(open);\n if (!open) { setFocusedIdx(-1); setSub(null); setSubFocusedIdx(-1); }\n }\n\n // Sprint 25.7: focus hands back on close, and Tab out carries on from the trigger (lib/focus usePopupFocus).\n const { isTop: isTopLayer, returnFocus } = usePopupFocus(open, { trigger: triggerRef, panel: panelsRef, close: () => setOpen(false), tabExits: \"any\" });\n\n /** The submenu closes, and focus goes back to the entry that opened it. */\n const closeSub = () => {\n if (sub === null) return;\n setFocusedIdx(sub);\n setSub(null);\n setSubFocusedIdx(-1);\n };\n const openSub = (i: number, focusFirst: boolean) => {\n const entry = items[i];\n if (!isSubmenu(entry) || isUnavailable(entry)) return;\n cancelGrace();\n setSub(i);\n setFocusedIdx(i);\n setSubFocusedIdx(focusFirst ? stopsOf(entry.items)[0] ?? -1 : -1);\n };\n\n useEffect(() => {\n if (!open) return;\n\n // An Escape a control inside already handled is its own; the one this acts on is marked for overlays around it.\n // In a submenu it closes the submenu alone.\n const onKeyDown = (e: globalThis.KeyboardEvent) => {\n if (e.key !== \"Escape\" || e.defaultPrevented || !isTopLayer()) return;\n e.preventDefault();\n if (sub !== null) { closeSub(); return; }\n returnFocus();\n setOpen(false);\n };\n\n const onClick = (e: MouseEvent) => {\n const target = e.target as Node;\n if (triggerRef.current?.contains(target) || panelsRef.current?.contains(target)) return;\n setOpen(false);\n };\n\n document.addEventListener(\"keydown\", onKeyDown);\n document.addEventListener(\"mousedown\", onClick);\n return () => {\n document.removeEventListener(\"keydown\", onKeyDown);\n document.removeEventListener(\"mousedown\", onClick);\n };\n });\n\n useEffect(() => {\n if (!open) return;\n // By the item's own index: items and dividers share one numbering, so indexing the buttons alone left every\n // item after a divider unreachable by the arrows (the shell's user menu: Settings, divider, Log out).\n if (sub !== null && subFocusedIdx >= 0) panelsRef.current?.querySelector<HTMLElement>(`[data-sub-index=\"${subFocusedIdx}\"]`)?.focus();\n else if (sub !== null && isPhone) panelsRef.current?.querySelector<HTMLElement>(\"[data-sub-back]\")?.focus();\n else if (focusedIdx >= 0) menuRef.current?.querySelector<HTMLElement>(`[data-index=\"${focusedIdx}\"]`)?.focus();\n }, [open, focusedIdx, sub, subFocusedIdx, isPhone]);\n\n const step = (stops: number[], at: number, by: number) => stops[((stops.indexOf(at) + by) % stops.length + stops.length) % stops.length];\n function handleMenuKeyDown(e: ReactKeyboardEvent) {\n const inSub = sub !== null;\n const stops = inSub ? subIndices : enabledIndices;\n const at = inSub ? subFocusedIdx : focusedIdx;\n const go = (i: number | undefined) => { if (i === undefined) return; if (inSub) setSubFocusedIdx(i); else setFocusedIdx(i); };\n if (e.key === \"ArrowDown\") { e.preventDefault(); go(at < 0 ? stops[0] : step(stops, at, 1)); }\n else if (e.key === \"ArrowUp\") { e.preventDefault(); go(at < 0 ? stops[stops.length - 1] : step(stops, at, -1)); }\n else if (e.key === \"Home\") { e.preventDefault(); go(stops[0]); }\n else if (e.key === \"End\") { e.preventDefault(); go(stops[stops.length - 1]); }\n else if (e.key === \"ArrowRight\" && !inSub && focusedIdx >= 0 && isSubmenu(items[focusedIdx])) { e.preventDefault(); openSub(focusedIdx, true); }\n else if (e.key === \"ArrowLeft\" && inSub) { e.preventDefault(); closeSub(); }\n }\n\n /** A choice: focus back on the trigger first, so a dialog the item opens returns there (Sprint 25.7). */\n const choose = (item: DropdownMenuItem) => {\n if (item.disabled) return;\n returnFocus();\n item.onClick();\n setOpen(false);\n };\n\n const subItems = (entries: (DropdownMenuItem | DropdownMenuDivider)[]) => entries.map((item, i) => (isDivider(item) ? <Divider key={i} /> : (\n <button type=\"button\" key={i} data-sub-index={i} role=\"menuitem\" disabled={item.disabled} tabIndex={subFocusedIdx === i ? 0 : -1} onFocus={() => setSubFocusedIdx(i)} onClick={() => choose(item)} className={itemLook(item)}>\n {item.icon && <Icon name={item.icon} size={16} className=\"shrink-0\" />}\n <span>{item.label}</span>\n </button>\n )));\n\n return (\n <>\n <Slot\n {...triggerType(trigger)}\n // the APG's menu button: the trigger says it opens a menu, and whether it is open\n aria-haspopup=\"menu\"\n aria-expanded={open}\n ref={triggerRef}\n onClick={() => {\n setOpen(!open);\n if (!open && enabledIndices.length) setFocusedIdx(enabledIndices[0]);\n }}\n >\n {trigger}\n </Slot>\n {open && createPortal(\n <div ref={panelsRef} className=\"contents\">\n <div\n ref={menuRef}\n role=\"menu\"\n data-gs=\"menu\"\n aria-label={isPhone && subEntry ? subEntry.label : undefined}\n onKeyDown={handleMenuKeyDown}\n className={cn(PANEL, className)}\n style={{ top: pos.top, left: pos.left }}\n // A choice is the menu's own: the click must not also reach a clickable row or card the menu sits in\n // (React carries events from a portal to its React parents).\n onClick={(e) => e.stopPropagation()}\n >\n {isPhone && subEntry ? (\n // on a phone the submenu takes the menu's place: Back first, its entry's name on it\n <>\n <button type=\"button\" data-sub-back=\"\" role=\"menuitem\" aria-label={`Back from ${subEntry.label}`} tabIndex={-1} onClick={closeSub} className={cn(itemLook(subEntry), \"text-[var(--ds-text-secondary)] font-[var(--ds-weight-heading)]\")}>\n <Icon name=\"chevron-left\" size={16} className=\"shrink-0\" />\n <span>{subEntry.label}</span>\n </button>\n <Divider />\n {subItems(subEntry.items)}\n </>\n ) : items.map((item, i) => {\n if (isDivider(item)) return <Divider key={i} />;\n if (isSubmenu(item)) {\n const isOpen = sub === i;\n return (\n <button type=\"button\"\n key={i}\n ref={isOpen ? (el) => { subItemRef.current = el; } : undefined}\n data-index={i}\n role=\"menuitem\"\n aria-haspopup=\"menu\"\n aria-expanded={isOpen}\n disabled={isUnavailable(item)}\n tabIndex={focusedIdx === i ? 0 : -1}\n // the arrows go on from wherever focus is, however it got there (a click, a screen reader's command)\n onFocus={() => setFocusedIdx(i)}\n // Enter (its click) or a press goes into the submenu, its first item focused, whether a hover had\n // opened it or not (the APG's menu: Enter opens it and focuses its first item); ←, Escape or Back\n // closes it\n onClick={() => openSub(i, true)}\n // a hover opens it at once where no other is open; with another open it waits, as the pointer may be\n // on its way to that one (SUBMENU_GRACE_MS); back on its own entry, the wait for a close is dropped\n onMouseEnter={(e) => {\n if (isPhone || e.buttons !== 0) return;\n if (isOpen) cancelGrace();\n else if (sub === null) openSub(i, false);\n else afterGrace(() => openSub(i, false));\n }}\n className={itemLook(item, isOpen)}\n >\n {item.icon && <Icon name={item.icon} size={16} className=\"shrink-0\" />}\n <span className=\"flex-1\">{item.label}</span>\n <Icon name=\"chevron-right\" size={14} className=\"shrink-0 text-[var(--ds-text-muted)]\" />\n </button>\n );\n }\n return (\n <button type=\"button\"\n key={i}\n data-index={i}\n role=\"menuitem\"\n disabled={item.disabled}\n tabIndex={focusedIdx === i ? 0 : -1}\n onFocus={() => setFocusedIdx(i)}\n onClick={() => choose(item)}\n // a hover on another entry closes an open submenu, as moving on from it does, after a moment: the\n // pointer may only be crossing it on its way to the submenu (SUBMENU_GRACE_MS)\n onMouseEnter={() => { if (!isPhone && sub !== null) afterGrace(() => { setSub(null); setSubFocusedIdx(-1); }); }}\n className={itemLook(item)}\n >\n {item.icon && <Icon name={item.icon} size={16} className=\"shrink-0\" />}\n <span>{item.label}</span>\n </button>\n );\n })}\n </div>\n {!isPhone && subEntry && (\n <div\n ref={subRef}\n role=\"menu\"\n aria-label={subEntry.label}\n onKeyDown={handleMenuKeyDown}\n className={PANEL}\n style={{ top: subPos.top, left: subPos.left }}\n onClick={(e) => e.stopPropagation()}\n // the pointer has reached the submenu: a hover it crossed on the way closes nothing\n onMouseEnter={cancelGrace}\n >\n {subItems(subEntry.items)}\n </div>\n )}\n </div>,\n container ?? document.body\n )}\n </>\n );\n}\n"]}
|
|
@@ -18,6 +18,9 @@ function warnOnce(component, message) {
|
|
|
18
18
|
warned.add(text);
|
|
19
19
|
console.warn(text);
|
|
20
20
|
}
|
|
21
|
+
function warnNameLeftOut(component, attr, value, whose) {
|
|
22
|
+
warnOnce(component, `the page's ${attr} ("${value}") is left out of its name beside a visible label (${whose}), which its name must start with (WCAG 2.5.3). Remove the ${attr}, or the label on screen to name it by the ${attr} alone.`);
|
|
23
|
+
}
|
|
21
24
|
|
|
22
25
|
// ../../showcase/src/lib/icon-pack-imports.ts
|
|
23
26
|
var ICON_PACK_IMPORTS = {
|
|
@@ -204,6 +207,6 @@ function NotAnIcon({ text, from }) {
|
|
|
204
207
|
return null;
|
|
205
208
|
}
|
|
206
209
|
|
|
207
|
-
export { Icon, IconProvider, preloadPack, registerIconPack, slotDraws, slotIcon, warnOnce };
|
|
208
|
-
//# sourceMappingURL=chunk-
|
|
209
|
-
//# sourceMappingURL=chunk-
|
|
210
|
+
export { Icon, IconProvider, preloadPack, registerIconPack, slotDraws, slotIcon, warnNameLeftOut, warnOnce };
|
|
211
|
+
//# sourceMappingURL=chunk-53F7GVBQ.js.map
|
|
212
|
+
//# sourceMappingURL=chunk-53F7GVBQ.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../showcase/src/lib/dev-warn.ts","../../../showcase/src/lib/icon-pack-imports.ts","../../../showcase/src/lib/icons.tsx"],"names":[],"mappings":";;;;;;;AAEA,IAAM,MAAA,uBAAa,GAAA,EAAY;AAGxB,SAAS,QAAA,CAAS,WAAmB,OAAA,EAAuB;AACjE,EAAA,IAAI,GAAA,GAAM,KAAA;AACV,EAAA,IAAI;AACF,IAAA,GAAA,GAAM,OAAA,CAAQ,IAAI,QAAA,KAAa,YAAA;AAAA,EACjC,CAAA,CAAA,MAAQ;AACN,IAAA,GAAA,GAAM,KAAA;AAAA,EACR;AACA,EAAA,MAAM,IAAA,GAAO,CAAA,EAAG,SAAS,CAAA,EAAA,EAAK,OAAO,CAAA,CAAA;AACrC,EAAA,IAAI,CAAC,GAAA,IAAO,MAAA,CAAO,GAAA,CAAI,IAAI,CAAA,EAAG;AAC9B,EAAA,MAAA,CAAO,IAAI,IAAI,CAAA;AACf,EAAA,OAAA,CAAQ,KAAK,IAAI,CAAA;AACnB;AAMO,SAAS,eAAA,CAAgB,SAAA,EAAmB,IAAA,EAAwC,KAAA,EAAe,KAAA,EAAqB;AAC7H,EAAA,QAAA,CAAS,SAAA,EAAW,CAAA,WAAA,EAAc,IAAI,CAAA,GAAA,EAAM,KAAK,CAAA,mDAAA,EAAsD,KAAK,CAAA,2DAAA,EAA8D,IAAI,CAAA,2CAAA,EAA8C,IAAI,CAAA,OAAA,CAAS,CAAA;AAC3O;;;AClBO,IAAM,iBAAA,GAAoB;AAAA,EAC/B,OAAA,EAAS,gBAAA;AAAA,EACT,SAAA,EAAW,6BAAA;AAAA,EACX,QAAA,EAAU,uBAAA;AAAA,EACV,MAAA,EAAQ;AACV,CAAA;AAMO,SAAS,qBAAqB,IAAA,EAAgE;AACnG,EAAA,OAAO;AAAA,IACL,UAAA,EAAY,uDAAA;AAAA,IACZ,MAAM,CAAA,kBAAA,EAAqB,IAAI,CAAA,iBAAA,EAAoB,iBAAA,CAAkB,IAAI,CAAC,CAAA,GAAA;AAAA,GAC5E;AACF;AC4BA,IAAM,WAAA,uBAAkB,GAAA,EAAgB;AACxC,SAAS,iBAAA,GAAoB;AAC3B,EAAA,WAAA,CAAY,OAAA,CAAQ,CAAC,EAAA,KAAO,EAAA,EAAI,CAAA;AAClC;AACA,SAAS,UAAU,EAAA,EAA4B;AAC7C,EAAA,WAAA,CAAY,IAAI,EAAE,CAAA;AAClB,EAAA,OAAO,MAAM;AAAE,IAAA,WAAA,CAAY,OAAO,EAAE,CAAA;AAAA,EAAG,CAAA;AACzC;AAEA,IAAM,YAAA,uBAAmB,GAAA,EAAuB;AAChD,IAAM,aAAA,uBAAoB,GAAA,EAAuB;AAEjD,SAAS,cAAc,IAAA,EAAgC;AACrD,EAAA,OAAO,oBAAA;AAAA,IACL,SAAA;AAAA,IACA,MAAM,YAAA,CAAa,GAAA,CAAI,IAAI,CAAA,IAAK,IAAA;AAAA,IAChC,MAAM;AAAA,GACR;AACF;AAEA,SAAS,eAAe,IAAA,EAAgC;AACtD,EAAA,OAAO,oBAAA;AAAA,IACL,SAAA;AAAA,IACA,MAAM,aAAA,CAAc,GAAA,CAAI,IAAI,CAAA,IAAK,IAAA;AAAA,IACjC,MAAM;AAAA,GACR;AACF;AAgBA,IAAM,YAAA,uBAAmB,GAAA,EAAc;AACvC,IAAM,WAAA,uBAAkB,GAAA,EAAc;AAQtC,IAAM,WAAA,mBAAc,IAAI,GAAA,CAAwB,CAAC,CAAC,QAAA,EAAU,MAAM,OAAO,cAAc,CAAC,CAAC,CAAC,CAAA;AAQnF,SAAS,gBAAA,CAAiB,MAA0B,IAAA,EAAsB;AAC/E,EAAA,WAAA,CAAY,GAAA,CAAI,MAAM,IAAI,CAAA;AAG1B,EAAA,IAAI,YAAY,MAAA,CAAO,IAAI,CAAA,EAAG,KAAK,SAAS,IAAI,CAAA;AAClD;AAGA,IAAM,YAAA,GAAyC;AAAA,EAC7C,MAAA,EAAQ,cAAA;AAAA,EACR,QAAA,EAAU,uBAAA;AAAA,EACV,SAAA,EAAW,kBAAA;AAAA,EACX,MAAA,EAAQ,qBAAA;AAAA,EACR,OAAA,EAAS;AACX,CAAA;AAEA,eAAe,SAAS,IAAA,EAA+B;AACrD,EAAA,IAAI,YAAA,CAAa,GAAA,CAAI,IAAI,CAAA,IAAK,YAAA,CAAa,GAAA,CAAI,IAAI,CAAA,IAAK,WAAA,CAAY,GAAA,CAAI,IAAI,CAAA,EAAG;AAC/E,EAAA,YAAA,CAAa,IAAI,IAAI,CAAA;AAKrB,EAAA,MAAM,IAAA,GAAO,WAAA,CAAY,GAAA,CAAI,IAAI,CAAA;AACjC,EAAA,IAAI,CAAC,IAAA,EAAM;AAET,IAAA,WAAA,CAAY,IAAI,IAAI,CAAA;AACpB,IAAA,YAAA,CAAa,OAAO,IAAI,CAAA;AACxB,IAAA,MAAM,KAAA,GAAQ,qBAAqB,IAA0B,CAAA;AAC7D,IAAA,QAAA,CAAS,cAAA,EAAgB,OAAO,IAAI,CAAA,kHAAA,EAAqH,MAAM,UAAU,CAAA,EAAA,EAAK,KAAA,CAAM,IAAI,CAAA,CAAE,CAAA;AAC1L,IAAA;AAAA,EACF;AACA,EAAA,IAAI,GAAA;AACJ,EAAA,IAAI,OAAA;AACJ,EAAA,IAAI;AACF,IAAA,QAAQ,IAAA;AAAM,MACZ,KAAK,QAAA;AAAU,QAAA,GAAA,GAAM,MAAM,OAAO,sBAAqB,CAAA;AAAG,QAAA;AAAA,MAC1D,KAAK,UAAA;AAAY,QAAA,GAAA,GAAM,MAAM,OAAO,wBAAuB,CAAA;AAAG,QAAA;AAAA,MAC9D,KAAK,WAAA;AAAa,QAAA,GAAA,GAAM,MAAM,OAAO,yBAAwB,CAAA;AAAG,QAAA;AAAA,MAChE,KAAK,QAAA;AAAU,QAAA,GAAA,GAAM,MAAM,OAAO,sBAAqB,CAAA;AAAG,QAAA;AAAA,MAC1D,KAAK,SAAA;AAAW,QAAA,GAAA,GAAM,MAAM,OAAO,uBAAsB,CAAA;AAAG,QAAA;AAAA;AAE9D,IAAA,OAAA,GAAU,MAAM,GAAA,CAAK,WAAA,CAAY,IAAI,CAAA;AAAA,EACvC,CAAA,CAAA,MAAQ;AACN,IAAA,YAAA,CAAa,OAAO,IAAI,CAAA;AACxB,IAAA,WAAA,CAAY,IAAI,IAAI,CAAA;AACpB,IAAA,OAAA,CAAQ,KAAA,CAAM,iBAAiB,YAAA,CAAa,IAAI,CAAC,CAAA,oCAAA,EAAuC,YAAA,CAAa,IAAI,CAAC,CAAA,CAAE,CAAA;AAC5G,IAAA;AAAA,EACF;AAEA,EAAA,YAAA,CAAa,GAAA,CAAI,MAAM,OAAO,CAAA;AAC9B,EAAA,iBAAA,EAAkB;AAElB,EAAA,GAAA,CAAK,YAAA,CAAa,IAAI,CAAA,CAAE,IAAA,CAAK,CAAC,QAAA,KAAa;AACzC,IAAA,aAAA,CAAc,GAAA,CAAI,MAAM,QAAQ,CAAA;AAChC,IAAA,iBAAA,EAAkB;AAAA,EACpB,CAAC,CAAA,CAAE,KAAA,CAAM,MAAM;AAAA,EAGf,CAAC,CAAA;AACH;AA4BA,IAAM,UAAU,aAAA,CAAgC,EAAE,MAAM,QAAA,EAAU,MAAA,EAAQ,KAAK,CAAA;AAMxE,SAAS,YAAA,CAAa,EAAE,IAAA,EAAM,MAAA,EAAQ,UAAS,EAA4D;AAChH,EAAA,SAAA,CAAU,MAAM;AACd,IAAA,QAAA,CAAS,IAAI,CAAA;AACb,IAAA,IAAI,IAAA,KAAS,QAAA,EAAU,QAAA,CAAS,QAAQ,CAAA;AAAA,EAC1C,CAAA,EAAG,CAAC,IAAI,CAAC,CAAA;AAET,EAAA,uBAAO,GAAA,CAAC,QAAQ,QAAA,EAAR,EAAiB,OAAO,EAAE,IAAA,EAAM,MAAA,EAAO,EAAI,QAAA,EAAS,CAAA;AAC9D;AAWO,SAAS,YAAY,IAAA,EAAsB;AAChD,EAAA,QAAA,CAAS,IAAI,CAAA;AACf;AAUO,SAAS,kBAAkB,aAAA,EAA8D;AAC9F,EAAA,IAAI,aAAA,IAAiB,MAAM,OAAO,MAAA;AAClC,EAAA,IAAI,aAAA,IAAiB,MAAM,OAAO,OAAA;AAClC,EAAA,IAAI,aAAA,IAAiB,MAAM,OAAO,SAAA;AAClC,EAAA,OAAO,MAAA;AACT;AAcA,SAAS,IAAA,CAAK,KAAuD,IAAA,EAAoD;AACvH,EAAA,OAAO,CAAC,CAAC,GAAA,IAAO,MAAA,CAAO,UAAU,cAAA,CAAe,IAAA,CAAK,KAAK,IAAI,CAAA;AAChE;AAOO,SAAS,KAAK,EAAE,IAAA,EAAM,OAAO,EAAA,EAAI,SAAA,EAAW,MAAK,EAAyE;AAC/H,EAAA,MAAM,EAAE,IAAA,EAAM,MAAA,EAAO,GAAI,WAAW,OAAO,CAAA;AAE3C,EAAA,MAAM,UAAA,GAAa,cAAc,IAAI,CAAA;AACrC,EAAA,MAAM,WAAA,GAAc,eAAe,IAAI,CAAA;AACvC,EAAA,MAAM,aAAA,GAAgB,cAAc,QAAQ,CAAA;AAC5C,EAAA,MAAM,cAAA,GAAiB,eAAe,QAAQ,CAAA;AAI9C,EAAA,IAAI,CAAC,aAAa,GAAA,CAAI,IAAI,KAAK,CAAC,YAAA,CAAa,GAAA,CAAI,IAAI,CAAA,EAAG;AACtD,IAAA,QAAA,CAAS,IAAI,CAAA;AAAA,EACf;AAEA,EAAA,IAAI,IAAA,GAAyC,IAAA;AAC7C,EAAA,IAAI,UAAA,GAAuB,IAAA;AAG3B,EAAA,IAAI,IAAA,CAAK,UAAA,EAAY,IAAI,CAAA,EAAG;AAC1B,IAAA,IAAA,GAAO,WAAW,IAAI,CAAA;AAAA,EACxB;AAGA,EAAA,IAAI,CAAC,IAAA,IAAQ,IAAA,CAAK,WAAA,EAAa,IAAI,CAAA,EAAG;AACpC,IAAA,IAAA,GAAO,YAAY,IAAI,CAAA;AAAA,EACzB;AAGA,EAAA,IAAI,CAAC,IAAA,IAAQ,IAAA,KAAS,QAAA,EAAU;AAC9B,IAAA,MAAM,QAAA,GAAW,IAAA,CAAK,aAAA,EAAe,IAAI,IAAI,aAAA,CAAc,IAAI,CAAA,GAAI,IAAA,CAAK,cAAA,EAAgB,IAAI,CAAA,GAAI,cAAA,CAAe,IAAI,CAAA,GAAI,MAAA;AACvH,IAAA,IAAI,QAAA,EAAU;AACZ,MAAA,IAAA,GAAO,QAAA;AACP,MAAA,UAAA,GAAa,QAAA;AAAA,IACf;AAAA,EACF;AAEA,EAAA,MAAM,gBAAgB,CAAC,EAAE,cAAc,WAAA,KAAgB,IAAA,KAAS,YAAa,aAAA,IAAiB,cAAA,CAAA,CAAA;AAC9F,EAAA,MAAM,OAAA,GAAU,CAAC,IAAA,IAAQ,aAAA;AACzB,EAAA,SAAA,CAAU,MAAM;AACd,IAAA,IAAI,OAAA,EAAS,QAAA,CAAS,MAAA,EAAQ,CAAA,CAAA,EAAI,IAAI,CAAA,6BAAA,EAAgC,IAAI,CAAA,KAAA,EAAQ,IAAA,KAAS,WAAW,EAAA,GAAK,YAAY,CAAA,KAAA,EAAQ,IAAA,IAAQ,IAAI,CAAA,iDAAA,CAAmD,CAAA;AAAA,EAChM,GAAG,CAAC,OAAA,EAAS,IAAA,EAAM,IAAA,EAAM,IAAI,CAAC,CAAA;AAE9B,EAAA,IAAI,CAAC,IAAA,EAAM;AACT,IAAA,IAAI,eAAe,OAAO,IAAA;AAC1B,IAAA,uBAAO,GAAA,CAAC,MAAA,EAAA,EAAK,KAAA,EAAO,EAAE,OAAA,EAAS,cAAA,EAAgB,KAAA,EAAO,IAAA,EAAM,MAAA,EAAQ,IAAA,EAAK,EAAG,SAAA,EAAsB,CAAA;AAAA,EACpG;AAEA,EAAA,IAAI,eAAe,UAAA,EAAY;AAC7B,IAAA,2BAAQ,IAAA,EAAA,EAAK,IAAA,EAAY,QAAQ,iBAAA,CAAkB,MAAM,GAAG,SAAA,EAAsB,CAAA;AAAA,EACpF;AAEA,EAAA,IAAI,eAAe,WAAA,EAAa;AAC9B,IAAA,uBAAO,GAAA,CAAC,IAAA,EAAA,EAAK,SAAA,EAAsB,KAAA,EAAO,EAAE,KAAA,EAAO,IAAA,EAAM,MAAA,EAAQ,IAAA,EAAM,WAAA,EAAa,MAAA,EAAO,EAAG,CAAA;AAAA,EAChG;AAEA,EAAA,IAAI,eAAe,QAAA,EAAU;AAC3B,IAAA,uBAAO,GAAA,CAAC,IAAA,EAAA,EAAK,IAAA,EAAY,MAAA,EAAQ,QAAQ,SAAA,EAAsB,CAAA;AAAA,EACjE;AAEA,EAAA,IAAI,eAAe,SAAA,EAAW;AAC5B,IAAA,uBAAO,GAAA,CAAC,IAAA,EAAA,EAAK,IAAA,EAAY,SAAA,EAAsB,aAAa,MAAA,EAAQ,CAAA;AAAA,EACtE;AAEA,EAAA,uBAAO,GAAA,CAAC,IAAA,EAAA,EAAK,IAAA,EAAY,WAAA,EAAa,QAAQ,SAAA,EAAsB,CAAA;AACtE;AAYO,SAAS,QAAA,CAAS,IAAA,EAA4B,IAAA,EAAc,IAAA,EAAyB;AAC1F,EAAA,IAAI,OAAO,SAAS,QAAA,EAAU;AAE5B,IAAA,IAAI,CAAC,MAAM,OAAO,IAAA;AAClB,IAAA,OAAO,eAAA,CAAgB,IAAA,CAAK,IAAI,CAAA,uBAAK,IAAA,EAAA,EAAK,IAAA,EAAM,IAAA,EAAM,IAAA,EAAY,MAAY,CAAA,mBAAK,GAAA,CAAC,SAAA,EAAA,EAAU,IAAA,EAAM,MAAM,IAAA,EAAY,CAAA;AAAA,EACxH;AACA,EAAA,OAAO,IAAA;AACT;AAGA,IAAM,eAAA,GAAkB,4BAAA;AAMjB,SAAS,UAAU,IAAA,EAAqC;AAC7D,EAAA,IAAI,OAAO,IAAA,KAAS,QAAA,EAAU,OAAO,eAAA,CAAgB,KAAK,IAAI,CAAA;AAC9D,EAAA,OAAO,IAAA,IAAQ,IAAA,IAAQ,IAAA,KAAS,KAAA,IAAS,IAAA,KAAS,IAAA;AACpD;AAGA,SAAS,SAAA,CAAU,EAAE,IAAA,EAAM,IAAA,EAAK,EAAmC;AACjE,EAAA,SAAA,CAAU,MAAM;AACd,IAAA,QAAA,CAAS,MAAA,EAAQ,CAAA,CAAA,EAAI,IAAI,CAAA,0BAAA,EAA6B,IAAI,CAAA,wEAAA,CAA0E,CAAA;AAAA,EACtI,CAAA,EAAG,CAAC,IAAA,EAAM,IAAI,CAAC,CAAA;AACf,EAAA,OAAO,IAAA;AACT","file":"chunk-53F7GVBQ.js","sourcesContent":["// Bundlers set process.env.NODE_ENV. Where none did (a page with no build step), reading it throws and warnings stay off.\ndeclare const process: { env: { NODE_ENV?: string } };\nconst warned = new Set<string>();\n\n/** A mistake in a component's props, told to the developer once a page, in a development build. */\nexport function warnOnce(component: string, message: string): void {\n let dev = false;\n try {\n dev = process.env.NODE_ENV !== \"production\";\n } catch {\n dev = false;\n }\n const text = `${component}: ${message}`;\n if (!dev || warned.has(text)) return;\n warned.add(text);\n console.warn(text);\n}\n\n/**\n * The naming rule's warning (Sprint 25.31): a page's aria-label or aria-labelledby left out of a control's name, beside a\n * label on screen (`whose`: its own, or its Field's), which its name must start with (WCAG 2.5.3).\n */\nexport function warnNameLeftOut(component: string, attr: \"aria-label\" | \"aria-labelledby\", value: string, whose: string): void {\n warnOnce(component, `the page's ${attr} (\"${value}\") is left out of its name beside a visible label (${whose}), which its name must start with (WCAG 2.5.3). Remove the ${attr}, or the label on screen to name it by the ${attr} alone.`);\n}\n","/**\n * The import each icon pack takes, as an app writes it when it registers its theme's pack (Sprint 25.21 P1:\n * `registerIconPack('feather', () => import('react-icons/fi'))`). One table for the library's warning, the shells\n * `get_app_shell` writes and the engine's fix. Lucide is the library's own: every app installs it, as the fallback.\n * No React here, so the platform reads it as it reads the other showcase modules.\n */\nexport const ICON_PACK_IMPORTS = {\n feather: \"react-icons/fi\",\n heroicons: \"@heroicons/react/24/outline\",\n phosphor: \"@phosphor-icons/react\",\n tabler: \"@tabler/icons-react\",\n} as const;\n\n/** A pack an app registers: every pack but Lucide. */\nexport type RegisteredIconPack = keyof typeof ICON_PACK_IMPORTS;\n\n/** The two lines that register a pack, at the top of an app's shell, before any preloadPack. */\nexport function iconPackRegistration(pack: RegisteredIconPack): { importLine: string; call: string } {\n return {\n importLine: \"import { registerIconPack } from 'gridsmith-ui/icons'\",\n call: `registerIconPack('${pack}', () => import('${ICON_PACK_IMPORTS[pack]}'))`,\n };\n}\n","import { createContext, useContext, useEffect, useSyncExternalStore, type ReactElement, type ReactNode, type ComponentType } from \"react\";\nimport { warnOnce } from \"./dev-warn\";\nimport { iconPackRegistration, type RegisteredIconPack } from \"./icon-pack-imports\";\nimport type { PackLoad } from \"./icon-packs/types\";\n\n/* ── Types ────────────────────────────────────────────────────── */\n\n/**\n * Runtime list of curated icon names. The CuratedIconName type derives from\n * this array, so adding a name here extends autocomplete automatically.\n * Every name must have an alias (or auto-kebab match) in ALL five packs —\n * icon-completeness.test.tsx enforces this.\n */\nexport const CURATED_ICON_NAMES = [\n \"home\", \"folder\", \"chart\", \"settings\",\n \"chevron-right\", \"close\", \"plus\", \"search\",\n \"bell\", \"user\", \"check\", \"warning\", \"info\", \"error\",\n \"clock\", \"calendar\",\n \"bold\", \"italic\", \"underline\", \"strikethrough\",\n \"align-left\", \"align-center\", \"align-right\",\n \"list\", \"list-ordered\", \"list-checks\",\n \"link\", \"image\", \"table\", \"code\", \"quote\", \"minus\",\n \"superscript\", \"subscript\", \"highlight\", \"type\",\n \"chevron-up\", \"chevron-down\", \"chevron-left\", \"trash\",\n \"undo\", \"redo\",\n \"arrow-left\", \"grip-vertical\",\n \"mic\", \"paperclip\", \"send\",\n \"log-out\", \"edit\", \"menu\", \"more-horizontal\",\n \"star\", \"bookmark\", \"download\", \"upload\", \"sparkles\",\n \"file\", \"file-text\", \"eye\", \"eye-off\", \"copy\", \"external-link\", \"filter\", \"lock\",\n \"map-pin\", \"play\", \"pause\",\n] as const;\n\ntype CuratedIconName = (typeof CURATED_ICON_NAMES)[number];\n\n/**\n * Curated names provide autocomplete and work identically across all 5 packs.\n * Any kebab-case string is also accepted — it resolves directly against the\n * active pack's native exports (e.g. \"clipboard-copy\" → Lucide's ClipboardCopy).\n */\nexport type IconName = CuratedIconName | (string & {});\n\nexport type IconPack = \"lucide\" | \"phosphor\" | \"heroicons\" | \"tabler\" | \"feather\";\n\ntype IconComponent = ComponentType<Record<string, unknown>>;\ntype IconMap = Record<string, IconComponent>;\n\n/* ── External store ──────────────────────────────────────────── */\n\ntype Subscriber = () => void;\nconst subscribers = new Set<Subscriber>();\nfunction notifySubscribers() {\n subscribers.forEach((fn) => fn());\n}\nfunction subscribe(cb: Subscriber): () => void {\n subscribers.add(cb);\n return () => { subscribers.delete(cb); };\n}\n\nconst curatedCache = new Map<IconPack, IconMap>();\nconst extendedCache = new Map<IconPack, IconMap>();\n\nfunction useCuratedMap(pack: IconPack): IconMap | null {\n return useSyncExternalStore(\n subscribe,\n () => curatedCache.get(pack) ?? null,\n () => null,\n );\n}\n\nfunction useExtendedMap(pack: IconPack): IconMap | null {\n return useSyncExternalStore(\n subscribe,\n () => extendedCache.get(pack) ?? null,\n () => null,\n );\n}\n\n/* ── Pack loading ────────────────────────────────────────────────\n Each pack is isolated in its own file under ./icon-packs/, and reads\n its package through an import handed to it: the app's own for its\n theme's pack (registerIconPack, Sprint 25.21 P1), the library's own\n for Lucide. No file the library publishes names a pack the app may\n not have installed, so no bundler looks for one: Next.js's webpack\n resolved every pack the library named, and failed the build.\n\n Loading is two-phase:\n 1. Curated map (50 static named imports) — available as soon as\n the pack chunk loads, cached and notified immediately.\n 2. Extended map (full barrel import + kebab-case iteration) —\n loaded in the background, cached and notified when ready. */\n\nconst loadingPacks = new Set<IconPack>();\nconst failedPacks = new Set<IconPack>();\n\ntype PackModule = {\n loadCurated(load: PackLoad): Promise<IconMap>;\n loadExtended(load: PackLoad): Promise<IconMap>;\n};\n\n/** Each pack's import: Lucide's is the library's own (every app installs it); the others are the app's, registered. */\nconst packImports = new Map<IconPack, PackLoad>([[\"lucide\", () => import(\"lucide-react\")]]);\n\n/**\n * The theme's icon pack, as the app's own import (Sprint 25.21 P1): `registerIconPack('feather', () =>\n * import('react-icons/fi'))` at the top of the shell, before any preloadPack. The library names no pack itself, so an\n * app's bundler only ever meets the pack the app installed. A pack drawn before it is registered draws Lucide's icons,\n * and draws its own once registered. Lucide needs no line.\n */\nexport function registerIconPack(pack: RegisteredIconPack, load: PackLoad): void {\n packImports.set(pack, load);\n // a draw that met the pack unregistered loads it now: its icons redraw once the pack's map arrives (a notice alone\n // changes no snapshot, so nothing would redraw to ask)\n if (failedPacks.delete(pack)) void loadPack(pack);\n}\n\n/** The npm package each pack needs — named in the error when a pack is missing. */\nconst PEER_PACKAGE: Record<IconPack, string> = {\n lucide: \"lucide-react\",\n phosphor: \"@phosphor-icons/react\",\n heroicons: \"@heroicons/react\",\n tabler: \"@tabler/icons-react\",\n feather: \"react-icons\",\n};\n\nasync function loadPack(pack: IconPack): Promise<void> {\n if (curatedCache.has(pack) || loadingPacks.has(pack) || failedPacks.has(pack)) return;\n loadingPacks.add(pack);\n\n // Each pack module reaches its package only through the import handed to it\n // (see icon-packs/*.ts), so the chunk import below always succeeds and it is\n // loadCurated() that rejects when the package is not installed.\n const load = packImports.get(pack);\n if (!load) {\n // not registered: Lucide's icons stand in (Icon's third tier), and the developer is told the line to add\n failedPacks.add(pack);\n loadingPacks.delete(pack);\n const lines = iconPackRegistration(pack as RegisteredIconPack);\n warnOnce(\"IconProvider\", `the ${pack} icon pack isn't registered, so its icons are drawn from Lucide. At the top of the shell, before any preloadPack: ${lines.importLine}; ${lines.call}`);\n return;\n }\n let mod: PackModule;\n let curated: IconMap;\n try {\n switch (pack) {\n case \"lucide\": mod = await import(\"./icon-packs/lucide\"); break;\n case \"phosphor\": mod = await import(\"./icon-packs/phosphor\"); break;\n case \"heroicons\": mod = await import(\"./icon-packs/heroicons\"); break;\n case \"tabler\": mod = await import(\"./icon-packs/tabler\"); break;\n case \"feather\": mod = await import(\"./icon-packs/feather\"); break;\n }\n curated = await mod!.loadCurated(load);\n } catch {\n loadingPacks.delete(pack);\n failedPacks.add(pack);\n console.error(`gridsmith-ui: ${PEER_PACKAGE[pack]} is not installed. Run: npm install ${PEER_PACKAGE[pack]}`);\n return;\n }\n\n curatedCache.set(pack, curated);\n notifySubscribers();\n\n mod!.loadExtended(load).then((extended) => {\n extendedCache.set(pack, extended);\n notifySubscribers();\n }).catch(() => {\n // Extended map failed (e.g. network error loading the full barrel).\n // Curated icons still work; extended names will not resolve.\n });\n}\n\n/** A pack's registered import, for the tests that load the packs directly; they register them first. */\nfunction importOf(pack: IconPack): PackLoad {\n const load = packImports.get(pack);\n if (!load) throw new Error(`gridsmith-ui: register the ${pack} icon pack first (registerIconPack)`);\n return load;\n}\n\n/** Exported for the icon-completeness test — not part of the public API. */\nexport const packLoaders: Record<IconPack, {\n curated: () => Promise<IconMap>;\n extended: () => Promise<IconMap>;\n}> = {\n lucide: { curated: () => import(\"./icon-packs/lucide\").then((m) => m.loadCurated(importOf(\"lucide\"))), extended: () => import(\"./icon-packs/lucide\").then((m) => m.loadExtended(importOf(\"lucide\"))) },\n phosphor: { curated: () => import(\"./icon-packs/phosphor\").then((m) => m.loadCurated(importOf(\"phosphor\"))), extended: () => import(\"./icon-packs/phosphor\").then((m) => m.loadExtended(importOf(\"phosphor\"))) },\n heroicons: { curated: () => import(\"./icon-packs/heroicons\").then((m) => m.loadCurated(importOf(\"heroicons\"))), extended: () => import(\"./icon-packs/heroicons\").then((m) => m.loadExtended(importOf(\"heroicons\"))) },\n tabler: { curated: () => import(\"./icon-packs/tabler\").then((m) => m.loadCurated(importOf(\"tabler\"))), extended: () => import(\"./icon-packs/tabler\").then((m) => m.loadExtended(importOf(\"tabler\"))) },\n feather: { curated: () => import(\"./icon-packs/feather\").then((m) => m.loadCurated(importOf(\"feather\"))), extended: () => import(\"./icon-packs/feather\").then((m) => m.loadExtended(importOf(\"feather\"))) },\n};\n\n/* ── Context ──────────────────────────────────────────────────── */\n\ninterface IconContextValue {\n pack: IconPack;\n weight: number;\n}\n\nconst IconCtx = createContext<IconContextValue>({ pack: \"lucide\", weight: 1.5 });\n\n/**\n * Sets the icon pack and stroke weight for every Icon inside it, and starts loading the pack. The app shell's generated\n * code puts it around the app with the theme's pack, so a page doesn't add its own.\n */\nexport function IconProvider({ pack, weight, children }: { pack: IconPack; weight: number; children: ReactNode }) {\n useEffect(() => {\n loadPack(pack);\n if (pack !== \"lucide\") loadPack(\"lucide\");\n }, [pack]);\n\n return <IconCtx.Provider value={{ pack, weight }}>{children}</IconCtx.Provider>;\n}\n\n/**\n * Start loading a pack's icon chunk before React mounts. Call this in your\n * app's entry point or shell for the fastest possible cold start, after the\n * pack is registered (every pack but Lucide, Sprint 25.21 P1):\n *\n * import { preloadPack, registerIconPack } from 'gridsmith-ui/icons'\n * registerIconPack('feather', () => import('react-icons/fi'))\n * preloadPack('feather')\n */\nexport function preloadPack(pack: IconPack): void {\n loadPack(pack);\n}\n\n/* ── Phosphor weight mapping ──────────────────────────────────── */\n\n/**\n * A stroke weight maps to a stroke weight only. `fill` and `duotone` change the\n * glyph itself (Phosphor's filled Check is a check inside a filled square), so\n * a theme's heaviest stroke must never reach them — the Sprint 22 bench found\n * every tick on a 2.5-weight theme rendered as a filled box (14 Sep 2026).\n */\nexport function getPhosphorWeight(numericWeight: number): \"thin\" | \"light\" | \"regular\" | \"bold\" {\n if (numericWeight <= 0.75) return \"thin\";\n if (numericWeight <= 1.25) return \"light\";\n if (numericWeight <= 1.75) return \"regular\";\n return \"bold\";\n}\n\n/* ── Icon component ───────────────────────────────────────────── */\n\n/**\n * `from`: the component slot that drew the icon (\"Button's iconLeft\"), named in the development warning when the theme's\n * pack has no icon by that name. A name no pack has draws nothing (Sprint 25.17 A26: an arrow or a word passed as an\n * icon was printed as text before slots took names).\n */\n/**\n * Whether a map holds an icon of its own by this name, never one its prototype lends: a slot now takes any string, and\n * \"constructor\", \"toString\" or \"__proto__\" read off a plain object drew junk or threw (Sprint 25.17's review). The icon\n * is then read off the map itself, so the compiler sees a component the map holds, not one made while rendering.\n */\nfunction owns(map: Record<string, IconComponent> | null | undefined, name: string): map is Record<string, IconComponent> {\n return !!map && Object.prototype.hasOwnProperty.call(map, name);\n}\n\n/**\n * An icon by name, drawn from the pack the IconProvider around it sets (the theme's, through the app shell), with Lucide\n * as the fallback when the pack lacks the name; `size` in pixels, 16 by default. A name neither has draws nothing and\n * warns once in development, where `from` names the caller.\n */\nexport function Icon({ name, size = 16, className, from }: { name: IconName; size?: number; className?: string; from?: string }) {\n const { pack, weight } = useContext(IconCtx);\n\n const curatedMap = useCuratedMap(pack);\n const extendedMap = useExtendedMap(pack);\n const lucideCurated = useCuratedMap(\"lucide\");\n const lucideExtended = useExtendedMap(\"lucide\");\n\n // Ensure the active pack is loading (covers cases where Icon mounts\n // before IconProvider's useEffect fires, e.g. during SSR hydration)\n if (!curatedCache.has(pack) && !loadingPacks.has(pack)) {\n loadPack(pack);\n }\n\n let Comp: IconComponent | undefined | null = null;\n let renderPack: IconPack = pack;\n\n // Tier 1: curated map (available once pack chunk has loaded)\n if (owns(curatedMap, name)) {\n Comp = curatedMap[name];\n }\n\n // Tier 2: extended map (available after barrel import completes)\n if (!Comp && owns(extendedMap, name)) {\n Comp = extendedMap[name];\n }\n\n // Tier 3: lucide fallback (curated first, then extended)\n if (!Comp && pack !== \"lucide\") {\n const fallback = owns(lucideCurated, name) ? lucideCurated[name] : owns(lucideExtended, name) ? lucideExtended[name] : undefined;\n if (fallback) {\n Comp = fallback;\n renderPack = \"lucide\";\n }\n }\n\n const allMapsLoaded = !!(curatedMap && extendedMap && (pack === \"lucide\" || (lucideCurated && lucideExtended)));\n const missing = !Comp && allMapsLoaded;\n useEffect(() => {\n if (missing) warnOnce(\"Icon\", `\"${name}\" is not an icon name in the ${pack} pack${pack === \"lucide\" ? \"\" : \" or Lucide\"}, so ${from ?? \"it\"} draws nothing. Pass an icon name, or an element.`);\n }, [missing, name, pack, from]);\n\n if (!Comp) {\n if (allMapsLoaded) return null;\n return <span style={{ display: \"inline-block\", width: size, height: size }} className={className} />;\n }\n\n if (renderPack === \"phosphor\") {\n return <Comp size={size} weight={getPhosphorWeight(weight)} className={className} />;\n }\n\n if (renderPack === \"heroicons\") {\n return <Comp className={className} style={{ width: size, height: size, strokeWidth: weight }} />;\n }\n\n if (renderPack === \"tabler\") {\n return <Comp size={size} stroke={weight} className={className} />;\n }\n\n if (renderPack === \"feather\") {\n return <Comp size={size} className={className} strokeWidth={weight} />;\n }\n\n return <Comp size={size} strokeWidth={weight} className={className} />;\n}\n\n/* ── Icon slots ───────────────────────────────────────────────── */\n\n/**\n * What an icon slot takes (Sprint 25.17 A26): an icon name, drawn as the theme's icon at the slot's size, or an element,\n * drawn as given. `IconName` accepts any string, so text type-checks: in development a name the theme's pack lacks warns,\n * naming the slot, and draws nothing; the engine flags a literal that is no icon name (gs:icon-slot-not-an-icon).\n */\nexport type IconSlot = IconName | ReactElement | false | null | undefined;\n\n/** What an icon slot holds, drawn: a name as the theme's icon at `size`, anything else as given. */\nexport function slotIcon(icon: IconSlot | ReactNode, size: number, from: string): ReactNode {\n if (typeof icon === \"string\") {\n // \"\" is no icon (`saved ? \"check\" : \"\"`): nothing drawn, nothing warned\n if (!icon) return null;\n return ICON_NAME_SHAPE.test(icon) ? <Icon name={icon} size={size} from={from} /> : <NotAnIcon text={icon} from={from} />;\n }\n return icon;\n}\n\n/** The shape of an icon name: kebab case, as every pack's names are written (\"arrow-right\", \"file-text\"). */\nconst ICON_NAME_SHAPE = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;\n\n/**\n * Whether a slot draws something: an element, or a string shaped as an icon name. Text (\"→\", \"Save\") draws nothing, so\n * a button keeps its even padding for it (Sprint 25.17's review); a name the pack lacks still warns, once its maps load.\n */\nexport function slotDraws(icon: IconSlot | ReactNode): boolean {\n if (typeof icon === \"string\") return ICON_NAME_SHAPE.test(icon);\n return icon != null && icon !== false && icon !== true;\n}\n\n/** Text given to an icon slot: it draws nothing, and says so in development, naming the slot. */\nfunction NotAnIcon({ text, from }: { text: string; from: string }) {\n useEffect(() => {\n warnOnce(\"Icon\", `\"${text}\" is not an icon name, so ${from} draws nothing. Words go in the label; pass an icon name, or an element.`);\n }, [text, from]);\n return null;\n}\n"]}
|
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { useFieldControl, Dropdown } from './chunk-IZW5XHKA.js';
|
|
3
|
+
import { useReportedChange } from './chunk-72SDAX54.js';
|
|
4
|
+
import { ControlSizeContext } from './chunk-HQFTP7Y5.js';
|
|
5
|
+
import { cn, rovingTabStop, FOCUS_RING } from './chunk-EZ3ZGFGA.js';
|
|
6
|
+
import { jsx, jsxs } from 'react/jsx-runtime';
|
|
7
|
+
import { useContext, useRef, useState, useCallback, useLayoutEffect, useEffect } from 'react';
|
|
8
|
+
|
|
9
|
+
function Bone({ className, style, "data-gs": marker }) {
|
|
10
|
+
return /* @__PURE__ */ jsx(
|
|
11
|
+
"div",
|
|
12
|
+
{
|
|
13
|
+
"data-gs": marker,
|
|
14
|
+
className: cn("rounded-[var(--ds-radius-sm)] bg-[var(--ds-bg-emphasis)] animate-pulse", className),
|
|
15
|
+
style
|
|
16
|
+
}
|
|
17
|
+
);
|
|
18
|
+
}
|
|
19
|
+
function Skeleton({ variant = "text", lines = 3, width, height, className }) {
|
|
20
|
+
if (variant === "custom") {
|
|
21
|
+
return /* @__PURE__ */ jsx(Bone, { "data-gs": "skeleton", className, style: { width, height } });
|
|
22
|
+
}
|
|
23
|
+
if (variant === "avatar") {
|
|
24
|
+
return /* @__PURE__ */ jsx(Bone, { "data-gs": "skeleton", className: cn("h-10 w-10 rounded-[var(--ds-radius-full)]", className) });
|
|
25
|
+
}
|
|
26
|
+
if (variant === "card") {
|
|
27
|
+
return /* @__PURE__ */ jsxs("div", { "data-gs": "skeleton", className: cn("space-y-ds-3", className), children: [
|
|
28
|
+
/* @__PURE__ */ jsx(Bone, { className: "h-32 w-full rounded-[var(--ds-radius-md)]" }),
|
|
29
|
+
/* @__PURE__ */ jsx(Bone, { className: "h-4 w-3/4" }),
|
|
30
|
+
/* @__PURE__ */ jsx(Bone, { className: "h-4 w-1/2" })
|
|
31
|
+
] });
|
|
32
|
+
}
|
|
33
|
+
if (variant === "table-row") {
|
|
34
|
+
return /* @__PURE__ */ jsxs("div", { "data-gs": "skeleton", className: cn("flex gap-ds-4", className), children: [
|
|
35
|
+
/* @__PURE__ */ jsx(Bone, { className: "h-4 flex-[3]" }),
|
|
36
|
+
/* @__PURE__ */ jsx(Bone, { className: "h-4 flex-[2]" }),
|
|
37
|
+
/* @__PURE__ */ jsx(Bone, { className: "h-4 flex-1" }),
|
|
38
|
+
/* @__PURE__ */ jsx(Bone, { className: "h-4 flex-1" })
|
|
39
|
+
] });
|
|
40
|
+
}
|
|
41
|
+
const widths = ["w-3/4", "w-1/2", "w-5/6", "w-2/3", "w-4/5"];
|
|
42
|
+
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)) });
|
|
43
|
+
}
|
|
44
|
+
function optionsKeyForFit(options) {
|
|
45
|
+
return options.map((o) => `${o.value}:${o.label}`).join("|");
|
|
46
|
+
}
|
|
47
|
+
var sizes = {
|
|
48
|
+
xs: "text-xs px-ds-2",
|
|
49
|
+
sm: "text-xs px-ds-3",
|
|
50
|
+
md: "text-sm px-ds-3",
|
|
51
|
+
lg: "text-base px-ds-3"
|
|
52
|
+
};
|
|
53
|
+
var containerPadding = {
|
|
54
|
+
xs: "p-[2px] gap-[2px]",
|
|
55
|
+
sm: "p-ds-1 gap-ds-0.5",
|
|
56
|
+
md: "p-ds-1 gap-ds-0.5",
|
|
57
|
+
lg: "p-ds-1 gap-ds-0.5"
|
|
58
|
+
};
|
|
59
|
+
var indicatorInset = {
|
|
60
|
+
xs: "2px",
|
|
61
|
+
sm: "4px",
|
|
62
|
+
md: "4px",
|
|
63
|
+
lg: "4px"
|
|
64
|
+
};
|
|
65
|
+
function segmentRadius(inset, bordered) {
|
|
66
|
+
return `max(var(--ds-radius-sm), calc(var(--ds-radius-md) - ${inset}${bordered ? " - var(--ds-border-width)" : ""}))`;
|
|
67
|
+
}
|
|
68
|
+
function SegmentedControl({
|
|
69
|
+
value,
|
|
70
|
+
onChange: onChangeProp,
|
|
71
|
+
options,
|
|
72
|
+
size: sizeProp,
|
|
73
|
+
variant = "default",
|
|
74
|
+
fullWidth = false,
|
|
75
|
+
onHoverChange,
|
|
76
|
+
className,
|
|
77
|
+
"aria-label": ariaLabel,
|
|
78
|
+
maxOptions = 6,
|
|
79
|
+
...wiring
|
|
80
|
+
}) {
|
|
81
|
+
const onChange = useReportedChange(onChangeProp, value);
|
|
82
|
+
const contextSize = useContext(ControlSizeContext);
|
|
83
|
+
const control = useFieldControl({ ...wiring, "aria-label": ariaLabel }, { component: "SegmentedControl" });
|
|
84
|
+
const { "aria-labelledby": _pageLabelledBy, ...dropdownWiring } = wiring;
|
|
85
|
+
const size = sizeProp ?? contextSize ?? "md";
|
|
86
|
+
const containerRef = useRef(null);
|
|
87
|
+
const [indicator, setIndicator] = useState({ left: 0, width: 0 });
|
|
88
|
+
const parentRef = useRef(null);
|
|
89
|
+
const neededRef = useRef(0);
|
|
90
|
+
const [overflows, setOverflows] = useState(false);
|
|
91
|
+
const measureFit = useCallback(() => {
|
|
92
|
+
const row = containerRef.current;
|
|
93
|
+
if (row) {
|
|
94
|
+
parentRef.current = row.parentElement;
|
|
95
|
+
if (row.scrollWidth > 0) neededRef.current = row.scrollWidth;
|
|
96
|
+
}
|
|
97
|
+
const parent = parentRef.current;
|
|
98
|
+
const needed = neededRef.current;
|
|
99
|
+
if (!parent || !needed) return;
|
|
100
|
+
const style = getComputedStyle(parent);
|
|
101
|
+
const available = parent.clientWidth - (parseFloat(style.paddingLeft) || 0) - (parseFloat(style.paddingRight) || 0);
|
|
102
|
+
if (available <= 0) return;
|
|
103
|
+
setOverflows((prev) => prev ? needed > available - 8 : needed > available);
|
|
104
|
+
}, []);
|
|
105
|
+
useLayoutEffect(() => {
|
|
106
|
+
measureFit();
|
|
107
|
+
const parent = parentRef.current;
|
|
108
|
+
if (!parent || typeof ResizeObserver === "undefined") return;
|
|
109
|
+
const observer = new ResizeObserver(measureFit);
|
|
110
|
+
observer.observe(parent);
|
|
111
|
+
return () => observer.disconnect();
|
|
112
|
+
}, [measureFit, optionsKeyForFit(options)]);
|
|
113
|
+
const collapsed = options.length > maxOptions || overflows;
|
|
114
|
+
const updateIndicator = useCallback(() => {
|
|
115
|
+
if (!containerRef.current) return;
|
|
116
|
+
const active = containerRef.current.querySelector("[data-active='true']");
|
|
117
|
+
if (active) {
|
|
118
|
+
setIndicator({
|
|
119
|
+
left: active.offsetLeft,
|
|
120
|
+
width: active.offsetWidth
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
}, []);
|
|
124
|
+
const optionsKey = options.map((o) => `${o.value}:${o.label}`).join("|");
|
|
125
|
+
useLayoutEffect(() => {
|
|
126
|
+
updateIndicator();
|
|
127
|
+
}, [value, optionsKey, updateIndicator]);
|
|
128
|
+
useEffect(() => {
|
|
129
|
+
const fonts = document.fonts;
|
|
130
|
+
if (!fonts?.ready) return;
|
|
131
|
+
let live = true;
|
|
132
|
+
fonts.ready.then(() => {
|
|
133
|
+
if (live) updateIndicator();
|
|
134
|
+
});
|
|
135
|
+
return () => {
|
|
136
|
+
live = false;
|
|
137
|
+
};
|
|
138
|
+
}, [updateIndicator]);
|
|
139
|
+
const [animate, setAnimate] = useState(false);
|
|
140
|
+
const select = useCallback((v) => {
|
|
141
|
+
setAnimate(true);
|
|
142
|
+
onChange(v);
|
|
143
|
+
}, [onChange]);
|
|
144
|
+
useLayoutEffect(() => {
|
|
145
|
+
if (collapsed || !containerRef.current || typeof ResizeObserver === "undefined") return;
|
|
146
|
+
const observer = new ResizeObserver(updateIndicator);
|
|
147
|
+
observer.observe(containerRef.current);
|
|
148
|
+
containerRef.current.querySelectorAll("button").forEach((b) => observer.observe(b));
|
|
149
|
+
return () => observer.disconnect();
|
|
150
|
+
}, [updateIndicator, optionsKey, collapsed]);
|
|
151
|
+
const containerHeight = {
|
|
152
|
+
xs: "h-ds-6",
|
|
153
|
+
sm: "h-[var(--ds-control-height-sm)]",
|
|
154
|
+
md: "h-[var(--ds-control-height-md)]",
|
|
155
|
+
lg: "h-[var(--ds-control-height-lg)]"
|
|
156
|
+
};
|
|
157
|
+
const tabStop = rovingTabStop(options, (o) => o.value === value, (o) => !!o.disabled);
|
|
158
|
+
const handleKeyDown = (e) => {
|
|
159
|
+
const enabled = options.filter((o) => !o.disabled);
|
|
160
|
+
if (enabled.length === 0) return;
|
|
161
|
+
const idx = Math.max(0, enabled.findIndex((o) => o.value === value));
|
|
162
|
+
let next = null;
|
|
163
|
+
if (e.key === "ArrowRight" || e.key === "ArrowDown") next = (idx + 1) % enabled.length;
|
|
164
|
+
else if (e.key === "ArrowLeft" || e.key === "ArrowUp") next = (idx - 1 + enabled.length) % enabled.length;
|
|
165
|
+
else if (e.key === "Home") next = 0;
|
|
166
|
+
else if (e.key === "End") next = enabled.length - 1;
|
|
167
|
+
if (next === null) return;
|
|
168
|
+
e.preventDefault();
|
|
169
|
+
select(enabled[next].value);
|
|
170
|
+
const buttons = containerRef.current?.querySelectorAll('[role="radio"]');
|
|
171
|
+
buttons?.forEach((b) => {
|
|
172
|
+
if (b.dataset.value === enabled[next].value) b.focus();
|
|
173
|
+
});
|
|
174
|
+
};
|
|
175
|
+
if (collapsed) {
|
|
176
|
+
return /* @__PURE__ */ jsx(
|
|
177
|
+
Dropdown,
|
|
178
|
+
{
|
|
179
|
+
...dropdownWiring,
|
|
180
|
+
"aria-label": control.shown ? void 0 : control.label,
|
|
181
|
+
"aria-labelledby": control.shown ? void 0 : wiring["aria-labelledby"],
|
|
182
|
+
size: size === "md" || size === "lg" ? "md" : "sm",
|
|
183
|
+
options: options.filter((o) => !o.disabled).map((o) => ({ id: o.value, label: o.label })),
|
|
184
|
+
value,
|
|
185
|
+
onChange: (v) => onChange(v),
|
|
186
|
+
className: cn("inline-block", fullWidth ? "w-full" : "w-auto", className)
|
|
187
|
+
}
|
|
188
|
+
);
|
|
189
|
+
}
|
|
190
|
+
return /* @__PURE__ */ jsxs(
|
|
191
|
+
"div",
|
|
192
|
+
{
|
|
193
|
+
ref: containerRef,
|
|
194
|
+
role: "radiogroup",
|
|
195
|
+
id: control.id,
|
|
196
|
+
"aria-label": control.labelId ? void 0 : control.label,
|
|
197
|
+
"aria-labelledby": control.labelId,
|
|
198
|
+
"aria-describedby": control.describedBy,
|
|
199
|
+
"aria-invalid": control.invalid || void 0,
|
|
200
|
+
"aria-required": control.required || void 0,
|
|
201
|
+
onKeyDown: handleKeyDown,
|
|
202
|
+
className: cn(
|
|
203
|
+
"relative items-center",
|
|
204
|
+
// Sprint 25.14 A21: in a Field's column a track that isn't full width keeps its own width; w-fit, not self-start,
|
|
205
|
+
// so a row or a centred column still aligns it (the review of 30 September)
|
|
206
|
+
fullWidth ? "flex w-full" : "inline-flex w-fit",
|
|
207
|
+
containerPadding[size],
|
|
208
|
+
containerHeight[size],
|
|
209
|
+
"bg-[var(--ds-bg-muted)]",
|
|
210
|
+
// The owner's review of 1 October 2026: the frame's corner is a field's, so the two change together with the
|
|
211
|
+
// theme's radius; the segments inside sit concentric with it (SEGMENT_RADIUS)
|
|
212
|
+
"rounded-[var(--ds-radius-md)]",
|
|
213
|
+
// The frame's edge is a button's: each segment is a labelled button, so the frame isn't what identifies the
|
|
214
|
+
// control, and WCAG 1.4.11 asks no 3:1 of it
|
|
215
|
+
variant === "default" && "border-[length:var(--ds-border-width)] border-[var(--ds-border-default)]",
|
|
216
|
+
control.invalid && "border-[length:var(--ds-border-width)] border-[var(--ds-status-error)]",
|
|
217
|
+
className
|
|
218
|
+
),
|
|
219
|
+
style: { "--ds-segment-radius": segmentRadius(indicatorInset[size], variant === "default" || !!control.invalid) },
|
|
220
|
+
children: [
|
|
221
|
+
/* @__PURE__ */ jsx(
|
|
222
|
+
"div",
|
|
223
|
+
{
|
|
224
|
+
className: cn(
|
|
225
|
+
"absolute rounded-[var(--ds-segment-radius)]",
|
|
226
|
+
"bg-[var(--ds-bg-surface)]",
|
|
227
|
+
// The selected segment is a kind of button, so it takes a secondary button's surface and edge, as the frame
|
|
228
|
+
// takes a button's edge (the owner, 3 October 2026: the 3:1 strong edge it kept from Sprint 25.14 drew the
|
|
229
|
+
// darker outline 25.19 took off the fields). It shows its state as the raised surface on the muted track, with
|
|
230
|
+
// the default variant's shadow and its label's stronger colour. While its segment has keyboard focus the shadow
|
|
231
|
+
// steps aside, as a button's does, so the segment's outline sits clear (Sprint 30, D7)
|
|
232
|
+
"border-[length:var(--ds-border-width)] border-[var(--ds-border-default)]",
|
|
233
|
+
variant === "default" && "shadow-[var(--ds-shadow-sm)]",
|
|
234
|
+
"[[role=radiogroup]:has([aria-checked=true]:focus-visible)_&]:shadow-none",
|
|
235
|
+
"transition-all motion-reduce:transition-none"
|
|
236
|
+
),
|
|
237
|
+
"data-animate": animate ? "true" : "false",
|
|
238
|
+
style: {
|
|
239
|
+
left: indicator.left,
|
|
240
|
+
width: indicator.width,
|
|
241
|
+
top: indicatorInset[size],
|
|
242
|
+
bottom: indicatorInset[size],
|
|
243
|
+
transitionDuration: animate ? "var(--ds-transition-speed)" : "0ms",
|
|
244
|
+
transitionTimingFunction: "var(--ds-transition-ease)"
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
),
|
|
248
|
+
options.map((opt, i) => /* @__PURE__ */ jsx(
|
|
249
|
+
"button",
|
|
250
|
+
{
|
|
251
|
+
type: "button",
|
|
252
|
+
role: "radio",
|
|
253
|
+
"aria-checked": opt.value === value,
|
|
254
|
+
"data-active": opt.value === value,
|
|
255
|
+
"data-value": opt.value,
|
|
256
|
+
tabIndex: i === tabStop ? 0 : -1,
|
|
257
|
+
disabled: opt.disabled,
|
|
258
|
+
onClick: () => !opt.disabled && select(opt.value),
|
|
259
|
+
onMouseEnter: onHoverChange && !opt.disabled ? () => onHoverChange(opt.value) : void 0,
|
|
260
|
+
onMouseLeave: onHoverChange ? () => onHoverChange(null) : void 0,
|
|
261
|
+
className: cn(
|
|
262
|
+
"relative z-10 inline-flex items-center justify-center self-stretch font-medium whitespace-nowrap",
|
|
263
|
+
fullWidth && "flex-1",
|
|
264
|
+
"rounded-[var(--ds-segment-radius)]",
|
|
265
|
+
"transition-colors cursor-pointer",
|
|
266
|
+
FOCUS_RING,
|
|
267
|
+
sizes[size],
|
|
268
|
+
opt.value === value ? "text-[var(--ds-text-primary)]" : "text-[var(--ds-text-muted)] hover:text-[var(--ds-text-secondary)]",
|
|
269
|
+
opt.disabled && "opacity-disabled cursor-not-allowed"
|
|
270
|
+
),
|
|
271
|
+
children: opt.label
|
|
272
|
+
},
|
|
273
|
+
opt.value
|
|
274
|
+
))
|
|
275
|
+
]
|
|
276
|
+
}
|
|
277
|
+
);
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
// ../../showcase/src/lib/title-text.ts
|
|
281
|
+
var TITLE_TEXT = "font-[family-name:var(--ds-font-display)] text-lg font-[var(--ds-weight-heading)] text-[var(--ds-text-primary)] break-words";
|
|
282
|
+
|
|
283
|
+
export { SegmentedControl, Skeleton, TITLE_TEXT };
|
|
284
|
+
//# sourceMappingURL=chunk-BHTAVJIG.js.map
|
|
285
|
+
//# sourceMappingURL=chunk-BHTAVJIG.js.map
|