@heyramzi/ui 2.1.0 → 2.3.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 (317) hide show
  1. package/COMPONENTS.md +1 -1
  2. package/package.json +7 -8
  3. package/scripts/sync-shadcn.mjs +3 -4
  4. package/src/agents/agent-avatar.svelte +604 -575
  5. package/src/agents/agent-cast.svelte +179 -183
  6. package/src/agents/demo.svelte +125 -113
  7. package/src/agents/helpers.ts +23 -17
  8. package/src/agents/index.ts +4 -4
  9. package/src/agents/page-state.ts +6 -6
  10. package/src/agents/types.ts +6 -6
  11. package/src/ai/ai-button/AiButton.svelte +4 -12
  12. package/src/ai/generation-progress/GenerationProgress.svelte +10 -3
  13. package/src/ai/prompt-input/PromptInputTextarea.svelte +1 -2
  14. package/src/ai/response/Response.css +2 -3
  15. package/src/animations/avatar-group-hover.css +2 -16
  16. package/src/animations/card-tilt.css +3 -6
  17. package/src/animations/digit-pop-in.css +2 -15
  18. package/src/animations/dropdown.css +4 -17
  19. package/src/animations/input-clear-dissolve.css +5 -18
  20. package/src/animations/input-shake.css +9 -41
  21. package/src/animations/learn-more-hover.css +17 -9
  22. package/src/animations/like-button.css +2 -4
  23. package/src/animations/page-side-by-side.css +2 -10
  24. package/src/animations/plus-menu-morph.css +2 -5
  25. package/src/animations/shimmer-text.css +5 -18
  26. package/src/animations/spinning-counter.css +2 -6
  27. package/src/animations/success-check.css +2 -15
  28. package/src/animations/thinking-states.css +2 -7
  29. package/src/brand/brand.test.ts +21 -5
  30. package/src/brand/index.ts +4 -10
  31. package/src/canvas/card/card-frame.svelte +43 -39
  32. package/src/canvas/card/resize-handles.svelte +89 -80
  33. package/src/canvas/chrome/chrome-zoom.svelte +101 -100
  34. package/src/canvas/chrome/lock-badge.svelte +8 -8
  35. package/src/canvas/chrome/side-handles.svelte +24 -19
  36. package/src/canvas/chrome/size-chip.svelte +30 -30
  37. package/src/canvas/chrome/snap-guides.svelte +20 -20
  38. package/src/canvas/components/icons/ToolIcon.svelte +8 -8
  39. package/src/canvas/components/shared/svg/PreviewSvg.svelte +19 -19
  40. package/src/canvas/components/shared/svg/ShapeOutline.svelte +71 -48
  41. package/src/canvas/components/shared/svg/StrokeNode.svelte +21 -27
  42. package/src/canvas/components/shared/svg/TextRule.svelte +25 -17
  43. package/src/canvas/edges/bend-handle.svelte +46 -46
  44. package/src/canvas/edges/edge-gradient.svelte +10 -10
  45. package/src/canvas/edges/gradient-edge.svelte +45 -29
  46. package/src/canvas/editor/lock.test.ts +6 -1
  47. package/src/canvas/editor/style.ts +7 -1
  48. package/src/canvas/headless/canvas-cleanup.test.ts +1 -1
  49. package/src/canvas/headless/svg-figure.test.ts +5 -1
  50. package/src/canvas/lod/lod-switch.svelte +111 -106
  51. package/src/canvas/ops/ops.ts +1 -1
  52. package/src/canvas/pen/PenField.svelte +87 -81
  53. package/src/canvas/pen/PenOverlay.svelte +127 -127
  54. package/src/canvas/pen/ShapeNode.svelte +31 -32
  55. package/src/canvas/pen/TextNode.svelte +54 -53
  56. package/src/canvas/pen/host-field.test.svelte +7 -7
  57. package/src/canvas/pen/shapes.ts +7 -1
  58. package/src/canvas/screen/GlyphIcon.svelte +22 -17
  59. package/src/canvas/screen/PartView.svelte +138 -72
  60. package/src/canvas/screen/ScreenBody.svelte +81 -60
  61. package/src/canvas/screen/ScreenCard.svelte +20 -20
  62. package/src/canvas/ui/CanvasToolbar.svelte +173 -85
  63. package/src/canvas/ui/FrameLayer.svelte +59 -59
  64. package/src/canvas/ui/PenLayer.svelte +39 -30
  65. package/src/canvas/ui/StylePanel.svelte +119 -102
  66. package/src/canvas/ui/canvas-grid.svelte +137 -129
  67. package/src/charts/animated-value.svelte +1 -1
  68. package/src/charts/area-chart.css +2 -5
  69. package/src/charts/area-chart.svelte +1 -1
  70. package/src/charts/bar-chart.css +2 -3
  71. package/src/charts/bar-chart.svelte +1 -1
  72. package/src/charts/charts.css +5 -17
  73. package/src/charts/crosshair.svelte +1 -1
  74. package/src/charts/dashboard/dashboard-empty.svelte +25 -25
  75. package/src/charts/dashboard/dashboard-section.svelte +24 -24
  76. package/src/charts/dashboard/data-panel.svelte +50 -50
  77. package/src/charts/dashboard/data-sort-head.svelte +56 -56
  78. package/src/charts/dashboard/data-table.svelte +138 -138
  79. package/src/charts/dashboard/panel-grid.svelte +30 -30
  80. package/src/charts/dashboard/period-filter.svelte +25 -25
  81. package/src/charts/dashboard/rating-chip.svelte +19 -19
  82. package/src/charts/dashboard/stat-band.svelte +32 -32
  83. package/src/charts/dashboard/stat-tile.svelte +106 -99
  84. package/src/charts/donut-chart.svelte +1 -1
  85. package/src/charts/edge-fade.svelte +1 -1
  86. package/src/charts/figure.svelte +1 -1
  87. package/src/charts/gauge-chart.css +4 -7
  88. package/src/charts/gauge-chart.svelte +1 -1
  89. package/src/charts/hover-focus.svelte +1 -1
  90. package/src/charts/legend.css +1 -3
  91. package/src/charts/legend.svelte +1 -1
  92. package/src/charts/line-chart.svelte +1 -1
  93. package/src/charts/number-chart.css +6 -9
  94. package/src/charts/number-chart.svelte +1 -1
  95. package/src/charts/reference-line.svelte +19 -19
  96. package/src/charts/reveal.svelte +1 -1
  97. package/src/charts/ring-chart.css +2 -4
  98. package/src/charts/ring-chart.svelte +1 -1
  99. package/src/charts/sparkline.svelte +5 -2
  100. package/src/charts/stat-card.svelte +1 -1
  101. package/src/charts/time-series-chart.css +2 -5
  102. package/src/charts/trend-pill.css +2 -4
  103. package/src/charts/trend-pill.svelte +1 -1
  104. package/src/components/copy/copy-button.svelte +93 -98
  105. package/src/components/copy/copy-button.test.ts +10 -2
  106. package/src/components/data-table/infinite-scroll.svelte +42 -42
  107. package/src/components/data-table/pagination.svelte +34 -22
  108. package/src/components/data-table/row-arrow.svelte +19 -16
  109. package/src/components/data-table/row-context-menu.svelte +52 -52
  110. package/src/components/data-table/sortable-head.svelte +25 -25
  111. package/src/components/data-table/table-skeleton.svelte +41 -36
  112. package/src/components/docs/doc-page.svelte +18 -9
  113. package/src/components/docs/on-this-page.svelte +16 -3
  114. package/src/components/docs/scroll-spy.svelte.ts +2 -3
  115. package/src/components/field-row/field-row.svelte +10 -8
  116. package/src/components/hold-to-confirm/hold-to-confirm.svelte +5 -1
  117. package/src/components/icon/icon.svelte +14 -14
  118. package/src/components/language-flag/language-flag.svelte +3 -1
  119. package/src/components/list-page/detail-empty.svelte +34 -34
  120. package/src/components/list-page/detail-header.svelte +55 -47
  121. package/src/components/list-page/detail-panel.svelte +51 -59
  122. package/src/components/list-page/detail-row.svelte +19 -19
  123. package/src/components/list-page/detail-stack.svelte +21 -21
  124. package/src/components/list-page/empty-state.svelte +30 -30
  125. package/src/components/list-page/filter-item.svelte +20 -20
  126. package/src/components/list-page/filter-popover.svelte +60 -55
  127. package/src/components/list-page/grouped-list.svelte +4 -2
  128. package/src/components/list-page/index.ts +5 -1
  129. package/src/components/list-page/list-page-shell.svelte +2 -5
  130. package/src/components/list-page/master-detail.svelte +87 -88
  131. package/src/components/list-page/notice-bar.svelte +38 -38
  132. package/src/components/list-page/prev-next.svelte +63 -63
  133. package/src/components/list-page/rail-body.svelte +18 -18
  134. package/src/components/list-page/rail-empty.svelte +23 -30
  135. package/src/components/list-page/rail-head.svelte +24 -24
  136. package/src/components/list-page/rail-row.svelte +77 -73
  137. package/src/components/list-page/section-host.ts +2 -3
  138. package/src/components/locale-switch/locale-switch.svelte +41 -41
  139. package/src/components/mark/mark.svelte +34 -38
  140. package/src/components/open-in/index.ts +1 -5
  141. package/src/components/open-in/open-in.svelte +40 -40
  142. package/src/components/picker/mark-picker.svelte +49 -49
  143. package/src/components/picker/picker.svelte +5 -3
  144. package/src/components/section-header/section-header.test.ts +7 -1
  145. package/src/components/section-header/section-label.svelte +20 -20
  146. package/src/components/segmented/animated-tabs.svelte +31 -2
  147. package/src/components/segmented/collapse.test.ts +2 -1
  148. package/src/components/segmented/index.ts +2 -0
  149. package/src/components/segmented/segment.svelte +52 -49
  150. package/src/components/segmented/segmented-filter.svelte +79 -80
  151. package/src/components/segmented/segmented-nav.svelte +34 -40
  152. package/src/components/segmented/segmented-overflow.svelte +53 -50
  153. package/src/components/segmented/segmented-track.svelte +62 -46
  154. package/src/components/segmented/segmented.ts +2 -3
  155. package/src/components/star-rating-input/star-rating-input.svelte +103 -103
  156. package/src/components/svg/svg.svelte +4 -4
  157. package/src/components/table-view/index.ts +1 -1
  158. package/src/components/table-view/table-view.svelte +352 -13
  159. package/src/components/table-view/table-view.test.ts +108 -1
  160. package/src/components/table-view/types.ts +22 -11
  161. package/src/components/tags-input/index.ts +13 -0
  162. package/src/components/tags-input/tags-input.svelte +305 -0
  163. package/src/components/tags-input/tags.test.ts +64 -0
  164. package/src/components/tags-input/tags.ts +142 -0
  165. package/src/components/theme/index.ts +2 -4
  166. package/src/components/theme/theme-provider.svelte +1 -3
  167. package/src/components/toolbar/toolbar-search.svelte +25 -25
  168. package/src/components/toolbar/toolbar.svelte +11 -7
  169. package/src/components/toolbar/toolbar.test.ts +10 -1
  170. package/src/design/index.css +21 -78
  171. package/src/design/polish.css +26 -35
  172. package/src/editor/editor-toolbar.svelte +104 -99
  173. package/src/editor/editor.svelte +296 -266
  174. package/src/editor/image-toolbar.svelte +20 -22
  175. package/src/editor/index.ts +1 -4
  176. package/src/editor/inline-editor.svelte +280 -238
  177. package/src/editor/markdown.ts +15 -15
  178. package/src/editor/renderer.svelte +71 -71
  179. package/src/editor/slash-menu.svelte +24 -27
  180. package/src/effects/animated-beam/diagrams/BeamSvg.svelte +1 -1
  181. package/src/effects/image-comparison/ImageComparison.svelte +11 -1
  182. package/src/effects/interactive-hover-button/InteractiveHoverButton.svelte +59 -46
  183. package/src/effects/magic-card/MagicCard.css +14 -7
  184. package/src/effects/marquee/Marquee.svelte +3 -5
  185. package/src/effects/reveal/observe.ts +0 -3
  186. package/src/effects/spotlight/Spotlight.svelte +1 -1
  187. package/src/effects/theme-toggle/ThemeToggle.css +25 -22
  188. package/src/effects/theme-toggle/ThemeToggle.svelte +6 -7
  189. package/src/gallery.test.ts +14 -4
  190. package/src/icons/trades.ts +1 -1
  191. package/src/inbox/README.md +1 -3
  192. package/src/inbox/inbox.test.ts +181 -138
  193. package/src/inbox/index.ts +20 -20
  194. package/src/inbox/logic.ts +114 -111
  195. package/src/inbox/thread-inbox.svelte +227 -151
  196. package/src/inbox/types.ts +130 -108
  197. package/src/lib/theme-transition.ts +2 -4
  198. package/src/lib/tone.ts +2 -4
  199. package/src/lib/utils.ts +2 -2
  200. package/src/modules.test.ts +79 -69
  201. package/src/onboarding/define-tours.test.ts +55 -40
  202. package/src/onboarding/define-tours.ts +23 -23
  203. package/src/onboarding/demo.svelte +24 -5
  204. package/src/onboarding/index.ts +1 -8
  205. package/src/onboarding/onboarding-provider.svelte +19 -7
  206. package/src/onboarding/onboarding.svelte.ts +16 -16
  207. package/src/onboarding/pick.test.ts +61 -52
  208. package/src/onboarding/pick.ts +16 -13
  209. package/src/onboarding/stores.test.ts +80 -80
  210. package/src/onboarding/stores.ts +41 -39
  211. package/src/onboarding/tour-card.svelte +30 -5
  212. package/src/onboarding/tour-runner.svelte +12 -3
  213. package/src/onboarding/types.ts +20 -12
  214. package/src/onboarding/wait.ts +22 -22
  215. package/src/panel/side-panel.svelte +94 -93
  216. package/src/record/demo-data.ts +291 -225
  217. package/src/record/field-section.svelte +38 -34
  218. package/src/record/fields.ts +148 -96
  219. package/src/record/format.ts +63 -56
  220. package/src/record/index.ts +49 -35
  221. package/src/record/info-list.svelte +145 -114
  222. package/src/record/kinds.ts +31 -31
  223. package/src/record/logic.ts +216 -214
  224. package/src/record/next-up.svelte +45 -34
  225. package/src/record/project-list.svelte +56 -50
  226. package/src/record/record-demo.svelte +130 -110
  227. package/src/record/record-header.svelte +37 -28
  228. package/src/record/record-shell.svelte +54 -54
  229. package/src/record/record-stats.svelte +25 -25
  230. package/src/record/record-timeline.svelte +134 -114
  231. package/src/record/record.test.ts +430 -309
  232. package/src/record/steps-rail.svelte +225 -216
  233. package/src/record/types.ts +234 -215
  234. package/src/record/where-we-are.svelte +62 -58
  235. package/src/shadcn/button/button.svelte +1 -1
  236. package/src/shadcn/card/card-title.svelte +1 -5
  237. package/src/shadcn/hooks/use-highlighted-code.svelte.ts +2 -13
  238. package/src/shadcn/sheet/sheet-content.svelte +2 -4
  239. package/src/shadcn/table/table-cell.svelte +2 -3
  240. package/src/shadcn/utilities.css +3 -8
  241. package/src/slides/Controls.svelte +150 -124
  242. package/src/slides/Deck.svelte +243 -238
  243. package/src/slides/Fragment.svelte +64 -57
  244. package/src/slides/Overview.svelte +106 -101
  245. package/src/slides/Presenter.svelte +300 -266
  246. package/src/slides/Slide.svelte +185 -170
  247. package/src/slides/SlideList.svelte +16 -12
  248. package/src/slides/ambient.d.ts +1 -1
  249. package/src/slides/bridges.ts +42 -23
  250. package/src/slides/deck-controller.svelte.ts +47 -35
  251. package/src/slides/demo-slides.svelte +330 -306
  252. package/src/slides/demo.svelte +164 -160
  253. package/src/slides/export/capture.ts +20 -16
  254. package/src/slides/export/ffmpeg.ts +43 -38
  255. package/src/slides/export/index.ts +5 -5
  256. package/src/slides/export/launch.ts +3 -1
  257. package/src/slides/export/pdf.ts +30 -27
  258. package/src/slides/export/types.ts +10 -8
  259. package/src/slides/index.ts +11 -11
  260. package/src/slides/labels.ts +22 -19
  261. package/src/slides/layout.ts +9 -2
  262. package/src/slides/model.ts +4 -4
  263. package/src/slides/navigation.ts +43 -31
  264. package/src/slides/theme.css +5 -7
  265. package/src/slides/types.ts +9 -10
  266. package/src/status/define-statuses.ts +17 -24
  267. package/src/status/index.ts +5 -5
  268. package/src/status/status-badge.svelte +3 -1
  269. package/src/status/status-dot.svelte +1 -3
  270. package/src/status/status-icon.svelte +1 -1
  271. package/src/status/status-types.ts +21 -21
  272. package/src/tasks/board-order.ts +2 -4
  273. package/src/tasks/demo.svelte +189 -31
  274. package/src/tasks/detail/detail-demo.svelte +73 -13
  275. package/src/tasks/detail/detail-types.ts +45 -45
  276. package/src/tasks/detail/task-detail.svelte +40 -10
  277. package/src/tasks/detail/task-property.svelte +8 -2
  278. package/src/tasks/detail/task-thread.svelte +53 -12
  279. package/src/tasks/gantt/gantt-bar.svelte +17 -6
  280. package/src/tasks/gantt/gantt-grid.svelte +40 -9
  281. package/src/tasks/gantt/gantt-group-row.svelte +8 -2
  282. package/src/tasks/gantt/gantt-header.svelte +9 -3
  283. package/src/tasks/gantt/gantt-task-row.svelte +26 -6
  284. package/src/tasks/gantt/gantt.css +2 -1
  285. package/src/tasks/gantt/gantt.test.ts +3 -1
  286. package/src/tasks/gantt/logic.test.ts +110 -20
  287. package/src/tasks/gantt/logic.ts +42 -6
  288. package/src/tasks/inbox/inbox-card.svelte +13 -5
  289. package/src/tasks/inbox/inbox-draft.svelte +1 -1
  290. package/src/tasks/inbox/inbox-logic.ts +6 -40
  291. package/src/tasks/inbox/inbox-statuses.ts +23 -23
  292. package/src/tasks/inbox/inbox-trace.svelte +9 -3
  293. package/src/tasks/inbox/inbox-types.ts +54 -58
  294. package/src/tasks/inbox/index.ts +22 -21
  295. package/src/tasks/index.ts +49 -52
  296. package/src/tasks/kanban-board.svelte +16 -12
  297. package/src/tasks/kanban-types.ts +2 -3
  298. package/src/tasks/presets/sales.ts +6 -6
  299. package/src/tasks/task-gantt.svelte +28 -8
  300. package/src/tasks/task-list.svelte +41 -17
  301. package/src/tasks/time/duration.ts +40 -40
  302. package/src/tasks/time/time-chip.svelte +9 -2
  303. package/src/tasks/time/time-dot.svelte +1 -1
  304. package/src/tasks/time/time-ring.svelte +11 -3
  305. package/src/tasks/time/time-tracker.svelte +57 -15
  306. package/src/tasks/time/time-types.ts +57 -57
  307. package/src/tasks/use-kanban-dnd.svelte.ts +2 -3
  308. package/src/thread/index.ts +7 -7
  309. package/src/thread/logic.ts +36 -33
  310. package/src/thread/thread.svelte +82 -16
  311. package/src/thread/thread.test.ts +58 -47
  312. package/src/thread/types.ts +68 -68
  313. package/src/tokens/craft.css +17 -53
  314. package/src/tokens/index.css +3 -13
  315. package/src/tokens/palette.css +13 -11
  316. package/src/tokens/primitives.css +10 -23
  317. package/src/tasks/inbox/inbox.svelte +0 -203
