@heyramzi/ui 2.11.1 → 2.11.2

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 (266) hide show
  1. package/dist/agents/helpers.js +38 -38
  2. package/dist/agents/page-state.js +49 -50
  3. package/dist/agents/vendor/color.js +146 -127
  4. package/dist/agents/vendor/draw.js +617 -508
  5. package/dist/agents/vendor/engine.js +698 -599
  6. package/dist/agents/vendor/mesh.js +653 -683
  7. package/dist/agents/vendor/plastic.js +3913 -3112
  8. package/dist/agents/vendor/presets.js +163 -164
  9. package/dist/agents/vendor/shapes.js +21 -20
  10. package/dist/agents/vendor/ticker.js +41 -42
  11. package/dist/agents/vendor/types.js +1 -0
  12. package/dist/agents/vendor/wear.js +1421 -1448
  13. package/dist/ai/confirmation/confirmation-context.svelte.js +6 -6
  14. package/dist/ai/confirmation/index.js +3 -1
  15. package/dist/ai/index.js +33 -0
  16. package/dist/ai/prompt-input/index.js +3 -1
  17. package/dist/ai/reasoning/index.js +3 -1
  18. package/dist/ai/reasoning/reasoning-context.svelte.js +6 -6
  19. package/dist/ai/suggestion/index.js +3 -1
  20. package/dist/ai/typing-dots/index.js +3 -1
  21. package/dist/animations/text-swap.js +24 -24
  22. package/dist/brand/index.js +54 -48
  23. package/dist/canvas/card/resize.js +5 -3
  24. package/dist/canvas/edges/edge-path.js +255 -242
  25. package/dist/canvas/editor/context-menu.js +39 -36
  26. package/dist/canvas/editor/dispatch.svelte.js +154 -151
  27. package/dist/canvas/editor/editor-context.js +7 -2
  28. package/dist/canvas/editor/editor-mode.js +1 -0
  29. package/dist/canvas/editor/frame.js +73 -76
  30. package/dist/canvas/editor/hand-context.js +5 -2
  31. package/dist/canvas/editor/history-core.js +163 -191
  32. package/dist/canvas/editor/keymap.js +95 -169
  33. package/dist/canvas/editor/layout-actions.js +143 -144
  34. package/dist/canvas/editor/lock.js +52 -51
  35. package/dist/canvas/editor/rebase.js +217 -240
  36. package/dist/canvas/editor/style.js +132 -137
  37. package/dist/canvas/editor/text-field-context.js +7 -2
  38. package/dist/canvas/editor/z-order.js +65 -70
  39. package/dist/canvas/headless/alignment.js +22 -16
  40. package/dist/canvas/headless/break-apart-find.js +286 -279
  41. package/dist/canvas/headless/break-apart-pieces.js +62 -58
  42. package/dist/canvas/headless/break-apart-plan.js +299 -325
  43. package/dist/canvas/headless/break-apart-style.js +75 -70
  44. package/dist/canvas/headless/break-apart.js +188 -181
  45. package/dist/canvas/headless/canvas-cleanup.js +78 -58
  46. package/dist/canvas/headless/edge-anchors.js +115 -116
  47. package/dist/canvas/headless/edge-memo.js +24 -22
  48. package/dist/canvas/headless/edge-serialization.js +17 -27
  49. package/dist/canvas/headless/figure-geometry.js +232 -264
  50. package/dist/canvas/headless/fit-viewport.js +84 -67
  51. package/dist/canvas/headless/grid.js +88 -86
  52. package/dist/canvas/headless/icon-node.js +44 -36
  53. package/dist/canvas/headless/index.js +1 -0
  54. package/dist/canvas/headless/linkify.js +17 -19
  55. package/dist/canvas/headless/pen-marks.js +101 -95
  56. package/dist/canvas/headless/sketch.js +231 -209
  57. package/dist/canvas/headless/snap-guides.js +99 -83
  58. package/dist/canvas/headless/svg-figure.js +133 -117
  59. package/dist/canvas/headless/table-layout.js +146 -107
  60. package/dist/canvas/index.js +2 -0
  61. package/dist/canvas/lod/lod-mode.svelte.js +20 -18
  62. package/dist/canvas/lod/render-all.svelte.js +36 -39
  63. package/dist/canvas/ops/arrange.js +78 -71
  64. package/dist/canvas/ops/ops.js +260 -255
  65. package/dist/canvas/pen/colors.js +37 -35
  66. package/dist/canvas/pen/eraser.js +28 -27
  67. package/dist/canvas/pen/geometry.js +120 -130
  68. package/dist/canvas/pen/index.js +1 -0
  69. package/dist/canvas/pen/ops.js +11 -11
  70. package/dist/canvas/pen/pen-session.js +150 -159
  71. package/dist/canvas/pen/plugin.js +107 -104
  72. package/dist/canvas/pen/shapes.js +73 -68
  73. package/dist/canvas/pen/stroke-path.js +32 -30
  74. package/dist/canvas/pen/types.js +21 -21
  75. package/dist/canvas/pen/ui.js +10 -8
  76. package/dist/canvas/plugin.js +46 -46
  77. package/dist/canvas/screen/glyphs.js +12 -11
  78. package/dist/canvas/screen/labels.js +22 -22
  79. package/dist/canvas/screen/parts.js +98 -111
  80. package/dist/canvas/screen/plugin.js +47 -56
  81. package/dist/canvas/screen/types.js +30 -35
  82. package/dist/canvas/screen/ui.js +7 -7
  83. package/dist/canvas/test-utils.js +14 -14
  84. package/dist/canvas/types.js +1 -0
  85. package/dist/canvas/ui/forward-wheel.js +24 -25
  86. package/dist/canvas/ui/icons.js +64 -131
  87. package/dist/canvas/ui/place-panel.js +58 -49
  88. package/dist/canvas/ui/shortcut-tip.js +14 -12
  89. package/dist/canvas/ui/tools.js +300 -385
  90. package/dist/charts/dashboard/chart.js +4 -0
  91. package/dist/charts/dashboard/data-table.js +11 -0
  92. package/dist/charts/dashboard/windows.js +8 -3
  93. package/dist/charts/edge-fade.js +10 -5
  94. package/dist/charts/hover-band.js +16 -11
  95. package/dist/charts/index.js +0 -1
  96. package/dist/charts/motion.js +11 -7
  97. package/dist/charts/primitives/chart-utils.js +12 -12
  98. package/dist/components/copy/clipboard.js +32 -32
  99. package/dist/components/copy/copied.svelte.js +31 -23
  100. package/dist/components/data-table/data-table.js +39 -39
  101. package/dist/components/docs/index.js +4 -0
  102. package/dist/components/docs/scroll-spy.svelte.js +73 -68
  103. package/dist/components/list-page/filter-section.js +4 -1
  104. package/dist/components/list-page/index.js +2 -0
  105. package/dist/components/list-page/list-table.js +7 -2
  106. package/dist/components/list-page/queue-rail.js +14 -0
  107. package/dist/components/list-page/section-host.js +4 -2
  108. package/dist/components/list-page/step.js +4 -3
  109. package/dist/components/list-page/types.js +1 -0
  110. package/dist/components/picker/trigger.js +48 -30
  111. package/dist/components/picker/types.js +1 -0
  112. package/dist/components/segmented/collapse.js +38 -27
  113. package/dist/components/segmented/index.js +8 -0
  114. package/dist/components/segmented/segmented.js +67 -42
  115. package/dist/components/star-rating-input/index.js +3 -1
  116. package/dist/components/table-view/types.js +1 -0
  117. package/dist/components/tags-input/tags.js +81 -69
  118. package/dist/components/theme/index.js +4 -0
  119. package/dist/components/toolbar/index.js +4 -0
  120. package/dist/components/toolbar/toolbar-trigger.js +14 -1
  121. package/dist/editor/anchors.js +17 -13
  122. package/dist/editor/block-id.js +52 -51
  123. package/dist/editor/block-ops.js +108 -80
  124. package/dist/editor/doc-blocks.js +155 -166
  125. package/dist/editor/extensions.js +139 -180
  126. package/dist/editor/index.js +4 -0
  127. package/dist/editor/markdown-test-helpers.js +4 -2
  128. package/dist/editor/markdown.js +420 -408
  129. package/dist/editor/serializer.js +204 -200
  130. package/dist/editor/slash-items.js +89 -101
  131. package/dist/editor/youtube-url.js +25 -20
  132. package/dist/effects/animated-beam/path-calculator.svelte.js +43 -38
  133. package/dist/effects/animated-beam/types.js +1 -0
  134. package/dist/effects/border-beam/css.js +92 -78
  135. package/dist/effects/border-beam/presets.js +1705 -1720
  136. package/dist/effects/border-beam/register-properties.js +43 -33
  137. package/dist/effects/border-beam/types.js +1 -0
  138. package/dist/effects/image-comparison/compare.js +3 -2
  139. package/dist/effects/image-reveal/types.js +1 -0
  140. package/dist/effects/magnetic/magnetic.js +58 -52
  141. package/dist/effects/reveal/observe.js +50 -40
  142. package/dist/effects/reveal/types.js +1 -0
  143. package/dist/effects/shader-gradient/color.js +36 -29
  144. package/dist/effects/shader-gradient/renderer.js +384 -295
  145. package/dist/effects/shader-gradient/shaders.js +31 -0
  146. package/dist/effects/shader-gradient/types.js +1 -0
  147. package/dist/icons/apple-symbol-exports.js +99 -124
  148. package/dist/icons/sf-symbols.js +357 -461
  149. package/dist/icons/trades.js +43 -28
  150. package/dist/inbox/index.js +1 -0
  151. package/dist/inbox/logic.js +126 -122
  152. package/dist/inbox/types.js +43 -43
  153. package/dist/lib/arrive.js +19 -12
  154. package/dist/lib/icon-types.js +1 -0
  155. package/dist/lib/initials.js +16 -10
  156. package/dist/lib/layout.js +5 -0
  157. package/dist/lib/panel.js +13 -0
  158. package/dist/lib/result.js +22 -22
  159. package/dist/lib/sliding-pill.js +59 -44
  160. package/dist/lib/theme-transition.js +43 -43
  161. package/dist/lib/tone.js +31 -24
  162. package/dist/lib/utils.js +3 -1
  163. package/dist/onboarding/define-tours.js +24 -23
  164. package/dist/onboarding/onboarding.svelte.js +16 -12
  165. package/dist/onboarding/pick.js +10 -6
  166. package/dist/onboarding/stores.js +40 -41
  167. package/dist/onboarding/wait.js +24 -23
  168. package/dist/panel/side-panel-types.js +1 -0
  169. package/dist/record/demo-data.js +459 -486
  170. package/dist/record/fields.js +129 -131
  171. package/dist/record/format.js +70 -82
  172. package/dist/record/gutter.js +1 -0
  173. package/dist/record/kinds.js +95 -105
  174. package/dist/record/logic.js +238 -219
  175. package/dist/record/types.js +49 -48
  176. package/dist/shadcn/accordion/index.js +3 -1
  177. package/dist/shadcn/alert/index.js +3 -1
  178. package/dist/shadcn/alert-dialog/index.js +3 -1
  179. package/dist/shadcn/attachment/index.js +3 -1
  180. package/dist/shadcn/avatar/index.js +3 -1
  181. package/dist/shadcn/breadcrumb/index.js +3 -1
  182. package/dist/shadcn/bubble/index.js +3 -1
  183. package/dist/shadcn/button/index.js +3 -1
  184. package/dist/shadcn/calendar/index.js +3 -1
  185. package/dist/shadcn/card/index.js +3 -1
  186. package/dist/shadcn/checkbox/index.js +3 -1
  187. package/dist/shadcn/code-block/index.js +3 -1
  188. package/dist/shadcn/collapsible/index.js +3 -1
  189. package/dist/shadcn/command/index.js +4 -1
  190. package/dist/shadcn/context-menu/index.js +3 -1
  191. package/dist/shadcn/dialog/index.js +3 -1
  192. package/dist/shadcn/dropdown-menu/menu-item.js +40 -20
  193. package/dist/shadcn/empty/index.js +3 -1
  194. package/dist/shadcn/field/index.js +3 -1
  195. package/dist/shadcn/hooks/highlighter.js +77 -75
  196. package/dist/shadcn/hooks/is-mobile.svelte.js +3 -3
  197. package/dist/shadcn/hooks/use-highlighted-code.svelte.js +62 -62
  198. package/dist/shadcn/input/index.js +3 -1
  199. package/dist/shadcn/kbd/index.js +3 -1
  200. package/dist/shadcn/label/index.js +3 -1
  201. package/dist/shadcn/marker/index.js +3 -1
  202. package/dist/shadcn/message/index.js +3 -1
  203. package/dist/shadcn/message-scroller/index.js +3 -1
  204. package/dist/shadcn/popover/index.js +3 -1
  205. package/dist/shadcn/progress/index.js +3 -1
  206. package/dist/shadcn/progress/tone.js +8 -7
  207. package/dist/shadcn/radio-group/index.js +3 -1
  208. package/dist/shadcn/scroll-area/index.js +3 -1
  209. package/dist/shadcn/select/index.js +3 -1
  210. package/dist/shadcn/separator/index.js +3 -1
  211. package/dist/shadcn/sheet/index.js +3 -1
  212. package/dist/shadcn/sidebar/context.svelte.js +42 -28
  213. package/dist/shadcn/sidebar/index.js +3 -1
  214. package/dist/shadcn/skeleton/index.js +3 -1
  215. package/dist/shadcn/slider/index.js +3 -1
  216. package/dist/shadcn/switch/index.js +3 -1
  217. package/dist/shadcn/table/index.js +3 -1
  218. package/dist/shadcn/tabs/index.js +3 -1
  219. package/dist/shadcn/textarea/index.js +3 -1
  220. package/dist/shadcn/toggle/index.js +3 -1
  221. package/dist/shadcn/toggle-group/index.js +3 -1
  222. package/dist/shadcn/tooltip/context.js +7 -2
  223. package/dist/shadcn/tooltip/index.js +3 -1
  224. package/dist/shadcn/trace/index.js +3 -1
  225. package/dist/slides/bridges.js +65 -59
  226. package/dist/slides/deck-controller.svelte.js +210 -207
  227. package/dist/slides/export/capture.js +66 -71
  228. package/dist/slides/export/ffmpeg.js +76 -72
  229. package/dist/slides/export/index.js +3 -0
  230. package/dist/slides/export/launch.js +9 -6
  231. package/dist/slides/export/pdf.js +60 -54
  232. package/dist/slides/export/types.js +1 -0
  233. package/dist/slides/labels.js +24 -24
  234. package/dist/slides/layout.js +39 -41
  235. package/dist/slides/model.js +9 -7
  236. package/dist/slides/navigation.js +78 -73
  237. package/dist/slides/types.js +2 -2
  238. package/dist/status/define-statuses.js +18 -14
  239. package/dist/status/status-types.js +1 -0
  240. package/dist/test-setup.js +24 -20
  241. package/dist/thread/logic.js +73 -77
  242. package/dist/thread/types.js +21 -21
  243. package/dist/work/board-order.js +94 -90
  244. package/dist/work/dates.js +16 -9
  245. package/dist/work/detail/detail-types.js +18 -18
  246. package/dist/work/docs/edited.js +32 -29
  247. package/dist/work/docs/index.js +4 -0
  248. package/dist/work/docs/labels.js +34 -34
  249. package/dist/work/docs/menu.js +113 -106
  250. package/dist/work/docs/outline.js +39 -29
  251. package/dist/work/gantt/logic.js +211 -235
  252. package/dist/work/inbox/inbox-logic.js +71 -75
  253. package/dist/work/inbox/inbox-statuses.js +75 -72
  254. package/dist/work/inbox/inbox-types.js +1 -0
  255. package/dist/work/kanban-drag.js +27 -22
  256. package/dist/work/kanban-types.js +1 -0
  257. package/dist/work/presets/sales.js +36 -32
  258. package/dist/work/reorder.js +9 -8
  259. package/dist/work/task-types.js +1 -0
  260. package/dist/work/time/duration.js +51 -46
  261. package/dist/work/time/time-types.js +23 -23
  262. package/dist/work/use-kanban-dnd.svelte.js +126 -125
  263. package/package.json +3 -1
  264. package/src/components/list-page/master-detail.svelte +3 -1
  265. package/src/components/list-page/rail-row.svelte +4 -4
  266. package/src/editor/markdown.ts +30 -35
