@shapesos/clay 0.9.0 → 0.11.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/README.md +46 -1
  2. package/dist/artifacts.cjs +741 -0
  3. package/dist/artifacts.cjs.map +1 -0
  4. package/dist/artifacts.d.cts +56 -0
  5. package/dist/artifacts.d.ts +56 -0
  6. package/dist/artifacts.js +23 -0
  7. package/dist/artifacts.js.map +1 -0
  8. package/dist/blocks.cjs +1282 -0
  9. package/dist/blocks.cjs.map +1 -0
  10. package/dist/blocks.css +2 -0
  11. package/dist/blocks.d.cts +41 -0
  12. package/dist/blocks.d.ts +41 -0
  13. package/dist/blocks.js +31 -0
  14. package/dist/blocks.js.map +1 -0
  15. package/dist/button.d.cts +2 -2
  16. package/dist/button.d.ts +2 -2
  17. package/dist/chat.cjs +755 -281
  18. package/dist/chat.cjs.map +1 -1
  19. package/dist/chat.d.cts +31 -10
  20. package/dist/chat.d.ts +31 -10
  21. package/dist/chat.js +8 -3
  22. package/dist/chunk-BX5TCEPR.js +436 -0
  23. package/dist/chunk-BX5TCEPR.js.map +1 -0
  24. package/dist/chunk-JGMN6W72.js +12 -0
  25. package/dist/chunk-JGMN6W72.js.map +1 -0
  26. package/dist/{chunk-27GJUWVN.js → chunk-JJUIBBBU.js} +14 -8
  27. package/dist/chunk-JJUIBBBU.js.map +1 -0
  28. package/dist/chunk-L35M3OD5.js +99 -0
  29. package/dist/chunk-L35M3OD5.js.map +1 -0
  30. package/dist/chunk-MEJESPTZ.js +1 -0
  31. package/dist/chunk-MEJESPTZ.js.map +1 -0
  32. package/dist/chunk-MXOPG747.js +238 -0
  33. package/dist/chunk-MXOPG747.js.map +1 -0
  34. package/dist/{chunk-MLCRDVQ2.js → chunk-OBOXCBDL.js} +13 -5
  35. package/dist/chunk-OBOXCBDL.js.map +1 -0
  36. package/dist/chunk-OUW6PUEB.js +223 -0
  37. package/dist/chunk-OUW6PUEB.js.map +1 -0
  38. package/dist/{chunk-TDMJUF4A.js → chunk-WPQQVKWY.js} +4 -4
  39. package/dist/icon.cjs +12 -4
  40. package/dist/icon.cjs.map +1 -1
  41. package/dist/icon.d.cts +19 -6
  42. package/dist/icon.d.ts +19 -6
  43. package/dist/icon.js +1 -1
  44. package/dist/index.cjs +789 -273
  45. package/dist/index.cjs.map +1 -1
  46. package/dist/index.d.cts +7 -3
  47. package/dist/index.d.ts +7 -3
  48. package/dist/index.js +61 -21
  49. package/dist/lottie.d.cts +2 -2
  50. package/dist/lottie.d.ts +2 -2
  51. package/dist/table.cjs +142 -0
  52. package/dist/table.cjs.map +1 -0
  53. package/dist/table.d.cts +44 -0
  54. package/dist/table.d.ts +44 -0
  55. package/dist/table.js +24 -0
  56. package/dist/table.js.map +1 -0
  57. package/dist/text-area.d.cts +2 -2
  58. package/dist/text-area.d.ts +2 -2
  59. package/dist/text-area.js +2 -2
  60. package/dist/types-3Gzk7cRt.d.cts +121 -0
  61. package/dist/types-3Gzk7cRt.d.ts +121 -0
  62. package/dist/types-C9XX-Uhk.d.ts +68 -0
  63. package/dist/types-DuuRI4ll.d.cts +68 -0
  64. package/dist/utils.cjs +14 -7
  65. package/dist/utils.cjs.map +1 -1
  66. package/dist/utils.d.cts +18 -1
  67. package/dist/utils.d.ts +18 -1
  68. package/dist/utils.js +6 -4
  69. package/package.json +34 -4
  70. package/dist/chunk-27GJUWVN.js.map +0 -1
  71. package/dist/chunk-A77BGJH4.js +0 -458
  72. package/dist/chunk-A77BGJH4.js.map +0 -1
  73. package/dist/chunk-MLCRDVQ2.js.map +0 -1
  74. /package/dist/{chunk-TDMJUF4A.js.map → chunk-WPQQVKWY.js.map} +0 -0