@@ -1,258 +1,300 @@
1
1
  <script lang="ts" module>
2
- // The tiptap side of a small in-place text field (a canvas card). It speaks ProseMirror JSON
3
- // and hands the host a plain `InlineEditorApi`, so no tiptap type leaks out.
4
- export type InlineDoc = Record<string, unknown>;
2
+ // The tiptap side of a small in-place text field (a canvas card). It speaks ProseMirror JSON
3
+ // and hands the host a plain `InlineEditorApi`, so no tiptap type leaks out.
4
+ export type InlineDoc = Record<string, unknown>;
5
5
 
6
- export type InlineCommand = "bold" | "italic" | "strike" | "bulletList" | "orderedList" | "taskList";
6
+ export type InlineCommand =
7
+ | "bold"
8
+ | "italic"
9
+ | "strike"
10
+ | "bulletList"
11
+ | "orderedList"
12
+ | "taskList";
7
13
 
8
- export type InlineEditorApi = {
9
- toggle: (command: InlineCommand) => void;
10
- setLink: (href: string | null) => void;
11
- isActive: (name: string) => boolean;
12
- linkHref: () => string;
13
- hasSelection: () => boolean;
14
- /** Types at the caret, replacing any selection. */
15
- insertText: (text: string) => void;
16
- setDoc: (doc: InlineDoc) => void;
17
- getDoc: () => InlineDoc;
18
- focus: () => void;
19
- };
14
+ export type InlineEditorApi = {
15
+ toggle: (command: InlineCommand) => void;
16
+ setLink: (href: string | null) => void;
17
+ isActive: (name: string) => boolean;
18
+ linkHref: () => string;
19
+ hasSelection: () => boolean;
20
+ /** Types at the caret, replacing any selection. */
21
+ insertText: (text: string) => void;
22
+ setDoc: (doc: InlineDoc) => void;
23
+ getDoc: () => InlineDoc;
24
+ focus: () => void;
25
+ };
20
26
  </script>
