@wtfalch/design 0.11.0 → 0.12.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/README.md CHANGED
@@ -194,6 +194,36 @@ Icons and illustrations are the system's, shared by every product the way
194
194
  `Button` is. A product wanting its own inside the package's components is a
195
195
  case nobody has had; when it comes, the product entry is where to bind it.
196
196
 
197
+ ## Rich text
198
+
199
+ Prose, written and drawn, added in `0.11.0` for the CMS and the forum.
200
+
201
+ ```tsx
202
+ import { RichText, isEmptyRichText } from '@wtfalch/design' // drawing it
203
+ import { RichTextEditor } from '@wtfalch/design/editor' // writing it
204
+ import { richTextSchema } from '@wtfalch/design/rich-text' // storing it
205
+ ```
206
+
207
+ Three entries and not one, because they cost different things. Drawing prose
208
+ is a server component with no dependencies. Writing it loads TipTap, which is
209
+ ProseMirror, and a site that only reads should not download an editor.
210
+ Validating it needs zod. TipTap and zod are **optional peer dependencies**:
211
+ install them if you import those entries, and the front door works without
212
+ either.
213
+
214
+ **The restriction is the component.** The toolbar offers two heading levels,
215
+ bold, italic, a link and two kinds of list. `richTextSchema` admits exactly
216
+ those and `RichText` draws exactly those, so a document cannot contain
217
+ something a page cannot render — and a consumer that validates on the way
218
+ into its database gets that guarantee against a crafted request too, not just
219
+ against the toolbar. Tables, colours, fonts, code blocks and quotes are off,
220
+ each one a line in the component with the reason beside it. A seventh thing
221
+ is added in all three places, on purpose.
222
+
223
+ Nothing here produces an HTML string: `RichText` walks the value into React
224
+ elements, so there is no sanitiser to configure and none to get wrong. That
225
+ is the difference from `Markdown`, which parses and must sanitise.
226
+
197
227
  ## Status
198
228
 
199
229
  `0.3.1`. Twenty-eight components, every one of the 70 gallery specimens
