@marktiderman/genesis-ui 2.7.0 → 2.9.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/EXTENDING.md +40 -1
- package/SKILL.md +198 -9
- package/dist/{AppShell-CX0M-dfc.d.ts → AppShell-LXQykEqe.d.ts} +47 -5
- package/dist/{chunk-M62CHO3N.js → chunk-4C4ESW6O.js} +9 -4
- package/dist/chunk-4C4ESW6O.js.map +1 -0
- package/dist/chunk-DIV4BYXX.js +58 -0
- package/dist/chunk-DIV4BYXX.js.map +1 -0
- package/dist/chunk-FUVWKTPL.js +19 -0
- package/dist/chunk-FUVWKTPL.js.map +1 -0
- package/dist/{chunk-PLIYF6KI.js → chunk-MPI33I4K.js} +2 -2
- package/dist/{chunk-PLIYF6KI.js.map → chunk-MPI33I4K.js.map} +1 -1
- package/dist/{chunk-JA3ZKULS.js → chunk-OITWCIG2.js} +3 -3
- package/dist/chunk-OITWCIG2.js.map +1 -0
- package/dist/chunk-PKFDGGZV.js +65 -0
- package/dist/chunk-PKFDGGZV.js.map +1 -0
- package/dist/{chunk-IXGAEECT.js → chunk-RKPS7ERS.js} +9 -5
- package/dist/chunk-RKPS7ERS.js.map +1 -0
- package/dist/{chunk-TXOZMKNW.js → chunk-S7NXD37O.js} +27 -26
- package/dist/chunk-S7NXD37O.js.map +1 -0
- package/dist/{chunk-ZPPLUGLA.js → chunk-UYI3GLFJ.js} +162 -19
- package/dist/chunk-UYI3GLFJ.js.map +1 -0
- package/dist/{chunk-CDYZRZ6U.js → chunk-WGMEVX3Y.js} +2 -2
- package/dist/chunk-WGMEVX3Y.js.map +1 -0
- package/dist/chunk-ZDHWNQTR.js +552 -0
- package/dist/chunk-ZDHWNQTR.js.map +1 -0
- package/dist/components/data/index.d.ts +306 -19
- package/dist/components/data/index.js +1177 -284
- package/dist/components/data/index.js.map +1 -1
- package/dist/components/layout/form-page.d.ts +1 -1
- package/dist/components/layout/form-page.js +1 -1
- package/dist/components/layout/index.d.ts +2 -2
- package/dist/components/layout/index.js +8 -8
- package/dist/components/patterns/index.d.ts +1 -1
- package/dist/components/patterns/index.js +5 -5
- package/dist/components/patterns/sortable-list.js +1 -1
- package/dist/components/rich-text/rich-text-editor.d.ts +194 -0
- package/dist/components/rich-text/rich-text-editor.js +833 -0
- package/dist/components/rich-text/rich-text-editor.js.map +1 -0
- package/dist/components/rich-text/rich-text.d.ts +51 -0
- package/dist/components/rich-text/rich-text.js +4 -0
- package/dist/components/rich-text/rich-text.js.map +1 -0
- package/dist/components/ui/alert-dialog.d.ts +12 -1
- package/dist/components/ui/alert-dialog.js +1 -1
- package/dist/hooks/index.d.ts +4 -3
- package/dist/hooks/index.js +5 -4
- package/dist/hooks/use-data-filters.d.ts +5 -3
- package/dist/hooks/use-data-filters.js +2 -2
- package/dist/hooks/use-resource-page.d.ts +2 -1
- package/dist/hooks/use-resource-page.js +3 -2
- package/dist/hooks/use-resource-url-state.d.ts +68 -0
- package/dist/hooks/use-resource-url-state.js +3 -0
- package/dist/hooks/use-resource-url-state.js.map +1 -0
- package/dist/hooks/use-view-preference.d.ts +17 -2
- package/dist/hooks/use-view-preference.js +2 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js +32 -32
- package/dist/{navigation-DBwcZ6vb.d.ts → navigation-Bha0R3Fu.d.ts} +6 -3
- package/dist/rich-text-doc-C54fME2C.d.ts +180 -0
- package/dist/router/react-router.d.ts +47 -0
- package/dist/router/react-router.js +53 -0
- package/dist/router/react-router.js.map +1 -0
- package/dist/router/tanstack-router.d.ts +47 -0
- package/dist/router/tanstack-router.js +105 -0
- package/dist/router/tanstack-router.js.map +1 -0
- package/dist/{use-resource-page-Dd2VUv30.d.ts → use-resource-page-BDnnCssE.d.ts} +83 -5
- package/package.json +61 -14
- package/dist/chunk-CDYZRZ6U.js.map +0 -1
- package/dist/chunk-FTTNGGLA.js +0 -20
- package/dist/chunk-FTTNGGLA.js.map +0 -1
- package/dist/chunk-IXGAEECT.js.map +0 -1
- package/dist/chunk-JA3ZKULS.js.map +0 -1
- package/dist/chunk-M62CHO3N.js.map +0 -1
- package/dist/chunk-TXOZMKNW.js.map +0 -1
- package/dist/chunk-ZPPLUGLA.js.map +0 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/components/rich-text/rich-text-doc.ts","../src/components/rich-text/rich-text.tsx"],"names":[],"mappings":"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","file":"chunk-ZDHWNQTR.js","sourcesContent":["/**\n * Rich text documents: the JSON shape, the allow-list that sanitizes it, and\n * the serializers (HTML, plain text) built on that allow-list.\n *\n * This file has no React and no editor dependency, so a server can import it\n * (through `@marktiderman/genesis-ui/rich-text`) to validate what a client\n * posted and to produce the HTML it stores or sends. Everything here works in\n * Node, edge runtimes and browsers alike: no DOM, no `DOMParser`.\n *\n * WHY JSON IS CANONICAL. The editor's document is a tree (ProseMirror). JSON\n * is that tree written down; HTML is one rendering of it. Storing JSON keeps\n * the structure the editor understands, so a later schema change is a data\n * migration instead of an HTML scraping job. HTML is derived: produce it from\n * the JSON with `richTextToHtml`, on the server, after `sanitizeRichTextDoc`.\n *\n * WHY AN ALLOW-LIST. Anything not named below is dropped: unknown node and\n * mark types, unknown attributes, links whose URL is not http(s), mailto, tel\n * or relative, and embeds whose URL is not a recognised YouTube, Vimeo or Loom\n * video. The serializers only ever emit tags this file writes itself, and\n * every text and attribute value goes through `escapeHtml`, so the output\n * cannot carry a `<script>`, an event handler or a `javascript:` URL no matter\n * what JSON came in.\n */\n\n/** A mark on a text node. `link` carries its URL in `attrs.href`. */\nexport interface RichTextMark {\n type: string;\n attrs?: Record<string, unknown>;\n}\n\n/**\n * One node of a rich text document. Structurally the same as Tiptap's\n * `JSONContent`, so editor output can be passed straight in.\n */\nexport interface RichTextNode {\n type: string;\n attrs?: Record<string, unknown>;\n content?: RichTextNode[];\n marks?: RichTextMark[];\n text?: string;\n}\n\n/** The root node of a rich text document. */\nexport interface RichTextDoc extends RichTextNode {\n type: \"doc\";\n content: RichTextNode[];\n}\n\n/** Heading levels a document may contain. `h1` is left to the page title. */\nexport type RichTextHeadingLevel = 2 | 3 | 4;\n\n/** Video hosts the `videoEmbed` node accepts. */\nexport type VideoProvider = \"youtube\" | \"vimeo\" | \"loom\";\n\n/** A video reference, reduced to what is needed to rebuild its player URL. */\nexport interface VideoEmbedAttrs {\n provider: VideoProvider;\n /** The host's video id (YouTube 11 chars, Vimeo digits, Loom alphanumeric). */\n id: string;\n /** Vimeo's privacy hash for unlisted videos (`vimeo.com/123/abcdef`). */\n hash?: string;\n /** Accessible name for the player frame. */\n title?: string;\n}\n\nexport interface SanitizeRichTextOptions {\n /** Heading levels to keep. Others move to the nearest kept level. Default `[2, 3, 4]`. */\n headingLevels?: readonly RichTextHeadingLevel[];\n /** Keep `videoEmbed` nodes. Default `true`. When false they are dropped. */\n embeds?: boolean;\n /**\n * Reject documents whose text is longer than this (characters, counted\n * after sanitizing). Throws `RichTextLimitError`. Off by default; set it on\n * the server for every write that accepts rich text from a client.\n */\n maxCharacters?: number;\n /**\n * Reject documents with more nodes than this (every node except the root,\n * text included, counted after sanitizing). Throws `RichTextLimitError`.\n * Off by default; set it on the server alongside `maxCharacters`.\n */\n maxNodes?: number;\n /**\n * What to do with content nested deeper than 32 levels. `\"drop\"` (default)\n * leaves it out, which suits rendering stored documents; `\"throw\"` rejects\n * the whole document with `RichTextLimitError`, which suits server writes,\n * so nothing an author wrote disappears without them being told.\n */\n onTooDeep?: \"drop\" | \"throw\";\n}\n\n/** Thrown by `sanitizeRichTextDoc` when a document breaks a limit it was given. */\nexport class RichTextLimitError extends Error {\n readonly reason: \"characters\" | \"nodes\" | \"depth\";\n readonly limit: number;\n /** The measured size; for `\"depth\"`, the limit plus one. */\n readonly actual: number;\n constructor(reason: \"characters\" | \"nodes\" | \"depth\", limit: number, actual: number) {\n super(\n reason === \"depth\"\n ? `Rich text is nested more than ${limit} levels deep`\n : `Rich text has ${actual} ${reason}; the limit is ${limit}`,\n );\n this.name = \"RichTextLimitError\";\n this.reason = reason;\n this.limit = limit;\n this.actual = actual;\n }\n}\n\nexport interface RichTextToHtmlOptions extends SanitizeRichTextOptions {\n /**\n * How video embeds are written. `\"iframe\"` (default) emits a player frame\n * pointing at the host's embed URL; `\"link\"` emits a plain link to the\n * video, for email and other places iframes do not run.\n */\n embedAs?: \"iframe\" | \"link\";\n}\n\nconst MAX_DEPTH = 32;\nconst MAX_TITLE_LENGTH = 200;\n\n/** An empty document: one empty paragraph, which is what the editor produces. */\nexport function emptyRichTextDoc(): RichTextDoc {\n return { type: \"doc\", content: [{ type: \"paragraph\" }] };\n}\n\n// ---------------------------------------------------------------------------\n// URLs\n// ---------------------------------------------------------------------------\n\nconst SAFE_PROTOCOLS = new Set([\"http:\", \"https:\", \"mailto:\", \"tel:\"]);\n\n/**\n * True when `url` is safe to put in an `href`: an absolute http(s), mailto or\n * tel URL, a root-relative path (`/lessons/1`) or a fragment (`#notes`).\n *\n * Rejects `javascript:`, `data:`, `vbscript:` and every other scheme, as well\n * as protocol-relative `//host` URLs and anything containing whitespace or\n * control characters (browsers strip those, which is how `java\\tscript:`\n * slips past naive checks).\n */\nexport function isSafeUrl(url: unknown): url is string {\n if (typeof url !== \"string\") return false;\n const value = url.trim();\n if (value === \"\" || value.length > 2048) return false;\n // eslint-disable-next-line no-control-regex\n if (/[\\u0000- \\u007f-\\u009f]/.test(value)) return false;\n if (value.startsWith(\"#\")) return true;\n if (value.startsWith(\"/\")) return !value.startsWith(\"//\") && !value.startsWith(\"/\\\\\");\n let parsed: URL;\n try {\n parsed = new URL(value);\n } catch {\n return false;\n }\n return SAFE_PROTOCOLS.has(parsed.protocol);\n}\n\n/**\n * Turn what a person typed into the link field into a safe href, or `null`.\n * `example.com/page` becomes `https://example.com/page`; an email address\n * becomes a `mailto:` link.\n */\nexport function normalizeLinkUrl(input: string): string | null {\n const value = input.trim();\n if (value === \"\") return null;\n if (isSafeUrl(value)) return value;\n if (/^[^\\s@/:]+@[^\\s@/]+\\.[^\\s@/]+$/.test(value)) {\n const mail = `mailto:${value}`;\n return isSafeUrl(mail) ? mail : null;\n }\n // No scheme but looks like a host: assume https.\n if (!/^[a-z][a-z0-9+.-]*:/i.test(value) && /^[^\\s/]+\\.[^\\s/]+/.test(value)) {\n const withScheme = `https://${value}`;\n return isSafeUrl(withScheme) ? withScheme : null;\n }\n return null;\n}\n\n// ---------------------------------------------------------------------------\n// Video embeds\n// ---------------------------------------------------------------------------\n\nconst YOUTUBE_ID = /^[A-Za-z0-9_-]{11}$/;\nconst VIMEO_ID = /^\\d{1,12}$/;\nconst VIMEO_HASH = /^[a-f0-9]{6,32}$/;\nconst LOOM_ID = /^[A-Za-z0-9]{8,64}$/;\n\nfunction hostOf(url: URL): string {\n return url.hostname.toLowerCase().replace(/^www\\./, \"\").replace(/^m\\./, \"\");\n}\n\n/**\n * Recognise a YouTube, Vimeo or Loom video URL (watch, share, short and embed\n * forms) and reduce it to its provider and id. Returns `null` for anything\n * else, including other hosts that merely look similar.\n */\nexport function parseVideoUrl(input: string): VideoEmbedAttrs | null {\n let url: URL;\n try {\n url = new URL(input.trim());\n } catch {\n return null;\n }\n if (url.protocol !== \"https:\" && url.protocol !== \"http:\") return null;\n const host = hostOf(url);\n const parts = url.pathname.split(\"/\").filter(Boolean);\n\n if (host === \"youtube.com\" || host === \"youtube-nocookie.com\" || host === \"music.youtube.com\") {\n let id: string | null | undefined = null;\n if (parts[0] === \"watch\") id = url.searchParams.get(\"v\");\n else if (parts[0] === \"embed\" || parts[0] === \"shorts\" || parts[0] === \"live\" || parts[0] === \"v\")\n id = parts[1];\n return id && YOUTUBE_ID.test(id) ? { provider: \"youtube\", id } : null;\n }\n if (host === \"youtu.be\") {\n const id = parts[0];\n return id && YOUTUBE_ID.test(id) ? { provider: \"youtube\", id } : null;\n }\n if (host === \"vimeo.com\" || host === \"player.vimeo.com\") {\n // vimeo.com/123, vimeo.com/123/abcdef, vimeo.com/channels/x/123,\n // player.vimeo.com/video/123?h=abcdef\n // vimeo.com/showcase/1/video/2 and album/…/video/2: the video follows \"video\".\n const videoAt = parts.lastIndexOf(\"video\");\n const idIndex =\n videoAt !== -1 && parts[videoAt + 1] && VIMEO_ID.test(parts[videoAt + 1]!)\n ? videoAt + 1\n : parts.findIndex((p) => VIMEO_ID.test(p));\n if (idIndex === -1) return null;\n const id = parts[idIndex]!;\n // A privacy hash is the path segment right after the id only in the short\n // `vimeo.com/<id>/<hash>` form; otherwise it is the `h` query parameter.\n const hash = url.searchParams.get(\"h\") ?? (host === \"vimeo.com\" && idIndex === 0 ? parts[1] : undefined);\n return hash && VIMEO_HASH.test(hash)\n ? { provider: \"vimeo\", id, hash }\n : { provider: \"vimeo\", id };\n }\n if (host === \"loom.com\") {\n if (parts[0] !== \"share\" && parts[0] !== \"embed\") return null;\n const id = parts[1];\n return id && LOOM_ID.test(id) ? { provider: \"loom\", id } : null;\n }\n return null;\n}\n\n/** Validate untrusted embed attributes, returning a clean copy or `null`. */\nexport function sanitizeVideoEmbedAttrs(attrs: unknown): VideoEmbedAttrs | null {\n if (!attrs || typeof attrs !== \"object\") return null;\n const a = attrs as Record<string, unknown>;\n const id = typeof a.id === \"string\" ? a.id : \"\";\n let clean: VideoEmbedAttrs | null = null;\n if (a.provider === \"youtube\" && YOUTUBE_ID.test(id)) clean = { provider: \"youtube\", id };\n else if (a.provider === \"vimeo\" && VIMEO_ID.test(id)) {\n clean = { provider: \"vimeo\", id };\n if (typeof a.hash === \"string\" && VIMEO_HASH.test(a.hash)) clean.hash = a.hash;\n } else if (a.provider === \"loom\" && LOOM_ID.test(id)) clean = { provider: \"loom\", id };\n if (!clean) return null;\n if (typeof a.title === \"string\" && a.title.trim() !== \"\") {\n clean.title = a.title.trim().slice(0, MAX_TITLE_LENGTH);\n }\n return clean;\n}\n\nconst PROVIDER_NAMES: Record<VideoProvider, string> = {\n youtube: \"YouTube\",\n vimeo: \"Vimeo\",\n loom: \"Loom\",\n};\n\n/** The player URL for an embed. Built from validated parts only. */\nexport function videoEmbedSrc(attrs: VideoEmbedAttrs): string {\n switch (attrs.provider) {\n case \"youtube\":\n // The privacy-enhanced host: no tracking cookies until the viewer plays.\n return `https://www.youtube-nocookie.com/embed/${attrs.id}`;\n case \"vimeo\":\n return `https://player.vimeo.com/video/${attrs.id}${attrs.hash ? `?h=${attrs.hash}` : \"\"}`;\n case \"loom\":\n return `https://www.loom.com/embed/${attrs.id}`;\n }\n}\n\n/** The page URL for an embed (what a viewer would open in a new tab). */\nexport function videoWatchUrl(attrs: VideoEmbedAttrs): string {\n switch (attrs.provider) {\n case \"youtube\":\n return `https://www.youtube.com/watch?v=${attrs.id}`;\n case \"vimeo\":\n return `https://vimeo.com/${attrs.id}${attrs.hash ? `/${attrs.hash}` : \"\"}`;\n case \"loom\":\n return `https://www.loom.com/share/${attrs.id}`;\n }\n}\n\n/** Accessible name for an embed's frame: its title, or \"YouTube video\" etc. */\nexport function videoEmbedTitle(attrs: VideoEmbedAttrs): string {\n return attrs.title || `${PROVIDER_NAMES[attrs.provider]} video`;\n}\n\n/**\n * Attributes for an embed's `<iframe>`. Shared by the HTML serializer, the\n * React renderer and the editor so all three emit the same frame.\n *\n * `sandbox` keeps the player away from top-level navigation and forms.\n * `allow-same-origin` is safe here because the frame is always cross-origin\n * (the player host), and the players need it for their own storage.\n */\nexport const VIDEO_IFRAME_ATTRS = {\n allow:\n \"accelerometer; autoplay; clipboard-write; encrypted-media; fullscreen; gyroscope; picture-in-picture\",\n loading: \"lazy\",\n referrerPolicy: \"strict-origin-when-cross-origin\",\n sandbox:\n \"allow-scripts allow-same-origin allow-presentation allow-popups allow-popups-to-escape-sandbox\",\n} as const;\n\n// ---------------------------------------------------------------------------\n// Sanitizer\n// ---------------------------------------------------------------------------\n\nconst BLOCK_TYPES = new Set([\n \"paragraph\",\n \"heading\",\n \"bulletList\",\n \"orderedList\",\n \"listItem\",\n \"blockquote\",\n \"videoEmbed\",\n]);\n\ninterface Ctx {\n levels: readonly RichTextHeadingLevel[];\n embeds: boolean;\n throwTooDeep: boolean;\n}\n\n/** True when `depth` is past the limit; throws instead when the caller asked for that. */\nfunction tooDeep(depth: number, ctx: Ctx): boolean {\n if (depth <= MAX_DEPTH) return false;\n if (ctx.throwTooDeep) throw new RichTextLimitError(\"depth\", MAX_DEPTH, depth);\n return true;\n}\n\nfunction measure(nodes: RichTextNode[] | undefined, size = { nodes: 0, characters: 0 }) {\n for (const node of nodes ?? []) {\n size.nodes += 1;\n if (typeof node.text === \"string\") size.characters += node.text.length;\n measure(node.content, size);\n }\n return size;\n}\n\n/**\n * A cheap check on the raw input before sanitizing, so an oversized payload\n * is refused without walking all of it. It runs only when a limit is set,\n * iterates without recursion, and stops as soon as the input is clearly over:\n * more than 4x `maxNodes` raw nodes (the sanitizer drops wrappers and\n * unknown nodes) or 2x `maxCharacters` of raw text. Inputs within those\n * margins get the exact check after sanitizing.\n */\nfunction precheckRawSize(root: RichTextNode, options: SanitizeRichTextOptions): void {\n const nodeBudget = options.maxNodes !== undefined ? options.maxNodes * 4 : Infinity;\n const charBudget = options.maxCharacters !== undefined ? options.maxCharacters * 2 : Infinity;\n let nodes = 0;\n let characters = 0;\n const stack: unknown[] = [root.content];\n while (stack.length > 0) {\n const list = stack.pop();\n if (!Array.isArray(list)) continue;\n for (const node of list) {\n if (!node || typeof node !== \"object\") continue;\n nodes += 1;\n const text = (node as RichTextNode).text;\n if (typeof text === \"string\") characters += text.length;\n if (nodes > nodeBudget) throw new RichTextLimitError(\"nodes\", options.maxNodes!, nodes);\n if (characters > charBudget) {\n throw new RichTextLimitError(\"characters\", options.maxCharacters!, characters);\n }\n stack.push((node as RichTextNode).content);\n }\n }\n}\n\nfunction sanitizeMarks(marks: unknown): RichTextMark[] | undefined {\n if (!Array.isArray(marks)) return undefined;\n const out: RichTextMark[] = [];\n const seen = new Set<string>();\n for (const mark of marks) {\n if (!mark || typeof mark !== \"object\") continue;\n const type = (mark as RichTextMark).type;\n if (seen.has(type)) continue;\n if (type === \"bold\" || type === \"italic\" || type === \"underline\") {\n out.push({ type });\n seen.add(type);\n } else if (type === \"link\") {\n const href = (mark as RichTextMark).attrs?.href;\n if (isSafeUrl(href)) {\n out.push({ type: \"link\", attrs: { href: href.trim() } });\n seen.add(type);\n }\n }\n }\n return out.length > 0 ? out : undefined;\n}\n\nfunction sanitizeInline(nodes: unknown, depth: number, ctx: Ctx): RichTextNode[] {\n if (!Array.isArray(nodes) || tooDeep(depth, ctx)) return [];\n const out: RichTextNode[] = [];\n for (const node of nodes) {\n if (!node || typeof node !== \"object\") continue;\n const n = node as RichTextNode;\n if (n.type === \"text\") {\n if (typeof n.text !== \"string\" || n.text === \"\") continue;\n const marks = sanitizeMarks(n.marks);\n out.push(marks ? { type: \"text\", text: n.text, marks } : { type: \"text\", text: n.text });\n } else if (n.type === \"hardBreak\") {\n out.push({ type: \"hardBreak\" });\n } else if (Array.isArray(n.content)) {\n // An inline wrapper we do not know: keep its text, drop the wrapper.\n for (const child of sanitizeInline(n.content, depth + 1, ctx)) out.push(child);\n }\n }\n return out;\n}\n\nfunction withContent(\n type: string,\n content: RichTextNode[],\n attrs?: Record<string, unknown>,\n): RichTextNode {\n const node: RichTextNode = { type };\n if (attrs) node.attrs = attrs;\n if (content.length > 0) node.content = content;\n return node;\n}\n\nfunction nearestLevel(\n level: unknown,\n levels: readonly RichTextHeadingLevel[],\n): RichTextHeadingLevel {\n const n = typeof level === \"number\" ? level : 2;\n if ((levels as readonly number[]).includes(n)) return n as RichTextHeadingLevel;\n const sorted = [...levels].sort((a, b) => Math.abs(a - n) - Math.abs(b - n) || a - b);\n return sorted[0] ?? 2;\n}\n\nfunction sanitizeListItems(nodes: unknown, ctx: Ctx, depth: number): RichTextNode[] {\n if (!Array.isArray(nodes) || tooDeep(depth, ctx)) return [];\n const items: RichTextNode[] = [];\n for (const node of nodes) {\n if (!node || typeof node !== \"object\") continue;\n const n = node as RichTextNode;\n const inner = n.type === \"listItem\" ? n.content : [n];\n // The editor's list item is `paragraph block*`, so headings, quotes and\n // embeds may follow the first paragraph; keep them rather than losing\n // content the author can see.\n const blocks = sanitizeBlocks(inner, ctx, depth + 1);\n // A list item starts with a paragraph in the editor's schema.\n if (blocks.length === 0 || blocks[0]!.type !== \"paragraph\") blocks.unshift({ type: \"paragraph\" });\n items.push({ type: \"listItem\", content: blocks });\n }\n return items;\n}\n\nfunction sanitizeBlocks(nodes: unknown, ctx: Ctx, depth: number): RichTextNode[] {\n if (!Array.isArray(nodes) || tooDeep(depth, ctx)) return [];\n const out: RichTextNode[] = [];\n let pendingInline: RichTextNode[] = [];\n const flushInline = () => {\n if (pendingInline.length > 0) out.push(withContent(\"paragraph\", pendingInline));\n pendingInline = [];\n };\n\n for (const node of nodes) {\n if (!node || typeof node !== \"object\") continue;\n const n = node as RichTextNode;\n if (n.type === \"text\" || n.type === \"hardBreak\") {\n // Stray inline content at block level: gather it into a paragraph.\n for (const child of sanitizeInline([n], depth, ctx)) pendingInline.push(child);\n continue;\n }\n flushInline();\n switch (n.type) {\n case \"paragraph\":\n out.push(withContent(\"paragraph\", sanitizeInline(n.content, depth + 1, ctx)));\n break;\n case \"heading\":\n out.push(\n withContent(\"heading\", sanitizeInline(n.content, depth + 1, ctx), {\n level: nearestLevel(n.attrs?.level, ctx.levels),\n }),\n );\n break;\n case \"bulletList\":\n case \"orderedList\": {\n const items = sanitizeListItems(n.content, ctx, depth);\n if (items.length === 0) break;\n const start = n.attrs?.start;\n const attrs =\n n.type === \"orderedList\" &&\n typeof start === \"number\" &&\n Number.isInteger(start) &&\n start > 1\n ? { start: Math.min(start, 1_000_000) }\n : undefined;\n out.push(withContent(n.type, items, attrs));\n break;\n }\n case \"listItem\":\n // A list item outside a list: keep its blocks.\n for (const child of sanitizeBlocks(n.content, ctx, depth + 1)) out.push(child);\n break;\n case \"blockquote\": {\n const inner = sanitizeBlocks(n.content, ctx, depth + 1);\n out.push(withContent(\"blockquote\", inner.length > 0 ? inner : [{ type: \"paragraph\" }]));\n break;\n }\n case \"videoEmbed\": {\n if (!ctx.embeds) break;\n const attrs = sanitizeVideoEmbedAttrs(n.attrs);\n if (attrs) out.push({ type: \"videoEmbed\", attrs: { ...attrs } });\n break;\n }\n default:\n // Unknown block (code block, table, image…): keep its text as a\n // paragraph rather than silently losing what someone wrote.\n if (Array.isArray(n.content)) {\n const nested = n.content.some(\n (c) => !!c && typeof c === \"object\" && BLOCK_TYPES.has((c as RichTextNode).type),\n );\n if (nested) for (const child of sanitizeBlocks(n.content, ctx, depth + 1)) out.push(child);\n else {\n const inline = sanitizeInline(n.content, depth + 1, ctx);\n if (inline.length > 0) out.push(withContent(\"paragraph\", inline));\n }\n }\n }\n }\n flushInline();\n return out;\n}\n\n/**\n * Validate an untrusted value as a rich text document and return a clean\n * copy that contains only allow-listed nodes, marks and attributes.\n *\n * `null`, a string or a malformed object becomes an empty document. Run\n * this on the server on every write, before storing the JSON or deriving\n * HTML from it, with limits so a client cannot store an oversized or\n * pathologically nested document:\n *\n * ```ts\n * sanitizeRichTextDoc(body, { maxCharacters: 50_000, maxNodes: 5_000, onTooDeep: \"throw\" });\n * ```\n *\n * Without limits it never throws; with them it throws `RichTextLimitError`.\n */\nexport function sanitizeRichTextDoc(\n input: unknown,\n options: SanitizeRichTextOptions = {},\n): RichTextDoc {\n const levels =\n options.headingLevels && options.headingLevels.length > 0\n ? options.headingLevels\n : ([2, 3, 4] as const);\n const ctx: Ctx = { levels, embeds: options.embeds ?? true, throwTooDeep: options.onTooDeep === \"throw\" };\n const root = input && typeof input === \"object\" ? (input as RichTextNode) : null;\n if (root && (options.maxCharacters !== undefined || options.maxNodes !== undefined)) {\n precheckRawSize(root, options);\n }\n const content = root && root.type === \"doc\" ? sanitizeBlocks(root.content, ctx, 0) : [];\n // Trailing empty paragraphs carry no meaning (the editor keeps one after\n // the last block so there is always somewhere to type); drop them so\n // stored documents and derived HTML do not end in `<p></p>`.\n while (content.length > 1) {\n const last = content[content.length - 1]!;\n if (last.type !== \"paragraph\" || last.content) break;\n content.pop();\n }\n if (options.maxCharacters !== undefined || options.maxNodes !== undefined) {\n const size = measure(content);\n if (options.maxCharacters !== undefined && size.characters > options.maxCharacters) {\n throw new RichTextLimitError(\"characters\", options.maxCharacters, size.characters);\n }\n if (options.maxNodes !== undefined && size.nodes > options.maxNodes) {\n throw new RichTextLimitError(\"nodes\", options.maxNodes, size.nodes);\n }\n }\n return { type: \"doc\", content: content.length > 0 ? content : [{ type: \"paragraph\" }] };\n}\n\n/** True when `value` is shaped like a rich text document (`{ type: \"doc\" }`). */\nexport function isRichTextDoc(value: unknown): value is RichTextDoc {\n return (\n !!value &&\n typeof value === \"object\" &&\n (value as RichTextNode).type === \"doc\" &&\n Array.isArray((value as RichTextNode).content)\n );\n}\n\n// ---------------------------------------------------------------------------\n// Serializers\n// ---------------------------------------------------------------------------\n\n/** Escape text for use in HTML element content and quoted attributes. */\nexport function escapeHtml(value: string): string {\n return value\n .replace(/&/g, \"&\")\n .replace(/</g, \"<\")\n .replace(/>/g, \">\")\n .replace(/\"/g, \""\")\n .replace(/'/g, \"'\");\n}\n\n/** True for absolute http(s) links, which open in a new tab. */\nexport function isExternalHref(href: string): boolean {\n return /^https?:\\/\\//i.test(href);\n}\n\nconst MARK_ORDER = [\"link\", \"bold\", \"italic\", \"underline\"];\n\n/** Marks in a fixed nesting order (link outermost) so output is stable. */\nexport function orderedMarks(marks: RichTextMark[] | undefined): RichTextMark[] {\n if (!marks) return [];\n return [...marks].sort((a, b) => MARK_ORDER.indexOf(a.type) - MARK_ORDER.indexOf(b.type));\n}\n\nfunction linkAttrs(href: string): string {\n const safe = escapeHtml(href);\n return isExternalHref(href)\n ? `href=\"${safe}\" target=\"_blank\" rel=\"noopener noreferrer nofollow\"`\n : `href=\"${safe}\"`;\n}\n\nfunction inlineToHtml(nodes: RichTextNode[] | undefined): string {\n if (!nodes) return \"\";\n let html = \"\";\n for (const node of nodes) {\n if (node.type === \"hardBreak\") {\n html += \"<br>\";\n continue;\n }\n let text = escapeHtml(node.text ?? \"\");\n const marks = orderedMarks(node.marks);\n for (let i = marks.length - 1; i >= 0; i--) {\n const mark = marks[i]!;\n if (mark.type === \"bold\") text = `<strong>${text}</strong>`;\n else if (mark.type === \"italic\") text = `<em>${text}</em>`;\n else if (mark.type === \"underline\") text = `<u>${text}</u>`;\n else if (mark.type === \"link\") text = `<a ${linkAttrs(String(mark.attrs?.href))}>${text}</a>`;\n }\n html += text;\n }\n return html;\n}\n\nfunction embedToHtml(attrs: VideoEmbedAttrs, embedAs: \"iframe\" | \"link\"): string {\n const title = escapeHtml(videoEmbedTitle(attrs));\n if (embedAs === \"link\") {\n return `<p><a ${linkAttrs(videoWatchUrl(attrs))}>${title}</a></p>`;\n }\n const a = VIDEO_IFRAME_ATTRS;\n const data = `data-video-embed=\"\" data-provider=\"${attrs.provider}\" data-id=\"${escapeHtml(attrs.id)}\"${\n attrs.hash ? ` data-hash=\"${escapeHtml(attrs.hash)}\"` : \"\"\n }${attrs.title ? ` data-title=\"${title}\"` : \"\"}`;\n return (\n `<div ${data}>` +\n `<iframe src=\"${escapeHtml(videoEmbedSrc(attrs))}\" title=\"${title}\" allow=\"${a.allow}\" allowfullscreen ` +\n `loading=\"${a.loading}\" referrerpolicy=\"${a.referrerPolicy}\" sandbox=\"${a.sandbox}\"></iframe>` +\n `</div>`\n );\n}\n\nfunction blocksToHtml(nodes: RichTextNode[] | undefined, embedAs: \"iframe\" | \"link\"): string {\n if (!nodes) return \"\";\n let html = \"\";\n for (const node of nodes) {\n switch (node.type) {\n case \"paragraph\":\n html += `<p>${inlineToHtml(node.content)}</p>`;\n break;\n case \"heading\": {\n const level = node.attrs?.level as number;\n html += `<h${level}>${inlineToHtml(node.content)}</h${level}>`;\n break;\n }\n case \"bulletList\":\n html += `<ul>${blocksToHtml(node.content, embedAs)}</ul>`;\n break;\n case \"orderedList\": {\n const start = node.attrs?.start;\n html += `<ol${typeof start === \"number\" ? ` start=\"${start}\"` : \"\"}>${blocksToHtml(node.content, embedAs)}</ol>`;\n break;\n }\n case \"listItem\":\n html += `<li>${blocksToHtml(node.content, embedAs)}</li>`;\n break;\n case \"blockquote\":\n html += `<blockquote>${blocksToHtml(node.content, embedAs)}</blockquote>`;\n break;\n case \"videoEmbed\":\n html += embedToHtml(node.attrs as unknown as VideoEmbedAttrs, embedAs);\n break;\n }\n }\n return html;\n}\n\n/**\n * Serialize a document to HTML. The input is sanitized first, so this is\n * safe to call on untrusted JSON; the output contains only the tags listed\n * in this file's header, with every value escaped.\n */\nexport function richTextToHtml(doc: unknown, options: RichTextToHtmlOptions = {}): string {\n const clean = sanitizeRichTextDoc(doc, options);\n return blocksToHtml(clean.content, options.embedAs ?? \"iframe\");\n}\n\nfunction collectText(nodes: RichTextNode[] | undefined, out: string[]): void {\n if (!nodes) return;\n for (const node of nodes) {\n if (node.type === \"text\") out.push(node.text ?? \"\");\n else if (node.type === \"hardBreak\") out.push(\"\\n\");\n else if (node.type === \"videoEmbed\") {\n const attrs = sanitizeVideoEmbedAttrs(node.attrs);\n if (attrs) out.push(`${videoEmbedTitle(attrs)} (${videoWatchUrl(attrs)})`, \"\\n\\n\");\n } else if (\n node.type === \"listItem\" ||\n node.type === \"bulletList\" ||\n node.type === \"orderedList\" ||\n node.type === \"blockquote\"\n ) {\n collectText(node.content, out);\n } else {\n collectText(node.content, out);\n out.push(\"\\n\\n\");\n }\n }\n}\n\n/**\n * Plain text for search indexes, previews and notifications. Blocks are\n * separated by blank lines; embeds become \"Title (url)\".\n */\nexport function richTextToPlainText(doc: unknown): string {\n const clean = sanitizeRichTextDoc(doc);\n const out: string[] = [];\n collectText(clean.content, out);\n return out.join(\"\").replace(/\\n{3,}/g, \"\\n\\n\").trim();\n}\n\n/** True when the document has no visible text and no embeds. */\nexport function isRichTextEmpty(doc: unknown): boolean {\n const clean = sanitizeRichTextDoc(doc);\n const walk = (nodes: RichTextNode[] | undefined): boolean =>\n !nodes ||\n nodes.every((n) => {\n if (n.type === \"videoEmbed\") return false;\n if (n.type === \"text\") return (n.text ?? \"\").trim() === \"\";\n return walk(n.content);\n });\n return walk(clean.content);\n}\n","/**\n * RichText — read-only rendering of a rich text document (the JSON that\n * `RichTextEditor` produces), plus the server-safe helpers for that JSON.\n *\n * This subpath has no editor dependency and no `\"use client\"`: it renders in\n * a React Server Component, during SSR, or on the client, and a server can\n * import the helpers on their own to sanitize posted JSON and derive HTML.\n *\n * @stability Beta\n */\nimport * as React from \"react\";\nimport { cn } from \"../../utils\";\nimport {\n type RichTextDoc,\n type RichTextHeadingLevel,\n type RichTextNode,\n type VideoEmbedAttrs,\n VIDEO_IFRAME_ATTRS,\n isExternalHref,\n orderedMarks,\n sanitizeRichTextDoc,\n videoEmbedSrc,\n videoEmbedTitle,\n videoWatchUrl,\n} from \"./rich-text-doc\";\n\n// The public, server-safe surface. `orderedMarks` and `isExternalHref` stay\n// internal: they are rendering details, not API.\nexport {\n emptyRichTextDoc,\n escapeHtml,\n isRichTextDoc,\n isRichTextEmpty,\n isSafeUrl,\n normalizeLinkUrl,\n parseVideoUrl,\n RichTextLimitError,\n richTextToHtml,\n richTextToPlainText,\n sanitizeRichTextDoc,\n sanitizeVideoEmbedAttrs,\n videoEmbedSrc,\n videoEmbedTitle,\n videoWatchUrl,\n VIDEO_IFRAME_ATTRS,\n} from \"./rich-text-doc\";\nexport type {\n RichTextDoc,\n RichTextHeadingLevel,\n RichTextMark,\n RichTextNode,\n RichTextToHtmlOptions,\n SanitizeRichTextOptions,\n VideoEmbedAttrs,\n VideoProvider,\n} from \"./rich-text-doc\";\n\n/**\n * Typography for rendered rich text, shared by `RichText` and the editor's\n * editable area so a document looks the same while writing and reading.\n * Exported for apps that render stored HTML themselves.\n */\nexport const richTextClassName = cn(\n \"text-sm leading-relaxed text-foreground break-words\",\n \"[&>*+*]:mt-3\",\n \"[&_h2]:text-xl [&_h2]:font-semibold [&_h2]:tracking-tight [&_h2]:mt-6 first:[&_h2]:mt-0\",\n \"[&_h3]:text-lg [&_h3]:font-semibold [&_h3]:mt-5 first:[&_h3]:mt-0\",\n \"[&_h4]:text-base [&_h4]:font-semibold [&_h4]:mt-4 first:[&_h4]:mt-0\",\n \"[&_ul]:list-disc [&_ol]:list-decimal [&_ul]:pl-6 [&_ol]:pl-6 [&_li]:my-1 [&_li>p]:m-0\",\n \"[&_blockquote]:border-l-2 [&_blockquote]:border-border [&_blockquote]:pl-4 [&_blockquote]:text-muted-foreground [&_blockquote]:italic\",\n \"[&_a]:text-primary [&_a]:underline [&_a]:underline-offset-4\",\n \"[&_[data-video-embed]]:relative [&_[data-video-embed]]:aspect-video [&_[data-video-embed]]:w-full [&_[data-video-embed]]:overflow-hidden [&_[data-video-embed]]:rounded-md [&_[data-video-embed]]:bg-muted\",\n \"[&_[data-video-embed]_iframe]:absolute [&_[data-video-embed]_iframe]:inset-0 [&_[data-video-embed]_iframe]:h-full [&_[data-video-embed]_iframe]:w-full [&_[data-video-embed]_iframe]:border-0\",\n);\n\nexport interface RichTextProps extends Omit<React.HTMLAttributes<HTMLDivElement>, \"children\"> {\n /**\n * The document to render (the editor's JSON). Untrusted input is fine: it\n * is sanitized before rendering, and nothing is set as raw HTML.\n * `null`/`undefined` render an empty container.\n */\n value: RichTextDoc | null | undefined;\n /** Render video embeds as players (`true`, default) or as plain links. */\n embeds?: boolean;\n /** Heading levels to keep; others move to the nearest kept level. Default `[2, 3, 4]`. */\n headingLevels?: readonly RichTextHeadingLevel[];\n /** Stable test/automation selector (emitted as `data-testid`). */\n testID?: string;\n}\n\nfunction renderInline(nodes: RichTextNode[] | undefined): React.ReactNode[] {\n if (!nodes) return [];\n return nodes.map((node, i) => {\n if (node.type === \"hardBreak\") return <br key={i} />;\n let el: React.ReactNode = node.text ?? \"\";\n const marks = orderedMarks(node.marks);\n for (let m = marks.length - 1; m >= 0; m--) {\n const mark = marks[m]!;\n if (mark.type === \"bold\") el = <strong>{el}</strong>;\n else if (mark.type === \"italic\") el = <em>{el}</em>;\n else if (mark.type === \"underline\") el = <u>{el}</u>;\n else if (mark.type === \"link\") {\n const href = String(mark.attrs?.href);\n el = isExternalHref(href) ? (\n <a href={href} target=\"_blank\" rel=\"noopener noreferrer nofollow\">\n {el}\n </a>\n ) : (\n <a href={href}>{el}</a>\n );\n }\n }\n return <React.Fragment key={i}>{el}</React.Fragment>;\n });\n}\n\nfunction VideoEmbedView({ attrs, asLink }: { attrs: VideoEmbedAttrs; asLink: boolean }) {\n const title = videoEmbedTitle(attrs);\n if (asLink) {\n return (\n <p>\n <a href={videoWatchUrl(attrs)} target=\"_blank\" rel=\"noopener noreferrer nofollow\">\n {title}\n </a>\n </p>\n );\n }\n return (\n <div data-video-embed=\"\" data-provider={attrs.provider}>\n <iframe\n src={videoEmbedSrc(attrs)}\n title={title}\n allow={VIDEO_IFRAME_ATTRS.allow}\n allowFullScreen\n loading={VIDEO_IFRAME_ATTRS.loading}\n referrerPolicy={VIDEO_IFRAME_ATTRS.referrerPolicy}\n sandbox={VIDEO_IFRAME_ATTRS.sandbox}\n />\n </div>\n );\n}\n\nfunction renderBlocks(nodes: RichTextNode[] | undefined, asLink: boolean): React.ReactNode[] {\n if (!nodes) return [];\n return nodes.map((node, i) => {\n switch (node.type) {\n case \"paragraph\":\n return <p key={i}>{renderInline(node.content)}</p>;\n case \"heading\": {\n const Tag = `h${node.attrs?.level as RichTextHeadingLevel}` as \"h2\" | \"h3\" | \"h4\";\n return <Tag key={i}>{renderInline(node.content)}</Tag>;\n }\n case \"bulletList\":\n return <ul key={i}>{renderBlocks(node.content, asLink)}</ul>;\n case \"orderedList\":\n return (\n <ol key={i} start={node.attrs?.start as number | undefined}>\n {renderBlocks(node.content, asLink)}\n </ol>\n );\n case \"listItem\":\n return <li key={i}>{renderBlocks(node.content, asLink)}</li>;\n case \"blockquote\":\n return <blockquote key={i}>{renderBlocks(node.content, asLink)}</blockquote>;\n case \"videoEmbed\":\n return (\n <VideoEmbedView key={i} attrs={node.attrs as unknown as VideoEmbedAttrs} asLink={asLink} />\n );\n default:\n return null;\n }\n });\n}\n\n/**\n * RichText — renders a rich text document as React elements.\n *\n * The document is passed through `sanitizeRichTextDoc` first, and rendering\n * never uses `dangerouslySetInnerHTML`: text is React-escaped, links keep\n * only http(s)/mailto/tel/relative URLs, and the only iframes are YouTube,\n * Vimeo and Loom players rebuilt from a validated id. That makes it safe for\n * stored or user-supplied JSON.\n *\n * Server-component safe (no hooks, no `\"use client\"`).\n *\n * @stability Beta\n */\nexport function RichText({\n value,\n embeds = true,\n headingLevels,\n className,\n testID,\n ...props\n}: RichTextProps) {\n // No hooks on purpose, so this stays usable as a React Server Component.\n const doc = value ? sanitizeRichTextDoc(value, headingLevels ? { headingLevels } : {}) : null;\n return (\n <div data-testid={testID} className={cn(richTextClassName, className)} {...props}>\n {doc ? renderBlocks(doc.content, !embeds) : null}\n </div>\n );\n}\n"]}
|
|
@@ -1,22 +1,26 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
2
|
import { ReactNode, ComponentType, ComponentProps, RefObject } from 'react';
|
|
3
|
-
import {
|
|
4
|
-
export { a as AppShellOptions, b as AppShellProps, N as NavItem, d as PageHeaderProps } from '../../AppShell-
|
|
3
|
+
import { f as PageHeaderAction, c as PageHeaderOptions, P as PageHeader$1, A as AppShell$1 } from '../../AppShell-LXQykEqe.js';
|
|
4
|
+
export { a as AppShellOptions, b as AppShellProps, N as NavItem, d as PageHeaderProps, e as ParentNavigation } from '../../AppShell-LXQykEqe.js';
|
|
5
5
|
import { V as ViewMode } from '../../view-toggle-CahQ87PW.js';
|
|
6
6
|
export { a as ViewToggle } from '../../view-toggle-CahQ87PW.js';
|
|
7
7
|
import { LucideIcon } from 'lucide-react';
|
|
8
8
|
import { Density } from '../../hooks/use-view-settings.js';
|
|
9
|
-
import { R as ResourceActions, a as ResourceColumnDef, D as DataTable } from '../../use-resource-page-
|
|
10
|
-
export { c as DataTableColumn, d as DataTableProps, L as LinkCell, T as ThumbnailCell } from '../../use-resource-page-
|
|
9
|
+
import { R as ResourceActions, a as ResourceColumnDef, D as DataTable } from '../../use-resource-page-BDnnCssE.js';
|
|
10
|
+
export { c as DataTableColumn, d as DataTableProps, e as DataTableSort, L as LinkCell, T as ThumbnailCell } from '../../use-resource-page-BDnnCssE.js';
|
|
11
11
|
export { EmptyState } from '../patterns/empty-state.js';
|
|
12
12
|
export { V as ViewSettings } from '../../view-settings-kHKCHMjj.js';
|
|
13
|
-
import { L as LinkComponent,
|
|
14
|
-
export { D as DefaultLink, G as GenesisLinkProps, I as IsPathActiveOptions,
|
|
13
|
+
import { L as LinkComponent, a as NavigateFn, S as SearchParamsAdapter } from '../../navigation-Bha0R3Fu.js';
|
|
14
|
+
export { D as DefaultLink, G as GenesisLinkProps, I as IsPathActiveOptions, N as NavigationNode, R as ResolvedNavigation, d as defaultNavigate, i as isPathActive, r as resolveNavigation } from '../../navigation-Bha0R3Fu.js';
|
|
15
15
|
import { UseFormReturn, Control, FieldErrors } from 'react-hook-form';
|
|
16
|
+
import { WizardStep, MutationMode, UndoableWrite, UseResourceOptions } from '@marktiderman/genesis-core/hooks';
|
|
16
17
|
import { ResourceFormFieldDef } from '@marktiderman/genesis-core';
|
|
17
18
|
export { FieldType, ResourceFormFieldDef, SelectOption } from '@marktiderman/genesis-core';
|
|
18
|
-
import { WizardStep, UseResourceOptions } from '@marktiderman/genesis-core/hooks';
|
|
19
19
|
import { b as Slots, a as SlotPropsFor } from '../../slots-tR0wf89f.js';
|
|
20
|
+
export { S as SlotOverride } from '../../slots-tR0wf89f.js';
|
|
21
|
+
import { ViewModeDefault } from '../../hooks/use-view-preference.js';
|
|
22
|
+
export { ResponsiveViewMode } from '../../hooks/use-view-preference.js';
|
|
23
|
+
import { ResourceUrlStateOptions } from '../../hooks/use-resource-url-state.js';
|
|
20
24
|
import { FilterParam, SortParam, BaseRecord, Identifier } from '@marktiderman/genesis-core/provider';
|
|
21
25
|
export { CommandPalette, CommandPaletteItem, CommandPaletteProps, useCommandPaletteHotkey } from '../ui/command-palette.js';
|
|
22
26
|
export { UseSavedViewsReturn, useSavedViews } from '../../hooks/use-saved-views.js';
|
|
@@ -36,6 +40,12 @@ interface DataPageShellProps {
|
|
|
36
40
|
isEmpty?: boolean;
|
|
37
41
|
emptyIcon?: LucideIcon;
|
|
38
42
|
emptyMessage?: string;
|
|
43
|
+
/**
|
|
44
|
+
* A sentence under the empty-state title — what this list is for, or what
|
|
45
|
+
* to do first. Forwarded to `EmptyState`'s `description`; hidden while
|
|
46
|
+
* filters are active, where "no results" is the whole story.
|
|
47
|
+
*/
|
|
48
|
+
emptyDescription?: string;
|
|
39
49
|
emptyAction?: ReactNode;
|
|
40
50
|
/** Error state */
|
|
41
51
|
hasError?: boolean;
|
|
@@ -91,10 +101,15 @@ interface DataPageShellProps {
|
|
|
91
101
|
* menu, a status strip, a second row — rather than a reimplementation.
|
|
92
102
|
*
|
|
93
103
|
* `null` ejects it: shell layout, states and filter row, no header.
|
|
104
|
+
*
|
|
105
|
+
* `"toolbar"` keeps the header's actions — count, view toggle, view
|
|
106
|
+
* settings, create, overflow menu — and drops the title block, for a list
|
|
107
|
+
* embedded in a tab or card whose page already has the heading. `title`
|
|
108
|
+
* still feeds the generated copy ("Failed to load …", the count's noun).
|
|
94
109
|
*/
|
|
95
|
-
header?: ComponentType<ComponentProps<typeof PageHeader$1>> | null;
|
|
110
|
+
header?: ComponentType<ComponentProps<typeof PageHeader$1>> | null | "toolbar";
|
|
96
111
|
}
|
|
97
|
-
declare function DataPageShell({ title, icon, entityName, subtitle, createAction, secondaryActions, isLoading, isEmpty, emptyIcon, emptyMessage, emptyAction, hasError, errorMessage, onRetry, count, totalCount, viewMode, onViewModeChange, pageSize, onPageSizeChange, density, onDensityChange, stats, filters, hasActiveFilters, onClearFilters, filtersCollapsible, defaultFiltersOpen, filtersOpen, onFiltersOpenChange, filtersLabel, filtersHint, headerOptions, headerOptionsStorageKey, children, className, header, }: DataPageShellProps): React.JSX.Element;
|
|
112
|
+
declare function DataPageShell({ title, icon, entityName, subtitle, createAction, secondaryActions, isLoading, isEmpty, emptyIcon, emptyMessage, emptyDescription, emptyAction, hasError, errorMessage, onRetry, count, totalCount, viewMode, onViewModeChange, pageSize, onPageSizeChange, density, onDensityChange, stats, filters, hasActiveFilters, onClearFilters, filtersCollapsible, defaultFiltersOpen, filtersOpen, onFiltersOpenChange, filtersLabel, filtersHint, headerOptions, headerOptionsStorageKey, children, className, header, }: DataPageShellProps): React.JSX.Element;
|
|
98
113
|
|
|
99
114
|
interface FilterOption {
|
|
100
115
|
value: string;
|
|
@@ -207,8 +222,18 @@ interface DataGridProps<T> {
|
|
|
207
222
|
focusedIndex?: number;
|
|
208
223
|
/** Keyboard navigation: container ref for scroll management */
|
|
209
224
|
keyboardContainerRef?: RefObject<HTMLDivElement | null>;
|
|
225
|
+
/**
|
|
226
|
+
* Open an item. Makes each card (and list row) one keyboard-reachable
|
|
227
|
+
* button: click, Enter or Space activates it, with a focus ring. Clicks on
|
|
228
|
+
* a link, button or input inside the card are left to that control, so a
|
|
229
|
+
* card can still carry its own actions. Use this instead of wrapping cards
|
|
230
|
+
* in a hand-rolled `div role="button"`.
|
|
231
|
+
*/
|
|
232
|
+
onItemClick?: ((item: T) => void) | undefined;
|
|
233
|
+
/** Accessible name for an activatable item, e.g. `Open ${item.name}`. Used with `onItemClick`. */
|
|
234
|
+
getItemLabel?: ((item: T) => string) | undefined;
|
|
210
235
|
}
|
|
211
|
-
declare function DataGrid<T>({ items, viewMode, batchSize, gridCols, renderCard, renderTable, renderListItem, staggerAnimation, getKey, focusedIndex, keyboardContainerRef, }: DataGridProps<T>): React.JSX.Element;
|
|
236
|
+
declare function DataGrid<T>({ items, viewMode, batchSize, gridCols, renderCard, renderTable, renderListItem, staggerAnimation, getKey, focusedIndex, keyboardContainerRef, onItemClick, getItemLabel, }: DataGridProps<T>): React.JSX.Element;
|
|
212
237
|
|
|
213
238
|
/**
|
|
214
239
|
* One button in the bulk action bar.
|
|
@@ -426,9 +451,99 @@ interface ResourceFormProps {
|
|
|
426
451
|
steps?: WizardStep[];
|
|
427
452
|
/** Number of columns for the form layout. Default 1. */
|
|
428
453
|
columns?: 1 | 2;
|
|
454
|
+
/**
|
|
455
|
+
* Pre-fill a create form from the page URL: a query param named like a
|
|
456
|
+
* field (`?status=draft`) becomes that field's starting value. Default
|
|
457
|
+
* `true`, as before. Pass `false` when the page's query string means
|
|
458
|
+
* something else — a list's own filters, say — and must not leak into the
|
|
459
|
+
* new record.
|
|
460
|
+
*/
|
|
461
|
+
prefillFromUrl?: boolean;
|
|
462
|
+
/**
|
|
463
|
+
* Generate a placeholder ("Enter name") for fields whose `placeholder` is
|
|
464
|
+
* unset. Default `false`: a placeholder that restates the label adds noise,
|
|
465
|
+
* and reads as a filled value at a glance. A field's own `placeholder`
|
|
466
|
+
* always shows. (Select fields keep their "Select…" prompt either way.)
|
|
467
|
+
*/
|
|
468
|
+
autoPlaceholder?: boolean;
|
|
469
|
+
/**
|
|
470
|
+
* Warn before unsaved changes are lost. Default false. While the form is
|
|
471
|
+
* dirty, Cancel / Esc / clicking outside asks first, and closing or
|
|
472
|
+
* reloading the tab shows the browser's prompt. Pass `{ message }` to
|
|
473
|
+
* change the question. For router navigation, see `onDirtyChange`.
|
|
474
|
+
*/
|
|
475
|
+
warnWhenUnsaved?: boolean | {
|
|
476
|
+
message?: string;
|
|
477
|
+
};
|
|
478
|
+
/**
|
|
479
|
+
* Called when the form becomes dirty or clean, with a `confirmLeave` that
|
|
480
|
+
* asks when needed: the hook for a router blocker (SKILL.md "Unsaved
|
|
481
|
+
* changes").
|
|
482
|
+
*/
|
|
483
|
+
onDirtyChange?: (isDirty: boolean, confirmLeave: () => boolean) => void;
|
|
484
|
+
/**
|
|
485
|
+
* When a save reaches the list. Default `"pessimistic"`: the form waits
|
|
486
|
+
* for the provider. `"optimistic"` closes at once and updates the cached
|
|
487
|
+
* list, putting it back (and toasting the error) if the save fails.
|
|
488
|
+
* `"undoable"` does the same and shows an Undo toast; the save is sent
|
|
489
|
+
* after `undoDelayMs` unless undone (or at once, undone by the provider,
|
|
490
|
+
* when it serves `undo`). Needs a mounted `<Toaster />`.
|
|
491
|
+
*/
|
|
492
|
+
mutationMode?: MutationMode;
|
|
493
|
+
/** How long an `undoable` save waits for an undo, in ms. Default 5000. */
|
|
494
|
+
undoDelayMs?: number;
|
|
495
|
+
/** Replaces the undo toast. */
|
|
496
|
+
onUndoable?: (write: UndoableWrite) => void;
|
|
429
497
|
}
|
|
430
498
|
declare function ResourceForm(props: ResourceFormProps): React.JSX.Element | null;
|
|
431
499
|
|
|
500
|
+
/**
|
|
501
|
+
* Props for {@link ConfirmDeleteDialog}.
|
|
502
|
+
*
|
|
503
|
+
* @stability Beta
|
|
504
|
+
*/
|
|
505
|
+
interface ConfirmDeleteDialogProps {
|
|
506
|
+
open: boolean;
|
|
507
|
+
onOpenChange: (open: boolean) => void;
|
|
508
|
+
/**
|
|
509
|
+
* Runs the delete. The dialog stays open while it is pending, closes when
|
|
510
|
+
* it resolves, and shows the error's message (staying open, so the user
|
|
511
|
+
* can retry or cancel) when it rejects.
|
|
512
|
+
*/
|
|
513
|
+
onConfirm: () => void | Promise<void>;
|
|
514
|
+
/** Default "Delete this record?". */
|
|
515
|
+
title?: string;
|
|
516
|
+
/**
|
|
517
|
+
* What happens to the record: `"permanent"` (default) or `"recycle"` when
|
|
518
|
+
* a delete moves it to a recycle bin. Sets the default description.
|
|
519
|
+
*/
|
|
520
|
+
kind?: "permanent" | "recycle";
|
|
521
|
+
/**
|
|
522
|
+
* Default by `kind`: "This can't be undone.", or "It moves to the recycle
|
|
523
|
+
* bin, where it can be restored."
|
|
524
|
+
*/
|
|
525
|
+
description?: ReactNode;
|
|
526
|
+
/** Default "Delete". */
|
|
527
|
+
confirmLabel?: string;
|
|
528
|
+
/** Default "Cancel". */
|
|
529
|
+
cancelLabel?: string;
|
|
530
|
+
}
|
|
531
|
+
/**
|
|
532
|
+
* The confirm step in front of a destructive delete: an `AlertDialog` (Radix
|
|
533
|
+
* owns focus trap, Escape and focus return), a pending state, and the
|
|
534
|
+
* failure shown in place rather than swallowed. `ResourcePage` renders it
|
|
535
|
+
* for `allowDelete` / `confirmDelete`, as its `deleteDialog` slot.
|
|
536
|
+
*
|
|
537
|
+
* @stability Beta
|
|
538
|
+
*/
|
|
539
|
+
declare function ConfirmDeleteDialog({ open, onOpenChange, onConfirm, title, kind, description, confirmLabel, cancelLabel, }: ConfirmDeleteDialogProps): React.JSX.Element;
|
|
540
|
+
|
|
541
|
+
/**
|
|
542
|
+
* What a user may do with a list's records, for ResourcePage's `can`.
|
|
543
|
+
*
|
|
544
|
+
* @stability Beta
|
|
545
|
+
*/
|
|
546
|
+
type ResourceAction = "create" | "edit" | "delete";
|
|
432
547
|
/**
|
|
433
548
|
* Row-bound actions handed to `renderCard`, on top of the resource-wide
|
|
434
549
|
* `ResourceActions`. `open` is per-row, which is why it lives here rather
|
|
@@ -501,6 +616,11 @@ type ResourcePageSlotMap<T extends Record<string, unknown> = Record<string, unkn
|
|
|
501
616
|
detail: ComponentProps<typeof DetailPanel<T>>;
|
|
502
617
|
/** The create / edit form. */
|
|
503
618
|
form: ComponentProps<typeof ResourceForm>;
|
|
619
|
+
/**
|
|
620
|
+
* The confirm step in front of a delete. Rendered only when a delete is
|
|
621
|
+
* confirmed (`allowDelete`, or `onDelete` with `confirmDelete`).
|
|
622
|
+
*/
|
|
623
|
+
deleteDialog: ComponentProps<typeof ConfirmDeleteDialog>;
|
|
504
624
|
};
|
|
505
625
|
/**
|
|
506
626
|
* `slots` for {@link ResourcePage}, with one asymmetry the map cannot express:
|
|
@@ -516,6 +636,27 @@ type ResourcePageSlotMap<T extends Record<string, unknown> = Record<string, unkn
|
|
|
516
636
|
type ResourcePageSlots<T extends Record<string, unknown> = Record<string, unknown>> = Omit<Slots<ResourcePageSlotMap<T>>, "shell"> & {
|
|
517
637
|
shell?: ComponentType<ComponentProps<typeof DataPageShell>>;
|
|
518
638
|
};
|
|
639
|
+
/**
|
|
640
|
+
* `slotProps` for {@link ResourcePage}: extra props merged into a part you
|
|
641
|
+
* are keeping, winning over ResourcePage's own.
|
|
642
|
+
*/
|
|
643
|
+
type ResourcePageSlotProps<T extends Record<string, unknown> = Record<string, unknown>> = SlotPropsFor<ResourcePageSlotMap<T>>;
|
|
644
|
+
/**
|
|
645
|
+
* Copy for the built-in delete confirmation. Every field is optional;
|
|
646
|
+
* omitted ones fall back to `ConfirmDeleteDialog`'s defaults.
|
|
647
|
+
*
|
|
648
|
+
* @stability Beta
|
|
649
|
+
*/
|
|
650
|
+
interface ResourceDeleteConfirmCopy<T> {
|
|
651
|
+
/** Default "Delete this record?". A function receives the row. */
|
|
652
|
+
title?: string | ((item: T) => string);
|
|
653
|
+
/** Default "This can't be undone." */
|
|
654
|
+
description?: ReactNode | ((item: T) => ReactNode);
|
|
655
|
+
/** Default "Delete". */
|
|
656
|
+
confirmLabel?: string;
|
|
657
|
+
/** Default "Cancel". */
|
|
658
|
+
cancelLabel?: string;
|
|
659
|
+
}
|
|
519
660
|
interface ResourcePageProps<T extends Record<string, unknown> = Record<string, unknown>> {
|
|
520
661
|
/** Client-side mode: resource name passed to useResource / DataProvider. */
|
|
521
662
|
resource?: string;
|
|
@@ -533,6 +674,32 @@ interface ResourcePageProps<T extends Record<string, unknown> = Record<string, u
|
|
|
533
674
|
* {@link onPageChange}.
|
|
534
675
|
*/
|
|
535
676
|
total?: number;
|
|
677
|
+
/**
|
|
678
|
+
* How `resource` mode reaches rows past the first page. Ignored with
|
|
679
|
+
* `data` (server-data mode pages through the controlled props instead).
|
|
680
|
+
*
|
|
681
|
+
* - `"client"` (default): fetch up to `clientRowLimit` rows in one request
|
|
682
|
+
* and search, sort, filter and page them in the browser — header-click
|
|
683
|
+
* sort and instant search keep working. If the resource holds more rows
|
|
684
|
+
* than that, a notice under the list says how many are shown (and a
|
|
685
|
+
* console warning names this prop), rather than silently truncating.
|
|
686
|
+
* - `"server"`: fetch one page at a time. The pager under the list drives
|
|
687
|
+
* `useResource`'s page, the header's page size its `perPage`, and
|
|
688
|
+
* search / sort / status chips become provider queries (search is
|
|
689
|
+
* debounced), and the current page stays on screen while the next one
|
|
690
|
+
* loads. Give `statusFilter` explicit `options` here — derived ones can
|
|
691
|
+
* only see the current page's values. Search needs `searchFields` (or
|
|
692
|
+
* `resourceOptions.searchFields`) — the provider must be told which
|
|
693
|
+
* columns to match — so the search box is hidden without them. Table
|
|
694
|
+
* header sorting is withdrawn, as in server-data mode; the sort control
|
|
695
|
+
* in the filter row drives the query.
|
|
696
|
+
*/
|
|
697
|
+
pagination?: "client" | "server";
|
|
698
|
+
/**
|
|
699
|
+
* `pagination="client"` only: the most rows fetched. Default 1000. An
|
|
700
|
+
* explicit `resourceOptions.defaultPerPage` wins.
|
|
701
|
+
*/
|
|
702
|
+
clientRowLimit?: number;
|
|
536
703
|
title: string;
|
|
537
704
|
/** Page subtitle shown below the title. */
|
|
538
705
|
subtitle?: string;
|
|
@@ -551,6 +718,17 @@ interface ResourcePageProps<T extends Record<string, unknown> = Record<string, u
|
|
|
551
718
|
detailWidth?: "sm" | "md" | "lg";
|
|
552
719
|
titleField?: string;
|
|
553
720
|
subtitleField?: string;
|
|
721
|
+
/**
|
|
722
|
+
* Name a record from more than one field (`${first} ${last}`). Wins over
|
|
723
|
+
* `titleField` for the detail title and the card's accessible name.
|
|
724
|
+
*/
|
|
725
|
+
getTitle?: (item: T) => string;
|
|
726
|
+
/**
|
|
727
|
+
* The detail peek's subtitle, computed from the row ("2 staff", "Due
|
|
728
|
+
* Friday"). Wins over `subtitleField`, which can only show one column
|
|
729
|
+
* verbatim.
|
|
730
|
+
*/
|
|
731
|
+
getSubtitle?: (item: T) => string | undefined;
|
|
554
732
|
renderDetail?: (item: T) => ReactNode;
|
|
555
733
|
detailFields?: Array<{
|
|
556
734
|
key: string;
|
|
@@ -704,8 +882,54 @@ interface ResourcePageProps<T extends Record<string, unknown> = Record<string, u
|
|
|
704
882
|
pageSizeOptions?: number[];
|
|
705
883
|
onCreate?: () => void;
|
|
706
884
|
onEdit?: (item: T) => void;
|
|
707
|
-
|
|
885
|
+
/**
|
|
886
|
+
* Delete handler, shown as the detail peek's Delete button. Called
|
|
887
|
+
* directly, as before, unless `confirmDelete` is set — then it runs after
|
|
888
|
+
* the confirm dialog, which shows its error if the returned promise
|
|
889
|
+
* rejects, and closes the peek when it resolves.
|
|
890
|
+
*/
|
|
891
|
+
onDelete?: (item: T) => void | Promise<void>;
|
|
892
|
+
/**
|
|
893
|
+
* What the signed-in user may do: `can("create")`, `can("edit", item)`,
|
|
894
|
+
* `can("delete", item)`. Return false to hide the New button, or a row's
|
|
895
|
+
* Edit or Delete (and the built-in edit form won't open for it). Omitted,
|
|
896
|
+
* everything the handlers allow is offered. A UI gate only: the server
|
|
897
|
+
* must still refuse the write.
|
|
898
|
+
*/
|
|
899
|
+
can?: (action: ResourceAction, item?: T) => boolean;
|
|
900
|
+
/**
|
|
901
|
+
* @deprecated Use `can` — `can={(action, item) => !item || mayChange(item)}`.
|
|
902
|
+
* Removed in the next minor after one deprecation window. Still honoured:
|
|
903
|
+
* a row it refuses gets no Edit and no Delete.
|
|
904
|
+
*/
|
|
905
|
+
canEditItem?: (item: T) => boolean;
|
|
708
906
|
createLabel?: string;
|
|
907
|
+
/**
|
|
908
|
+
* @deprecated Use `can` — `can={(action, item) => action !== "edit" || !item || mayEdit(item)}`.
|
|
909
|
+
* Still honoured: a row it refuses gets no Edit.
|
|
910
|
+
*/
|
|
911
|
+
canEdit?: (item: T) => boolean;
|
|
912
|
+
/**
|
|
913
|
+
* @deprecated Use `can` — `can={(action, item) => action !== "delete" || !item || mayDelete(item)}`.
|
|
914
|
+
* Still honoured: a row it refuses gets no Delete.
|
|
915
|
+
*/
|
|
916
|
+
canDelete?: (item: T) => boolean;
|
|
917
|
+
/**
|
|
918
|
+
* Built-in delete for `resource` mode (ignored if `onDelete` is
|
|
919
|
+
* provided): the peek's Delete button opens a confirm dialog, then calls
|
|
920
|
+
* `useResource`'s `remove` with the row's `getKey` (when supplied) or its
|
|
921
|
+
* `id` — which invalidates the list — and closes the peek. A failure shows
|
|
922
|
+
* in the dialog.
|
|
923
|
+
*/
|
|
924
|
+
allowDelete?: boolean;
|
|
925
|
+
/**
|
|
926
|
+
* Confirm before deleting. Defaults to `true` for `allowDelete` and
|
|
927
|
+
* `false` for `onDelete` (which keeps its old, unconfirmed behaviour).
|
|
928
|
+
* Pass an object to set the dialog's copy.
|
|
929
|
+
*/
|
|
930
|
+
confirmDelete?: boolean | ResourceDeleteConfirmCopy<T>;
|
|
931
|
+
/** Called after a confirmed or built-in delete succeeds — e.g. a toast. */
|
|
932
|
+
onDeleted?: (item: T) => void;
|
|
709
933
|
/**
|
|
710
934
|
* Extra header actions beyond the create button, in one overflow menu.
|
|
711
935
|
* See {@link PageHeaderAction} — the budget is one primary, the rest here.
|
|
@@ -725,6 +949,13 @@ interface ResourcePageProps<T extends Record<string, unknown> = Record<string, u
|
|
|
725
949
|
formSteps?: WizardStep[];
|
|
726
950
|
/** Layout mode for the built-in form container. If not set, defaults to sheet for edit, dialog for create. */
|
|
727
951
|
formLayout?: "dialog" | "sheet" | "page";
|
|
952
|
+
/**
|
|
953
|
+
* When the built-in form's saves and deletes reach the list: `"pessimistic"`
|
|
954
|
+
* (default, wait for the provider), `"optimistic"` or `"undoable"` (an Undo
|
|
955
|
+
* toast). See ResourceForm `mutationMode`. `resourceOptions.mutationMode`
|
|
956
|
+
* wins for deletes.
|
|
957
|
+
*/
|
|
958
|
+
mutationMode?: MutationMode;
|
|
728
959
|
bulkActions?: Array<{
|
|
729
960
|
label: string;
|
|
730
961
|
icon?: LucideIcon;
|
|
@@ -772,6 +1003,14 @@ interface ResourcePageProps<T extends Record<string, unknown> = Record<string, u
|
|
|
772
1003
|
* Omitted, the default `DataTable` render is used unchanged.
|
|
773
1004
|
*/
|
|
774
1005
|
renderTable?: (items: T[]) => ReactNode;
|
|
1006
|
+
/**
|
|
1007
|
+
* With a custom `renderCard`: make each whole card (and list row) one
|
|
1008
|
+
* keyboard-reachable button that opens the detail, so the card needs no
|
|
1009
|
+
* `actions.open()` wiring and no hand-rolled `div role="button"`. Links and
|
|
1010
|
+
* buttons inside the card keep their own clicks. Off by default, because
|
|
1011
|
+
* an existing card that already calls `open()` would fire it twice.
|
|
1012
|
+
*/
|
|
1013
|
+
openOnCardClick?: boolean;
|
|
775
1014
|
/**
|
|
776
1015
|
* Tailwind grid-column classes for the card grid, passed through to
|
|
777
1016
|
* `DataGrid`'s `gridCols` — e.g. `"grid-cols-1 sm:grid-cols-2 lg:grid-cols-3"`.
|
|
@@ -786,9 +1025,10 @@ interface ResourcePageProps<T extends Record<string, unknown> = Record<string, u
|
|
|
786
1025
|
* opt out.
|
|
787
1026
|
*
|
|
788
1027
|
* Default-on, and scoped so that being on by default stays safe:
|
|
789
|
-
* -
|
|
790
|
-
*
|
|
791
|
-
*
|
|
1028
|
+
* - The j/k cursor runs while the grid or list view is showing. Table view
|
|
1029
|
+
* keeps its own sort/pagination, so there `DataTable` handles the keys on
|
|
1030
|
+
* its focusable rows instead (↑/↓ or j/k, Home/End, Enter, x); this prop
|
|
1031
|
+
* turns that off too.
|
|
792
1032
|
* - Cursor keys suspend while a detail panel/modal or the built-in form
|
|
793
1033
|
* is open, so they can't drive the list behind the overlay; ⌫ stays
|
|
794
1034
|
* live there so it can close it.
|
|
@@ -798,7 +1038,21 @@ interface ResourcePageProps<T extends Record<string, unknown> = Record<string, u
|
|
|
798
1038
|
*/
|
|
799
1039
|
keyboardNavigation?: boolean;
|
|
800
1040
|
toolbar?: ReactNode;
|
|
1041
|
+
/** The empty state's action, e.g. the create button. */
|
|
801
1042
|
emptyState?: ReactNode;
|
|
1043
|
+
/** The empty state's icon. */
|
|
1044
|
+
emptyIcon?: LucideIcon;
|
|
1045
|
+
/** The empty state's title, e.g. "No tasks yet". Default "No results found". */
|
|
1046
|
+
emptyMessage?: string;
|
|
1047
|
+
/** A sentence under the empty-state title — forwarded to `DataPageShell`. */
|
|
1048
|
+
emptyDescription?: string;
|
|
1049
|
+
/**
|
|
1050
|
+
* `"page"` (default) renders the full header with the title. `"toolbar"`
|
|
1051
|
+
* keeps the header's actions (count, view toggle, settings, create) and
|
|
1052
|
+
* drops the title block — for a list inside a tab or card whose page
|
|
1053
|
+
* already has the heading. See `DataPageShell`'s `header`.
|
|
1054
|
+
*/
|
|
1055
|
+
header?: "page" | "toolbar";
|
|
802
1056
|
/**
|
|
803
1057
|
* Imperative navigation used only when `detail="route"` (navigates to
|
|
804
1058
|
* `/{resource}/{id}` on row click). Defaults to a full-page navigation.
|
|
@@ -807,7 +1061,25 @@ interface ResourcePageProps<T extends Record<string, unknown> = Record<string, u
|
|
|
807
1061
|
*/
|
|
808
1062
|
onNavigate?: NavigateFn;
|
|
809
1063
|
resourceOptions?: UseResourceOptions<T>;
|
|
810
|
-
|
|
1064
|
+
/**
|
|
1065
|
+
* Keep the list's state in the URL: the open record (`?id=`), the search
|
|
1066
|
+
* (`q`), the sort (`sort=field:asc`) and, with `pagination="server"`, the
|
|
1067
|
+
* page. Pass your router's search-params adapter
|
|
1068
|
+
* (`useReactRouterSearchParams()`, `useTanStackRouterSearchParams()`, or
|
|
1069
|
+
* `useBrowserSearchParams()` with no router), or `{ searchParams, params }`
|
|
1070
|
+
* to rename the keys. A copied link reopens the same record, and Back
|
|
1071
|
+
* closes one that was opened. Search and sort are read when the page
|
|
1072
|
+
* mounts and written as they change. A server-controlled page owns its
|
|
1073
|
+
* own page, sort and search: use `useResourceUrlState` there.
|
|
1074
|
+
*/
|
|
1075
|
+
urlState?: SearchParamsAdapter | ResourceUrlStateOptions;
|
|
1076
|
+
/**
|
|
1077
|
+
* The view shown until the user picks one with the view toggle. Default
|
|
1078
|
+
* `"table"`. Pass `{ mobile: "grid", desktop: "table" }` for cards on
|
|
1079
|
+
* phones and a table on desktops. Only an explicit pick is persisted, so
|
|
1080
|
+
* the responsive default stays responsive until the user chooses.
|
|
1081
|
+
*/
|
|
1082
|
+
defaultView?: ViewModeDefault;
|
|
811
1083
|
/**
|
|
812
1084
|
* Persist key for density / page size via `useViewSettings`.
|
|
813
1085
|
* Defaults to `resource` or a slug of `title`.
|
|
@@ -824,7 +1096,7 @@ interface ResourcePageProps<T extends Record<string, unknown> = Record<string, u
|
|
|
824
1096
|
*/
|
|
825
1097
|
slots?: ResourcePageSlots<T>;
|
|
826
1098
|
/** Extra props merged into a part you are KEEPING. Wins over ours. */
|
|
827
|
-
slotProps?:
|
|
1099
|
+
slotProps?: ResourcePageSlotProps<T>;
|
|
828
1100
|
/** Merged onto the page shell's root. */
|
|
829
1101
|
className?: string;
|
|
830
1102
|
}
|
|
@@ -914,16 +1186,31 @@ interface RelationFieldProps {
|
|
|
914
1186
|
value: string;
|
|
915
1187
|
onChange: (value: string) => void;
|
|
916
1188
|
disabled?: boolean;
|
|
1189
|
+
/** Id for the trigger, so a `<label htmlFor>` names it. */
|
|
1190
|
+
id?: string;
|
|
1191
|
+
"aria-describedby"?: string;
|
|
1192
|
+
"aria-invalid"?: boolean;
|
|
1193
|
+
"aria-required"?: boolean;
|
|
917
1194
|
}
|
|
918
|
-
declare function RelationField({ field, value, onChange, disabled, }: RelationFieldProps): React.JSX.Element;
|
|
1195
|
+
declare function RelationField({ field, value, onChange, disabled, id, "aria-describedby": ariaDescribedBy, "aria-invalid": ariaInvalid, "aria-required": ariaRequired, }: RelationFieldProps): React.JSX.Element;
|
|
919
1196
|
|
|
920
1197
|
interface FileUploadFieldProps {
|
|
921
1198
|
field: ResourceFormFieldDef;
|
|
922
1199
|
value: File | File[] | null;
|
|
923
1200
|
onChange: (value: File | File[] | null) => void;
|
|
924
1201
|
disabled?: boolean;
|
|
1202
|
+
/** Id for the file input, so a `<label htmlFor>` names it. */
|
|
1203
|
+
id?: string;
|
|
1204
|
+
/**
|
|
1205
|
+
* The label's id. The drop zone is what takes keyboard focus (the input
|
|
1206
|
+
* is hidden), so it is named by `aria-labelledby` rather than `htmlFor`.
|
|
1207
|
+
*/
|
|
1208
|
+
"aria-labelledby"?: string;
|
|
1209
|
+
"aria-describedby"?: string;
|
|
1210
|
+
"aria-invalid"?: boolean;
|
|
1211
|
+
"aria-required"?: boolean;
|
|
925
1212
|
}
|
|
926
|
-
declare function FileUploadField({ field, value, onChange, disabled, }: FileUploadFieldProps): React.JSX.Element;
|
|
1213
|
+
declare function FileUploadField({ field, value, onChange, disabled, id, "aria-labelledby": ariaLabelledBy, "aria-describedby": ariaDescribedBy, "aria-invalid": ariaInvalid, "aria-required": ariaRequired, }: FileUploadFieldProps): React.JSX.Element;
|
|
927
1214
|
|
|
928
1215
|
declare function FieldArrayRenderer({ field, control, action, watchedValues, }: {
|
|
929
1216
|
field: ResourceFormFieldDef;
|
|
@@ -959,4 +1246,4 @@ declare const AppShell: typeof AppShell$1;
|
|
|
959
1246
|
*/
|
|
960
1247
|
declare const PageHeader: typeof PageHeader$1;
|
|
961
1248
|
|
|
962
|
-
export { AppShell, BadgeCell, type BulkAction, type ColumnStatus, CurrencyCell, DataBulkBar, type DataFilterConfig, DataFilters, DataGrid, DataList, type DataListProps, DataPageShell, type DataPageShellProps, DataTable, DateCell, Density, DetailPanel, type DetailPanelProps, FieldArrayRenderer, FileUploadField, FilterCombobox, type FilterOption, KanbanBoard, type KanbanBoardProps, type KanbanColumn, LinkComponent, NavigateFn, NumberCell, PageHeader, PageHeaderOptions, RelationField, ResourceActions, type ResourceCardActions, ResourceColumnDef, ResourceDetailPage, type ResourceDetailPageProps, ResourceForm, type ResourceFormProps, ResourcePage, type ResourcePageProps, type SavedViewItem, StatCard, type StatCardColor, type StatCardProps, type StatusChipOption, type StatusChipsVariant, ViewMode, WizardForm };
|
|
1249
|
+
export { AppShell, BadgeCell, type BulkAction, type ColumnStatus, ConfirmDeleteDialog, type ConfirmDeleteDialogProps, CurrencyCell, DataBulkBar, type DataFilterConfig, DataFilters, DataGrid, DataList, type DataListProps, DataPageShell, type DataPageShellProps, DataTable, DateCell, Density, DetailPanel, type DetailPanelProps, FieldArrayRenderer, FileUploadField, FilterCombobox, type FilterOption, KanbanBoard, type KanbanBoardProps, type KanbanColumn, LinkComponent, NavigateFn, NumberCell, PageHeader, PageHeaderOptions, RelationField, type ResourceAction, ResourceActions, type ResourceCardActions, ResourceColumnDef, type ResourceDeleteConfirmCopy, ResourceDetailPage, type ResourceDetailPageProps, ResourceForm, type ResourceFormProps, ResourcePage, type ResourcePageProps, type ResourcePageSlotMap, type ResourcePageSlotProps, type ResourcePageSlots, type SavedViewItem, SlotPropsFor, Slots, StatCard, type StatCardColor, type StatCardProps, type StatusChipOption, type StatusChipsVariant, ViewMode, ViewModeDefault, WizardForm };
|