@@ -1,268 +1,272 @@
1
+ // WHY our own slugify: the website's uses NFKD, which strips accents from French
2
+ // headings. The vibe-kit utils one doesn't, so accented anchors would differ.
1
3
  function slugify(input, maxLength = 60) {
2
- return input.normalize("NFKD").replace(/[̀-ͯ]/g, "").toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/(^-|-$)/g, "").slice(0, maxLength).replace(/-$/g, "");
4
+ return input.normalize("NFKD").replace(/[̀-ͯ]/g, "").toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/(^-|-$)/g, "").slice(0, maxLength).replace(/-$/g, "");
3
5
  }
4
6
  function newContext() {
5
- return {
6
- headingSlugs: new Map()
7
- };
7
+ return { headingSlugs: new Map() };
8
8
  }
9
9
  function uniqueHeadingId(text, ctx) {
10
- const base = slugify(text) || "section";
11
- const seen = ctx.headingSlugs.get(base) ?? 0;
12
- ctx.headingSlugs.set(base, seen + 1);
13
- return seen === 0 ? base : `${base}-${seen + 1}`;
10
+ const base = slugify(text) || "section";
11
+ const seen = ctx.headingSlugs.get(base) ?? 0;
12
+ ctx.headingSlugs.set(base, seen + 1);
13
+ return seen === 0 ? base : `${base}-${seen + 1}`;
14
14
  }