@@ -0,0 +1,24 @@
1
+ import type { RichTextValue } from '../rich-text/schema.js';
2
+ /**
3
+ * Rich text, drawn.
4
+ *
5
+ * The reading half of the pair: `RichTextEditor` writes the value, this
6
+ * draws it, and `richTextSchema` is what they both agree on. A server
7
+ * component — no hooks, no handlers — so a page that only shows prose never
8
+ * loads an editor.
9
+ *
10
+ * **No HTML string anywhere in this file**, which is the whole security
11
+ * argument and the difference from `Markdown`. Nothing is parsed and nothing
12
+ * is injected, so there is no sanitiser whose configuration could be wrong.
13
+ * A value that has been through `richTextSchema` holds only the small closed
14
+ * set below; one that has not is still safe here, because a node this has no
15
+ * case for is skipped rather than trusted.
16
+ *
17
+ * **Every outward link gets `rel="noopener noreferrer"`.** The prose is
18
+ * written by somebody, and where a reader came from is not the destination's
19
+ * business.
20
+ */
21
+ export default function RichText({ value, className, }: {
22
+ value: RichTextValue;
23
+ className?: string;
24
+ }): import("react").JSX.Element;
@@ -0,0 +1,69 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ /**
3
+ * Rich text, drawn.
4
+ *
5
+ * The reading half of the pair: `RichTextEditor` writes the value, this
6
+ * draws it, and `richTextSchema` is what they both agree on. A server
7
+ * component — no hooks, no handlers — so a page that only shows prose never
8
+ * loads an editor.
9
+ *
10
+ * **No HTML string anywhere in this file**, which is the whole security
11
+ * argument and the difference from `Markdown`. Nothing is parsed and nothing
12
+ * is injected, so there is no sanitiser whose configuration could be wrong.
13
+ * A value that has been through `richTextSchema` holds only the small closed
14
+ * set below; one that has not is still safe here, because a node this has no
15
+ * case for is skipped rather than trusted.
16
+ *
17
+ * **Every outward link gets `rel="noopener noreferrer"`.** The prose is
18
+ * written by somebody, and where a reader came from is not the destination's
19
+ * business.
20
+ */
21
+ export default function RichText({ value, className, }) {
22
+ return (_jsx("div", { className: className ? `rich-text ${className}` : 'rich-text', children: (value.content ?? []).map((node, index) => renderBlock(node, index)) }));
23
+ }
24
+ function renderBlock(node, key) {
25
+ switch (node.type) {
26
+ case 'paragraph':
27
+ return _jsx("p", { children: renderInline(node.content) }, key);
28
+ case 'heading':
29
+ return node.attrs.level === 2 ? (_jsx("h2", { children: renderInline(node.content) }, key)) : (_jsx("h3", { children: renderInline(node.content) }, key));
30
+ case 'bulletList':
31
+ return _jsx("ul", { children: (node.content ?? []).map((item, i) => renderItem(item, i)) }, key);
32
+ case 'orderedList':
33
+ return (_jsx("ol", { start: node.attrs?.start, children: (node.content ?? []).map((item, i) => renderItem(item, i)) }, key));
34
+ default:
35
+ return null;
36
+ }
37
+ }
38
+ function renderItem(item, key) {
39
+ return (_jsx("li", { children: item.content.map((paragraph, i) => (
40
+ // The index is the right key here and in `renderInline`: this draws a
41
+ // finished value, the list is never reordered, inserted into or
42
+ // filtered, and the text is not unique — two paragraphs both saying
43
+ // "Yes" are two paragraphs.
44
+ // biome-ignore lint/suspicious/noArrayIndexKey: a finished value, never reconciled
45
+ _jsx("p", { children: renderInline(paragraph.content) }, i))) }, key));
46
+ }
47
+ function renderInline(content) {
48
+ if (!content || content.length === 0)
49
+ return null;
50
+ return content.map((node, index) => {
51
+ let element = node.text;
52
+ // Applied innermost first, so the order the marks arrive in cannot change
53
+ // the nesting: the link is always outermost, which is what a reader
54
+ // clicks and what a screen reader announces.
55
+ for (const mark of node.marks ?? []) {
56
+ if (mark.type === 'bold')
57
+ element = _jsx("strong", { children: element });
58
+ if (mark.type === 'italic')
59
+ element = _jsx("em", { children: element });
60
+ }
61
+ const link = (node.marks ?? []).find((mark) => mark.type === 'link');
62
+ if (link && link.type === 'link') {
63
+ const external = /^https?:\/\//i.test(link.attrs.href);
64
+ element = (_jsx("a", { href: link.attrs.href, ...(external ? { rel: 'noopener noreferrer' } : {}), children: element }));
65
+ }
66
+ // biome-ignore lint/suspicious/noArrayIndexKey: a finished value, never reconciled
67
+ return _jsx("span", { children: element }, index);
68
+ });
69
+ }
@@ -0,0 +1,32 @@
1
+ import type { RichTextValue } from '../rich-text/schema.js';
2
+ /**
3
+ * Writing prose, with a toolbar that offers six things.
4
+ *
5
+ * **The restriction is the component.** TipTap is ProseMirror, which will
6
+ * model tables, colours, fonts, code blocks and arbitrary nesting given the
7
+ * chance. Every one of those is switched off below. What is left — two
8
+ * heading levels, bold, italic, a link and two kinds of list — is exactly
9
+ * what `richTextSchema` admits and exactly what `RichText` can draw, so the
10
+ * three cannot disagree about what a document may contain. A consumer that
11
+ * wants a seventh thing adds it in all three places, on purpose, rather than
12
+ * discovering that a paste brought one in.
13
+ *
14
+ * **`immediatelyRender: false`** because this will be rendered on a server
15
+ * first: without it TipTap builds a document during SSR and React finds a
16
+ * different one on hydration.
17
+ *
18
+ * **The value is TipTap's JSON**, handed back on every change. The component
19
+ * holds no copy of it: the consumer owns the document, which is what lets an
20
+ * editor sit inside a larger form that saves everything at once.
21
+ *
22
+ * TipTap is an optional peer dependency. Import this entry and you need it;
23
+ * import the package's front door and you do not.
24
+ */
25
+ export default function RichTextEditor({ value, onChange, label, placeholder, className, }: {
26
+ value: RichTextValue;
27
+ onChange: (value: RichTextValue) => void;
28
+ /** What this field is, for a screen reader and for the toolbar's own name. */
29
+ label: string;
30
+ placeholder?: string;
31
+ className?: string;
32
+ }): import("react").JSX.Element;
@@ -0,0 +1,163 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ /* Client, because this module calls hooks and attaches handlers. It is also
4
+ the only component in the package that loads an editor, which is why it
5
+ ships from its own entry (`@wtfalch/design/editor`) rather than the front
6
+ door: a site that renders prose should not download one to do it. */
7
+ import Link from '@tiptap/extension-link';
8
+ import { Placeholder } from '@tiptap/extensions';
9
+ import { EditorContent, useEditor } from '@tiptap/react';
10
+ import StarterKit from '@tiptap/starter-kit';
11
+ import { useCallback } from 'react';
12
+ import Button from './Button.js';
13
+ /**
14
+ * Writing prose, with a toolbar that offers six things.
15
+ *
16
+ * **The restriction is the component.** TipTap is ProseMirror, which will
17
+ * model tables, colours, fonts, code blocks and arbitrary nesting given the
18
+ * chance. Every one of those is switched off below. What is left — two
19
+ * heading levels, bold, italic, a link and two kinds of list — is exactly
20
+ * what `richTextSchema` admits and exactly what `RichText` can draw, so the
21
+ * three cannot disagree about what a document may contain. A consumer that
22
+ * wants a seventh thing adds it in all three places, on purpose, rather than
23
+ * discovering that a paste brought one in.
24
+ *
25
+ * **`immediatelyRender: false`** because this will be rendered on a server
26
+ * first: without it TipTap builds a document during SSR and React finds a
27
+ * different one on hydration.
28
+ *
29
+ * **The value is TipTap's JSON**, handed back on every change. The component
30
+ * holds no copy of it: the consumer owns the document, which is what lets an
31
+ * editor sit inside a larger form that saves everything at once.
32
+ *
33
+ * TipTap is an optional peer dependency. Import this entry and you need it;
34
+ * import the package's front door and you do not.
35
+ */
36
+ export default function RichTextEditor({ value, onChange, label, placeholder, className, }) {
37
+ const editor = useEditor({
38
+ extensions: [
39
+ StarterKit.configure({
40
+ // Left on: paragraph, text, bold, italic, the two lists, listItem,
41
+ // history, and the two cursors that make dragging sane.
42
+ heading: { levels: [2, 3] },
43
+ // Off, each one a decision rather than an oversight. A code block and
44
+ // a quote are shapes a block-based consumer expresses as their own
45
+ // blocks; a horizontal rule is a spacer, and spacing belongs to the
46
+ // theme; strike and inline code are marks nobody asked for, and every
47
+ // one of them is another thing two documents can disagree about.
48
+ codeBlock: false,
49
+ blockquote: false,
50
+ horizontalRule: false,
51
+ strike: false,
52
+ code: false,
53
+ // Configured separately below, so its defaults — which allow any
54
+ // scheme — never apply.
55
+ link: false,
56
+ }),
57
+ // What an empty editor says. TipTap's own extension rather than a CSS
58
+ // rule of ours: `attr()` reads the attribute of the element the
59
+ // pseudo-element belongs to, so a rule on the paragraph cannot reach a
60
+ // `data-placeholder` on the box, and ProseMirror's empty paragraph
61
+ // holds a trailing `<br>` so `:empty` never matches it either. Both of
62
+ // those were tried, and the picture of the second is why this is here.
63
+ Placeholder.configure({ placeholder: placeholder ?? '' }),
64
+ Link.configure({
65
+ openOnClick: false,
66
+ autolink: false,
67
+ // The same three `richTextSchema` admits. This is the first of two
68
+ // gates and the schema is the one that counts.
69
+ protocols: ['http', 'https', 'mailto'],
70
+ HTMLAttributes: { rel: 'noopener noreferrer' },
71
+ }),
72
+ ],
73
+ content: value,
74
+ immediatelyRender: false,
75
+ editorProps: {
76
+ attributes: {
77
+ // `rich-text` styles the elements ProseMirror builds; the rest is
78
+ // this surface's own box, which is ours to draw. Two of the largest
79
+ // spacing step is about six lines — enough to write a paragraph in
80
+ // without the box growing under the cursor on the first one.
81
+ class: 'rich-text p-3 min-h-[calc(var(--space-15)*2)] outline-none',
82
+ // A contenteditable div is a `generic` to an accessibility tree, and
83
+ // `aria-label` is prohibited on a generic — axe said so on the first
84
+ // run, which is what the suite is for. `textbox` with
85
+ // `aria-multiline` is what a rich text area is, and it is what makes
86
+ // the label legal and announced.
87
+ role: 'textbox',
88
+ 'aria-multiline': 'true',
89
+ 'aria-label': label,
90
+ },
91
+ },
92
+ onUpdate: ({ editor: instance }) => onChange(instance.getJSON()),
93
+ });
94
+ const setLink = useCallback(() => {
95
+ if (!editor)
96
+ return;
97
+ const current = editor.getAttributes('link').href;
98
+ // A prompt rather than a dialog: it is one field, it is modal either way,
99
+ // and a dialog here would be the first piece of state this component has
100
+ // to own. Worth replacing the day a link needs a second field.
101
+ const href = window.prompt('Link to', current ?? 'https://');
102
+ if (href === null)
103
+ return;
104
+ if (href.trim() === '') {
105
+ editor.chain().focus().unsetLink().run();
106
+ return;
107
+ }
108
+ editor.chain().focus().extendMarkRange('link').setLink({ href: href.trim() }).run();
109
+ }, [editor]);
110
+ /* The box.
111
+
112
+ `rounded-(--radius)`, not `rounded`: Tailwind v4 emits a literal
113
+ `0.25rem` for the bare utility and never reads `--radius-DEFAULT`, so
114
+ `rounded` is an unthemeable 4px corner where the vocabulary says 6. The
115
+ screenshots caught it as four moved corners and nothing else. Nothing
116
+ else in the package uses the bare form.
117
+
118
+ `shadow`, not `outline`: `--focus-ring` is a box-shadow value
119
+ (`0 0 0 2px var(--accent)`), so `outline: var(--focus-ring)` is invalid
120
+ and drops the whole declaration silently — which this had, and which no
121
+ screenshot would ever have shown because a baseline is never focused.
122
+ `pagination.css` records the same bug. The ring belongs to the box
123
+ rather than to the contenteditable, so it does not draw a second,
124
+ thinner rectangle inside the border. */
125
+ const box = 'grid overflow-hidden rounded-(--radius) border border-border-strong surface-control focus-within:shadow-(--focus-ring)';
126
+ if (!editor) {
127
+ return _jsx("div", { className: className ? `${box} ${className}` : box, "aria-busy": "true" });
128
+ }
129
+ const controls = [
130
+ {
131
+ label: 'Heading',
132
+ active: editor.isActive('heading', { level: 2 }),
133
+ run: () => editor.chain().focus().toggleHeading({ level: 2 }).run(),
134
+ },
135
+ {
136
+ label: 'Subheading',
137
+ active: editor.isActive('heading', { level: 3 }),
138
+ run: () => editor.chain().focus().toggleHeading({ level: 3 }).run(),
139
+ },
140
+ {
141
+ label: 'Bold',
142
+ active: editor.isActive('bold'),
143
+ run: () => editor.chain().focus().toggleBold().run(),
144
+ },
145
+ {
146
+ label: 'Italic',
147
+ active: editor.isActive('italic'),
148
+ run: () => editor.chain().focus().toggleItalic().run(),
149
+ },
150
+ { label: 'Link', active: editor.isActive('link'), run: setLink },
151
+ {
152
+ label: 'Bullets',
153
+ active: editor.isActive('bulletList'),
154
+ run: () => editor.chain().focus().toggleBulletList().run(),
155
+ },
156
+ {
157
+ label: 'Numbers',
158
+ active: editor.isActive('orderedList'),
159
+ run: () => editor.chain().focus().toggleOrderedList().run(),
160
+ },
161
+ ];
162
+ return (_jsxs("div", { className: className ? `${box} ${className}` : box, children: [_jsx("div", { className: "flex flex-wrap gap-1 border-b border-border p-1 surface-panel", role: "toolbar", "aria-label": `${label} formatting`, children: controls.map((control) => (_jsx(Button, { type: "button", size: "sm", kind: control.active ? 'primary' : 'ghost', "aria-pressed": control.active, onPress: control.run, children: control.label }, control.label))) }), _jsx(EditorContent, { editor: editor })] }));
163
+ }
@@ -0,0 +1,16 @@
1
+ /**
2
+ * The editor entry: `@wtfalch/design/editor`.
3
+ *
4
+ * Its own entry because it is the one component in the package that loads an
5
+ * editor — TipTap, which is ProseMirror — and a site that only *renders*
6
+ * prose should not download one to do it. `RichText` and the value helpers
7
+ * ship from the package's front door for exactly that reason; the schema
8
+ * ships from `@wtfalch/design/rich-text`, because it needs zod.
9
+ *
10
+ * TipTap and zod are optional peer dependencies. Import this and you install
11
+ * them; import anything else in the package and you do not.
12
+ */
13
+ export { default as RichTextEditor } from './components/RichTextEditor.js';
14
+ /** Re-exported, so writing prose needs one import rather than three. */
15
+ export { type BlockNode, type RichTextMark, type RichTextValue, blockNodeSchema, richTextMarkSchema, richTextSchema, } from './rich-text/schema.js';
16
+ export { emptyRichText, isEmptyRichText, richTextToPlain } from './rich-text/value.js';
package/dist/editor.js ADDED
@@ -0,0 +1,16 @@
1
+ /**
2
+ * The editor entry: `@wtfalch/design/editor`.
3
+ *
4
+ * Its own entry because it is the one component in the package that loads an
5
+ * editor — TipTap, which is ProseMirror — and a site that only *renders*
6
+ * prose should not download one to do it. `RichText` and the value helpers
7
+ * ship from the package's front door for exactly that reason; the schema
8
+ * ships from `@wtfalch/design/rich-text`, because it needs zod.
9
+ *
10
+ * TipTap and zod are optional peer dependencies. Import this and you install
11
+ * them; import anything else in the package and you do not.
12
+ */
13
+ export { default as RichTextEditor } from './components/RichTextEditor.js';
14
+ /** Re-exported, so writing prose needs one import rather than three. */
15
+ export { blockNodeSchema, richTextMarkSchema, richTextSchema, } from './rich-text/schema.js';
16
+ export { emptyRichText, isEmptyRichText, richTextToPlain } from './rich-text/value.js';
package/dist/index.d.ts CHANGED
@@ -55,6 +55,14 @@ export type { Props as InputProps } from './components/Input.js';
55
55
  * of, now one component a consumer can reach. */