@@ -0,0 +1,121 @@
1
+ import { ComponentType } from 'react';
2
+
3
+ /**
4
+ * Artifact-domain types — wire-protocol shapes shared between artifact components, block-services, and
5
+ * consumers. The block layer (`@/components/blocks`) imports these to define
6
+ * `ArtifactRefBlockRecord`; the artifact components consume them directly.
7
+ *
8
+ * Mirrored from the agent / io-server contract — see the `chat-typed-content-blocks-and-artifacts`
9
+ * plan, §6.4 (table contract).
10
+ */
11
+
12
+ /**
13
+ * Artifact-type discriminator constants. Uppercase to match the server-side `artifactTypes`
14
+ * constant — io-server stores `artifact.type === "TABLE"` (aligned with the GraphQL enum).
15
+ */
16
+ declare const artifactTypes: {
17
+ readonly TABLE: "TABLE";
18
+ };
19
+ /** Discriminator for artifact types. Additive — new types extend this union. */
20
+ type ArtifactType = (typeof artifactTypes)[keyof typeof artifactTypes];
21
+ /**
22
+ * A single column descriptor for a `TableArtifact`. Carries the ORDER and LABEL for the
23
+ * rendered table — `key` selects a column from the CSV header; `label` overrides the rendered
24
+ * header text. The CSV is always the source of truth for the data shape; clay never coerces,
25
+ * reformats, or interprets cell values.
26
+ */
27
+ interface TableColumn {
28
+ /** Matches a header in the CSV. Required. Used to project + order the CSV's columns. */
29
+ key: string;
30
+ /** Human-readable header text. Required. */
31
+ label: string;
32
+ /**
33
+ * Accepted in the wire payload but not consulted by the renderer — every cell is rendered as
34
+ * the raw CSV string (or empty for null). Format conversion is a server concern; if a column
35
+ * needs to be "Yes/No" instead of "true/false", the server should emit it that way in the CSV.
36
+ * Kept in the interface so callers that pass `type` (e.g. io-client) don't get a type error.
37
+ */
38
+ type?: "string" | "number" | "date" | "boolean";
39
+ /**
40
+ * Accepted in the wire payload but not consulted by the renderer — every column is left-aligned
41
+ * by design. Kept in the interface for forward-compat; restore per-column alignment by editing
42
+ * `table-artifact-content.tsx` if a future use case justifies it.
43
+ */
44
+ align?: "left" | "center" | "right";
45
+ }
46
+ /**
47
+ * Optional stats embedded in `TableConfig` so clients can pre-render UI before fetching the
48
+ * actual data payload (e.g. N loading shimmers sized to the eventual row count).
49
+ *
50
+ * Backward-compat: optional everywhere — older artifacts persisted before this field shipped
51
+ * still validate. The renderer falls back to a single status-message line when `stats` is absent.
52
+ */
53
+ interface TableStats {
54
+ /** Total number of rows in the materialised CSV. Lets the loading branch render shimmers. */
55
+ rowCount: number;
56
+ }
57
+ /** Rendering config for a table artifact, stored at `artifact.config` (JSONB on the server). */
58
+ interface TableConfig {
59
+ /** Column descriptors in render order. */
60
+ columns: TableColumn[];
61
+ /** Optional metadata describing the underlying data — used for loading-state UI. */
62
+ stats?: TableStats;
63
+ }
64
+ /**
65
+ * A row inside a table artifact's parsed CSV. Every cell is the raw string from the CSV (or
66
+ * `null` for the empty string). The renderer never coerces or interprets — type-aware formatting
67
+ * is a server concern (the CSV's contents are what the user sees).
68
+ */
69
+ type TableRow = Record<string, string | null>;
70
+ /** Common shape every artifact record carries. Discriminated by `type`. */
71
+ interface ArtifactBase<TType extends ArtifactType, TConfig> {
72
+ /** Artifact-type discriminator. */
73
+ type: TType;
74
+ /** Human-readable label. Nullable when the artifact has no title. */
75
+ title: string | null;
76
+ /** Rendering config — shape varies per artifact type. */
77
+ config: TConfig;
78
+ /** Protected-asset metadata pointing to the underlying data payload. Null while the asset is unavailable. */
79
+ protectedAsset: {
80
+ /** Time-limited URL the renderer fetches the artifact's data from. */
81
+ presignedUrl: string;
82
+ } | null;
83
+ }
84
+ /** Concrete table artifact record. Future artifact types extend `ArtifactRecord` here. */
85
+ type TableArtifactRecord = ArtifactBase<typeof artifactTypes.TABLE, TableConfig>;
86
+ /**
87
+ * Discriminated union of all supported artifacts. Single-member today; extension pattern:
88
+ *
89
+ * export type ArtifactRecord = TableArtifactRecord | ChartArtifactRecord;
90
+ *
91
+ * Every consumer that narrows on `artifact.type` must add the new branch here AND in the
92
+ * `ArtifactServices` map (single source of truth for the type→component mapping). Older clay
93
+ * versions that don't know about a new type silently render nothing — see `ArtifactRefBlock` guard.
94
+ */
95
+ type ArtifactRecord = TableArtifactRecord;
96
+ /** Props every concrete artifact component receives from the dispatcher. */
97
+ interface ArtifactServiceProps<TArtifact extends ArtifactRecord = ArtifactRecord> {
98
+ /** The artifact metadata (config + presigned URL). */
99
+ artifact: TArtifact;
100
+ }
101
+ /** Per-artifact-type service — registry entry that pairs a discriminator with its concrete component. */
102
+ interface ArtifactService<TArtifact extends ArtifactRecord = ArtifactRecord> {
103
+ /** Discriminator value this service handles. */
104
+ type: ArtifactType;
105
+ /** React component that renders an artifact of this type — composes ai-elements primitives
106
+ * (`Artifact`, `ArtifactHeader`, `ArtifactTitle`, `ArtifactActions`, `ArtifactContent`) directly. */
107
+ Component: ComponentType<ArtifactServiceProps<TArtifact>>;
108
+ /**
109
+ * Returns a plain-text / markdown string suitable for clipboard copy.
110
+ *
111
+ * Called by `ArtifactRefBlockService.toClipboardText` when the user copies a message that
112
+ * contains this artifact. Must be pure and synchronous.
113
+ *
114
+ * Implementation notes per type:
115
+ * - `table`: returns the artifact title as plain text (see `TableArtifactService`).
116
+ * - Future types implement their own representation in a co-located `*-service.ts` file.
117
+ */
118
+ toClipboardText: (artifact: TArtifact) => string;
119
+ }
120
+
121
+ export { type ArtifactRecord as A, type TableArtifactRecord as T, type ArtifactService as a, type ArtifactServiceProps as b, type ArtifactType as c, type TableColumn as d, type TableConfig as e, type TableRow as f, type TableStats as g, artifactTypes as h };
@@ -0,0 +1,68 @@
1
+ import { ComponentType } from 'react';
2
+ import { A as ArtifactRecord } from './types-3Gzk7cRt.js';
3
+
4
+ /** Block-type discriminator constants. Mirrors the wire shape from io-server / shapes-agent. */
5
+ declare const blockTypes: {
6
+ readonly TEXT: "TEXT";
7
+ readonly ARTIFACT_REF: "ARTIFACT_REF";
8
+ };
9
+ /** Discriminator for content blocks. */
10
+ type BlockType = (typeof blockTypes)[keyof typeof blockTypes];
11
+ /** Plain-text content block — rendered as Markdown. */
12
+ interface TextBlockRecord {
13
+ /** Block-type discriminator. */
14
+ type: typeof blockTypes.TEXT;
15
+ /** Type-specific data; kept as `payload` to match the wire shape 1:1. */
16
+ payload: {
17
+ /** The Markdown source for this text segment. */
18
+ text: string;
19
+ };
20
+ }
21
+ /**
22
+ * Reference-to-an-artifact content block — dispatched to the per-type artifact component.
23
+ *
24
+ * The artifact-domain types (`ArtifactRecord`, `TableArtifactRecord`, etc.) live in
25
+ * `@/components/artifacts/types` so the artifact layer can be consumed independently of the
26
+ * block layer (e.g. by a future surface that renders an artifact outside chat). The block layer
27
+ * imports them here to wrap an artifact in an `ARTIFACT_REF` block.
28
+ */
29
+ interface ArtifactRefBlockRecord {
30
+ /** Block-type discriminator. */
31
+ type: typeof blockTypes.ARTIFACT_REF;
32
+ /** Type-specific data; kept as `payload` to match the wire shape 1:1. */
33
+ payload: {
34
+ /** The artifact's stable identifier, used for keying / future cross-message reuse. */
35
+ artifactId: string;
36
+ /** Inlined artifact metadata — clients fetch the underlying data via `artifact.protectedAsset.presignedUrl`. */
37
+ artifact: ArtifactRecord;
38
+ };
39
+ }
40
+ /** Discriminated union of all supported content blocks. Additive — new types extend this union. */
41
+ type BlockRecord = TextBlockRecord | ArtifactRefBlockRecord;
42
+ /** Props every concrete block component receives from the dispatcher. */
43
+ interface BlockComponentProps<TBlock extends BlockRecord = BlockRecord> {
44
+ /** The block to render. */
45
+ block: TBlock;
46
+ }
47
+ /** Per-block-type service — registry entry that pairs a discriminator with its concrete component. */
48
+ interface BlockService<TBlock extends BlockRecord = BlockRecord> {
49
+ /** Discriminator value this service handles. */
50
+ type: BlockType;
51
+ /** React component that renders a block of this type. */
52
+ Component: ComponentType<BlockComponentProps<TBlock>>;
53
+ /**
54
+ * Returns a plain-text or markdown string representation of this block suitable for clipboard
55
+ * copy. Called by `copyMessageText` when the user clicks "copy message".
56
+ *
57
+ * - `TextBlockService`: returns `block.payload.text` verbatim (markdown preserved).
58
+ * - `ArtifactRefBlockService`: delegates to `ArtifactServices[artifact.type].toClipboardText`.
59
+ * Falls back to `[Artifact: <title>]` when the artifact type has no registered component.
60
+ *
61
+ * Optional so external `BlockService` implementors don't break on upgrade — when omitted, the
62
+ * aggregator treats the block as contributing the empty string (silently skipped). Implementations
63
+ * must be pure, synchronous functions — no async, no hooks, no side effects.
64
+ */
65
+ toClipboardText?: (block: TBlock) => string;
66
+ }
67
+
68
+ export { type ArtifactRefBlockRecord as A, type BlockComponentProps as B, type TextBlockRecord as T, type BlockRecord as a, type BlockService as b, type BlockType as c, blockTypes as d };
@@ -0,0 +1,68 @@
1
+ import { ComponentType } from 'react';
2
+ import { A as ArtifactRecord } from './types-3Gzk7cRt.cjs';
3
+
4
+ /** Block-type discriminator constants. Mirrors the wire shape from io-server / shapes-agent. */
5
+ declare const blockTypes: {
6
+ readonly TEXT: "TEXT";
7
+ readonly ARTIFACT_REF: "ARTIFACT_REF";
8
+ };
9
+ /** Discriminator for content blocks. */
10
+ type BlockType = (typeof blockTypes)[keyof typeof blockTypes];
11
+ /** Plain-text content block — rendered as Markdown. */
12
+ interface TextBlockRecord {
13
+ /** Block-type discriminator. */
14
+ type: typeof blockTypes.TEXT;
15
+ /** Type-specific data; kept as `payload` to match the wire shape 1:1. */
16
+ payload: {
17
+ /** The Markdown source for this text segment. */
18
+ text: string;
19
+ };
20
+ }
21
+ /**
22
+ * Reference-to-an-artifact content block — dispatched to the per-type artifact component.
23
+ *
24
+ * The artifact-domain types (`ArtifactRecord`, `TableArtifactRecord`, etc.) live in
25
+ * `@/components/artifacts/types` so the artifact layer can be consumed independently of the
26
+ * block layer (e.g. by a future surface that renders an artifact outside chat). The block layer
27
+ * imports them here to wrap an artifact in an `ARTIFACT_REF` block.
28
+ */
29
+ interface ArtifactRefBlockRecord {
30
+ /** Block-type discriminator. */
31
+ type: typeof blockTypes.ARTIFACT_REF;
32
+ /** Type-specific data; kept as `payload` to match the wire shape 1:1. */
33
+ payload: {
34
+ /** The artifact's stable identifier, used for keying / future cross-message reuse. */
35
+ artifactId: string;
36
+ /** Inlined artifact metadata — clients fetch the underlying data via `artifact.protectedAsset.presignedUrl`. */
37
+ artifact: ArtifactRecord;
38
+ };
39
+ }
40
+ /** Discriminated union of all supported content blocks. Additive — new types extend this union. */
41
+ type BlockRecord = TextBlockRecord | ArtifactRefBlockRecord;
42
+ /** Props every concrete block component receives from the dispatcher. */
43
+ interface BlockComponentProps<TBlock extends BlockRecord = BlockRecord> {
44
+ /** The block to render. */
45
+ block: TBlock;
46
+ }
47
+ /** Per-block-type service — registry entry that pairs a discriminator with its concrete component. */
48
+ interface BlockService<TBlock extends BlockRecord = BlockRecord> {
49
+ /** Discriminator value this service handles. */
50
+ type: BlockType;
51
+ /** React component that renders a block of this type. */
52
+ Component: ComponentType<BlockComponentProps<TBlock>>;
53
+ /**
54
+ * Returns a plain-text or markdown string representation of this block suitable for clipboard
55
+ * copy. Called by `copyMessageText` when the user clicks "copy message".
56
+ *
57
+ * - `TextBlockService`: returns `block.payload.text` verbatim (markdown preserved).
58
+ * - `ArtifactRefBlockService`: delegates to `ArtifactServices[artifact.type].toClipboardText`.
59
+ * Falls back to `[Artifact: <title>]` when the artifact type has no registered component.
60
+ *
61
+ * Optional so external `BlockService` implementors don't break on upgrade — when omitted, the
62
+ * aggregator treats the block as contributing the empty string (silently skipped). Implementations
63
+ * must be pure, synchronous functions — no async, no hooks, no side effects.
64
+ */
65
+ toClipboardText?: (block: TBlock) => string;
66
+ }
67
+
68
+ export { type ArtifactRefBlockRecord as A, type BlockComponentProps as B, type TextBlockRecord as T, type BlockRecord as a, type BlockService as b, type BlockType as c, blockTypes as d };
package/dist/utils.cjs CHANGED
@@ -21,6 +21,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
21
21
  var utils_exports = {};