15
15
  export function renderTiptapContent(content) {
16
- if (!content || typeof content !== "object") return "";
17
- return renderNode(content, newContext());
16
+ if (!content || typeof content !== "object") return "";
17
+ return renderNode(content, newContext());
18
18
  }
19
19
  export function extractHeadings(content, options = {}) {
20
- if (!content || typeof content !== "object") return [];
21
- const levels = options.levels ?? [
22
- 2,
23
- 3
24
- ];
25
- const ctx = newContext();
26
- const headings = [];
27
- walkHeadings(content, ctx, levels, headings);
28
- return headings;
20
+ if (!content || typeof content !== "object") return [];
21
+ const levels = options.levels ?? [2, 3];
22
+ const ctx = newContext();
23
+ const headings = [];
24
+ walkHeadings(content, ctx, levels, headings);
25
+ return headings;
29
26
  }
30
27
  function walkHeadings(node, ctx, levels, out) {
31
- if (node.type === "heading") {
32
- const level = clampHeadingLevel(node.attrs?.level);
33
- const text = extractText(node.content).trim();
34
- const id = uniqueHeadingId(text, ctx);
35
- if (text && levels.includes(level)) out.push({
36
- id,
37
- level,
38
- text
39
- });
40
- return;
41
- }
42
- if (node.content?.length) {
43
- for (const child of node.content)walkHeadings(child, ctx, levels, out);
44
- }
45
- }
28
+ if (node.type === "heading") {
29
+ const level = clampHeadingLevel(node.attrs?.level);
30
+ const text = extractText(node.content).trim();
31
+ const id = uniqueHeadingId(text, ctx);
32
+ if (text && levels.includes(level)) out.push({
33
+ id,
34
+ level,
35
+ text
36
+ });
37
+ return;
38
+ }
39
+ if (node.content?.length) {
40
+ for (const child of node.content) walkHeadings(child, ctx, levels, out);
41
+ }
42
+ }
43
+ // WHY: a consumer like the website's VideoObject schema gets one entry per
44
+ // `youtube` node without redoing the walk or the videoId check.
46
45
  export function extractYoutubeVideos(content) {
47
- if (!content || typeof content !== "object") return [];
48
- const videos = [];
49
- walkYoutubeVideos(content, videos);
50
- return videos;
46
+ if (!content || typeof content !== "object") return [];
47
+ const videos = [];
48
+ walkYoutubeVideos(content, videos);
49
+ return videos;
51
50
  }
