@marktiderman/genesis-ui 2.6.0 → 2.8.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 +145 -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-IXGAEECT.js → chunk-DWCAC2E7.js} +6 -3
- package/dist/chunk-DWCAC2E7.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-S7NXD37O.js +321 -0
- package/dist/chunk-S7NXD37O.js.map +1 -0
- package/dist/{chunk-DWM7ELFI.js → chunk-UYI3GLFJ.js} +163 -20
- 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 +239 -15
- package/dist/components/data/index.js +524 -109
- 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 +9 -9
- package/dist/components/patterns/index.d.ts +1 -0
- package/dist/components/patterns/index.js +6 -4
- package/dist/components/patterns/sortable-list.d.ts +78 -0
- package/dist/components/patterns/sortable-list.js +5 -0
- package/dist/components/patterns/sortable-list.js.map +1 -0
- 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 +3 -3
- package/dist/hooks/index.js +4 -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-view-preference.d.ts +17 -2
- package/dist/hooks/use-view-preference.js +2 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.js +30 -29
- 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-D4byuwAs.d.ts} +20 -3
- package/package.json +62 -7
- package/dist/chunk-CDYZRZ6U.js.map +0 -1
- package/dist/chunk-DWM7ELFI.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
|
@@ -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,25 @@
|
|
|
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-D4byuwAs.js';
|
|
10
|
+
export { c as DataTableColumn, d as DataTableProps, L as LinkCell, T as ThumbnailCell } from '../../use-resource-page-D4byuwAs.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 } 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
16
|
import { ResourceFormFieldDef } from '@marktiderman/genesis-core';
|
|
17
17
|
export { FieldType, ResourceFormFieldDef, SelectOption } from '@marktiderman/genesis-core';
|
|
18
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';
|
|
20
23
|
import { FilterParam, SortParam, BaseRecord, Identifier } from '@marktiderman/genesis-core/provider';
|
|
21
24
|
export { CommandPalette, CommandPaletteItem, CommandPaletteProps, useCommandPaletteHotkey } from '../ui/command-palette.js';
|
|
22
25
|
export { UseSavedViewsReturn, useSavedViews } from '../../hooks/use-saved-views.js';
|
|
@@ -36,6 +39,12 @@ interface DataPageShellProps {
|
|
|
36
39
|
isEmpty?: boolean;
|
|
37
40
|
emptyIcon?: LucideIcon;
|
|
38
41
|
emptyMessage?: string;
|
|
42
|
+
/**
|
|
43
|
+
* A sentence under the empty-state title — what this list is for, or what
|
|
44
|
+
* to do first. Forwarded to `EmptyState`'s `description`; hidden while
|
|
45
|
+
* filters are active, where "no results" is the whole story.
|
|
46
|
+
*/
|
|
47
|
+
emptyDescription?: string;
|
|
39
48
|
emptyAction?: ReactNode;
|
|
40
49
|
/** Error state */
|
|
41
50
|
hasError?: boolean;
|
|
@@ -91,10 +100,15 @@ interface DataPageShellProps {
|
|
|
91
100
|
* menu, a status strip, a second row — rather than a reimplementation.
|
|
92
101
|
*
|
|
93
102
|
* `null` ejects it: shell layout, states and filter row, no header.
|
|
103
|
+
*
|
|
104
|
+
* `"toolbar"` keeps the header's actions — count, view toggle, view
|
|
105
|
+
* settings, create, overflow menu — and drops the title block, for a list
|
|
106
|
+
* embedded in a tab or card whose page already has the heading. `title`
|
|
107
|
+
* still feeds the generated copy ("Failed to load …", the count's noun).
|
|
94
108
|
*/
|
|
95
|
-
header?: ComponentType<ComponentProps<typeof PageHeader$1>> | null;
|
|
109
|
+
header?: ComponentType<ComponentProps<typeof PageHeader$1>> | null | "toolbar";
|
|
96
110
|
}
|
|
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;
|
|
111
|
+
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
112
|
|
|
99
113
|
interface FilterOption {
|
|
100
114
|
value: string;
|
|
@@ -207,8 +221,18 @@ interface DataGridProps<T> {
|
|
|
207
221
|
focusedIndex?: number;
|
|
208
222
|
/** Keyboard navigation: container ref for scroll management */
|
|
209
223
|
keyboardContainerRef?: RefObject<HTMLDivElement | null>;
|
|
224
|
+
/**
|
|
225
|
+
* Open an item. Makes each card (and list row) one keyboard-reachable
|
|
226
|
+
* button: click, Enter or Space activates it, with a focus ring. Clicks on
|
|
227
|
+
* a link, button or input inside the card are left to that control, so a
|
|
228
|
+
* card can still carry its own actions. Use this instead of wrapping cards
|
|
229
|
+
* in a hand-rolled `div role="button"`.
|
|
230
|
+
*/
|
|
231
|
+
onItemClick?: ((item: T) => void) | undefined;
|
|
232
|
+
/** Accessible name for an activatable item, e.g. `Open ${item.name}`. Used with `onItemClick`. */
|
|
233
|
+
getItemLabel?: ((item: T) => string) | undefined;
|
|
210
234
|
}
|
|
211
|
-
declare function DataGrid<T>({ items, viewMode, batchSize, gridCols, renderCard, renderTable, renderListItem, staggerAnimation, getKey, focusedIndex, keyboardContainerRef, }: DataGridProps<T>): React.JSX.Element;
|
|
235
|
+
declare function DataGrid<T>({ items, viewMode, batchSize, gridCols, renderCard, renderTable, renderListItem, staggerAnimation, getKey, focusedIndex, keyboardContainerRef, onItemClick, getItemLabel, }: DataGridProps<T>): React.JSX.Element;
|
|
212
236
|
|
|
213
237
|
/**
|
|
214
238
|
* One button in the bulk action bar.
|
|
@@ -426,9 +450,57 @@ interface ResourceFormProps {
|
|
|
426
450
|
steps?: WizardStep[];
|
|
427
451
|
/** Number of columns for the form layout. Default 1. */
|
|
428
452
|
columns?: 1 | 2;
|
|
453
|
+
/**
|
|
454
|
+
* Pre-fill a create form from the page URL: a query param named like a
|
|
455
|
+
* field (`?status=draft`) becomes that field's starting value. Default
|
|
456
|
+
* `true`, as before. Pass `false` when the page's query string means
|
|
457
|
+
* something else — a list's own filters, say — and must not leak into the
|
|
458
|
+
* new record.
|
|
459
|
+
*/
|
|
460
|
+
prefillFromUrl?: boolean;
|
|
461
|
+
/**
|
|
462
|
+
* Generate a placeholder ("Enter name") for fields whose `placeholder` is
|
|
463
|
+
* unset. Default `false`: a placeholder that restates the label adds noise,
|
|
464
|
+
* and reads as a filled value at a glance. A field's own `placeholder`
|
|
465
|
+
* always shows. (Select fields keep their "Select…" prompt either way.)
|
|
466
|
+
*/
|
|
467
|
+
autoPlaceholder?: boolean;
|
|
429
468
|
}
|
|
430
469
|
declare function ResourceForm(props: ResourceFormProps): React.JSX.Element | null;
|
|
431
470
|
|
|
471
|
+
/**
|
|
472
|
+
* Props for {@link ConfirmDeleteDialog}.
|
|
473
|
+
*
|
|
474
|
+
* @stability Beta
|
|
475
|
+
*/
|
|
476
|
+
interface ConfirmDeleteDialogProps {
|
|
477
|
+
open: boolean;
|
|
478
|
+
onOpenChange: (open: boolean) => void;
|
|
479
|
+
/**
|
|
480
|
+
* Runs the delete. The dialog stays open while it is pending, closes when
|
|
481
|
+
* it resolves, and shows the error's message (staying open, so the user
|
|
482
|
+
* can retry or cancel) when it rejects.
|
|
483
|
+
*/
|
|
484
|
+
onConfirm: () => void | Promise<void>;
|
|
485
|
+
/** Default "Delete this record?". */
|
|
486
|
+
title?: string;
|
|
487
|
+
/** Default "This can't be undone." */
|
|
488
|
+
description?: ReactNode;
|
|
489
|
+
/** Default "Delete". */
|
|
490
|
+
confirmLabel?: string;
|
|
491
|
+
/** Default "Cancel". */
|
|
492
|
+
cancelLabel?: string;
|
|
493
|
+
}
|
|
494
|
+
/**
|
|
495
|
+
* The confirm step in front of a destructive delete: an `AlertDialog` (Radix
|
|
496
|
+
* owns focus trap, Escape and focus return), a pending state, and the
|
|
497
|
+
* failure shown in place rather than swallowed. `ResourcePage` renders it
|
|
498
|
+
* for `allowDelete` / `confirmDelete`, as its `deleteDialog` slot.
|
|
499
|
+
*
|
|
500
|
+
* @stability Beta
|
|
501
|
+
*/
|
|
502
|
+
declare function ConfirmDeleteDialog({ open, onOpenChange, onConfirm, title, description, confirmLabel, cancelLabel, }: ConfirmDeleteDialogProps): React.JSX.Element;
|
|
503
|
+
|
|
432
504
|
/**
|
|
433
505
|
* Row-bound actions handed to `renderCard`, on top of the resource-wide
|
|
434
506
|
* `ResourceActions`. `open` is per-row, which is why it lives here rather
|
|
@@ -501,6 +573,11 @@ type ResourcePageSlotMap<T extends Record<string, unknown> = Record<string, unkn
|
|
|
501
573
|
detail: ComponentProps<typeof DetailPanel<T>>;
|
|
502
574
|
/** The create / edit form. */
|
|
503
575
|
form: ComponentProps<typeof ResourceForm>;
|
|
576
|
+
/**
|
|
577
|
+
* The confirm step in front of a delete. Rendered only when a delete is
|
|
578
|
+
* confirmed (`allowDelete`, or `onDelete` with `confirmDelete`).
|
|
579
|
+
*/
|
|
580
|
+
deleteDialog: ComponentProps<typeof ConfirmDeleteDialog>;
|
|
504
581
|
};
|
|
505
582
|
/**
|
|
506
583
|
* `slots` for {@link ResourcePage}, with one asymmetry the map cannot express:
|
|
@@ -516,6 +593,27 @@ type ResourcePageSlotMap<T extends Record<string, unknown> = Record<string, unkn
|
|
|
516
593
|
type ResourcePageSlots<T extends Record<string, unknown> = Record<string, unknown>> = Omit<Slots<ResourcePageSlotMap<T>>, "shell"> & {
|
|
517
594
|
shell?: ComponentType<ComponentProps<typeof DataPageShell>>;
|
|
518
595
|
};
|
|
596
|
+
/**
|
|
597
|
+
* `slotProps` for {@link ResourcePage}: extra props merged into a part you
|
|
598
|
+
* are keeping, winning over ResourcePage's own.
|
|
599
|
+
*/
|
|
600
|
+
type ResourcePageSlotProps<T extends Record<string, unknown> = Record<string, unknown>> = SlotPropsFor<ResourcePageSlotMap<T>>;
|
|
601
|
+
/**
|
|
602
|
+
* Copy for the built-in delete confirmation. Every field is optional;
|
|
603
|
+
* omitted ones fall back to `ConfirmDeleteDialog`'s defaults.
|
|
604
|
+
*
|
|
605
|
+
* @stability Beta
|
|
606
|
+
*/
|
|
607
|
+
interface ResourceDeleteConfirmCopy<T> {
|
|
608
|
+
/** Default "Delete this record?". A function receives the row. */
|
|
609
|
+
title?: string | ((item: T) => string);
|
|
610
|
+
/** Default "This can't be undone." */
|
|
611
|
+
description?: ReactNode | ((item: T) => ReactNode);
|
|
612
|
+
/** Default "Delete". */
|
|
613
|
+
confirmLabel?: string;
|
|
614
|
+
/** Default "Cancel". */
|
|
615
|
+
cancelLabel?: string;
|
|
616
|
+
}
|
|
519
617
|
interface ResourcePageProps<T extends Record<string, unknown> = Record<string, unknown>> {
|
|
520
618
|
/** Client-side mode: resource name passed to useResource / DataProvider. */
|
|
521
619
|
resource?: string;
|
|
@@ -533,6 +631,32 @@ interface ResourcePageProps<T extends Record<string, unknown> = Record<string, u
|
|
|
533
631
|
* {@link onPageChange}.
|
|
534
632
|
*/
|
|
535
633
|
total?: number;
|
|
634
|
+
/**
|
|
635
|
+
* How `resource` mode reaches rows past the first page. Ignored with
|
|
636
|
+
* `data` (server-data mode pages through the controlled props instead).
|
|
637
|
+
*
|
|
638
|
+
* - `"client"` (default): fetch up to `clientRowLimit` rows in one request
|
|
639
|
+
* and search, sort, filter and page them in the browser — header-click
|
|
640
|
+
* sort and instant search keep working. If the resource holds more rows
|
|
641
|
+
* than that, a notice under the list says how many are shown (and a
|
|
642
|
+
* console warning names this prop), rather than silently truncating.
|
|
643
|
+
* - `"server"`: fetch one page at a time. The pager under the list drives
|
|
644
|
+
* `useResource`'s page, the header's page size its `perPage`, and
|
|
645
|
+
* search / sort / status chips become provider queries (search is
|
|
646
|
+
* debounced), and the current page stays on screen while the next one
|
|
647
|
+
* loads. Give `statusFilter` explicit `options` here — derived ones can
|
|
648
|
+
* only see the current page's values. Search needs `searchFields` (or
|
|
649
|
+
* `resourceOptions.searchFields`) — the provider must be told which
|
|
650
|
+
* columns to match — so the search box is hidden without them. Table
|
|
651
|
+
* header sorting is withdrawn, as in server-data mode; the sort control
|
|
652
|
+
* in the filter row drives the query.
|
|
653
|
+
*/
|
|
654
|
+
pagination?: "client" | "server";
|
|
655
|
+
/**
|
|
656
|
+
* `pagination="client"` only: the most rows fetched. Default 1000. An
|
|
657
|
+
* explicit `resourceOptions.defaultPerPage` wins.
|
|
658
|
+
*/
|
|
659
|
+
clientRowLimit?: number;
|
|
536
660
|
title: string;
|
|
537
661
|
/** Page subtitle shown below the title. */
|
|
538
662
|
subtitle?: string;
|
|
@@ -551,6 +675,17 @@ interface ResourcePageProps<T extends Record<string, unknown> = Record<string, u
|
|
|
551
675
|
detailWidth?: "sm" | "md" | "lg";
|
|
552
676
|
titleField?: string;
|
|
553
677
|
subtitleField?: string;
|
|
678
|
+
/**
|
|
679
|
+
* Name a record from more than one field (`${first} ${last}`). Wins over
|
|
680
|
+
* `titleField` for the detail title and the card's accessible name.
|
|
681
|
+
*/
|
|
682
|
+
getTitle?: (item: T) => string;
|
|
683
|
+
/**
|
|
684
|
+
* The detail peek's subtitle, computed from the row ("2 staff", "Due
|
|
685
|
+
* Friday"). Wins over `subtitleField`, which can only show one column
|
|
686
|
+
* verbatim.
|
|
687
|
+
*/
|
|
688
|
+
getSubtitle?: (item: T) => string | undefined;
|
|
554
689
|
renderDetail?: (item: T) => ReactNode;
|
|
555
690
|
detailFields?: Array<{
|
|
556
691
|
key: string;
|
|
@@ -704,8 +839,54 @@ interface ResourcePageProps<T extends Record<string, unknown> = Record<string, u
|
|
|
704
839
|
pageSizeOptions?: number[];
|
|
705
840
|
onCreate?: () => void;
|
|
706
841
|
onEdit?: (item: T) => void;
|
|
707
|
-
|
|
842
|
+
/**
|
|
843
|
+
* Delete handler, shown as the detail peek's Delete button. Called
|
|
844
|
+
* directly, as before, unless `confirmDelete` is set — then it runs after
|
|
845
|
+
* the confirm dialog, which shows its error if the returned promise
|
|
846
|
+
* rejects, and closes the peek when it resolves.
|
|
847
|
+
*/
|
|
848
|
+
onDelete?: (item: T) => void | Promise<void>;
|
|
849
|
+
/**
|
|
850
|
+
* Whether the signed-in user may change this record at all: shorthand for
|
|
851
|
+
* `canEdit` and `canDelete` together. When it returns false the peek
|
|
852
|
+
* offers neither Edit nor Delete.
|
|
853
|
+
*
|
|
854
|
+
* Precedence: `canEditItem` is checked first. A row it refuses gets no Edit
|
|
855
|
+
* and no Delete, whatever `canEdit` / `canDelete` say. A row it allows is
|
|
856
|
+
* then gated by `canEdit` (Edit) and `canDelete` (Delete) when given. Use
|
|
857
|
+
* `canEditItem` alone for one owner rule, or the pair for separate rules.
|
|
858
|
+
*/
|
|
859
|
+
canEditItem?: (item: T) => boolean;
|
|
708
860
|
createLabel?: string;
|
|
861
|
+
/**
|
|
862
|
+
* Whether this row may be edited. The peek's Edit button is hidden for a
|
|
863
|
+
* row that returns `false` (and the built-in edit form will not open for
|
|
864
|
+
* it). Omitted, every row is editable, as before. A UI gate only: the
|
|
865
|
+
* server must still refuse the write.
|
|
866
|
+
*/
|
|
867
|
+
canEdit?: (item: T) => boolean;
|
|
868
|
+
/**
|
|
869
|
+
* Whether this row may be deleted. The peek's Delete button is hidden for
|
|
870
|
+
* a row that returns `false`. Omitted, every row is deletable. A UI gate
|
|
871
|
+
* only: the server must still refuse the write.
|
|
872
|
+
*/
|
|
873
|
+
canDelete?: (item: T) => boolean;
|
|
874
|
+
/**
|
|
875
|
+
* Built-in delete for `resource` mode (ignored if `onDelete` is
|
|
876
|
+
* provided): the peek's Delete button opens a confirm dialog, then calls
|
|
877
|
+
* `useResource`'s `remove` with the row's `getKey` (when supplied) or its
|
|
878
|
+
* `id` — which invalidates the list — and closes the peek. A failure shows
|
|
879
|
+
* in the dialog.
|
|
880
|
+
*/
|
|
881
|
+
allowDelete?: boolean;
|
|
882
|
+
/**
|
|
883
|
+
* Confirm before deleting. Defaults to `true` for `allowDelete` and
|
|
884
|
+
* `false` for `onDelete` (which keeps its old, unconfirmed behaviour).
|
|
885
|
+
* Pass an object to set the dialog's copy.
|
|
886
|
+
*/
|
|
887
|
+
confirmDelete?: boolean | ResourceDeleteConfirmCopy<T>;
|
|
888
|
+
/** Called after a confirmed or built-in delete succeeds — e.g. a toast. */
|
|
889
|
+
onDeleted?: (item: T) => void;
|
|
709
890
|
/**
|
|
710
891
|
* Extra header actions beyond the create button, in one overflow menu.
|
|
711
892
|
* See {@link PageHeaderAction} — the budget is one primary, the rest here.
|
|
@@ -772,6 +953,14 @@ interface ResourcePageProps<T extends Record<string, unknown> = Record<string, u
|
|
|
772
953
|
* Omitted, the default `DataTable` render is used unchanged.
|
|
773
954
|
*/
|
|
774
955
|
renderTable?: (items: T[]) => ReactNode;
|
|
956
|
+
/**
|
|
957
|
+
* With a custom `renderCard`: make each whole card (and list row) one
|
|
958
|
+
* keyboard-reachable button that opens the detail, so the card needs no
|
|
959
|
+
* `actions.open()` wiring and no hand-rolled `div role="button"`. Links and
|
|
960
|
+
* buttons inside the card keep their own clicks. Off by default, because
|
|
961
|
+
* an existing card that already calls `open()` would fire it twice.
|
|
962
|
+
*/
|
|
963
|
+
openOnCardClick?: boolean;
|
|
775
964
|
/**
|
|
776
965
|
* Tailwind grid-column classes for the card grid, passed through to
|
|
777
966
|
* `DataGrid`'s `gridCols` — e.g. `"grid-cols-1 sm:grid-cols-2 lg:grid-cols-3"`.
|
|
@@ -798,7 +987,21 @@ interface ResourcePageProps<T extends Record<string, unknown> = Record<string, u
|
|
|
798
987
|
*/
|
|
799
988
|
keyboardNavigation?: boolean;
|
|
800
989
|
toolbar?: ReactNode;
|
|
990
|
+
/** The empty state's action, e.g. the create button. */
|
|
801
991
|
emptyState?: ReactNode;
|
|
992
|
+
/** The empty state's icon. */
|
|
993
|
+
emptyIcon?: LucideIcon;
|
|
994
|
+
/** The empty state's title, e.g. "No tasks yet". Default "No results found". */
|
|
995
|
+
emptyMessage?: string;
|
|
996
|
+
/** A sentence under the empty-state title — forwarded to `DataPageShell`. */
|
|
997
|
+
emptyDescription?: string;
|
|
998
|
+
/**
|
|
999
|
+
* `"page"` (default) renders the full header with the title. `"toolbar"`
|
|
1000
|
+
* keeps the header's actions (count, view toggle, settings, create) and
|
|
1001
|
+
* drops the title block — for a list inside a tab or card whose page
|
|
1002
|
+
* already has the heading. See `DataPageShell`'s `header`.
|
|
1003
|
+
*/
|
|
1004
|
+
header?: "page" | "toolbar";
|
|
802
1005
|
/**
|
|
803
1006
|
* Imperative navigation used only when `detail="route"` (navigates to
|
|
804
1007
|
* `/{resource}/{id}` on row click). Defaults to a full-page navigation.
|
|
@@ -807,7 +1010,13 @@ interface ResourcePageProps<T extends Record<string, unknown> = Record<string, u
|
|
|
807
1010
|
*/
|
|
808
1011
|
onNavigate?: NavigateFn;
|
|
809
1012
|
resourceOptions?: UseResourceOptions<T>;
|
|
810
|
-
|
|
1013
|
+
/**
|
|
1014
|
+
* The view shown until the user picks one with the view toggle. Default
|
|
1015
|
+
* `"table"`. Pass `{ mobile: "grid", desktop: "table" }` for cards on
|
|
1016
|
+
* phones and a table on desktops. Only an explicit pick is persisted, so
|
|
1017
|
+
* the responsive default stays responsive until the user chooses.
|
|
1018
|
+
*/
|
|
1019
|
+
defaultView?: ViewModeDefault;
|
|
811
1020
|
/**
|
|
812
1021
|
* Persist key for density / page size via `useViewSettings`.
|
|
813
1022
|
* Defaults to `resource` or a slug of `title`.
|
|
@@ -824,7 +1033,7 @@ interface ResourcePageProps<T extends Record<string, unknown> = Record<string, u
|
|
|
824
1033
|
*/
|
|
825
1034
|
slots?: ResourcePageSlots<T>;
|
|
826
1035
|
/** Extra props merged into a part you are KEEPING. Wins over ours. */
|
|
827
|
-
slotProps?:
|
|
1036
|
+
slotProps?: ResourcePageSlotProps<T>;
|
|
828
1037
|
/** Merged onto the page shell's root. */
|
|
829
1038
|
className?: string;
|
|
830
1039
|
}
|
|
@@ -914,16 +1123,31 @@ interface RelationFieldProps {
|
|
|
914
1123
|
value: string;
|
|
915
1124
|
onChange: (value: string) => void;
|
|
916
1125
|
disabled?: boolean;
|
|
1126
|
+
/** Id for the trigger, so a `<label htmlFor>` names it. */
|
|
1127
|
+
id?: string;
|
|
1128
|
+
"aria-describedby"?: string;
|
|
1129
|
+
"aria-invalid"?: boolean;
|
|
1130
|
+
"aria-required"?: boolean;
|
|
917
1131
|
}
|
|
918
|
-
declare function RelationField({ field, value, onChange, disabled, }: RelationFieldProps): React.JSX.Element;
|
|
1132
|
+
declare function RelationField({ field, value, onChange, disabled, id, "aria-describedby": ariaDescribedBy, "aria-invalid": ariaInvalid, "aria-required": ariaRequired, }: RelationFieldProps): React.JSX.Element;
|
|
919
1133
|
|
|
920
1134
|
interface FileUploadFieldProps {
|
|
921
1135
|
field: ResourceFormFieldDef;
|
|
922
1136
|
value: File | File[] | null;
|
|
923
1137
|
onChange: (value: File | File[] | null) => void;
|
|
924
1138
|
disabled?: boolean;
|
|
1139
|
+
/** Id for the file input, so a `<label htmlFor>` names it. */
|
|
1140
|
+
id?: string;
|
|
1141
|
+
/**
|
|
1142
|
+
* The label's id. The drop zone is what takes keyboard focus (the input
|
|
1143
|
+
* is hidden), so it is named by `aria-labelledby` rather than `htmlFor`.
|
|
1144
|
+
*/
|
|
1145
|
+
"aria-labelledby"?: string;
|
|
1146
|
+
"aria-describedby"?: string;
|
|
1147
|
+
"aria-invalid"?: boolean;
|
|
1148
|
+
"aria-required"?: boolean;
|
|
925
1149
|
}
|
|
926
|
-
declare function FileUploadField({ field, value, onChange, disabled, }: FileUploadFieldProps): React.JSX.Element;
|
|
1150
|
+
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
1151
|
|
|
928
1152
|
declare function FieldArrayRenderer({ field, control, action, watchedValues, }: {
|
|
929
1153
|
field: ResourceFormFieldDef;
|
|
@@ -959,4 +1183,4 @@ declare const AppShell: typeof AppShell$1;
|
|
|
959
1183
|
*/
|
|
960
1184
|
declare const PageHeader: typeof PageHeader$1;
|
|
961
1185
|
|
|
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 };
|
|
1186
|
+
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, 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 };
|