21
27
 
22
28
  <script lang="ts">
23
- // =========================
24
- // IMPORTS
25
- // =========================
26
- import { Editor, Extension, Node as TiptapNode, mergeAttributes, wrappingInputRule } from "@tiptap/core";
27
- import { StarterKit } from "@tiptap/starter-kit";
28
- import { Placeholder } from "@tiptap/extension-placeholder";
29
- import { untrack } from "svelte";
29
+ // =========================
30
+ // IMPORTS
31
+ // =========================
32
+ import {
33
+ Editor,
34
+ Extension,
35
+ Node as TiptapNode,
36
+ mergeAttributes,
37
+ wrappingInputRule,
38
+ } from "@tiptap/core";
39
+ import { StarterKit } from "@tiptap/starter-kit";
40
+ import { Placeholder } from "@tiptap/extension-placeholder";
41
+ import { untrack } from "svelte";
30
42
 
31
- // =========================
32
- // TYPES
33
- // =========================
34
- interface Props {
35
- doc: InlineDoc;
36
- placeholder?: string;
37
- class?: string;
38
- /** Marks only: bold, italic, strike, code and link. No lists, so a table cell stays a line of words. */
39
- inline?: boolean;
40
- /** Where the click landed, so the caret opens there. Without it the caret goes to the end. */
41
- at?: { x: number; y: number };
42
- /** Opens with all the text selected, so the first key replaces it. */
43
- selectAll?: boolean;
44
- /** Opens with the text replaced by these characters and the caret after them. */
45
- seed?: string;
46
- onchange: (doc: InlineDoc) => void;
47
- onexit: () => void;
48
- onlink?: () => void;
49
- onstate?: () => void;
50
- onready?: (api: InlineEditorApi) => void;
51
- };
43
+ // =========================
44
+ // TYPES
45
+ // =========================
46
+ interface Props {
47
+ doc: InlineDoc;
48
+ placeholder?: string;
49
+ class?: string;
50
+ /** Marks only: bold, italic, strike, code and link. No lists, so a table cell stays a line of words. */
51
+ inline?: boolean;
52
+ /** Where the click landed, so the caret opens there. Without it the caret goes to the end. */
53
+ at?: { x: number; y: number };
54
+ /** Opens with all the text selected, so the first key replaces it. */
55
+ selectAll?: boolean;
56
+ /** Opens with the text replaced by these characters and the caret after them. */
57
+ seed?: string;
58
+ onchange: (doc: InlineDoc) => void;
59
+ onexit: () => void;
60
+ onlink?: () => void;
61
+ onstate?: () => void;
62
+ onready?: (api: InlineEditorApi) => void;
63
+ }
52
64
 