52
51
  function walkYoutubeVideos(node, out) {
53
- if (node.type === "youtube") {
54
- const video = youtubeVideoOf(node);
55
- if (video) out.push(video);
56
- return;
57
- }
58
- for (const child of node.content ?? [])walkYoutubeVideos(child, out);
52
+ if (node.type === "youtube") {
53
+ const video = youtubeVideoOf(node);
54
+ if (video) out.push(video);
55
+ return;
56
+ }
57
+ for (const child of node.content ?? []) walkYoutubeVideos(child, out);
59
58
  }
60
59
  function youtubeVideoOf(node) {
61
- const videoId = strAttr(node.attrs, "videoId") ?? "";
62
- if (!YOUTUBE_VIDEO_ID_RE.test(videoId)) return null;
63
- return {
64
- videoId,
65
- title: strAttr(node.attrs, "title") ?? "",
66
- start: numAttr(node.attrs, "start") ?? 0
67
- };
68
- }
60
+ const videoId = strAttr(node.attrs, "videoId") ?? "";
61
+ if (!YOUTUBE_VIDEO_ID_RE.test(videoId)) return null;
62
+ return {
63
+ videoId,
64
+ title: strAttr(node.attrs, "title") ?? "",
65
+ start: numAttr(node.attrs, "start") ?? 0
66
+ };
67
+ }
68
+ // A typed read of one attr: the value when it has the asked type, else undefined.
69
69
  function strAttr(attrs, key) {
70
- const value = attrs?.[key];
71
- return typeof value === "string" ? value : undefined;
70
+ const value = attrs?.[key];
71
+ return typeof value === "string" ? value : undefined;
72
72
  }