56
56
  export { default as Kbd } from './components/Kbd.js';
57
57
  export { default as Markdown } from './components/Markdown.js';
58
+ /** Prose written with `RichTextEditor` (`@wtfalch/design/editor`), drawn.
59
+ * Here rather than in that entry because reading prose should not download
60
+ * an editor; the schema the two agree on ships with it. */
61
+ export { default as RichText } from './components/RichText.js';
62
+ /** The value's shape and the three helpers that need no validator. The schema
63
+ * itself is `@wtfalch/design/rich-text`, because zod is an optional peer. */
64
+ export type { BlockNode, RichTextMark, RichTextValue } from './rich-text/schema.js';
65
+ export { emptyRichText, isEmptyRichText, richTextToPlain } from './rich-text/value.js';
58
66
  export { default as Menu } from './components/Menu.js';
59
67
  export type { Item as MenuItem, Section as MenuSection } from './components/Menu.js';
60
68
  export { default as Modal } from './components/Modal.js';
package/dist/index.js CHANGED
@@ -49,6 +49,11 @@ export { default as Input } from './components/Input.js';
49
49
  * of, now one component a consumer can reach. */
50
50
  export { default as Kbd } from './components/Kbd.js';
51
51
  export { default as Markdown } from './components/Markdown.js';