22
22
  __export(utils_exports, {
23
23
  convertTableToCSV: () => convertTableToCSV,
24
+ escapeCsvField: () => escapeCsvField,
24
25
  markdownToPlainText: () => markdownToPlainText,
25
26
  mergeRefs: () => mergeRefs,
26
27
  parseMarkdownTable: () => parseMarkdownTable
@@ -112,21 +113,27 @@ function parseMarkdownTable(markdown, tableIndex = 0) {
112
113
  return null;
113
114
  }
114
115
 
115
- // src/utils/convert-table-to-csv.ts
116
- function escapeField(field) {
117
- if (field.includes(",") || field.includes('"') || field.includes("\n") || field.includes("\r")) {
118
- return `"${field.replace(/"/g, '""')}"`;
116
+ // src/utils/escape-csv-field.ts
117
+ var FORMULA_TRIGGERS = /* @__PURE__ */ new Set(["=", "+", "@", " ", "\r"]);
118
+ function escapeCsvField(field) {
119
+ const needsNeutralise = field.length > 0 && FORMULA_TRIGGERS.has(field[0]);
120
+ const safeField = needsNeutralise ? ` ${field}` : field;
121
+ if (needsNeutralise || safeField.includes(",") || safeField.includes('"') || safeField.includes("\n") || safeField.includes("\r")) {
122
+ return `"${safeField.replace(/"/g, '""')}"`;
119
123
  }
120
- return field;
124
+ return safeField;
121
125
  }
126
+
127
+ // src/utils/convert-table-to-csv.ts
122
128
  function convertTableToCSV(table) {
123
- const headerRow = table.columns.map(escapeField).join(",");
124
- const dataRows = table.rows.map((row) => row.map(escapeField).join(","));
129
+ const headerRow = table.columns.map(escapeCsvField).join(",");
130
+ const dataRows = table.rows.map((row) => row.map(escapeCsvField).join(","));
125
131
  return [headerRow, ...dataRows].join("\r\n");
126
132
  }
127
133
  // Annotate the CommonJS export names for ESM import in node:
128
134
  0 && (module.exports = {
129
135
  convertTableToCSV,
136
+ escapeCsvField,
130
137
  markdownToPlainText,
131
138
  mergeRefs,
132
139
  parseMarkdownTable
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/utils/index.ts","../src/utils/markdown-to-plain-text.ts","../src/utils/merge-refs.ts","../src/utils/parse-markdown-table.ts","../src/utils/convert-table-to-csv.ts"],"sourcesContent":["export { markdownToPlainText } from \"./markdown-to-plain-text\";\nexport { mergeRefs } from \"./merge-refs\";\nexport { parseMarkdownTable } from \"./parse-markdown-table\";\nexport type { MarkdownTable } from \"./parse-markdown-table\";\nexport { convertTableToCSV } from \"./convert-table-to-csv\";\n","const PLACEHOLDER_PREFIX = \"\\u200B\\u200BIC\";\nconst PLACEHOLDER_SUFFIX = \"\\u200B\\u200B\";\nconst PLACEHOLDER_REGEX = new RegExp(`${PLACEHOLDER_PREFIX}(\\\\d+)${PLACEHOLDER_SUFFIX}`, \"g\");\n\n/**\n * Strips only inline markdown marks (bold, italic, strikethrough) from a string.\n * Does not touch block-level syntax (headings, blockquotes, horizontal rules, links, images)\n * so values like `> 100`, `# items`, or `---` are preserved as-is.\n * @param text - The text to strip inline marks from.\n * @returns The text with inline marks removed.\n */\nexport function stripInlineMarks(text: string): string {\n return (\n text\n // Bold + italic\n .replace(/\\*{3}(.+?)\\*{3}/g, \"$1\")\n // Bold\n .replace(/\\*{2}(.+?)\\*{2}/g, \"$1\")\n .replace(/_{2}(.+?)_{2}/g, \"$1\")\n // Italic\n .replace(/\\*(.+?)\\*/g, \"$1\")\n .replace(/_(.+?)_/g, \"$1\")\n // Strikethrough\n .replace(/~~(.+?)~~/g, \"$1\")\n );\n}\n\n/**\n * Converts a Markdown string to plain text by stripping all formatting syntax.\n *\n * Handles headings, bold/italic/strikethrough, links, images, blockquotes, code blocks,\n * inline code, tables, and horizontal rules. Inline code content is preserved literally\n * (formatting inside backticks is not stripped).\n *\n * @param markdown - The Markdown source string.\n * @returns A plain-text string with all Markdown syntax removed.\n */\nexport function markdownToPlainText(markdown: string): string {\n if (!markdown) return \"\";\n\n // Protect inline code content from bold/italic stripping by replacing with placeholders\n const inlineCodeSnippets: string[] = [];\n let result = markdown\n // HTML comments (e.g. <!-- table-title: ... -->), including multiline and --!> variants\n .replace(/^<!--[\\s\\S]*?--!?>\\n?/gm, \"\")\n // Code blocks (must come first)\n .replace(/```[\\s\\S]*?```/g, \"\")\n // Inline code → placeholder\n .replace(/`([^`]+)`/g, (_match, code: string) => {\n inlineCodeSnippets.push(code);\n return `${PLACEHOLDER_PREFIX}${inlineCodeSnippets.length - 1}${PLACEHOLDER_SUFFIX}`;\n })\n // Remove headings markers, keep text\n .replace(/^#{1,6}\\s+/gm, \"\")\n // Remove horizontal rules\n .replace(/^(?:---|\\*\\*\\*|___)\\s*$/gm, \"\");\n result = stripInlineMarks(result)\n // Images: ![alt](url) → alt (must come before links)\n .replace(/!\\[([^\\]]*)\\]\\([^)]+\\)/g, \"$1\")\n // Links: [text](url) → text\n .replace(/\\[([^\\]]+)\\]\\([^)]+\\)/g, \"$1\")\n // Blockquote markers\n .replace(/^>\\s?/gm, \"\")\n // Table separator rows (|---|---|)\n .replace(/^\\|[-:\\s|]+\\|\\s*$/gm, \"\")\n // Table rows: | a | b | → a | b\n .replace(/^\\|\\s?/gm, \"\")\n .replace(/\\s?\\|$/gm, \"\")\n // Collapse multiple blank lines\n .replace(/\\n{3,}/g, \"\\n\\n\")\n .trim();\n\n // Restore inline code content\n result = result.replace(PLACEHOLDER_REGEX, (_match, index: string) => inlineCodeSnippets[Number(index)]);\n\n return result;\n}\n","import type { Ref } from \"react\";\n\n/**\n * Combines multiple React refs into a single callback ref.\n * Useful when a component needs to forward its ref while also keeping a local ref.\n *\n * @param refs - Any number of React refs (callback refs, RefObjects, or `undefined`).\n * @returns A callback ref that updates all provided refs when the DOM node changes.\n */\nexport function mergeRefs<T>(...refs: (Ref<T> | undefined)[]): (node: T | null) => void {\n return (node) => {\n for (const ref of refs) {\n if (typeof ref === \"function\") {\n ref(node);\n } else if (ref) {\n (ref as React.MutableRefObject<T | null>).current = node;\n }\n }\n };\n}\n","import { stripInlineMarks } from \"./markdown-to-plain-text\";\n\n/** Structured representation of a parsed markdown table. */\nexport interface MarkdownTable {\n /** Column header labels. */\n columns: string[];\n /** Data rows, each an array of cell values matching the column order. */\n rows: string[][];\n /** Optional title extracted from a `<!-- table-title: ... -->` comment above the table. */\n title?: string;\n}\n\n/**\n * Parses cells from a markdown table row, trimming whitespace and stripping leading/trailing pipes.\n * @param line - A single line of a markdown table.\n * @returns An array of trimmed cell values.\n */\nfunction parseCells(line: string): string[] {\n const trimmed = line.trim();\n const stripped = trimmed.startsWith(\"|\") ? trimmed.slice(1) : trimmed;\n const withoutTrailing = stripped.endsWith(\"|\") ? stripped.slice(0, -1) : stripped;\n return withoutTrailing.split(\"|\").map((cell) => cell.trim());\n}\n\n/**\n * Tests whether a line is a GFM table separator row (e.g. `| --- | --- |`).\n * @param line - A single line to test.\n * @returns `true` if the line matches the separator pattern.\n */\nfunction isSeparatorRow(line: string): boolean {\n const cells = parseCells(line);\n return cells.length > 0 && cells.every((cell) => /^:?-{1,}:?$/.test(cell));\n}\n\n/**\n * Tests whether a line looks like a markdown table row (contains at least one pipe).\n * @param line - A single line to test.\n * @returns `true` if the line contains a pipe character.\n */\nfunction isTableRow(line: string): boolean {\n return line.includes(\"|\");\n}\n\nconst TABLE_TITLE_PREFIX = \"<!-- table-title:\";\nconst TABLE_TITLE_SUFFIX = \"-->\";\n\n/**\n * Extracts a table title from a `<!-- table-title: ... -->` HTML comment.\n * Uses string methods instead of regex to avoid ReDoS on untrusted input.\n * @param line - A single line to test.\n * @returns The title string, or `undefined` if the line is not a title comment.\n */\nfunction extractTableTitle(line: string): string | undefined {\n const trimmed = line.trim();\n if (!trimmed.startsWith(TABLE_TITLE_PREFIX) || !trimmed.endsWith(TABLE_TITLE_SUFFIX)) {\n return undefined;\n }\n return trimmed.slice(TABLE_TITLE_PREFIX.length, -TABLE_TITLE_SUFFIX.length).trim() || undefined;\n}\n\n/**\n * Extracts the Nth GFM markdown table from a string.\n * @param markdown - The markdown content to parse.\n * @param tableIndex - Zero-based index of the table to extract.\n * @returns The parsed table, or `null` if no table exists at the given index.\n */\nexport function parseMarkdownTable(markdown: string, tableIndex = 0): MarkdownTable | null {\n const lines = markdown.split(\"\\n\");\n let tableCount = 0;\n\n for (let i = 0; i < lines.length - 1; i++) {\n const headerLine = lines[i];\n const separatorLine = lines[i + 1];\n\n if (!isTableRow(headerLine) || !isSeparatorRow(separatorLine)) {\n continue;\n }\n\n if (tableCount === tableIndex) {\n const columns = parseCells(headerLine).map(stripInlineMarks);\n const rows: string[][] = [];\n const title = i > 0 ? extractTableTitle(lines[i - 1]) : undefined;\n\n for (let j = i + 2; j < lines.length; j++) {\n if (!isTableRow(lines[j]) || isSeparatorRow(lines[j])) {\n break;\n }\n const trimmedLine = lines[j].trim();\n if (trimmedLine === \"\") {\n break;\n }\n rows.push(parseCells(lines[j]).map(stripInlineMarks));\n }\n\n return { columns, rows, ...(title ? { title } : {}) };\n }\n\n tableCount++;\n }\n\n return null;\n}\n","import type { MarkdownTable } from \"./parse-markdown-table\";\n\n/**\n * Escapes and optionally quotes a CSV field per RFC 4180.\n * Fields containing commas, double quotes, or newlines are wrapped in double quotes.\n * Existing double quotes are escaped by doubling them.\n * @param field - The raw field value.\n * @returns The escaped/quoted field string.\n */\nfunction escapeField(field: string): string {\n if (field.includes(\",\") || field.includes('\"') || field.includes(\"\\n\") || field.includes(\"\\r\")) {\n return `\"${field.replace(/\"/g, '\"\"')}\"`;\n }\n return field;\n}\n\n/**\n * Converts a parsed markdown table to a CSV string (RFC 4180).\n * Fields containing commas, double quotes, or newlines are quoted.\n * @param table - The structured table data to convert.\n * @returns A CSV-formatted string with CRLF line endings.\n */\nexport function convertTableToCSV(table: MarkdownTable): string {\n const headerRow = table.columns.map(escapeField).join(\",\");\n const dataRows = table.rows.map((row) => row.map(escapeField).join(\",\"));\n return [headerRow, ...dataRows].join(\"\\r\\n\");\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/utils/index.ts","../src/utils/markdown-to-plain-text.ts","../src/utils/merge-refs.ts","../src/utils/parse-markdown-table.ts","../src/utils/escape-csv-field.ts","../src/utils/convert-table-to-csv.ts"],"sourcesContent":["export { markdownToPlainText } from \"./markdown-to-plain-text\";\nexport { mergeRefs } from \"./merge-refs\";\nexport { parseMarkdownTable } from \"./parse-markdown-table\";\nexport type { MarkdownTable } from \"./parse-markdown-table\";\nexport { convertTableToCSV } from \"./convert-table-to-csv\";\nexport { escapeCsvField } from \"./escape-csv-field\";\n","const PLACEHOLDER_PREFIX = \"\\u200B\\u200BIC\";\nconst PLACEHOLDER_SUFFIX = \"\\u200B\\u200B\";\nconst PLACEHOLDER_REGEX = new RegExp(`${PLACEHOLDER_PREFIX}(\\\\d+)${PLACEHOLDER_SUFFIX}`, \"g\");\n\n/**\n * Strips only inline markdown marks (bold, italic, strikethrough) from a string.\n * Does not touch block-level syntax (headings, blockquotes, horizontal rules, links, images)\n * so values like `> 100`, `# items`, or `---` are preserved as-is.\n * @param text - The text to strip inline marks from.\n * @returns The text with inline marks removed.\n */\nexport function stripInlineMarks(text: string): string {\n return (\n text\n // Bold + italic\n .replace(/\\*{3}(.+?)\\*{3}/g, \"$1\")\n // Bold\n .replace(/\\*{2}(.+?)\\*{2}/g, \"$1\")\n .replace(/_{2}(.+?)_{2}/g, \"$1\")\n // Italic\n .replace(/\\*(.+?)\\*/g, \"$1\")\n .replace(/_(.+?)_/g, \"$1\")\n // Strikethrough\n .replace(/~~(.+?)~~/g, \"$1\")\n );\n}\n\n/**\n * Converts a Markdown string to plain text by stripping all formatting syntax.\n *\n * Handles headings, bold/italic/strikethrough, links, images, blockquotes, code blocks,\n * inline code, tables, and horizontal rules. Inline code content is preserved literally\n * (formatting inside backticks is not stripped).\n *\n * @param markdown - The Markdown source string.\n * @returns A plain-text string with all Markdown syntax removed.\n */\nexport function markdownToPlainText(markdown: string): string {\n if (!markdown) return \"\";\n\n // Protect inline code content from bold/italic stripping by replacing with placeholders\n const inlineCodeSnippets: string[] = [];\n let result = markdown\n // HTML comments (e.g. <!-- table-title: ... -->), including multiline and --!> variants\n .replace(/^<!--[\\s\\S]*?--!?>\\n?/gm, \"\")\n // Code blocks (must come first)\n .replace(/```[\\s\\S]*?```/g, \"\")\n // Inline code → placeholder\n .replace(/`([^`]+)`/g, (_match, code: string) => {\n inlineCodeSnippets.push(code);\n return `${PLACEHOLDER_PREFIX}${inlineCodeSnippets.length - 1}${PLACEHOLDER_SUFFIX}`;\n })\n // Remove headings markers, keep text\n .replace(/^#{1,6}\\s+/gm, \"\")\n // Remove horizontal rules\n .replace(/^(?:---|\\*\\*\\*|___)\\s*$/gm, \"\");\n result = stripInlineMarks(result)\n // Images: ![alt](url) → alt (must come before links)\n .replace(/!\\[([^\\]]*)\\]\\([^)]+\\)/g, \"$1\")\n // Links: [text](url) → text\n .replace(/\\[([^\\]]+)\\]\\([^)]+\\)/g, \"$1\")\n // Blockquote markers\n .replace(/^>\\s?/gm, \"\")\n // Table separator rows (|---|---|)\n .replace(/^\\|[-:\\s|]+\\|\\s*$/gm, \"\")\n // Table rows: | a | b | → a | b\n .replace(/^\\|\\s?/gm, \"\")\n .replace(/\\s?\\|$/gm, \"\")\n // Collapse multiple blank lines\n .replace(/\\n{3,}/g, \"\\n\\n\")\n .trim();\n\n // Restore inline code content\n result = result.replace(PLACEHOLDER_REGEX, (_match, index: string) => inlineCodeSnippets[Number(index)]);\n\n return result;\n}\n","import type { Ref } from \"react\";\n\n/**\n * Combines multiple React refs into a single callback ref.\n * Useful when a component needs to forward its ref while also keeping a local ref.\n *\n * @param refs - Any number of React refs (callback refs, RefObjects, or `undefined`).\n * @returns A callback ref that updates all provided refs when the DOM node changes.\n */\nexport function mergeRefs<T>(...refs: (Ref<T> | undefined)[]): (node: T | null) => void {\n return (node) => {\n for (const ref of refs) {\n if (typeof ref === \"function\") {\n ref(node);\n } else if (ref) {\n (ref as React.MutableRefObject<T | null>).current = node;\n }\n }\n };\n}\n","import { stripInlineMarks } from \"./markdown-to-plain-text\";\n\n/** Structured representation of a parsed markdown table. */\nexport interface MarkdownTable {\n /** Column header labels. */\n columns: string[];\n /** Data rows, each an array of cell values matching the column order. */\n rows: string[][];\n /** Optional title extracted from a `<!-- table-title: ... -->` comment above the table. */\n title?: string;\n}\n\n/**\n * Parses cells from a markdown table row, trimming whitespace and stripping leading/trailing pipes.\n * @param line - A single line of a markdown table.\n * @returns An array of trimmed cell values.\n */\nfunction parseCells(line: string): string[] {\n const trimmed = line.trim();\n const stripped = trimmed.startsWith(\"|\") ? trimmed.slice(1) : trimmed;\n const withoutTrailing = stripped.endsWith(\"|\") ? stripped.slice(0, -1) : stripped;\n return withoutTrailing.split(\"|\").map((cell) => cell.trim());\n}\n\n/**\n * Tests whether a line is a GFM table separator row (e.g. `| --- | --- |`).\n * @param line - A single line to test.\n * @returns `true` if the line matches the separator pattern.\n */\nfunction isSeparatorRow(line: string): boolean {\n const cells = parseCells(line);\n return cells.length > 0 && cells.every((cell) => /^:?-{1,}:?$/.test(cell));\n}\n\n/**\n * Tests whether a line looks like a markdown table row (contains at least one pipe).\n * @param line - A single line to test.\n * @returns `true` if the line contains a pipe character.\n */\nfunction isTableRow(line: string): boolean {\n return line.includes(\"|\");\n}\n\nconst TABLE_TITLE_PREFIX = \"<!-- table-title:\";\nconst TABLE_TITLE_SUFFIX = \"-->\";\n\n/**\n * Extracts a table title from a `<!-- table-title: ... -->` HTML comment.\n * Uses string methods instead of regex to avoid ReDoS on untrusted input.\n * @param line - A single line to test.\n * @returns The title string, or `undefined` if the line is not a title comment.\n */\nfunction extractTableTitle(line: string): string | undefined {\n const trimmed = line.trim();\n if (!trimmed.startsWith(TABLE_TITLE_PREFIX) || !trimmed.endsWith(TABLE_TITLE_SUFFIX)) {\n return undefined;\n }\n return trimmed.slice(TABLE_TITLE_PREFIX.length, -TABLE_TITLE_SUFFIX.length).trim() || undefined;\n}\n\n/**\n * Extracts the Nth GFM markdown table from a string.\n * @param markdown - The markdown content to parse.\n * @param tableIndex - Zero-based index of the table to extract.\n * @returns The parsed table, or `null` if no table exists at the given index.\n */\nexport function parseMarkdownTable(markdown: string, tableIndex = 0): MarkdownTable | null {\n const lines = markdown.split(\"\\n\");\n let tableCount = 0;\n\n for (let i = 0; i < lines.length - 1; i++) {\n const headerLine = lines[i];\n const separatorLine = lines[i + 1];\n\n if (!isTableRow(headerLine) || !isSeparatorRow(separatorLine)) {\n continue;\n }\n\n if (tableCount === tableIndex) {\n const columns = parseCells(headerLine).map(stripInlineMarks);\n const rows: string[][] = [];\n const title = i > 0 ? extractTableTitle(lines[i - 1]) : undefined;\n\n for (let j = i + 2; j < lines.length; j++) {\n if (!isTableRow(lines[j]) || isSeparatorRow(lines[j])) {\n break;\n }\n const trimmedLine = lines[j].trim();\n if (trimmedLine === \"\") {\n break;\n }\n rows.push(parseCells(lines[j]).map(stripInlineMarks));\n }\n\n return { columns, rows, ...(title ? { title } : {}) };\n }\n\n tableCount++;\n }\n\n return null;\n}\n","/**\n * Characters that trigger spreadsheet-formula evaluation when present at the start of a CSV cell.\n * Excel, Google Sheets, and Numbers all treat a field beginning with one of these as a formula\n * (e.g. `=HYPERLINK(...)`, `+1+1`, `@SUM(...)`), so a field exfiltrated from user-controlled\n * data can run arbitrary expressions in the spreadsheet on open.\n *\n * Note: `-` is intentionally excluded. While `+` and `-` are listed in some CSV injection docs,\n * `-42` is a valid negative number and Excel/Sheets do NOT evaluate it as a formula. Including `-`\n * would corrupt all negative-number cells. The actual risk from `-` is extremely low in practice.\n */\nconst FORMULA_TRIGGERS = new Set([\"=\", \"+\", \"@\", \"\\t\", \"\\r\"]);\n\n/**\n * Escapes and optionally quotes a CSV field per RFC 4180, with OWASP-recommended formula-injection\n * neutralisation for fields beginning with a trigger character.\n *\n * - Fields starting with `=`, `+`, `@`, `\\t`, or `\\r` are prefixed with a tab so the spreadsheet\n * renders them as literal text instead of evaluating them as a formula. The tab itself disappears\n * in the rendered cell.\n * - Fields containing commas, double quotes, newlines, or a leading tab (from formula neutralisation)\n * are wrapped in double quotes per RFC 4180. The double-quote wrapping ensures the tab prefix is\n * preserved in parsers that would otherwise strip leading whitespace.\n * - Existing double quotes are escaped by doubling them.\n *\n * @param field - The raw field value.\n * @returns The escaped/quoted field string.\n */\nexport function escapeCsvField(field: string): string {\n const needsNeutralise = field.length > 0 && FORMULA_TRIGGERS.has(field[0]);\n const safeField = needsNeutralise ? `\\t${field}` : field;\n if (\n needsNeutralise ||\n safeField.includes(\",\") ||\n safeField.includes('\"') ||\n safeField.includes(\"\\n\") ||\n safeField.includes(\"\\r\")\n ) {\n return `\"${safeField.replace(/\"/g, '\"\"')}\"`;\n }\n return safeField;\n}\n","import { escapeCsvField } from \"./escape-csv-field\";\nimport type { MarkdownTable } from \"./parse-markdown-table\";\n\n/**\n * Converts a parsed markdown table to a CSV string (RFC 4180).\n * Fields containing commas, double quotes, or newlines are quoted.\n * @param table - The structured table data to convert.\n * @returns A CSV-formatted string with CRLF line endings.\n */\nexport function convertTableToCSV(table: MarkdownTable): string {\n const headerRow = table.columns.map(escapeCsvField).join(\",\");\n const dataRows = table.rows.map((row) => row.map(escapeCsvField).join(\",\"));\n return [headerRow, ...dataRows].join(\"\\r\\n\");\n}\n"],"mappings":"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","names":[]}
package/dist/utils.d.cts CHANGED
@@ -46,4 +46,21 @@ declare function parseMarkdownTable(markdown: string, tableIndex?: number): Mark
46
46
  */
47
47
  declare function convertTableToCSV(table: MarkdownTable): string;
48
48
 
49
- export { type MarkdownTable, convertTableToCSV, markdownToPlainText, mergeRefs, parseMarkdownTable };
49
+ /**
50
+ * Escapes and optionally quotes a CSV field per RFC 4180, with OWASP-recommended formula-injection
51
+ * neutralisation for fields beginning with a trigger character.
52
+ *
53
+ * - Fields starting with `=`, `+`, `@`, `\t`, or `\r` are prefixed with a tab so the spreadsheet
54
+ * renders them as literal text instead of evaluating them as a formula. The tab itself disappears
55
+ * in the rendered cell.
56
+ * - Fields containing commas, double quotes, newlines, or a leading tab (from formula neutralisation)
57
+ * are wrapped in double quotes per RFC 4180. The double-quote wrapping ensures the tab prefix is
58
+ * preserved in parsers that would otherwise strip leading whitespace.
59
+ * - Existing double quotes are escaped by doubling them.
60
+ *
61
+ * @param field - The raw field value.
62
+ * @returns The escaped/quoted field string.
63
+ */
64
+ declare function escapeCsvField(field: string): string;
65
+
66
+ export { type MarkdownTable, convertTableToCSV, escapeCsvField, markdownToPlainText, mergeRefs, parseMarkdownTable };
package/dist/utils.d.ts CHANGED
@@ -46,4 +46,21 @@ declare function parseMarkdownTable(markdown: string, tableIndex?: number): Mark
46
46
  */
47
47
  declare function convertTableToCSV(table: MarkdownTable): string;
48
48
 
49
- export { type MarkdownTable, convertTableToCSV, markdownToPlainText, mergeRefs, parseMarkdownTable };
49
+ /**
50
+ * Escapes and optionally quotes a CSV field per RFC 4180, with OWASP-recommended formula-injection
51
+ * neutralisation for fields beginning with a trigger character.
52
+ *
53
+ * - Fields starting with `=`, `+`, `@`, `\t`, or `\r` are prefixed with a tab so the spreadsheet
54
+ * renders them as literal text instead of evaluating them as a formula. The tab itself disappears
55
+ * in the rendered cell.
56
+ * - Fields containing commas, double quotes, newlines, or a leading tab (from formula neutralisation)
57
+ * are wrapped in double quotes per RFC 4180. The double-quote wrapping ensures the tab prefix is
58
+ * preserved in parsers that would otherwise strip leading whitespace.
59
+ * - Existing double quotes are escaped by doubling them.
60
+ *
61
+ * @param field - The raw field value.
62
+ * @returns The escaped/quoted field string.
63
+ */
64
+ declare function escapeCsvField(field: string): string;
65
+
66
+ export { type MarkdownTable, convertTableToCSV, escapeCsvField, markdownToPlainText, mergeRefs, parseMarkdownTable };
package/dist/utils.js CHANGED
@@ -1,16 +1,18 @@
1
1
  import {
2
2
  convertTableToCSV,
3
+ escapeCsvField,
3
4
  parseMarkdownTable
4
- } from "./chunk-27GJUWVN.js";
5
- import {
6
- markdownToPlainText
7
- } from "./chunk-NMKKU2UG.js";
5
+ } from "./chunk-JJUIBBBU.js";
8
6
  import {
9
7
  mergeRefs
10
8
  } from "./chunk-KNYB3RL7.js";
9
+ import {
10
+ markdownToPlainText
11
+ } from "./chunk-NMKKU2UG.js";
11
12
  import "./chunk-5WRI5ZAA.js";
12
13
  export {
13
14
  convertTableToCSV,
15
+ escapeCsvField,
14
16
  markdownToPlainText,
15
17
  mergeRefs,
16
18
  parseMarkdownTable
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shapesos/clay",
3
- "version": "0.9.0",
3
+ "version": "0.11.0",
4
4
  "main": "./dist/index.cjs",
5
5
  "module": "./dist/index.js",
6
6
  "devDependencies": {
@@ -13,11 +13,15 @@
13
13
  "@storybook/react": "^10.2.13",
14
14
  "@storybook/react-vite": "^10.2.13",
15
15
  "@tabler/icons-react": "^3.37.1",
16
+ "@tailwindcss/cli": "^4.2.4",
17
+ "@tailwindcss/postcss": "^4.2.4",
18
+ "@tailwindcss/vite": "^4.2.4",
16
19
  "@testing-library/dom": "^10.4.1",
17
20
  "@testing-library/jest-dom": "^6.9.1",
18
21
  "@testing-library/react": "^16.3.2",
19
22
  "@testing-library/user-event": "^14.6.1",
20
23
  "@types/node": "^25.3.2",
24
+ "@types/papaparse": "^5.5.2",
21
25
  "@types/react": "^19.2.14",
22
26
  "@types/react-dom": "^19.2.3",
23
27
  "@vitejs/plugin-react": "^5.1.4",
@@ -31,7 +35,9 @@
31
35
  "react-dom": "^19.2.4",
32
36
  "storybook": "^10.2.13",
33
37
  "styled-components": "^6.3.11",
38
+ "tailwindcss": "^4",
34
39
  "tsup": "~8.5.1",
40
+ "tw-animate-css": "^1.4.0",
35
41
  "typescript": "~5.9.3",
36
42
  "typescript-eslint": "^8.56.1",
37
43
  "vite": "^7.3.1",
@@ -58,6 +64,22 @@
58
64
  "import": "./dist/chat.js",
59
65
  "require": "./dist/chat.cjs"
60
66
  },
67
+ "./blocks": {
68
+ "types": "./dist/blocks.d.ts",
69
+ "import": "./dist/blocks.js",
70
+ "require": "./dist/blocks.cjs"
71
+ },
72
+ "./blocks/styles.css": "./dist/blocks.css",
73
+ "./artifacts": {
74
+ "types": "./dist/artifacts.d.ts",
75
+ "import": "./dist/artifacts.js",
76
+ "require": "./dist/artifacts.cjs"
77
+ },
78
+ "./table": {
79
+ "types": "./dist/table.d.ts",
80
+ "import": "./dist/table.js",
81
+ "require": "./dist/table.cjs"
82
+ },
61
83
  "./icon": {
62
84
  "types": "./dist/icon.d.ts",
63
85
  "import": "./dist/icon.js",
@@ -107,7 +129,9 @@
107
129
  "registry": "https://registry.npmjs.org/"
108
130
  },
109
131
  "scripts": {
110
- "build": "tsup",
132
+ "build": "bun run gen:theme && tsup && bun run build:css",
133
+ "build:css": "tailwindcss -i src/styles/tailwind.css -o dist/blocks.css --minify",
134
+ "gen:theme": "bun run scripts/generate-theme-tokens.ts",
111
135
  "dev": "tsup --watch",
112
136
  "storybook": "storybook dev -p 6006",
113
137
  "build-storybook": "storybook build",
@@ -123,13 +147,19 @@
123
147
  "check": "eslint . && tsc --noEmit && vitest run",
124
148
  "prepare": "lefthook install"
125
149
  },
126
- "sideEffects": false,
150
+ "sideEffects": [
151
+ "**/*.css"
152
+ ],
127
153
  "type": "module",
128
154
  "types": "./dist/index.d.ts",
129
155
  "dependencies": {
156
+ "@radix-ui/react-tooltip": "^1.2.8",
157
+ "clsx": "^2.1.1",
130
158
  "lottie-react": "^2.4.1",
159
+ "papaparse": "^5.5.3",
131
160
  "react-markdown": "^8.0.7",
132
161
  "remark-breaks": "^4.0.0",
133
- "remark-gfm": "^3.0.1"
162
+ "remark-gfm": "^3.0.1",
163
+ "tailwind-merge": "^3.5.0"
134
164
  }
135
165
  }
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/utils/parse-markdown-table.ts","../src/utils/convert-table-to-csv.ts"],"sourcesContent":["import { stripInlineMarks } from \"./markdown-to-plain-text\";\n\n/** Structured representation of a parsed markdown table. */\nexport interface MarkdownTable {\n /** Column header labels. */\n columns: string[];\n /** Data rows, each an array of cell values matching the column order. */\n rows: string[][];\n /** Optional title extracted from a `<!-- table-title: ... -->` comment above the table. */\n title?: string;\n}\n\n/**\n * Parses cells from a markdown table row, trimming whitespace and stripping leading/trailing pipes.\n * @param line - A single line of a markdown table.\n * @returns An array of trimmed cell values.\n */\nfunction parseCells(line: string): string[] {\n const trimmed = line.trim();\n const stripped = trimmed.startsWith(\"|\") ? trimmed.slice(1) : trimmed;\n const withoutTrailing = stripped.endsWith(\"|\") ? stripped.slice(0, -1) : stripped;\n return withoutTrailing.split(\"|\").map((cell) => cell.trim());\n}\n\n/**\n * Tests whether a line is a GFM table separator row (e.g. `| --- | --- |`).\n * @param line - A single line to test.\n * @returns `true` if the line matches the separator pattern.\n */\nfunction isSeparatorRow(line: string): boolean {\n const cells = parseCells(line);\n return cells.length > 0 && cells.every((cell) => /^:?-{1,}:?$/.test(cell));\n}\n\n/**\n * Tests whether a line looks like a markdown table row (contains at least one pipe).\n * @param line - A single line to test.\n * @returns `true` if the line contains a pipe character.\n */\nfunction isTableRow(line: string): boolean {\n return line.includes(\"|\");\n}\n\nconst TABLE_TITLE_PREFIX = \"<!-- table-title:\";\nconst TABLE_TITLE_SUFFIX = \"-->\";\n\n/**\n * Extracts a table title from a `<!-- table-title: ... -->` HTML comment.\n * Uses string methods instead of regex to avoid ReDoS on untrusted input.\n * @param line - A single line to test.\n * @returns The title string, or `undefined` if the line is not a title comment.\n */\nfunction extractTableTitle(line: string): string | undefined {\n const trimmed = line.trim();\n if (!trimmed.startsWith(TABLE_TITLE_PREFIX) || !trimmed.endsWith(TABLE_TITLE_SUFFIX)) {\n return undefined;\n }\n return trimmed.slice(TABLE_TITLE_PREFIX.length, -TABLE_TITLE_SUFFIX.length).trim() || undefined;\n}\n\n/**\n * Extracts the Nth GFM markdown table from a string.\n * @param markdown - The markdown content to parse.\n * @param tableIndex - Zero-based index of the table to extract.\n * @returns The parsed table, or `null` if no table exists at the given index.\n */\nexport function parseMarkdownTable(markdown: string, tableIndex = 0): MarkdownTable | null {\n const lines = markdown.split(\"\\n\");\n let tableCount = 0;\n\n for (let i = 0; i < lines.length - 1; i++) {\n const headerLine = lines[i];\n const separatorLine = lines[i + 1];\n\n if (!isTableRow(headerLine) || !isSeparatorRow(separatorLine)) {\n continue;\n }\n\n if (tableCount === tableIndex) {\n const columns = parseCells(headerLine).map(stripInlineMarks);\n const rows: string[][] = [];\n const title = i > 0 ? extractTableTitle(lines[i - 1]) : undefined;\n\n for (let j = i + 2; j < lines.length; j++) {\n if (!isTableRow(lines[j]) || isSeparatorRow(lines[j])) {\n break;\n }\n const trimmedLine = lines[j].trim();\n if (trimmedLine === \"\") {\n break;\n }\n rows.push(parseCells(lines[j]).map(stripInlineMarks));\n }\n\n return { columns, rows, ...(title ? { title } : {}) };\n }\n\n tableCount++;\n }\n\n return null;\n}\n","import type { MarkdownTable } from \"./parse-markdown-table\";\n\n/**\n * Escapes and optionally quotes a CSV field per RFC 4180.\n * Fields containing commas, double quotes, or newlines are wrapped in double quotes.\n * Existing double quotes are escaped by doubling them.\n * @param field - The raw field value.\n * @returns The escaped/quoted field string.\n */\nfunction escapeField(field: string): string {\n if (field.includes(\",\") || field.includes('\"') || field.includes(\"\\n\") || field.includes(\"\\r\")) {\n return `\"${field.replace(/\"/g, '\"\"')}\"`;\n }\n return field;\n}\n\n/**\n * Converts a parsed markdown table to a CSV string (RFC 4180).\n * Fields containing commas, double quotes, or newlines are quoted.\n * @param table - The structured table data to convert.\n * @returns A CSV-formatted string with CRLF line endings.\n */\nexport function convertTableToCSV(table: MarkdownTable): string {\n const headerRow = table.columns.map(escapeField).join(\",\");\n const dataRows = table.rows.map((row) => row.map(escapeField).join(\",\"));\n return [headerRow, ...dataRows].join(\"\\r\\n\");\n}\n"],"mappings":";;;;;AAiBA,SAAS,WAAW,MAAwB;AAC1C,QAAM,UAAU,KAAK,KAAK;AAC1B,QAAM,WAAW,QAAQ,WAAW,GAAG,IAAI,QAAQ,MAAM,CAAC,IAAI;AAC9D,QAAM,kBAAkB,SAAS,SAAS,GAAG,IAAI,SAAS,MAAM,GAAG,EAAE,IAAI;AACzE,SAAO,gBAAgB,MAAM,GAAG,EAAE,IAAI,CAAC,SAAS,KAAK,KAAK,CAAC;AAC7D;AAOA,SAAS,eAAe,MAAuB;AAC7C,QAAM,QAAQ,WAAW,IAAI;AAC7B,SAAO,MAAM,SAAS,KAAK,MAAM,MAAM,CAAC,SAAS,cAAc,KAAK,IAAI,CAAC;AAC3E;AAOA,SAAS,WAAW,MAAuB;AACzC,SAAO,KAAK,SAAS,GAAG;AAC1B;AAEA,IAAM,qBAAqB;AAC3B,IAAM,qBAAqB;AAQ3B,SAAS,kBAAkB,MAAkC;AAC3D,QAAM,UAAU,KAAK,KAAK;AAC1B,MAAI,CAAC,QAAQ,WAAW,kBAAkB,KAAK,CAAC,QAAQ,SAAS,kBAAkB,GAAG;AACpF,WAAO;AAAA,EACT;AACA,SAAO,QAAQ,MAAM,mBAAmB,QAAQ,CAAC,mBAAmB,MAAM,EAAE,KAAK,KAAK;AACxF;AAQO,SAAS,mBAAmB,UAAkB,aAAa,GAAyB;AACzF,QAAM,QAAQ,SAAS,MAAM,IAAI;AACjC,MAAI,aAAa;AAEjB,WAAS,IAAI,GAAG,IAAI,MAAM,SAAS,GAAG,KAAK;AACzC,UAAM,aAAa,MAAM,CAAC;AAC1B,UAAM,gBAAgB,MAAM,IAAI,CAAC;AAEjC,QAAI,CAAC,WAAW,UAAU,KAAK,CAAC,eAAe,aAAa,GAAG;AAC7D;AAAA,IACF;AAEA,QAAI,eAAe,YAAY;AAC7B,YAAM,UAAU,WAAW,UAAU,EAAE,IAAI,gBAAgB;AAC3D,YAAM,OAAmB,CAAC;AAC1B,YAAM,QAAQ,IAAI,IAAI,kBAAkB,MAAM,IAAI,CAAC,CAAC,IAAI;AAExD,eAAS,IAAI,IAAI,GAAG,IAAI,MAAM,QAAQ,KAAK;AACzC,YAAI,CAAC,WAAW,MAAM,CAAC,CAAC,KAAK,eAAe,MAAM,CAAC,CAAC,GAAG;AACrD;AAAA,QACF;AACA,cAAM,cAAc,MAAM,CAAC,EAAE,KAAK;AAClC,YAAI,gBAAgB,IAAI;AACtB;AAAA,QACF;AACA,aAAK,KAAK,WAAW,MAAM,CAAC,CAAC,EAAE,IAAI,gBAAgB,CAAC;AAAA,MACtD;AAEA,aAAO,EAAE,SAAS,MAAM,GAAI,QAAQ,EAAE,MAAM,IAAI,CAAC,EAAG;AAAA,IACtD;AAEA;AAAA,EACF;AAEA,SAAO;AACT;;;AC5FA,SAAS,YAAY,OAAuB;AAC1C,MAAI,MAAM,SAAS,GAAG,KAAK,MAAM,SAAS,GAAG,KAAK,MAAM,SAAS,IAAI,KAAK,MAAM,SAAS,IAAI,GAAG;AAC9F,WAAO,IAAI,MAAM,QAAQ,MAAM,IAAI,CAAC;AAAA,EACtC;AACA,SAAO;AACT;AAQO,SAAS,kBAAkB,OAA8B;AAC9D,QAAM,YAAY,MAAM,QAAQ,IAAI,WAAW,EAAE,KAAK,GAAG;AACzD,QAAM,WAAW,MAAM,KAAK,IAAI,CAAC,QAAQ,IAAI,IAAI,WAAW,EAAE,KAAK,GAAG,CAAC;AACvE,SAAO,CAAC,WAAW,GAAG,QAAQ,EAAE,KAAK,MAAM;AAC7C;","names":[]}