73
73
  function numAttr(attrs, key) {
74
- const value = attrs?.[key];
75
- return typeof value === "number" ? value : undefined;
74
+ const value = attrs?.[key];
75
+ return typeof value === "number" ? value : undefined;
76
76
  }
77
- const wrapIn = (tag)=>(node, ctx)=>`<${tag}>${renderChildren(node.content, ctx) || ""}</${tag}>`;
77
+ // Wraps a node's rendered children in one tag.
78
+ const wrapIn = (tag) => (node, ctx) => `<${tag}>${renderChildren(node.content, ctx) || ""}</${tag}>`;
78
79
  function renderHeading(node, ctx) {
79
- const level = clampHeadingLevel(node.attrs?.level);
80
- const text = extractText(node.content).trim();
81
- const inner = renderChildren(node.content, ctx) || "";
82
- if (!text) return `<h${level}>${inner}</h${level}>`;
83
- const id = uniqueHeadingId(text, ctx);
84
- return `<h${level} id="${escapeAttr(id)}">${inner}</h${level}>`;
80
+ const level = clampHeadingLevel(node.attrs?.level);
81
+ const text = extractText(node.content).trim();
82
+ const inner = renderChildren(node.content, ctx) || "";
83
+ if (!text) return `<h${level}>${inner}</h${level}>`;
84
+ const id = uniqueHeadingId(text, ctx);
85
+ return `<h${level} id="${escapeAttr(id)}">${inner}</h${level}>`;
85
86
  }
86
87
  function renderOrderedList(node, ctx) {
87
- const start = numAttr(node.attrs, "start");
88
- const attrs = renderAttributes({
89
- start: start === undefined ? undefined : String(start)
90
- });
91
- return `<ol${attrs}>${renderChildren(node.content, ctx) || ""}</ol>`;
88
+ const start = numAttr(node.attrs, "start");
89
+ const attrs = renderAttributes({ start: start === undefined ? undefined : String(start) });
90
+ return `<ol${attrs}>${renderChildren(node.content, ctx) || ""}</ol>`;
92
91
  }
93
92
  function renderCodeBlock(node) {
94
- const lang = strAttr(node.attrs, "language");
95
- const language = lang ? ` class="language-${escapeAttr(lang)}"` : "";
96
- return `<pre><code${language}>${escapeHtml(extractText(node.content))}</code></pre>`;
93
+ const lang = strAttr(node.attrs, "language");
94
+ const language = lang ? ` class="language-${escapeAttr(lang)}"` : "";
95
+ return `<pre><code${language}>${escapeHtml(extractText(node.content))}</code></pre>`;
97
96
  }
98
97
  function numString(attrs, key) {
99
- const value = numAttr(attrs, key);
100
- return value === undefined ? undefined : String(value);
98
+ const value = numAttr(attrs, key);
99
+ return value === undefined ? undefined : String(value);
101
100
  }
102
101
  function renderImage(node) {
103
- const src = strAttr(node.attrs, "src");
104
- if (!src) return "";
105
- const attrs = renderAttributes({
106
- alt: strAttr(node.attrs, "alt") ?? "",
107
- title: strAttr(node.attrs, "title"),
108
- width: numString(node.attrs, "width"),
109
- height: numString(node.attrs, "height"),
110
- "data-align": strAttr(node.attrs, "align")
111
- });
112
- return `<img src="${escapeAttr(src)}"${attrs} />`;
102
+ const src = strAttr(node.attrs, "src");
103
+ if (!src) return "";
104
+ const attrs = renderAttributes({
105
+ alt: strAttr(node.attrs, "alt") ?? "",
106
+ title: strAttr(node.attrs, "title"),
107
+ width: numString(node.attrs, "width"),
108
+ height: numString(node.attrs, "height"),
109
+ "data-align": strAttr(node.attrs, "align")
110
+ });
111
+ return `<img src="${escapeAttr(src)}"${attrs} />`;
113
112
  }