53
- // =========================
54
- // PROPS
55
- // =========================
56
- let { doc, placeholder = "", class: klass = "", inline = false, at, selectAll = false, seed, onchange, onexit, onlink, onstate, onready }: Props = $props();
65
+ // =========================
66
+ // PROPS
67
+ // =========================
68
+ let {
69
+ doc,
70
+ placeholder = "",
71
+ class: klass = "",
72
+ inline = false,
73
+ at,
74
+ selectAll = false,
75
+ seed,
76
+ onchange,
77
+ onexit,
78
+ onlink,
79
+ onstate,
80
+ onready,
81
+ }: Props = $props();
57
82
 
58
- // =========================
59
- // STATE
60
- // =========================
61
- let element: HTMLDivElement | undefined = $state();
83
+ // =========================
84
+ // STATE
85
+ // =========================
86
+ let element: HTMLDivElement | undefined = $state();
62
87
 
63
- // =========================
64
- // CONSTANTS
65
- // =========================
66
- const TaskList = TiptapNode.create({
67
- name: "taskList",
68
- group: "block list",
69
- content: "taskItem+",
70
- parseHTML: () => [{ tag: 'ul[data-type="taskList"]' }],
71
- renderHTML: ({ HTMLAttributes }) => ["ul", mergeAttributes(HTMLAttributes, { "data-type": "taskList" }), 0],
72
- addInputRules() {
73
- return [wrappingInputRule({ find: /^\s*\[\s?\]\s$/, type: this.type })];
74
- },
75
- });
88
+ // =========================
89
+ // CONSTANTS
90
+ // =========================
91
+ const TaskList = TiptapNode.create({
92
+ name: "taskList",
93
+ group: "block list",
94
+ content: "taskItem+",
95
+ parseHTML: () => [{ tag: 'ul[data-type="taskList"]' }],
96
+ renderHTML: ({ HTMLAttributes }) => [
97
+ "ul",
98
+ mergeAttributes(HTMLAttributes, { "data-type": "taskList" }),
99
+ 0,
100
+ ],
101
+ addInputRules() {
102
+ return [wrappingInputRule({ find: /^\s*\[\s?\]\s$/, type: this.type })];
103
+ },
104
+ });
76
105
 