52
+ /** Prose written with `RichTextEditor` (`@wtfalch/design/editor`), drawn.
53
+ * Here rather than in that entry because reading prose should not download
54
+ * an editor; the schema the two agree on ships with it. */
55
+ export { default as RichText } from './components/RichText.js';
56
+ export { emptyRichText, isEmptyRichText, richTextToPlain } from './rich-text/value.js';
52
57
  export { default as Menu } from './components/Menu.js';
53
58
  export { default as Modal } from './components/Modal.js';
54
59
  /** Moving through a list that does not fit, counted in items rather than
@@ -0,0 +1,219 @@
1
+ import { z } from 'zod';
2
+ /**
3
+ * What a rich-text value is, and the only shape `RichTextEditor` produces or
4
+ * `RichText` draws.
5
+ *
6
+ * **A restricted document, on purpose.** The editor is TipTap, which is
7
+ * ProseMirror, which will model tables, colours, fonts and arbitrary nesting
8
+ * given the chance. A system where every writer can reach all of that is one
9
+ * where two pages share nothing but a logo. What is left here is
10
+ * paragraphs, two heading levels, two kinds of list, bold, italic and links
11
+ * — the set a piece of prose actually needs.
12
+ *
13
+ * **The schema is the contract between three things**: what the toolbar can
14
+ * produce, what a consumer should store, and what the view can draw. A
15
+ * consumer validates with this on the way into its database, so a crafted
16
+ * request can no more insert a table than the editor can, and a node the
17
+ * view has no case for cannot arrive.
18
+ *
19
+ * Zod is an OPTIONAL peer dependency, and this file is the only one that
20
+ * reaches it — which is why it ships from `@wtfalch/design/rich-text` rather
21
+ * than the front door. A consumer that renders prose without validating it
22
+ * should not have to install a validator to load the package. The types and
23
+ * the pure helpers live in `./value`, which imports from here with `import
24
+ * type` and so compiles to nothing.
25
+ */
26
+ /** The marks a run of text may carry. `link` is the only one with a value, and its href is checked. */
27
+ export declare const richTextMarkSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
28
+ type: z.ZodLiteral<"bold">;
29
+ }, z.core.$strip>, z.ZodObject<{
30
+ type: z.ZodLiteral<"italic">;
31
+ }, z.core.$strip>, z.ZodObject<{
32
+ type: z.ZodLiteral<"link">;
33
+ attrs: z.ZodObject<{
34
+ href: z.ZodString;
35
+ target: z.ZodOptional<z.ZodNullable<z.ZodString>>;
36
+ }, z.core.$strip>;
37
+ }, z.core.$strip>], "type">;
38
+ /** Named `RichTextMark` rather than `Mark`, which this package already uses for a product's brand mark. */
39
+ export type RichTextMark = z.infer<typeof richTextMarkSchema>;
40
+ export declare const blockNodeSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
41
+ type: z.ZodLiteral<"paragraph">;
42
+ content: z.ZodOptional<z.ZodArray<z.ZodObject<{
43
+ type: z.ZodLiteral<"text">;
44
+ text: z.ZodString;
45
+ marks: z.ZodOptional<z.ZodArray<z.ZodDiscriminatedUnion<[z.ZodObject<{
46
+ type: z.ZodLiteral<"bold">;
47
+ }, z.core.$strip>, z.ZodObject<{
48
+ type: z.ZodLiteral<"italic">;
49
+ }, z.core.$strip>, z.ZodObject<{
50
+ type: z.ZodLiteral<"link">;
51
+ attrs: z.ZodObject<{
52
+ href: z.ZodString;
53
+ target: z.ZodOptional<z.ZodNullable<z.ZodString>>;
54
+ }, z.core.$strip>;
55
+ }, z.core.$strip>], "type">>>;
56
+ }, z.core.$strip>>>;
57
+ }, z.core.$strip>, z.ZodObject<{
58
+ type: z.ZodLiteral<"heading">;
59
+ attrs: z.ZodObject<{
60
+ level: z.ZodUnion<readonly [z.ZodLiteral<2>, z.ZodLiteral<3>]>;
61
+ }, z.core.$strip>;
62
+ content: z.ZodOptional<z.ZodArray<z.ZodObject<{
63
+ type: z.ZodLiteral<"text">;
64
+ text: z.ZodString;
65
+ marks: z.ZodOptional<z.ZodArray<z.ZodDiscriminatedUnion<[z.ZodObject<{
66
+ type: z.ZodLiteral<"bold">;
67
+ }, z.core.$strip>, z.ZodObject<{
68
+ type: z.ZodLiteral<"italic">;
69
+ }, z.core.$strip>, z.ZodObject<{
70
+ type: z.ZodLiteral<"link">;
71
+ attrs: z.ZodObject<{
72
+ href: z.ZodString;
73
+ target: z.ZodOptional<z.ZodNullable<z.ZodString>>;
74
+ }, z.core.$strip>;
75
+ }, z.core.$strip>], "type">>>;
76
+ }, z.core.$strip>>>;
77
+ }, z.core.$strip>, z.ZodObject<{
78
+ type: z.ZodLiteral<"bulletList">;
79
+ content: z.ZodOptional<z.ZodArray<z.ZodObject<{
80
+ type: z.ZodLiteral<"listItem">;
81
+ content: z.ZodArray<z.ZodObject<{
82
+ type: z.ZodLiteral<"paragraph">;
83
+ content: z.ZodOptional<z.ZodArray<z.ZodObject<{
84
+ type: z.ZodLiteral<"text">;
85
+ text: z.ZodString;
86
+ marks: z.ZodOptional<z.ZodArray<z.ZodDiscriminatedUnion<[z.ZodObject<{
87
+ type: z.ZodLiteral<"bold">;
88
+ }, z.core.$strip>, z.ZodObject<{
89
+ type: z.ZodLiteral<"italic">;
90
+ }, z.core.$strip>, z.ZodObject<{
91
+ type: z.ZodLiteral<"link">;
92
+ attrs: z.ZodObject<{
93
+ href: z.ZodString;
94
+ target: z.ZodOptional<z.ZodNullable<z.ZodString>>;
95
+ }, z.core.$strip>;
96
+ }, z.core.$strip>], "type">>>;
97
+ }, z.core.$strip>>>;
98
+ }, z.core.$strip>>;
99
+ }, z.core.$strip>>>;
100
+ }, z.core.$strip>, z.ZodObject<{
101
+ type: z.ZodLiteral<"orderedList">;
102
+ attrs: z.ZodOptional<z.ZodObject<{
103
+ start: z.ZodNumber;
104
+ }, z.core.$strip>>;
105
+ content: z.ZodOptional<z.ZodArray<z.ZodObject<{
106
+ type: z.ZodLiteral<"listItem">;
107
+ content: z.ZodArray<z.ZodObject<{
108
+ type: z.ZodLiteral<"paragraph">;
109
+ content: z.ZodOptional<z.ZodArray<z.ZodObject<{
110
+ type: z.ZodLiteral<"text">;
111
+ text: z.ZodString;
112
+ marks: z.ZodOptional<z.ZodArray<z.ZodDiscriminatedUnion<[z.ZodObject<{
113
+ type: z.ZodLiteral<"bold">;
114
+ }, z.core.$strip>, z.ZodObject<{
115
+ type: z.ZodLiteral<"italic">;
116
+ }, z.core.$strip>, z.ZodObject<{
117
+ type: z.ZodLiteral<"link">;
118
+ attrs: z.ZodObject<{
119
+ href: z.ZodString;
120
+ target: z.ZodOptional<z.ZodNullable<z.ZodString>>;
121
+ }, z.core.$strip>;
122
+ }, z.core.$strip>], "type">>>;
123
+ }, z.core.$strip>>>;
124
+ }, z.core.$strip>>;
125
+ }, z.core.$strip>>>;
126
+ }, z.core.$strip>], "type">;
127
+ export type BlockNode = z.infer<typeof blockNodeSchema>;
128
+ /** A whole value: what TipTap calls the document. */
129
+ export declare const richTextSchema: z.ZodObject<{
130
+ type: z.ZodLiteral<"doc">;
131
+ content: z.ZodOptional<z.ZodArray<z.ZodDiscriminatedUnion<[z.ZodObject<{
132
+ type: z.ZodLiteral<"paragraph">;
133
+ content: z.ZodOptional<z.ZodArray<z.ZodObject<{
134
+ type: z.ZodLiteral<"text">;
135
+ text: z.ZodString;
136
+ marks: z.ZodOptional<z.ZodArray<z.ZodDiscriminatedUnion<[z.ZodObject<{
137
+ type: z.ZodLiteral<"bold">;
138
+ }, z.core.$strip>, z.ZodObject<{
139
+ type: z.ZodLiteral<"italic">;
140
+ }, z.core.$strip>, z.ZodObject<{
141
+ type: z.ZodLiteral<"link">;
142
+ attrs: z.ZodObject<{
143
+ href: z.ZodString;
144
+ target: z.ZodOptional<z.ZodNullable<z.ZodString>>;
145
+ }, z.core.$strip>;
146
+ }, z.core.$strip>], "type">>>;
147
+ }, z.core.$strip>>>;
148
+ }, z.core.$strip>, z.ZodObject<{
149
+ type: z.ZodLiteral<"heading">;
150
+ attrs: z.ZodObject<{
151
+ level: z.ZodUnion<readonly [z.ZodLiteral<2>, z.ZodLiteral<3>]>;
152
+ }, z.core.$strip>;
153
+ content: z.ZodOptional<z.ZodArray<z.ZodObject<{
154
+ type: z.ZodLiteral<"text">;
155
+ text: z.ZodString;
156
+ marks: z.ZodOptional<z.ZodArray<z.ZodDiscriminatedUnion<[z.ZodObject<{
157
+ type: z.ZodLiteral<"bold">;
158
+ }, z.core.$strip>, z.ZodObject<{
159
+ type: z.ZodLiteral<"italic">;
160
+ }, z.core.$strip>, z.ZodObject<{
161
+ type: z.ZodLiteral<"link">;
162
+ attrs: z.ZodObject<{
163
+ href: z.ZodString;
164
+ target: z.ZodOptional<z.ZodNullable<z.ZodString>>;
165
+ }, z.core.$strip>;
166
+ }, z.core.$strip>], "type">>>;
167
+ }, z.core.$strip>>>;
168
+ }, z.core.$strip>, z.ZodObject<{
169
+ type: z.ZodLiteral<"bulletList">;
170
+ content: z.ZodOptional<z.ZodArray<z.ZodObject<{
171
+ type: z.ZodLiteral<"listItem">;
172
+ content: z.ZodArray<z.ZodObject<{
173
+ type: z.ZodLiteral<"paragraph">;
174
+ content: z.ZodOptional<z.ZodArray<z.ZodObject<{
175
+ type: z.ZodLiteral<"text">;
176
+ text: z.ZodString;
177
+ marks: z.ZodOptional<z.ZodArray<z.ZodDiscriminatedUnion<[z.ZodObject<{
178
+ type: z.ZodLiteral<"bold">;
179
+ }, z.core.$strip>, z.ZodObject<{
180
+ type: z.ZodLiteral<"italic">;
181
+ }, z.core.$strip>, z.ZodObject<{
182
+ type: z.ZodLiteral<"link">;
183
+ attrs: z.ZodObject<{
184
+ href: z.ZodString;
185
+ target: z.ZodOptional<z.ZodNullable<z.ZodString>>;
186
+ }, z.core.$strip>;
187
+ }, z.core.$strip>], "type">>>;
188
+ }, z.core.$strip>>>;
189
+ }, z.core.$strip>>;
190
+ }, z.core.$strip>>>;
191
+ }, z.core.$strip>, z.ZodObject<{
192
+ type: z.ZodLiteral<"orderedList">;
193
+ attrs: z.ZodOptional<z.ZodObject<{
194
+ start: z.ZodNumber;
195
+ }, z.core.$strip>>;
196
+ content: z.ZodOptional<z.ZodArray<z.ZodObject<{
197
+ type: z.ZodLiteral<"listItem">;
198
+ content: z.ZodArray<z.ZodObject<{
199
+ type: z.ZodLiteral<"paragraph">;
200
+ content: z.ZodOptional<z.ZodArray<z.ZodObject<{
201
+ type: z.ZodLiteral<"text">;
202
+ text: z.ZodString;
203
+ marks: z.ZodOptional<z.ZodArray<z.ZodDiscriminatedUnion<[z.ZodObject<{
204
+ type: z.ZodLiteral<"bold">;
205
+ }, z.core.$strip>, z.ZodObject<{
206
+ type: z.ZodLiteral<"italic">;
207
+ }, z.core.$strip>, z.ZodObject<{
208
+ type: z.ZodLiteral<"link">;
209
+ attrs: z.ZodObject<{
210
+ href: z.ZodString;
211
+ target: z.ZodOptional<z.ZodNullable<z.ZodString>>;
212
+ }, z.core.$strip>;
213
+ }, z.core.$strip>], "type">>>;
214
+ }, z.core.$strip>>>;
215
+ }, z.core.$strip>>;
216
+ }, z.core.$strip>>>;
217
+ }, z.core.$strip>], "type">>>;
218
+ }, z.core.$strip>;
219
+ export type RichTextValue = z.infer<typeof richTextSchema>;