114
113
  function renderTable(node, ctx) {
115
- const winnerColIndex = findWinnerColumnIndex(node);
116
- const rows = node.content ?? [];
117
- const body = rows.map((row, rowIdx)=>renderTableRow(row, ctx, {
118
- winnerColIndex,
119
- isFirstRow: rowIdx === 0
120
- })).join("");
121
- const tableClass = winnerColIndex !== -1 ? ' class="comparison-table"' : "";
122
- return `<div class="tiptap-table-wrap"><table${tableClass}>${body}</table></div>`;
114
+ const winnerColIndex = findWinnerColumnIndex(node);
115
+ const rows = node.content ?? [];
116
+ const body = rows.map((row, rowIdx) => renderTableRow(row, ctx, {
117
+ winnerColIndex,
118
+ isFirstRow: rowIdx === 0
119
+ })).join("");
120
+ const tableClass = winnerColIndex !== -1 ? " class=\"comparison-table\"" : "";
121
+ return `<div class="tiptap-table-wrap"><table${tableClass}>${body}</table></div>`;
123
122
  }
124
123
  const RENDERERS = {
125
- doc: (node, ctx)=>renderChildren(node.content, ctx),
126
- paragraph: wrapIn("p"),
127
- heading: renderHeading,
128
- blockquote: wrapIn("blockquote"),
129
- bulletList: wrapIn("ul"),
130
- orderedList: renderOrderedList,
131
- listItem: wrapIn("li"),
132
- codeBlock: renderCodeBlock,
133
- horizontalRule: ()=>"<hr />",
134
- hardBreak: ()=>"<br />",
135
- image: renderImage,
136
- cmsBlock: ()=>"",
137
- youtube: renderYoutube,
138
- table: renderTable,
139
- tableRow: (node, ctx)=>renderTableRow(node, ctx, {
140
- winnerColIndex: -1,
141
- isFirstRow: false
142
- }),
143
- tableCell: (node, ctx)=>renderTableCell(node, ctx, false),
144
- tableHeader: (node, ctx)=>renderTableCell(node, ctx, false),
145
- text: renderText
124
+ doc: (node, ctx) => renderChildren(node.content, ctx),
125
+ paragraph: wrapIn("p"),
126
+ heading: renderHeading,
127
+ blockquote: wrapIn("blockquote"),
128
+ bulletList: wrapIn("ul"),
129
+ orderedList: renderOrderedList,
130
+ listItem: wrapIn("li"),
131
+ codeBlock: renderCodeBlock,
132
+ horizontalRule: () => "<hr />",
133
+ hardBreak: () => "<br />",
134
+ image: renderImage,
135
+ cmsBlock: () => "",
136
+ youtube: renderYoutube,
137
+ table: renderTable,
138
+ tableRow: (node, ctx) => renderTableRow(node, ctx, {
139
+ winnerColIndex: -1,
140
+ isFirstRow: false
141
+ }),
142
+ tableCell: (node, ctx) => renderTableCell(node, ctx, false),
143
+ tableHeader: (node, ctx) => renderTableCell(node, ctx, false),
144
+ text: renderText
146
145
  };
146
+ // Own keys only, so a node typed "toString" never reaches Object.prototype.
147
147
  function pick(table, key) {
148
- return key !== undefined && Object.hasOwn(table, key) ? table[key] : undefined;
148
+ return key !== undefined && Object.hasOwn(table, key) ? table[key] : undefined;
149
149
  }
150
150
  function renderNode(node, ctx) {
151
- const render = pick(RENDERERS, node.type);
152
- return render ? render(node, ctx) : renderChildren(node.content, ctx);
151
+ const render = pick(RENDERERS, node.type);
152
+ return render ? render(node, ctx) : renderChildren(node.content, ctx);
153
153
  }
154
154
  function renderChildren(content, ctx) {
155
- if (!content?.length) return "";
156
- return content.map((child)=>renderNode(child, ctx)).join("");
155
+ if (!content?.length) return "";
156
+ return content.map((child) => renderNode(child, ctx)).join("");
157
157
  }
158
+ // WHY a facade, never a bare <iframe> on first paint: it's roughly 700KB and wrecks
159
+ // LCP. renderer.svelte's delegated click listener swaps in the iframe.
158
160
  const YOUTUBE_VIDEO_ID_RE = /^[A-Za-z0-9_-]{11}$/;