77
- // A checklist item. Its box is a real input, so a tick is one transaction and one undo step.
78
- const TaskItem = TiptapNode.create({
79
- name: "taskItem",
80
- content: "paragraph",
81
- defining: true,
82
- addAttributes() {
83
- return { checked: { default: false, keepOnSplit: false } };
84
- },
85
- parseHTML: () => [{ tag: 'li[data-type="taskItem"]', priority: 51 }],
86
- renderHTML: ({ node, HTMLAttributes }) => [
87
- "li",
88
- mergeAttributes(HTMLAttributes, { "data-type": "taskItem", "data-checked": String(node.attrs.checked) }),
89
- 0,
90
- ],
91
- addKeyboardShortcuts() {
92
- return {
93
- Enter: () => this.editor.commands.splitListItem(this.name),
94
- "Shift-Tab": () => this.editor.commands.liftListItem(this.name),
95
- };
96
- },
97
- addNodeView() {
98
- return ({ node, getPos, editor }) => {
99
- let current = node;
100
- const li = document.createElement("li");
101
- li.dataset.type = "taskItem";
102
- const label = document.createElement("label");
103
- label.contentEditable = "false";
104
- const input = document.createElement("input");
105
- input.type = "checkbox";
106
- label.append(input);
107
- const body = document.createElement("div");
108
- li.append(label, body);
109
- const paint = () => {
110
- li.dataset.checked = String(current.attrs.checked);
111
- input.checked = current.attrs.checked === true;
112
- };
113
- paint();
114
- input.addEventListener("change", () => {
115
- const pos = getPos();
116
- if (typeof pos !== "number") return;
117
- editor
118
- .chain()
119
- .command(({ tr }) => {
120
- tr.setNodeMarkup(pos, undefined, { ...current.attrs, checked: input.checked });
121
- return true;
122
- })
123
- .run();
124
- });
125
- return {
126
- dom: li,
127
- contentDOM: body,
128
- update(next) {
129
- if (next.type !== current.type) return false;
130
- current = next;
131
- paint();
132
- return true;
133
- },
134
- };
135
- };
136
- },
137
- });
106
+ // A checklist item. Its box is a real input, so a tick is one transaction and one undo step.
107
+ const TaskItem = TiptapNode.create({
108
+ name: "taskItem",
109
+ content: "paragraph",
110
+ defining: true,
111
+ addAttributes() {
112
+ return { checked: { default: false, keepOnSplit: false } };
113
+ },
114
+ parseHTML: () => [{ tag: 'li[data-type="taskItem"]', priority: 51 }],
115
+ renderHTML: ({ node, HTMLAttributes }) => [
116
+ "li",
117
+ mergeAttributes(HTMLAttributes, {
118
+ "data-type": "taskItem",
119
+ "data-checked": String(node.attrs.checked),
120
+ }),
121
+ 0,
122
+ ],
123
+ addKeyboardShortcuts() {
124
+ return {
125
+ Enter: () => this.editor.commands.splitListItem(this.name),
126
+ "Shift-Tab": () => this.editor.commands.liftListItem(this.name),
127
+ };
128
+ },
129
+ addNodeView() {
130
+ return ({ node, getPos, editor }) => {
131
+ let current = node;
132
+ const li = document.createElement("li");
133
+ li.dataset.type = "taskItem";
134
+ const label = document.createElement("label");
135
+ label.contentEditable = "false";
136
+ const input = document.createElement("input");
137
+ input.type = "checkbox";
138
+ label.append(input);
139
+ const body = document.createElement("div");
140
+ li.append(label, body);
141
+ const paint = () => {
142
+ li.dataset.checked = String(current.attrs.checked);
143
+ input.checked = current.attrs.checked === true;
144
+ };
145
+ paint();
146
+ input.addEventListener("change", () => {
147
+ const pos = getPos();
148
+ if (typeof pos !== "number") return;
149
+ editor
150
+ .chain()
151
+ .command(({ tr }) => {
152
+ tr.setNodeMarkup(pos, undefined, { ...current.attrs, checked: input.checked });
153
+ return true;
154
+ })
155
+ .run();
156
+ });
157
+ return {
158
+ dom: li,
159
+ contentDOM: body,
160
+ update(next) {
161
+ if (next.type !== current.type) return false;
162
+ current = next;
163
+ paint();
164
+ return true;
165
+ },
166
+ };
167
+ };
168
+ },
169
+ });
138
170
 
