@pilotiq/tiptap 3.13.0 → 3.15.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/CHANGELOG.md CHANGED
@@ -1,5 +1,61 @@
1
1
  # @pilotiq/tiptap
2
2
 
3
+ ## 3.15.0
4
+
5
+ ### Minor Changes
6
+
7
+ - c3816ae: In-block content-block controls now live behind a single **gear menu**.
8
+
9
+ Blocks with multiple variations used to scatter their controls (a width chip in one corner, a variant dropdown + color swatch + click-the-icon picker in another). They now share one consistent entry point — a gear button in the block's inline-end gutter that opens a **nested settings menu**, one submenu per setting.
10
+
11
+ - **New reusable `BlockSettingsMenu`** (replaces `BlockWidthControl`): a gear trigger + a Base UI `Menu` with a `SubmenuRoot` per setting. Two setting kinds — `select` (a radio submenu, e.g. Width / Type) and `custom` (caller-supplied submenu body, e.g. the icon grid / color swatches). The active value rides each row as a hint.
12
+ - **Alert** routes Width, Type, Icon (curated SVG library + Custom SVG paste), and Color (custom variant only) through the gear; the icon in column one is now static and changed from the menu.
13
+ - **Alert gains a `width` attr** (`contained` / `full`), mirroring the FAQ block — emitted read-side as `data-width`. To keep the gear from shifting when width changes, Alert now renders in **two layers** (same as FAQ): a full-width `.pilotiq-alert` anchor wrapping an inner `.pilotiq-alert-box` that carries the box chrome + width. Consumer CSS that targeted `.pilotiq-alert` for the box (border/background/padding/`pilotiq-alert-<type>`) should move to `.pilotiq-alert-box`; full-width is `.pilotiq-alert[data-width="full"] .pilotiq-alert-box`.
14
+ - **FAQ** moves its width toggle into the same gear menu.
15
+
16
+ Back-compat: node structures are unchanged; existing Alert/FAQ content loads as-is (alerts default to `contained`).
17
+
18
+ - b6b28bd: The **FAQ** content block is now a collapsible **accordion**.
19
+
20
+ - **Editor:** each Q&A item is a collapsible row (a React NodeView) — the question is the always-visible trigger with a chevron, the answer folds below it. The question stays editable on click; only the chevron toggles. New `open` attr on `faqItem` (defaults open) stores per-item state.
21
+ - **Read-side** (`renderRichTextToHtml`): renders as native **`<details>`/`<summary>`** — a real, accessible, **zero-JS** accordion the browser collapses on its own. Each item's `open` attr drives the platform `open` attribute. Consumer owns the `.pilotiq-faq*` CSS.
22
+ - Dropped the old "Q"/"A" markers in favor of the accordion chrome.
23
+ - **Block width:** an in-block toggle (a reusable `BlockWidthControl`) switches the FAQ between **contained** (max-width, centered) and **full** width — a `width` attr on the `faq` node, emitted read-side as `data-width`. Generic enough to reuse on other blocks.
24
+
25
+ Back-compat: the node structure is unchanged (`faq > faqItem > faqQuestion faqAnswer`), so existing FAQ content loads as-is and gains the default-open state; old HTML question/answer wrappers still parse via fallback rules.
26
+
27
+ - af935e7: The remaining inline content blocks (**Summary**, **Key takeaways**, **Pros & cons**) now use the React NodeView + gear-menu pattern, matching Alert and FAQ.
28
+
29
+ - Each gains an in-block **gear menu** with a **Width** setting (`contained` / `full`), surfaced read-side as `data-width`.
30
+ - **Summary** and **Key takeaways** share one new `LabeledBlockNodeView` (they're structurally identical — a label above a `block+` body), driven by the existing `labeledBlock()` factory, which now attaches the NodeView + `width` attr and carries the per-block `label` / `cssClass` on `addOptions()`.
31
+ - **Pros & cons** gets its own `ProsConsNodeView`; the gear lives on the container, the two columns keep their plain label markup.
32
+ - The `width` attr is consolidated into one shared `widthAttribute()` helper (FAQ, Alert, the labelled blocks, and Pros & cons all reuse it, so they can't drift).
33
+
34
+ **Read-side / consumer CSS:** each block now renders a full-width outer anchor wrapping an inner content layer (mirrors the FAQ outer/`-content` split, so the gear doesn't move on a width toggle):
35
+
36
+ - Summary / Key takeaways: the label + body now sit inside a `.pilotiq-block-content` wrapper; that wrapper carries the max-width / centering (full-width via `[data-width="full"] > .pilotiq-block-content`).
37
+ - Pros & cons: the two-column grid moves from `.pilotiq-pros-cons` onto a new inner `.pilotiq-pros-cons-content`; the outer becomes the full-width anchor.
38
+
39
+ Back-compat: node structures are unchanged and parsing is tolerant of the old (unwrapped) HTML, so existing stored content loads as-is and defaults to `contained`.
40
+
41
+ ## 3.14.0
42
+
43
+ ### Minor Changes
44
+
45
+ - b2fc753: Redesigned the **Alert** content block into an interactive, themeable callout — and made it round-trip through the Markdown editor.
46
+
47
+ **Rich-text + markdown editor:**
48
+
49
+ - shadcn-style card on the panel's theme tokens (icon column + editable **title** and **body** — previously the label was the fixed variant name).
50
+ - In-block **variant picker** — `info` / `warning` / `success` / `tip` / **`custom`** (the four slash-menu alert entries collapse into one "Alert").
51
+ - In-block **icon picker** — a curated inline-SVG library (~18 icons, ~1-2KB, no `lucide-react`) plus a **"Custom SVG"** paste field. Custom SVG is sanitized via a pure allowlist (`sanitizeIconSvg`) on input and on render — scripts, event handlers, external refs (`use`/`image`/`a`/`href`), `<style>`, `<foreignObject>` are all stripped.
52
+ - The **custom** variant gets an in-block **color** swatch; the box + icon tint via `color-mix` (the value is validated before it reaches inline CSS).
53
+ - **Markdown round-trip** — `:::alert{type=warning icon=rocket} Title` admonition syntax (title rides the opening fence line). `MarkdownField` gains an **Alert** toolbar button (added to the default toolbar).
54
+
55
+ **Read-side** (`renderRichTextToHtml`) emits the new `<div class="pilotiq-alert"><span class="pilotiq-alert-icon">…</span><div class="pilotiq-alert-title">…</div><div class="pilotiq-alert-description">…</div></div>` structure; icon SVGs are shared with the editor so the two never drift. Consumer owns the CSS.
56
+
57
+ **Back-compat:** the node's content model changed (`block+` → `alertTitle alertBody`). HTML from the previous alert (label + body divs) is parsed into the new shape; JSON-stored alerts from the prior release may lose their body and should be re-inserted.
58
+
3
59
  ## 3.13.0
4
60
 
5
61
  ### Minor Changes
@@ -189,24 +189,19 @@ export function buildSlashItems(blocks, mergeTags, query, insert) {
189
189
  }).run(),
190
190
  },
191
191
  {
192
- key: 'alert-info', label: 'Alert: Info', icon: 'ⓘ', group: 'Content',
193
- searchKey: 'alert callout notice info note',
194
- command: ({ editor, range }) => editor.chain().focus().deleteRange(range).insertContent({ type: 'alert', attrs: { type: 'info' }, content: [{ type: 'paragraph' }] }).run(),
195
- },
196
- {
197
- key: 'alert-warning', label: 'Alert: Warning', icon: '⚠️', group: 'Content',
198
- searchKey: 'alert callout notice warning caution danger',
199
- command: ({ editor, range }) => editor.chain().focus().deleteRange(range).insertContent({ type: 'alert', attrs: { type: 'warning' }, content: [{ type: 'paragraph' }] }).run(),
200
- },
201
- {
202
- key: 'alert-success', label: 'Alert: Success', icon: '', group: 'Content',
203
- searchKey: 'alert callout notice success ok done',
204
- command: ({ editor, range }) => editor.chain().focus().deleteRange(range).insertContent({ type: 'alert', attrs: { type: 'success' }, content: [{ type: 'paragraph' }] }).run(),
205
- },
206
- {
207
- key: 'alert-tip', label: 'Alert: Tip', icon: '💡', group: 'Content',
208
- searchKey: 'alert callout notice tip hint',
209
- command: ({ editor, range }) => editor.chain().focus().deleteRange(range).insertContent({ type: 'alert', attrs: { type: 'tip' }, content: [{ type: 'paragraph' }] }).run(),
192
+ // One Alert block the variant (info/warning/success/tip/custom) is
193
+ // switched in-block via the NodeView's picker. Defaults to Info with the
194
+ // label pre-filled as the editable title.
195
+ key: 'alert', label: 'Alert', icon: '⚠️', group: 'Content',
196
+ searchKey: 'alert callout notice info warning success tip note custom caution danger hint',
197
+ command: ({ editor, range }) => editor.chain().focus().deleteRange(range).insertContent({
198
+ type: 'alert',
199
+ attrs: { type: 'info' },
200
+ content: [
201
+ { type: 'alertTitle', content: [{ type: 'text', text: 'Info' }] },
202
+ { type: 'alertBody', content: [{ type: 'paragraph' }] },
203
+ ],
204
+ }).run(),
210
205
  },
211
206
  // Image entry shares the toolbar's attach-files dialog; only surfaced
212
207
  // when the panel has wired an `UploadAdapter`. Without one, the dialog
@@ -0,0 +1,44 @@
1
+ /**
2
+ * Alert variant primitives — shared by the node (`contentBlocks.ts`), its
3
+ * React NodeView (`AlertNodeView.tsx`), and the read-side serializer
4
+ * (`render.ts`). Kept dependency-free (no `@tiptap/*`) so the server
5
+ * renderer can import it without pulling a Tiptap runtime.
6
+ *
7
+ * `info` / `warning` / `success` / `tip` are presets (default icon + accent);
8
+ * `custom` is the user-driven variant (own icon + color, Phase 2).
9
+ */
10
+ export declare const ALERT_VARIANTS: readonly ["info", "warning", "success", "tip", "custom"];
11
+ export type AlertType = (typeof ALERT_VARIANTS)[number];
12
+ export declare const ALERT_VARIANT_LABEL: Record<AlertType, string>;
13
+ /** Coerce an unknown value to a known variant, defaulting to `info`. */
14
+ export declare function coerceAlertType(value: unknown): AlertType;
15
+ /**
16
+ * Curated icon library — inner SVG markup (lucide paths) keyed by name. Shared
17
+ * by the NodeView picker (`AlertNodeView`) and the read-side renderer
18
+ * (`render.ts`) so the editor and published output never drift. Hand-picked
19
+ * (no `lucide-react` dep, no icon font) so the whole set is ~1-2KB gzipped.
20
+ * Wrap in `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor"
21
+ * stroke-width="2" stroke-linecap="round" stroke-linejoin="round">…</svg>`.
22
+ */
23
+ export declare const ALERT_ICONS: Record<string, string>;
24
+ /** Icon keys, in picker display order. */
25
+ export declare const ALERT_ICON_KEYS: string[];
26
+ /** The default icon key per variant (used when the block sets no `icon`). */
27
+ export declare const VARIANT_DEFAULT_ICON: Record<AlertType, string>;
28
+ /**
29
+ * Resolve the inner SVG for a block: the explicit `icon` override when set +
30
+ * known, else the variant's default. Shared by the editor + read-side render.
31
+ */
32
+ export declare function resolveAlertIconInner(iconKey: string | undefined, variant: AlertType): string;
33
+ /**
34
+ * Sanitize a user-pasted SVG string to a safe subset. Returns '' for anything
35
+ * that isn't an `<svg>` element or that sanitizes to nothing.
36
+ */
37
+ export declare function sanitizeIconSvg(raw: unknown): string;
38
+ /**
39
+ * Build the FULL `<svg>` string for a block: a sanitized user `iconSvg` when
40
+ * present, else the (library or variant-default) icon wrapped in the standard
41
+ * stroke svg. Shared by the editor NodeView and the read-side renderer.
42
+ */
43
+ export declare function buildAlertIconSvg(iconKey: string | undefined, iconSvg: string | undefined, variant: AlertType): string;
44
+ //# sourceMappingURL=alertVariants.d.ts.map
@@ -0,0 +1,132 @@
1
+ /**
2
+ * Alert variant primitives — shared by the node (`contentBlocks.ts`), its
3
+ * React NodeView (`AlertNodeView.tsx`), and the read-side serializer
4
+ * (`render.ts`). Kept dependency-free (no `@tiptap/*`) so the server
5
+ * renderer can import it without pulling a Tiptap runtime.
6
+ *
7
+ * `info` / `warning` / `success` / `tip` are presets (default icon + accent);
8
+ * `custom` is the user-driven variant (own icon + color, Phase 2).
9
+ */
10
+ export const ALERT_VARIANTS = ['info', 'warning', 'success', 'tip', 'custom'];
11
+ export const ALERT_VARIANT_LABEL = {
12
+ info: 'Info', warning: 'Warning', success: 'Success', tip: 'Tip', custom: 'Custom',
13
+ };
14
+ /** Coerce an unknown value to a known variant, defaulting to `info`. */
15
+ export function coerceAlertType(value) {
16
+ return ALERT_VARIANTS.includes(String(value)) ? value : 'info';
17
+ }
18
+ /**
19
+ * Curated icon library — inner SVG markup (lucide paths) keyed by name. Shared
20
+ * by the NodeView picker (`AlertNodeView`) and the read-side renderer
21
+ * (`render.ts`) so the editor and published output never drift. Hand-picked
22
+ * (no `lucide-react` dep, no icon font) so the whole set is ~1-2KB gzipped.
23
+ * Wrap in `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor"
24
+ * stroke-width="2" stroke-linecap="round" stroke-linejoin="round">…</svg>`.
25
+ */
26
+ export const ALERT_ICONS = {
27
+ info: '<circle cx="12" cy="12" r="10"/><path d="M12 16v-4"/><path d="M12 8h.01"/>',
28
+ warning: '<path d="m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3Z"/><path d="M12 9v4"/><path d="M12 17h.01"/>',
29
+ check: '<circle cx="12" cy="12" r="10"/><path d="m9 12 2 2 4-4"/>',
30
+ lightbulb: '<path d="M15 14c.2-1 .7-1.7 1.5-2.5 1-.9 1.5-2.2 1.5-3.5A6 6 0 0 0 6 8c0 1 .2 2.2 1.5 3.5.7.7 1.3 1.5 1.5 2.5"/><path d="M9 18h6"/><path d="M10 22h4"/>',
31
+ sparkles: '<path d="M9.937 15.5A2 2 0 0 0 8.5 14.063l-6.135-1.582a.5.5 0 0 1 0-.962L8.5 9.936A2 2 0 0 0 9.937 8.5l1.582-6.135a.5.5 0 0 1 .962 0L14.063 8.5A2 2 0 0 0 15.5 9.937l6.135 1.581a.5.5 0 0 1 0 .964L15.5 14.063a2 2 0 0 0-1.437 1.437l-1.582 6.135a.5.5 0 0 1-.962 0z"/>',
32
+ bell: '<path d="M6 8a6 6 0 0 1 12 0c0 7 3 9 3 9H3s3-2 3-9"/><path d="M10.3 21a1.94 1.94 0 0 0 3.4 0"/>',
33
+ megaphone: '<path d="m3 11 18-5v12L3 14v-3z"/><path d="M11.6 16.8a3 3 0 1 1-5.8-1.6"/>',
34
+ flame: '<path d="M8.5 14.5A2.5 2.5 0 0 0 11 12c0-1.38-.5-2-1-3-1.072-2.143-.224-4.054 2-6 .5 2.5 2 4.9 4 6.5 2 1.6 3 3.5 3 5.5a7 7 0 1 1-14 0c0-1.153.433-2.294 1-3a2.5 2.5 0 0 0 2.5 2.5z"/>',
35
+ star: '<path d="M11.525 2.295a.53.53 0 0 1 .95 0l2.31 4.679a2.123 2.123 0 0 0 1.595 1.16l5.166.756a.53.53 0 0 1 .294.904l-3.736 3.638a2.123 2.123 0 0 0-.611 1.878l.882 5.14a.53.53 0 0 1-.771.56l-4.618-2.428a2.122 2.122 0 0 0-1.973 0L6.396 21.01a.53.53 0 0 1-.77-.56l.881-5.139a2.122 2.122 0 0 0-.611-1.879L2.16 9.795a.53.53 0 0 1 .294-.906l5.165-.755a2.122 2.122 0 0 0 1.597-1.16z"/>',
36
+ heart: '<path d="M19 14c1.49-1.46 3-3.21 3-5.5A5.5 5.5 0 0 0 16.5 3c-1.76 0-3 .5-4.5 2-1.5-1.5-2.74-2-4.5-2A5.5 5.5 0 0 0 2 8.5c0 2.3 1.5 4.05 3 5.5l7 7Z"/>',
37
+ shield: '<path d="M20 13c0 5-3.5 7.5-7.66 8.95a1 1 0 0 1-.67-.01C7.5 20.5 4 18 4 13V6a1 1 0 0 1 1-1c2 0 4.5-1.2 6.24-2.72a1.17 1.17 0 0 1 1.52 0C14.51 3.81 17 5 19 5a1 1 0 0 1 1 1z"/>',
38
+ zap: '<path d="M4 14a1 1 0 0 1-.78-1.63l9.9-10.2a.5.5 0 0 1 .86.46l-1.92 6.02A1 1 0 0 0 13 10h7a1 1 0 0 1 .78 1.63l-9.9 10.2a.5.5 0 0 1-.86-.46l1.92-6.02A1 1 0 0 0 11 14z"/>',
39
+ flag: '<path d="M4 15s1-1 4-1 5 2 8 2 4-1 4-1V3s-1 1-4 1-5-2-8-2-4 1-4 1z"/><line x1="4" x2="4" y1="22" y2="15"/>',
40
+ bookmark: '<path d="m19 21-7-4-7 4V5a2 2 0 0 1 2-2h10a2 2 0 0 1 2 2z"/>',
41
+ rocket: '<path d="M4.5 16.5c-1.5 1.26-2 5-2 5s3.74-.5 5-2c.71-.84.7-2.13-.09-2.91a2.18 2.18 0 0 0-2.91-.09z"/><path d="m12 15-3-3a22 22 0 0 1 2-3.95A12.88 12.88 0 0 1 22 2c0 2.72-.78 7.5-6 11a22.35 22.35 0 0 1-4 2z"/><path d="M9 12H4s.55-3.03 2-4c1.62-1.08 5 0 5 0"/><path d="M12 15v5s3.03-.55 4-2c1.08-1.62 0-5 0-5"/>',
42
+ help: '<circle cx="12" cy="12" r="10"/><path d="M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3"/><path d="M12 17h.01"/>',
43
+ error: '<circle cx="12" cy="12" r="10"/><path d="m15 9-6 6"/><path d="m9 9 6 6"/>',
44
+ pin: '<path d="M20 10c0 4.993-5.539 10.193-7.399 11.799a1 1 0 0 1-1.202 0C9.539 20.193 4 14.993 4 10a8 8 0 0 1 16 0"/><circle cx="12" cy="10" r="3"/>',
45
+ };
46
+ /** Icon keys, in picker display order. */
47
+ export const ALERT_ICON_KEYS = Object.keys(ALERT_ICONS);
48
+ /** The default icon key per variant (used when the block sets no `icon`). */
49
+ export const VARIANT_DEFAULT_ICON = {
50
+ info: 'info', warning: 'warning', success: 'check', tip: 'lightbulb', custom: 'sparkles',
51
+ };
52
+ /**
53
+ * Resolve the inner SVG for a block: the explicit `icon` override when set +
54
+ * known, else the variant's default. Shared by the editor + read-side render.
55
+ */
56
+ export function resolveAlertIconInner(iconKey, variant) {
57
+ if (iconKey && ALERT_ICONS[iconKey])
58
+ return ALERT_ICONS[iconKey];
59
+ return ALERT_ICONS[VARIANT_DEFAULT_ICON[variant]];
60
+ }
61
+ // ── Custom SVG (user-pasted) — sanitized allowlist ───────────────────────────
62
+ //
63
+ // Custom SVG is raw user markup that renders on PUBLIC pages, so it's an XSS
64
+ // surface. We sanitize with a conservative pure-string allowlist (no DOMPurify
65
+ // — keeps the bundle lean and works server-side in render.ts): only known
66
+ // presentational SVG tags + geometry/paint attributes survive; scripts, event
67
+ // handlers, external refs (`use`/`image`/`a`/`href`), `<style>`, `<animate>`,
68
+ // `<foreignObject>` and unknown attributes are dropped. Applied on input AND on
69
+ // every render (defence in depth — a tampered stored attr stays safe).
70
+ const SVG_ALLOWED_TAGS = new Set([
71
+ 'svg', 'g', 'path', 'circle', 'ellipse', 'rect', 'line', 'polyline', 'polygon',
72
+ 'defs', 'lineargradient', 'radialgradient', 'stop', 'title', 'desc', 'clippath',
73
+ ]);
74
+ const SVG_ALLOWED_ATTRS = new Set([
75
+ 'viewbox', 'xmlns', 'width', 'height', 'preserveaspectratio',
76
+ 'fill', 'stroke', 'stroke-width', 'stroke-linecap', 'stroke-linejoin',
77
+ 'stroke-dasharray', 'stroke-dashoffset', 'stroke-opacity', 'stroke-miterlimit',
78
+ 'fill-opacity', 'fill-rule', 'clip-rule', 'clip-path', 'opacity', 'transform',
79
+ 'd', 'cx', 'cy', 'r', 'rx', 'ry', 'x', 'y', 'x1', 'y1', 'x2', 'y2',
80
+ 'points', 'class', 'gradientunits', 'gradienttransform', 'offset',
81
+ 'stop-color', 'stop-opacity', 'id',
82
+ ]);
83
+ /**
84
+ * Sanitize a user-pasted SVG string to a safe subset. Returns '' for anything
85
+ * that isn't an `<svg>` element or that sanitizes to nothing.
86
+ */
87
+ export function sanitizeIconSvg(raw) {
88
+ if (typeof raw !== 'string')
89
+ return '';
90
+ let s = raw.trim();
91
+ if (!/^<svg[\s>]/i.test(s))
92
+ return '';
93
+ // Drop comments / CDATA / PIs / doctype, then dangerous element blocks whole.
94
+ s = s.replace(/<!--[\s\S]*?-->/g, '').replace(/<!\[CDATA\[[\s\S]*?\]\]>/g, '')
95
+ .replace(/<\?[\s\S]*?\?>/g, '').replace(/<!DOCTYPE[^>]*>/gi, '');
96
+ s = s.replace(/<(script|style|foreignObject)\b[\s\S]*?<\/\1\s*>/gi, '');
97
+ // Allowlist every remaining tag + its attributes.
98
+ s = s.replace(/<\/?([a-zA-Z][\w:-]*)((?:"[^"]*"|'[^']*'|[^>"'])*?)\s*(\/?)>/g, (_m, tag, attrs, slash) => {
99
+ const name = String(tag).toLowerCase();
100
+ if (!SVG_ALLOWED_TAGS.has(name))
101
+ return '';
102
+ if (_m.startsWith('</'))
103
+ return `</${name}>`;
104
+ const kept = [];
105
+ const attrRe = /([a-zA-Z_:][\w:.-]*)\s*=\s*("[^"]*"|'[^']*'|[^\s>]+)/g;
106
+ let am;
107
+ while ((am = attrRe.exec(attrs)) !== null) {
108
+ const an = (am[1] ?? '').toLowerCase();
109
+ if (!SVG_ALLOWED_ATTRS.has(an))
110
+ continue;
111
+ const av = (am[2] ?? '').replace(/^["']|["']$/g, '');
112
+ if (/javascript:|expression\(|[<>]/i.test(av))
113
+ continue;
114
+ kept.push(`${an}="${av.replace(/"/g, '&quot;')}"`);
115
+ }
116
+ return `<${name}${kept.length ? ' ' + kept.join(' ') : ''}${slash ? ' /' : ''}>`;
117
+ });
118
+ s = s.trim();
119
+ return /^<svg[\s>]/i.test(s) ? s : '';
120
+ }
121
+ const ICON_SVG_WRAP_OPEN = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">';
122
+ /**
123
+ * Build the FULL `<svg>` string for a block: a sanitized user `iconSvg` when
124
+ * present, else the (library or variant-default) icon wrapped in the standard
125
+ * stroke svg. Shared by the editor NodeView and the read-side renderer.
126
+ */
127
+ export function buildAlertIconSvg(iconKey, iconSvg, variant) {
128
+ const custom = sanitizeIconSvg(iconSvg);
129
+ if (custom)
130
+ return custom;
131
+ return `${ICON_SVG_WRAP_OPEN}${resolveAlertIconInner(iconKey, variant)}</svg>`;
132
+ }
@@ -1,15 +1,30 @@
1
1
  import { Node, Extension } from '@tiptap/core';
2
+ export { ALERT_VARIANTS, ALERT_VARIANT_LABEL, coerceAlertType, type AlertType } from './alertVariants.js';
2
3
  export declare const KeyTakeaways: Node<any, any>;
3
4
  export declare const Summary: Node<any, any>;
4
5
  export declare const Faq: Node<any, any>;
5
6
  export declare const FaqItem: Node<any, any>;
6
7
  export declare const FaqQuestion: Node<any, any>;
7
8
  export declare const FaqAnswer: Node<any, any>;
8
- export declare const ALERT_TYPES: readonly ["info", "warning", "success", "tip"];
9
- export type AlertType = (typeof ALERT_TYPES)[number];
10
- /** Exported so `render.ts` shares the same coercion at the server boundary. */
11
- export declare function coerceAlertType(value: unknown): AlertType;
9
+ type AnyMd = any;
10
+ /** Parse a directive info string (`alert{type=warning}`) into an attrs map. */
11
+ export declare function parseDirectiveAttrs(info: string): Record<string, string>;
12
+ /**
13
+ * Register the `:::alert{…}` block rule + HTML renderers on a markdown-it
14
+ * instance. Idempotent per instance. Wired as `Alert`'s `markdown.parse.setup`.
15
+ */
16
+ export declare function setupAlertDirective(md: AnyMd): void;
17
+ /**
18
+ * `tiptap-markdown` serializer for the Alert node → `:::alert{type=…} Title`.
19
+ * The title rides the opening fence line (admonition style); the body markdown
20
+ * sits between the fences.
21
+ */
22
+ export declare function serializeAlertMarkdown(state: AnyMd, node: AnyMd): void;
12
23
  export declare const Alert: Node<any, any>;
24
+ /** The Alert heading — a single editable line, defaults to the variant label. */
25
+ export declare const AlertTitle: Node<any, any>;
26
+ /** The Alert body — the editable description (`block+`). */
27
+ export declare const AlertBody: Node<any, any>;
13
28
  export declare const ProsCons: Node<any, any>;
14
29
  export declare const ProsColumn: Node<any, any>;
15
30
  export declare const ConsColumn: Node<any, any>;