159
161
  function renderYoutube(node) {
160
- const videoId = typeof node.attrs?.videoId === "string" ? node.attrs.videoId : "";
161
- if (!YOUTUBE_VIDEO_ID_RE.test(videoId)) return "";
162
- const title = typeof node.attrs?.title === "string" ? node.attrs.title : "";
163
- const start = typeof node.attrs?.start === "number" ? node.attrs.start : 0;
164
- const safeVideoId = escapeAttr(videoId);
165
- const safeTitle = escapeAttr(title);
166
- return `<div class="tiptap-youtube" data-video-id="${safeVideoId}" data-start="${start}">` + `<button type="button" class="tiptap-youtube-trigger" aria-label="Play video: ${safeTitle}">` + `<img class="tiptap-youtube-poster" src="https://i.ytimg.com/vi/${safeVideoId}/hqdefault.jpg" alt="" loading="lazy" width="480" height="360" />` + `<span class="tiptap-youtube-play" aria-hidden="true"></span>` + `<span class="tiptap-youtube-title">${escapeHtml(title)}</span>` + `</button></div>`;
162
+ const videoId = typeof node.attrs?.videoId === "string" ? node.attrs.videoId : "";
163
+ if (!YOUTUBE_VIDEO_ID_RE.test(videoId)) return "";
164
+ const title = typeof node.attrs?.title === "string" ? node.attrs.title : "";
165
+ const start = typeof node.attrs?.start === "number" ? node.attrs.start : 0;
166
+ const safeVideoId = escapeAttr(videoId);
167
+ const safeTitle = escapeAttr(title);
168
+ return `<div class="tiptap-youtube" data-video-id="${safeVideoId}" data-start="${start}">` + `<button type="button" class="tiptap-youtube-trigger" aria-label="Play video: ${safeTitle}">` + `<img class="tiptap-youtube-poster" src="https://i.ytimg.com/vi/${safeVideoId}/hqdefault.jpg" alt="" loading="lazy" width="480" height="360" />` + `<span class="tiptap-youtube-play" aria-hidden="true"></span>` + `<span class="tiptap-youtube-title">${escapeHtml(title)}</span>` + `</button></div>`;
167
169
  }
168
170
  function renderTableRow(row, ctx, { winnerColIndex, isFirstRow }) {
169
- if (row.type !== "tableRow" || !row.content?.length) {
170
- return `<tr>${renderChildren(row.content, ctx)}</tr>`;
171
- }
172
- const cellsHtml = row.content.map((cell, cellIdx)=>{
173
- const isWinnerCell = !isFirstRow && winnerColIndex !== -1 && cellIdx === winnerColIndex;
174
- return renderTableCell(cell, ctx, isWinnerCell);
175
- }).join("");
176
- return `<tr>${cellsHtml}</tr>`;
171
+ if (row.type !== "tableRow" || !row.content?.length) {
172
+ return `<tr>${renderChildren(row.content, ctx)}</tr>`;
173
+ }
174
+ const cellsHtml = row.content.map((cell, cellIdx) => {
175
+ const isWinnerCell = !isFirstRow && winnerColIndex !== -1 && cellIdx === winnerColIndex;
176
+ return renderTableCell(cell, ctx, isWinnerCell);
177
+ }).join("");
178
+ return `<tr>${cellsHtml}</tr>`;
177
179
  }
178
180
  function renderTableCell(cell, ctx, isWinnerCell) {
179
- const tag = cell.type === "tableHeader" ? "th" : "td";
180
- const colspan = typeof cell.attrs?.colspan === "number" && cell.attrs.colspan !== 1 ? String(cell.attrs.colspan) : undefined;
181
- const rowspan = typeof cell.attrs?.rowspan === "number" && cell.attrs.rowspan !== 1 ? String(cell.attrs.rowspan) : undefined;
182
- const inner = renderChildren(cell.content, ctx);
183
- if (!isWinnerCell) {
184
- const attrs = renderAttributes({
185
- colspan,
186
- rowspan
187
- });
188
- return `<${tag}${attrs}>${inner}</${tag}>`;
189
- }
190
- const winnerText = extractText(cell.content).trim();
191
- const winner = normalizeWinner(winnerText);
192
- const attrs = renderAttributes({
193
- class: "winner-cell",
194
- "data-winner": winner,
195
- colspan,
196
- rowspan
197
- });
198
- return `<${tag}${attrs}>${inner}</${tag}>`;
181
+ const tag = cell.type === "tableHeader" ? "th" : "td";
182
+ const colspan = typeof cell.attrs?.colspan === "number" && cell.attrs.colspan !== 1 ? String(cell.attrs.colspan) : undefined;
183
+ const rowspan = typeof cell.attrs?.rowspan === "number" && cell.attrs.rowspan !== 1 ? String(cell.attrs.rowspan) : undefined;
184
+ const inner = renderChildren(cell.content, ctx);
185
+ if (!isWinnerCell) {
186
+ const attrs = renderAttributes({
187
+ colspan,
188
+ rowspan
189
+ });
190
+ return `<${tag}${attrs}>${inner}</${tag}>`;
191
+ }
192
+ const winnerText = extractText(cell.content).trim();
193
+ const winner = normalizeWinner(winnerText);
194
+ const attrs = renderAttributes({
195
+ class: "winner-cell",
196
+ "data-winner": winner,
197
+ colspan,
198
+ rowspan
199
+ });
200
+ return `<${tag}${attrs}>${inner}</${tag}>`;
199
201
  }
200
202
  const WINNER_HEADERS = [
201
- "winner",
202
- "gagnant",
203
- "vainqueur"
203
+ "winner",
204
+ "gagnant",
205
+ "vainqueur"
204
206
  ];