139
- // =========================
140
- // EFFECTS
141
- // =========================
142
- $effect(() => {
143
- if (!element) return;
144
- const host = element;
145
- return untrack(() => build(host));
146
- });
171
+ // =========================
172
+ // EFFECTS
173
+ // =========================
174
+ $effect(() => {
175
+ if (!element) return;
176
+ const host = element;
177
+ return untrack(() => build(host));
178
+ });
147
179
 
148
- function build(host: HTMLElement) {
180
+ function build(host: HTMLElement) {
181
+ const keys = Extension.create({
182
+ name: "cardKeys",
183
+ priority: 1000,
184
+ addKeyboardShortcuts() {
185
+ return {
186
+ "Mod-Shift-x": () => this.editor.commands.toggleStrike(),
187
+ "Mod-k": () => {
188
+ onlink?.();
189
+ return true;
190
+ },
191
+ Escape: () => {
192
+ onexit();
193
+ return true;
194
+ },
195
+ // Lists stay flat: the stored text has no nesting, so Tab does nothing.
196
+ Tab: () => true,
197
+ };
198
+ },
199
+ });
149
200
 
150
- const keys = Extension.create({
151
- name: "cardKeys",
152
- priority: 1000,
153
- addKeyboardShortcuts() {
154
- return {
155
- "Mod-Shift-x": () => this.editor.commands.toggleStrike(),
156
- "Mod-k": () => {
157
- onlink?.();
158
- return true;
159
- },
160
- Escape: () => {
161
- onexit();
162
- return true;
163
- },
164
- // Lists stay flat: the stored text has no nesting, so Tab does nothing.
165
- Tab: () => true,
166
- };
167
- },
168
- });
201
+ const marksOnly = untrack(() => inline);
202
+ const editor = new Editor({
203
+ element: host,
204
+ content: doc,
205
+ extensions: [
206
+ StarterKit.configure({
207
+ ...(marksOnly && {
208
+ bulletList: false,
209
+ orderedList: false,
210
+ listItem: false,
211
+ listKeymap: false,
212
+ }),
213
+ heading: false,
214
+ blockquote: false,
215
+ codeBlock: false,
216
+ horizontalRule: false,
217
+ hardBreak: false,
218
+ underline: false,
219
+ trailingNode: false,
220
+ link: {
221
+ openOnClick: false,
222
+ autolink: false,
223
+ HTMLAttributes: { target: "_blank", rel: "noopener noreferrer" },
224
+ },
225
+ ...(!marksOnly && {
226
+ listKeymap: {
227
+ listTypes: [
228
+ { itemName: "listItem", wrapperNames: ["bulletList", "orderedList"] },
229
+ { itemName: "taskItem", wrapperNames: ["taskList"] },
230
+ ],
231
+ },
232
+ }),
233
+ }),
234
+ ...(marksOnly ? [] : [TaskList, TaskItem]),
235
+ Placeholder.configure({ placeholder }),
236
+ keys,
237
+ ],
238
+ editorProps: {
239
+ attributes: { class: `rich-text ${klass}`, role: "textbox", "aria-multiline": "true" },
240
+ handleKeyDown: (_view, event) => {
241
+ // Every key typed here is the field's. Only save leaves, so no canvas shortcut fires while typing.
242
+ if (!((event.metaKey || event.ctrlKey) && event.key.toLowerCase() === "s"))
243
+ event.stopPropagation();
244
+ return false;
245
+ },
246
+ handleDOMEvents: {
247
+ click: (_view, event) => {
248
+ const anchor = (event.target as HTMLElement).closest("a");
249
+ if (anchor && (event.metaKey || event.ctrlKey))
250
+ window.open(anchor.getAttribute("href") ?? "", "_blank", "noopener");
251
+ return false;
252
+ },
253
+ },
254
+ },
255
+ onUpdate: ({ editor: ed, transaction }) => {
256
+ if (transaction.docChanged) onchange(ed.getJSON());
257
+ },
258
+ onTransaction: () => onstate?.(),
259
+ });
169
260
 
170
- const marksOnly = untrack(() => inline);
171
- const editor = new Editor({
172
- element: host,
173
- content: doc,
174
- extensions: [
175
- StarterKit.configure({
176
- ...(marksOnly && { bulletList: false, orderedList: false, listItem: false, listKeymap: false }),
177
- heading: false,
178
- blockquote: false,
179
- codeBlock: false,
180
- horizontalRule: false,
181
- hardBreak: false,
182
- underline: false,
183
- trailingNode: false,
184
- link: {
185
- openOnClick: false,
186
- autolink: false,
187
- HTMLAttributes: { target: "_blank", rel: "noopener noreferrer" },
188
- },
189
- ...(!marksOnly && {
190
- listKeymap: {
191
- listTypes: [
192
- { itemName: "listItem", wrapperNames: ["bulletList", "orderedList"] },
193
- { itemName: "taskItem", wrapperNames: ["taskList"] },
194
- ],
195
- },
196
- }),
197
- }),
198
- ...(marksOnly ? [] : [TaskList, TaskItem]),
199
- Placeholder.configure({ placeholder }),
200
- keys,
201
- ],
202
- editorProps: {
203
- attributes: { class: `rich-text ${klass}`, role: "textbox", "aria-multiline": "true" },
204
- handleKeyDown: (_view, event) => {
205
- // Every key typed here is the field's. Only save leaves, so no canvas shortcut fires while typing.
206
- if (!((event.metaKey || event.ctrlKey) && event.key.toLowerCase() === "s")) event.stopPropagation();
207
- return false;
208
- },
209
- handleDOMEvents: {
210
- click: (_view, event) => {
211
- const anchor = (event.target as HTMLElement).closest("a");
212
- if (anchor && (event.metaKey || event.ctrlKey)) window.open(anchor.getAttribute("href") ?? "", "_blank", "noopener");
213
- return false;
214
- },
215
- },
216
- },
217
- onUpdate: ({ editor: ed, transaction }) => {
218
- if (transaction.docChanged) onchange(ed.getJSON());
219
- },
220
- onTransaction: () => onstate?.(),
221
- });
261
+ const hit = at ? editor.view.posAtCoords({ left: at.x, top: at.y }) : null;
262
+ if (seed) editor.chain().focus().selectAll().insertContent({ type: "text", text: seed }).run();
263
+ else if (selectAll) editor.chain().focus().selectAll().run();
264
+ else if (hit) editor.chain().setTextSelection(hit.pos).focus().run();
265
+ else editor.commands.focus("end");
222
266
 
223
- const hit = at ? editor.view.posAtCoords({ left: at.x, top: at.y }) : null;
224
- if (seed) editor.chain().focus().selectAll().insertContent({ type: "text", text: seed }).run();
225
- else if (selectAll) editor.chain().focus().selectAll().run();
226
- else if (hit) editor.chain().setTextSelection(hit.pos).focus().run();
227
- else editor.commands.focus("end");
267
+ onready?.({
268
+ toggle: (command) => {
269
+ if (
270
+ marksOnly &&
271
+ (command === "bulletList" || command === "orderedList" || command === "taskList")
272
+ )
273
+ return;
274
+ const chain = editor.chain().focus();
275
+ if (command === "bold") chain.toggleBold().run();
276
+ else if (command === "italic") chain.toggleItalic().run();
277
+ else if (command === "strike") chain.toggleStrike().run();
278
+ else if (command === "bulletList") chain.toggleBulletList().run();
279
+ else if (command === "orderedList") chain.toggleOrderedList().run();
280
+ else chain.toggleList("taskList", "taskItem").run();
281
+ },
282
+ setLink: (href) => {
283
+ const chain = editor.chain().focus().extendMarkRange("link");
284
+ if (href) chain.setLink({ href }).run();
285
+ else chain.unsetLink().run();
286
+ },
287
+ isActive: (name) => editor.isActive(name),
288
+ linkHref: () => String(editor.getAttributes("link").href ?? ""),
289
+ hasSelection: () => !editor.state.selection.empty,
290
+ insertText: (text) => editor.chain().focus().insertContent({ type: "text", text }).run(),
291
+ setDoc: (next) => editor.commands.setContent(next, { emitUpdate: false }),
292
+ getDoc: () => editor.getJSON(),
293
+ focus: () => editor.commands.focus(),
294
+ });
228
295
 
229
- onready?.({
230
- toggle: (command) => {
231
- if (marksOnly && (command === "bulletList" || command === "orderedList" || command === "taskList")) return;
232
- const chain = editor.chain().focus();
233
- if (command === "bold") chain.toggleBold().run();
234
- else if (command === "italic") chain.toggleItalic().run();
235
- else if (command === "strike") chain.toggleStrike().run();
236
- else if (command === "bulletList") chain.toggleBulletList().run();
237
- else if (command === "orderedList") chain.toggleOrderedList().run();
238
- else chain.toggleList("taskList", "taskItem").run();
239
- },
240
- setLink: (href) => {
241
- const chain = editor.chain().focus().extendMarkRange("link");
242
- if (href) chain.setLink({ href }).run();
243
- else chain.unsetLink().run();
244
- },
245
- isActive: (name) => editor.isActive(name),
246
- linkHref: () => String(editor.getAttributes("link").href ?? ""),
247
- hasSelection: () => !editor.state.selection.empty,
248
- insertText: (text) => editor.chain().focus().insertContent({ type: "text", text }).run(),
249
- setDoc: (next) => editor.commands.setContent(next, { emitUpdate: false }),
250
- getDoc: () => editor.getJSON(),
251
- focus: () => editor.commands.focus(),
252
- });
253
-
254
- return () => editor.destroy();
255
- }
296
+ return () => editor.destroy();
297
+ }
256
298
  </script>
