@heyramzi/ui 2.0.1 → 2.2.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 (341) hide show
  1. package/package.json +48 -24
  2. package/scripts/sync-shadcn.mjs +3 -4
  3. package/src/agents/agent-avatar.svelte +604 -575
  4. package/src/agents/agent-cast.svelte +179 -183
  5. package/src/agents/demo.svelte +125 -113
  6. package/src/agents/helpers.ts +23 -17
  7. package/src/agents/index.ts +4 -4
  8. package/src/agents/page-state.ts +6 -6
  9. package/src/agents/types.ts +6 -6
  10. package/src/ai/ai-button/AiButton.svelte +4 -12
  11. package/src/ai/generation-progress/GenerationProgress.svelte +10 -3
  12. package/src/ai/prompt-input/PromptInputTextarea.svelte +1 -2
  13. package/src/ai/response/Response.css +2 -3
  14. package/src/animations/avatar-group-hover.css +2 -16
  15. package/src/animations/card-tilt.css +3 -6
  16. package/src/animations/digit-pop-in.css +2 -15
  17. package/src/animations/dropdown.css +4 -17
  18. package/src/animations/input-clear-dissolve.css +5 -18
  19. package/src/animations/input-shake.css +9 -41
  20. package/src/animations/learn-more-hover.css +17 -9
  21. package/src/animations/like-button.css +2 -4
  22. package/src/animations/page-side-by-side.css +2 -10
  23. package/src/animations/plus-menu-morph.css +2 -5
  24. package/src/animations/shimmer-text.css +5 -18
  25. package/src/animations/spinning-counter.css +2 -6
  26. package/src/animations/success-check.css +2 -15
  27. package/src/animations/thinking-states.css +2 -7
  28. package/src/brand/brand.test.ts +21 -5
  29. package/src/brand/index.ts +4 -10
  30. package/src/canvas/README.md +9 -12
  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/svg-figure.test.ts +5 -1
  49. package/src/canvas/lod/lod-switch.svelte +111 -106
  50. package/src/canvas/pen/PenField.svelte +87 -81
  51. package/src/canvas/pen/PenOverlay.svelte +127 -127
  52. package/src/canvas/pen/ShapeNode.svelte +31 -32
  53. package/src/canvas/pen/TextNode.svelte +54 -53
  54. package/src/canvas/pen/host-field.test.svelte +7 -7
  55. package/src/canvas/pen/shapes.ts +7 -1
  56. package/src/canvas/screen/GlyphIcon.svelte +22 -17
  57. package/src/canvas/screen/PartView.svelte +138 -72
  58. package/src/canvas/screen/ScreenBody.svelte +81 -60
  59. package/src/canvas/screen/ScreenCard.svelte +20 -20
  60. package/src/canvas/ui/CanvasToolbar.svelte +173 -85
  61. package/src/canvas/ui/FrameLayer.svelte +59 -59
  62. package/src/canvas/ui/PenLayer.svelte +39 -30
  63. package/src/canvas/ui/StylePanel.svelte +119 -102
  64. package/src/canvas/ui/canvas-grid.svelte +137 -129
  65. package/src/charts/animated-value.svelte +1 -1
  66. package/src/charts/area-chart.css +2 -5
  67. package/src/charts/area-chart.svelte +1 -1
  68. package/src/charts/bar-chart.css +2 -3
  69. package/src/charts/bar-chart.svelte +1 -1
  70. package/src/charts/charts.css +5 -17
  71. package/src/charts/crosshair.svelte +1 -1
  72. package/src/charts/dashboard/dashboard-empty.svelte +25 -25
  73. package/src/charts/dashboard/dashboard-section.svelte +24 -24
  74. package/src/charts/dashboard/data-panel.svelte +50 -50
  75. package/src/charts/dashboard/data-sort-head.svelte +56 -56
  76. package/src/charts/dashboard/data-table.svelte +138 -138
  77. package/src/charts/dashboard/panel-grid.svelte +30 -30
  78. package/src/charts/dashboard/period-filter.svelte +25 -25
  79. package/src/charts/dashboard/rating-chip.svelte +19 -19
  80. package/src/charts/dashboard/stat-band.svelte +32 -32
  81. package/src/charts/dashboard/stat-tile.svelte +106 -99
  82. package/src/charts/donut-chart.svelte +1 -1
  83. package/src/charts/edge-fade.svelte +1 -1
  84. package/src/charts/figure.svelte +1 -1
  85. package/src/charts/gauge-chart.css +4 -7
  86. package/src/charts/gauge-chart.svelte +1 -1
  87. package/src/charts/hover-focus.svelte +1 -1
  88. package/src/charts/legend.css +1 -3
  89. package/src/charts/legend.svelte +1 -1
  90. package/src/charts/line-chart.svelte +1 -1
  91. package/src/charts/number-chart.css +6 -9
  92. package/src/charts/number-chart.svelte +1 -1
  93. package/src/charts/reference-line.svelte +19 -19
  94. package/src/charts/reveal.svelte +1 -1
  95. package/src/charts/ring-chart.css +2 -4
  96. package/src/charts/ring-chart.svelte +1 -1
  97. package/src/charts/sparkline.svelte +5 -2
  98. package/src/charts/stat-card.svelte +1 -1
  99. package/src/charts/time-series-chart.css +2 -5
  100. package/src/charts/trend-pill.css +2 -4
  101. package/src/charts/trend-pill.svelte +1 -1
  102. package/src/components/copy/copy-button.svelte +93 -98
  103. package/src/components/copy/copy-button.test.ts +10 -2
  104. package/src/components/data-table/infinite-scroll.svelte +42 -42
  105. package/src/components/data-table/pagination.svelte +34 -22
  106. package/src/components/data-table/row-arrow.svelte +19 -16
  107. package/src/components/data-table/row-context-menu.svelte +52 -52
  108. package/src/components/data-table/sortable-head.svelte +25 -25
  109. package/src/components/data-table/table-skeleton.svelte +41 -36
  110. package/src/components/docs/doc-page.svelte +18 -9
  111. package/src/components/docs/on-this-page.svelte +16 -3
  112. package/src/components/docs/scroll-spy.svelte.ts +2 -3
  113. package/src/components/field-row/field-row.svelte +10 -8
  114. package/src/components/hold-to-confirm/hold-to-confirm.svelte +5 -1
  115. package/src/components/icon/icon.svelte +14 -14
  116. package/src/components/language-flag/language-flag.svelte +3 -1
  117. package/src/components/list-page/detail-empty.svelte +34 -34
  118. package/src/components/list-page/detail-header.svelte +55 -47
  119. package/src/components/list-page/detail-panel.svelte +51 -59
  120. package/src/components/list-page/detail-row.svelte +19 -19
  121. package/src/components/list-page/detail-stack.svelte +21 -21
  122. package/src/components/list-page/empty-state.svelte +30 -30
  123. package/src/components/list-page/filter-item.svelte +20 -20
  124. package/src/components/list-page/filter-popover.svelte +60 -55
  125. package/src/components/list-page/grouped-list.svelte +4 -2
  126. package/src/components/list-page/index.ts +8 -4
  127. package/src/components/list-page/list-page-shell.svelte +2 -5
  128. package/src/components/list-page/master-detail.svelte +87 -88
  129. package/src/components/list-page/notice-bar.svelte +38 -38
  130. package/src/components/list-page/prev-next.svelte +63 -63
  131. package/src/components/list-page/rail-body.svelte +18 -18
  132. package/src/components/list-page/rail-empty.svelte +23 -30
  133. package/src/components/list-page/rail-head.svelte +24 -24
  134. package/src/components/list-page/rail-row.svelte +77 -73
  135. package/src/components/list-page/section-host.ts +2 -3
  136. package/src/components/locale-switch/locale-switch.svelte +41 -41
  137. package/src/components/mark/mark.svelte +34 -38
  138. package/src/components/open-in/index.ts +1 -5
  139. package/src/components/open-in/open-in.svelte +40 -40
  140. package/src/components/picker/mark-picker.svelte +49 -49
  141. package/src/components/picker/picker.svelte +5 -3
  142. package/src/components/section-header/index.ts +1 -0
  143. package/src/components/section-header/section-header.svelte +1 -1
  144. package/src/components/section-header/section-header.test.ts +7 -1
  145. package/src/components/section-header/section-label.svelte +39 -0
  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/table-view.svelte +17 -2
  158. package/src/components/table-view/table-view.test.ts +7 -1
  159. package/src/components/table-view/types.ts +11 -11
  160. package/src/components/tags-input/index.ts +13 -0
  161. package/src/components/tags-input/tags-input.svelte +305 -0
  162. package/src/components/tags-input/tags.test.ts +64 -0
  163. package/src/components/tags-input/tags.ts +142 -0
  164. package/src/components/theme/index.ts +2 -4
  165. package/src/components/theme/theme-provider.svelte +1 -3
  166. package/src/components/toolbar/index.ts +2 -0
  167. package/src/components/{list-page → toolbar}/toolbar-search.svelte +25 -25
  168. package/src/components/toolbar/toolbar.svelte +13 -9
  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/inbox/README.md +27 -0
  191. package/src/inbox/inbox.test.ts +214 -0
  192. package/src/inbox/index.ts +31 -0
  193. package/src/inbox/logic.ts +169 -0
  194. package/src/inbox/thread-inbox.svelte +439 -0
  195. package/src/inbox/types.ts +161 -0
  196. package/src/lib/theme-transition.ts +2 -4
  197. package/src/lib/tone.ts +2 -4
  198. package/src/lib/utils.ts +2 -2
  199. package/src/modules.test.ts +105 -0
  200. package/src/onboarding/define-tours.test.ts +55 -40
  201. package/src/onboarding/define-tours.ts +23 -23
  202. package/src/onboarding/demo.svelte +24 -5
  203. package/src/onboarding/index.ts +1 -8
  204. package/src/onboarding/onboarding-provider.svelte +19 -7
  205. package/src/onboarding/onboarding.svelte.ts +16 -16
  206. package/src/onboarding/pick.test.ts +61 -52
  207. package/src/onboarding/pick.ts +16 -13
  208. package/src/onboarding/stores.test.ts +80 -80
  209. package/src/onboarding/stores.ts +41 -39
  210. package/src/onboarding/tour-card.svelte +30 -5
  211. package/src/onboarding/tour-runner.svelte +12 -3
  212. package/src/onboarding/types.ts +20 -12
  213. package/src/onboarding/wait.ts +22 -22
  214. package/src/panel/README.md +5 -0
  215. package/src/panel/index.ts +2 -0
  216. package/src/panel/side-panel.svelte +194 -0
  217. package/src/record/README.md +3 -0
  218. package/src/record/demo-data.ts +352 -0
  219. package/src/record/field-section.svelte +57 -0
  220. package/src/record/fields.ts +194 -0
  221. package/src/record/format.ts +94 -0
  222. package/src/{tasks/record → record}/index.ts +49 -35
  223. package/src/{tasks/record → record}/info-list.svelte +145 -114
  224. package/src/record/kinds.ts +42 -0
  225. package/src/record/logic.ts +326 -0
  226. package/src/record/next-up.svelte +81 -0
  227. package/src/{tasks/record → record}/project-list.svelte +56 -50
  228. package/src/{tasks/record → record}/record-demo.svelte +130 -110
  229. package/src/{tasks/record → record}/record-header.svelte +37 -28
  230. package/src/{tasks/record → record}/record-shell.svelte +54 -54
  231. package/src/record/record-stats.svelte +48 -0
  232. package/src/record/record-timeline.svelte +251 -0
  233. package/src/record/record.test.ts +471 -0
  234. package/src/{tasks/record → record}/steps-rail.svelte +225 -216
  235. package/src/record/types.ts +299 -0
  236. package/src/record/where-we-are.svelte +125 -0
  237. package/src/shadcn/button/button.svelte +1 -1
  238. package/src/shadcn/card/card-title.svelte +1 -5
  239. package/src/shadcn/hooks/use-highlighted-code.svelte.ts +2 -13
  240. package/src/shadcn/sheet/sheet-content.svelte +2 -4
  241. package/src/shadcn/table/table-cell.svelte +2 -3
  242. package/src/shadcn/utilities.css +3 -8
  243. package/src/slides/Controls.svelte +150 -124
  244. package/src/slides/Deck.svelte +243 -238
  245. package/src/slides/Fragment.svelte +64 -57
  246. package/src/slides/Overview.svelte +106 -101
  247. package/src/slides/Presenter.svelte +300 -266
  248. package/src/slides/Slide.svelte +185 -170
  249. package/src/slides/SlideList.svelte +16 -12
  250. package/src/slides/ambient.d.ts +1 -1
  251. package/src/slides/bridges.ts +42 -23
  252. package/src/slides/deck-controller.svelte.ts +47 -35
  253. package/src/slides/demo-slides.svelte +330 -306
  254. package/src/slides/demo.svelte +164 -160
  255. package/src/slides/export/capture.ts +20 -16
  256. package/src/slides/export/ffmpeg.ts +43 -38
  257. package/src/slides/export/index.ts +5 -5
  258. package/src/slides/export/launch.ts +3 -1
  259. package/src/slides/export/pdf.ts +30 -27
  260. package/src/slides/export/types.ts +10 -8
  261. package/src/slides/index.ts +11 -11
  262. package/src/slides/labels.ts +22 -19
  263. package/src/slides/layout.ts +9 -2
  264. package/src/slides/model.ts +4 -4
  265. package/src/slides/navigation.ts +43 -31
  266. package/src/slides/theme.css +5 -7
  267. package/src/slides/types.ts +9 -10
  268. package/src/status/README.md +18 -0
  269. package/src/status/define-statuses.ts +25 -0
  270. package/src/status/index.ts +11 -0
  271. package/src/{tasks → status}/status-badge.svelte +3 -1
  272. package/src/{tasks → status}/status-dot.svelte +1 -3
  273. package/src/{tasks → status}/status-icon.svelte +1 -1
  274. package/src/status/status-types.ts +40 -0
  275. package/src/tasks/README.md +1 -1
  276. package/src/tasks/board-order.ts +2 -4
  277. package/src/tasks/demo.svelte +192 -34
  278. package/src/tasks/detail/detail-demo.svelte +75 -15
  279. package/src/tasks/detail/detail-types.ts +45 -45
  280. package/src/tasks/detail/task-detail.svelte +40 -10
  281. package/src/tasks/detail/task-property.svelte +8 -2
  282. package/src/tasks/detail/task-thread.svelte +53 -12
  283. package/src/tasks/gantt/gantt-bar.svelte +17 -6
  284. package/src/tasks/gantt/gantt-grid.svelte +40 -9
  285. package/src/tasks/gantt/gantt-group-row.svelte +8 -2
  286. package/src/tasks/gantt/gantt-header.svelte +9 -3
  287. package/src/tasks/gantt/gantt-task-row.svelte +26 -6
  288. package/src/tasks/gantt/gantt.css +2 -1
  289. package/src/tasks/gantt/gantt.test.ts +4 -2
  290. package/src/tasks/gantt/logic.test.ts +110 -20
  291. package/src/tasks/gantt/logic.ts +42 -6
  292. package/src/tasks/inbox/inbox-card.svelte +15 -7
  293. package/src/tasks/inbox/inbox-draft.svelte +1 -1
  294. package/src/tasks/inbox/inbox-logic.ts +6 -40
  295. package/src/tasks/inbox/inbox-statuses.ts +24 -24
  296. package/src/tasks/inbox/inbox-trace.svelte +9 -3
  297. package/src/tasks/inbox/inbox-types.ts +54 -58
  298. package/src/tasks/inbox/index.ts +22 -21
  299. package/src/tasks/index.ts +52 -53
  300. package/src/tasks/kanban-board.svelte +16 -12
  301. package/src/tasks/kanban-types.ts +2 -3
  302. package/src/tasks/presets/sales.ts +7 -7
  303. package/src/tasks/task-gantt.svelte +28 -8
  304. package/src/tasks/task-list.svelte +41 -17
  305. package/src/tasks/time/duration.ts +40 -40
  306. package/src/tasks/time/time-chip.svelte +9 -2
  307. package/src/tasks/time/time-dot.svelte +1 -1
  308. package/src/tasks/time/time-ring.svelte +11 -3
  309. package/src/tasks/time/time-tracker.svelte +57 -15
  310. package/src/tasks/time/time-types.ts +57 -57
  311. package/src/tasks/use-kanban-dnd.svelte.ts +2 -3
  312. package/src/thread/README.md +31 -0
  313. package/src/thread/index.ts +12 -0
  314. package/src/thread/logic.ts +61 -0
  315. package/src/thread/thread.svelte +432 -0
  316. package/src/thread/thread.test.ts +73 -0
  317. package/src/thread/types.ts +93 -0
  318. package/src/tokens/craft.css +17 -53
  319. package/src/tokens/index.css +3 -13
  320. package/src/tokens/palette.css +13 -11
  321. package/src/tokens/primitives.css +10 -23
  322. package/src/components/list-page/section-label.svelte +0 -39
  323. package/src/tasks/define-statuses.ts +0 -32
  324. package/src/tasks/inbox/inbox.svelte +0 -203
  325. package/src/tasks/record/demo-data.ts +0 -286
  326. package/src/tasks/record/field-section.svelte +0 -53
  327. package/src/tasks/record/fields.ts +0 -142
  328. package/src/tasks/record/format.ts +0 -87
  329. package/src/tasks/record/kinds.ts +0 -42
  330. package/src/tasks/record/logic.ts +0 -324
  331. package/src/tasks/record/next-up.svelte +0 -70
  332. package/src/tasks/record/record-stats.svelte +0 -48
  333. package/src/tasks/record/record-timeline.svelte +0 -231
  334. package/src/tasks/record/record.test.ts +0 -350
  335. package/src/tasks/record/types.ts +0 -280
  336. package/src/tasks/record/where-we-are.svelte +0 -121
  337. package/src/tasks/side-panel.svelte +0 -193
  338. package/src/tasks/status-types.ts +0 -40
  339. /package/src/components/{list-page → toolbar}/toolbar-trigger.ts +0 -0
  340. /package/src/{tasks → panel}/side-panel-types.ts +0 -0
  341. /package/src/{tasks/record → record}/gutter.ts +0 -0
@@ -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 {