205
207
  function findWinnerColumnIndex(table) {
206
- const firstRow = table.content?.[0];
207
- if (!firstRow?.content) return -1;
208
- for(let i = 0; i < firstRow.content.length; i++){
209
- const headerText = extractText(firstRow.content[i]?.content).trim().toLowerCase();
210
- if (WINNER_HEADERS.includes(headerText)) return i;
211
- }
212
- return -1;
213
- }
208
+ const firstRow = table.content?.[0];
209
+ if (!firstRow?.content) return -1;
210
+ for (let i = 0; i < firstRow.content.length; i++) {
211
+ const headerText = extractText(firstRow.content[i]?.content).trim().toLowerCase();
212
+ if (WINNER_HEADERS.includes(headerText)) return i;
213
+ }
214
+ return -1;
215
+ }
216
+ // WHY the first word: "Acme wins" and "Acme" are one winner. The host styles a
217
+ // product with `[data-winner="acme"]`, so the serializer names no product.
214
218
  function normalizeWinner(text) {
215
- const lower = text.toLowerCase().trim();
216
- if (!lower) return "other";
217
- if (lower.startsWith("tie") || lower.startsWith("égal") || lower.startsWith("egal")) {
218
- return "tie";
219
- }
220
- return lower.match(/^[\p{L}\p{N}]+/u)?.[0] ?? "other";
219
+ const lower = text.toLowerCase().trim();
220
+ if (!lower) return "other";
221
+ if (lower.startsWith("tie") || lower.startsWith("égal") || lower.startsWith("egal")) {
222
+ return "tie";
223
+ }
224
+ return lower.match(/^[\p{L}\p{N}]+/u)?.[0] ?? "other";
221
225
  }
222
226
  function renderText(node) {
223
- const text = escapeHtml(node.text ?? "");
224
- if (!node.marks?.length) return text;
225
- return node.marks.reduce((html, mark)=>wrapMark(html, mark), text);
227
+ const text = escapeHtml(node.text ?? "");
228
+ if (!node.marks?.length) return text;
229
+ return node.marks.reduce((html, mark) => wrapMark(html, mark), text);
226
230
  }
227
231
  const MARK_TAGS = {
228
- bold: "strong",
229
- italic: "em",
230
- strike: "s",
231
- underline: "u",
232
- code: "code"
232
+ bold: "strong",
233
+ italic: "em",
234
+ strike: "s",
235
+ underline: "u",
236
+ code: "code"
233
237
  };
234
238
  function wrapMark(content, mark) {
235
- if (mark.type === "link") {
236
- const attrs = renderAttributes({
237
- href: strAttr(mark.attrs, "href") ?? "",
238
- target: strAttr(mark.attrs, "target"),
239
- rel: strAttr(mark.attrs, "rel")
240
- });
241
- return `<a${attrs}>${content}</a>`;
242
- }
243
- const tag = pick(MARK_TAGS, mark.type);
244
- return tag ? `<${tag}>${content}</${tag}>` : content;
239
+ if (mark.type === "link") {
240
+ const attrs = renderAttributes({
241
+ href: strAttr(mark.attrs, "href") ?? "",
242
+ target: strAttr(mark.attrs, "target"),
243
+ rel: strAttr(mark.attrs, "rel")
244
+ });
245
+ return `<a${attrs}>${content}</a>`;
246
+ }
247
+ const tag = pick(MARK_TAGS, mark.type);
248
+ return tag ? `<${tag}>${content}</${tag}>` : content;
245
249
  }
246
250
  function renderAttributes(attrs) {
247
- const parts = Object.entries(attrs).filter(([, value])=>value !== undefined && value !== "").map(([key, value])=>` ${key}="${escapeAttr(value ?? "")}"`);
248
- return parts.join("");
251
+ const parts = Object.entries(attrs).filter(([, value]) => value !== undefined && value !== "").map(([key, value]) => ` ${key}="${escapeAttr(value ?? "")}"`);
252
+ return parts.join("");
249
253
  }
250
254
  function extractText(content) {
251
- if (!content?.length) return "";
252
- return content.map((node)=>{
253
- if (node.type === "text") return node.text ?? "";
254
- return extractText(node.content);
255
- }).join("");
255
+ if (!content?.length) return "";
256
+ return content.map((node) => {
257
+ if (node.type === "text") return node.text ?? "";
258
+ return extractText(node.content);
259
+ }).join("");
256
260
  }
257
261
  function clampHeadingLevel(level) {
258
- if (level === 1 || level === 2 || level === 3 || level === 4 || level === 5 || level === 6) {
259
- return level;
260
- }
261
- return 2;
262
+ if (level === 1 || level === 2 || level === 3 || level === 4 || level === 5 || level === 6) {
263
+ return level;
264
+ }
265
+ return 2;
262
266
  }
263
267
  function escapeHtml(value) {
264
- return value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
268
+ return value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll("\"", "&quot;").replaceAll("'", "&#39;");
265
269
  }
266
270
  function escapeAttr(value) {
267
- return escapeHtml(value).replaceAll("`", "&#96;");
271
+ return escapeHtml(value).replaceAll("`", "&#96;");
268
272
  }