257
299
 
258
300
  <!-- =========================== -->
@@ -144,13 +144,13 @@ function parseOrderedList(tokens: Token[], i: number): Parsed {
144
144
  }
145
145
 
146
146
  const BLOCK_PARSERS: Record<string, BlockParser> = {
147
- "paragraph_open": parseParagraph,
148
- "heading_open": parseHeading,
147
+ paragraph_open: parseParagraph,
148
+ heading_open: parseHeading,
149
149
  hr: (_tokens, i) => ({ node: { type: "horizontalRule", attrs: { id: null } }, next: i + 1 }),
150
150
  fence: parseFence,
151
- "blockquote_open": parseBlockquote,
152
- "bullet_list_open": parseBulletList,
153
- "ordered_list_open": parseOrderedList,
151
+ blockquote_open: parseBlockquote,
152
+ bullet_list_open: parseBulletList,
153
+ ordered_list_open: parseOrderedList,
154
154
  };
155
155
 
156
156
  // Collect tokens between the current position and the matching _close token.
@@ -274,23 +274,23 @@ function openLink(tok: Token, markStack: TiptapMark[]): void {
274
274
 
275
275
  const INLINE_HANDLERS: Record<string, InlineHandler> = {
276
276
  text: textNode,
277
- "text_special": textNode,
277
+ text_special: textNode,
278
278
  // WHY: a softbreak is a newline inside a paragraph, so it reads as a space.
279
279
  softbreak: () => ({ type: "text", text: " " }),
280
280
  hardbreak: () => ({ type: "hardBreak" }),
281
- "code_inline": (tok, markStack) => ({
281
+ code_inline: (tok, markStack) => ({
282
282
  type: "text",
283
283
  text: tok.content,
284
284
  marks: [...markStack, { type: "code" }],
285
285
  }),
286
- "strong_open": openMark("bold"),
287
- "strong_close": closeMark("bold"),
288
- "em_open": openMark("italic"),
289
- "em_close": closeMark("italic"),
290
- "s_open": openMark("strike"),
291
- "s_close": closeMark("strike"),
292
- "link_open": openLink,
293
- "link_close": closeMark("link"),
286
+ strong_open: openMark("bold"),
287
+ strong_close: closeMark("bold"),
288
+ em_open: openMark("italic"),
289
+ em_close: closeMark("italic"),
290
+ s_open: openMark("strike"),
291
+ s_close: closeMark("strike"),
292
+ link_open: openLink,
293
+ link_close: closeMark("link"),
294
294
  };
295
295
 
296
296
  function removeLastMark(stack: TiptapMark[], type: string): void {