@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.
Files changed (74) hide show
  1. package/EXTENDING.md +40 -1
  2. package/SKILL.md +198 -9
  3. package/dist/{AppShell-CX0M-dfc.d.ts → AppShell-LXQykEqe.d.ts} +47 -5
  4. package/dist/{chunk-M62CHO3N.js → chunk-4C4ESW6O.js} +9 -4
  5. package/dist/chunk-4C4ESW6O.js.map +1 -0
  6. package/dist/chunk-DIV4BYXX.js +58 -0
  7. package/dist/chunk-DIV4BYXX.js.map +1 -0
  8. package/dist/chunk-FUVWKTPL.js +19 -0
  9. package/dist/chunk-FUVWKTPL.js.map +1 -0
  10. package/dist/{chunk-PLIYF6KI.js → chunk-MPI33I4K.js} +2 -2
  11. package/dist/{chunk-PLIYF6KI.js.map → chunk-MPI33I4K.js.map} +1 -1
  12. package/dist/{chunk-JA3ZKULS.js → chunk-OITWCIG2.js} +3 -3
  13. package/dist/chunk-OITWCIG2.js.map +1 -0
  14. package/dist/chunk-PKFDGGZV.js +65 -0
  15. package/dist/chunk-PKFDGGZV.js.map +1 -0
  16. package/dist/{chunk-IXGAEECT.js → chunk-RKPS7ERS.js} +9 -5
  17. package/dist/chunk-RKPS7ERS.js.map +1 -0
  18. package/dist/{chunk-TXOZMKNW.js → chunk-S7NXD37O.js} +27 -26
  19. package/dist/chunk-S7NXD37O.js.map +1 -0
  20. package/dist/{chunk-ZPPLUGLA.js → chunk-UYI3GLFJ.js} +162 -19
  21. package/dist/chunk-UYI3GLFJ.js.map +1 -0
  22. package/dist/{chunk-CDYZRZ6U.js → chunk-WGMEVX3Y.js} +2 -2
  23. package/dist/chunk-WGMEVX3Y.js.map +1 -0
  24. package/dist/chunk-ZDHWNQTR.js +552 -0
  25. package/dist/chunk-ZDHWNQTR.js.map +1 -0
  26. package/dist/components/data/index.d.ts +306 -19
  27. package/dist/components/data/index.js +1177 -284
  28. package/dist/components/data/index.js.map +1 -1
  29. package/dist/components/layout/form-page.d.ts +1 -1
  30. package/dist/components/layout/form-page.js +1 -1
  31. package/dist/components/layout/index.d.ts +2 -2
  32. package/dist/components/layout/index.js +8 -8
  33. package/dist/components/patterns/index.d.ts +1 -1
  34. package/dist/components/patterns/index.js +5 -5
  35. package/dist/components/patterns/sortable-list.js +1 -1
  36. package/dist/components/rich-text/rich-text-editor.d.ts +194 -0
  37. package/dist/components/rich-text/rich-text-editor.js +833 -0
  38. package/dist/components/rich-text/rich-text-editor.js.map +1 -0
  39. package/dist/components/rich-text/rich-text.d.ts +51 -0
  40. package/dist/components/rich-text/rich-text.js +4 -0
  41. package/dist/components/rich-text/rich-text.js.map +1 -0
  42. package/dist/components/ui/alert-dialog.d.ts +12 -1
  43. package/dist/components/ui/alert-dialog.js +1 -1
  44. package/dist/hooks/index.d.ts +4 -3
  45. package/dist/hooks/index.js +5 -4
  46. package/dist/hooks/use-data-filters.d.ts +5 -3
  47. package/dist/hooks/use-data-filters.js +2 -2
  48. package/dist/hooks/use-resource-page.d.ts +2 -1
  49. package/dist/hooks/use-resource-page.js +3 -2
  50. package/dist/hooks/use-resource-url-state.d.ts +68 -0
  51. package/dist/hooks/use-resource-url-state.js +3 -0
  52. package/dist/hooks/use-resource-url-state.js.map +1 -0
  53. package/dist/hooks/use-view-preference.d.ts +17 -2
  54. package/dist/hooks/use-view-preference.js +2 -1
  55. package/dist/index.d.ts +2 -2
  56. package/dist/index.js +32 -32
  57. package/dist/{navigation-DBwcZ6vb.d.ts → navigation-Bha0R3Fu.d.ts} +6 -3
  58. package/dist/rich-text-doc-C54fME2C.d.ts +180 -0
  59. package/dist/router/react-router.d.ts +47 -0
  60. package/dist/router/react-router.js +53 -0
  61. package/dist/router/react-router.js.map +1 -0
  62. package/dist/router/tanstack-router.d.ts +47 -0
  63. package/dist/router/tanstack-router.js +105 -0
  64. package/dist/router/tanstack-router.js.map +1 -0
  65. package/dist/{use-resource-page-Dd2VUv30.d.ts → use-resource-page-BDnnCssE.d.ts} +83 -5
  66. package/package.json +61 -14
  67. package/dist/chunk-CDYZRZ6U.js.map +0 -1
  68. package/dist/chunk-FTTNGGLA.js +0 -20
  69. package/dist/chunk-FTTNGGLA.js.map +0 -1
  70. package/dist/chunk-IXGAEECT.js.map +0 -1
  71. package/dist/chunk-JA3ZKULS.js.map +0 -1
  72. package/dist/chunk-M62CHO3N.js.map +0 -1
  73. package/dist/chunk-TXOZMKNW.js.map +0 -1
  74. 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, \"&amp;\")\n .replace(/</g, \"&lt;\")\n .replace(/>/g, \"&gt;\")\n .replace(/\"/g, \"&quot;\")\n .replace(/'/g, \"&#39;\");\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 { e as PageHeaderAction, c as PageHeaderOptions, P as PageHeader$1, A as AppShell$1 } from '../../AppShell-CX0M-dfc.js';
4
- export { a as AppShellOptions, b as AppShellProps, N as NavItem, d as PageHeaderProps } from '../../AppShell-CX0M-dfc.js';
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-Dd2VUv30.js';
10
- export { c as DataTableColumn, d as DataTableProps, L as LinkCell, T as ThumbnailCell } from '../../use-resource-page-Dd2VUv30.js';
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, N as NavigateFn } from '../../navigation-DBwcZ6vb.js';
14
- export { D as DefaultLink, G as GenesisLinkProps, I as IsPathActiveOptions, a as NavigationNode, R as ResolvedNavigation, d as defaultNavigate, i as isPathActive, r as resolveNavigation } from '../../navigation-DBwcZ6vb.js';
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
- onDelete?: (item: T) => void;
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
- * - Only while the grid or list view is showing. Table view keeps its own
790
- * independent sort/pagination, so a flat focus index can't reliably map
791
- * onto its visible rows.
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
- defaultView?: ViewMode;
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?: SlotPropsFor<ResourcePageSlotMap<T>>;
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 };