@heyramzi/ui 2.1.0 → 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 (312) hide show
  1. package/package.json +7 -8
  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/card/card-frame.svelte +43 -39
  31. package/src/canvas/card/resize-handles.svelte +89 -80
  32. package/src/canvas/chrome/chrome-zoom.svelte +101 -100
  33. package/src/canvas/chrome/lock-badge.svelte +8 -8
  34. package/src/canvas/chrome/side-handles.svelte +24 -19
  35. package/src/canvas/chrome/size-chip.svelte +30 -30
  36. package/src/canvas/chrome/snap-guides.svelte +20 -20
  37. package/src/canvas/components/icons/ToolIcon.svelte +8 -8
  38. package/src/canvas/components/shared/svg/PreviewSvg.svelte +19 -19
  39. package/src/canvas/components/shared/svg/ShapeOutline.svelte +71 -48
  40. package/src/canvas/components/shared/svg/StrokeNode.svelte +21 -27
  41. package/src/canvas/components/shared/svg/TextRule.svelte +25 -17
  42. package/src/canvas/edges/bend-handle.svelte +46 -46
  43. package/src/canvas/edges/edge-gradient.svelte +10 -10
  44. package/src/canvas/edges/gradient-edge.svelte +45 -29
  45. package/src/canvas/editor/lock.test.ts +6 -1
  46. package/src/canvas/editor/style.ts +7 -1
  47. package/src/canvas/headless/svg-figure.test.ts +5 -1
  48. package/src/canvas/lod/lod-switch.svelte +111 -106
  49. package/src/canvas/pen/PenField.svelte +87 -81
  50. package/src/canvas/pen/PenOverlay.svelte +127 -127
  51. package/src/canvas/pen/ShapeNode.svelte +31 -32
  52. package/src/canvas/pen/TextNode.svelte +54 -53
  53. package/src/canvas/pen/host-field.test.svelte +7 -7
  54. package/src/canvas/pen/shapes.ts +7 -1
  55. package/src/canvas/screen/GlyphIcon.svelte +22 -17
  56. package/src/canvas/screen/PartView.svelte +138 -72
  57. package/src/canvas/screen/ScreenBody.svelte +81 -60
  58. package/src/canvas/screen/ScreenCard.svelte +20 -20
  59. package/src/canvas/ui/CanvasToolbar.svelte +173 -85
  60. package/src/canvas/ui/FrameLayer.svelte +59 -59
  61. package/src/canvas/ui/PenLayer.svelte +39 -30
  62. package/src/canvas/ui/StylePanel.svelte +119 -102
  63. package/src/canvas/ui/canvas-grid.svelte +137 -129
  64. package/src/charts/animated-value.svelte +1 -1
  65. package/src/charts/area-chart.css +2 -5
  66. package/src/charts/area-chart.svelte +1 -1
  67. package/src/charts/bar-chart.css +2 -3
  68. package/src/charts/bar-chart.svelte +1 -1
  69. package/src/charts/charts.css +5 -17
  70. package/src/charts/crosshair.svelte +1 -1
  71. package/src/charts/dashboard/dashboard-empty.svelte +25 -25
  72. package/src/charts/dashboard/dashboard-section.svelte +24 -24
  73. package/src/charts/dashboard/data-panel.svelte +50 -50
  74. package/src/charts/dashboard/data-sort-head.svelte +56 -56
  75. package/src/charts/dashboard/data-table.svelte +138 -138
  76. package/src/charts/dashboard/panel-grid.svelte +30 -30
  77. package/src/charts/dashboard/period-filter.svelte +25 -25
  78. package/src/charts/dashboard/rating-chip.svelte +19 -19
  79. package/src/charts/dashboard/stat-band.svelte +32 -32
  80. package/src/charts/dashboard/stat-tile.svelte +106 -99
  81. package/src/charts/donut-chart.svelte +1 -1
  82. package/src/charts/edge-fade.svelte +1 -1
  83. package/src/charts/figure.svelte +1 -1
  84. package/src/charts/gauge-chart.css +4 -7
  85. package/src/charts/gauge-chart.svelte +1 -1
  86. package/src/charts/hover-focus.svelte +1 -1
  87. package/src/charts/legend.css +1 -3
  88. package/src/charts/legend.svelte +1 -1
  89. package/src/charts/line-chart.svelte +1 -1
  90. package/src/charts/number-chart.css +6 -9
  91. package/src/charts/number-chart.svelte +1 -1
  92. package/src/charts/reference-line.svelte +19 -19
  93. package/src/charts/reveal.svelte +1 -1
  94. package/src/charts/ring-chart.css +2 -4
  95. package/src/charts/ring-chart.svelte +1 -1
  96. package/src/charts/sparkline.svelte +5 -2
  97. package/src/charts/stat-card.svelte +1 -1
  98. package/src/charts/time-series-chart.css +2 -5
  99. package/src/charts/trend-pill.css +2 -4
  100. package/src/charts/trend-pill.svelte +1 -1
  101. package/src/components/copy/copy-button.svelte +93 -98
  102. package/src/components/copy/copy-button.test.ts +10 -2
  103. package/src/components/data-table/infinite-scroll.svelte +42 -42
  104. package/src/components/data-table/pagination.svelte +34 -22
  105. package/src/components/data-table/row-arrow.svelte +19 -16
  106. package/src/components/data-table/row-context-menu.svelte +52 -52
  107. package/src/components/data-table/sortable-head.svelte +25 -25
  108. package/src/components/data-table/table-skeleton.svelte +41 -36
  109. package/src/components/docs/doc-page.svelte +18 -9
  110. package/src/components/docs/on-this-page.svelte +16 -3
  111. package/src/components/docs/scroll-spy.svelte.ts +2 -3
  112. package/src/components/field-row/field-row.svelte +10 -8
  113. package/src/components/hold-to-confirm/hold-to-confirm.svelte +5 -1
  114. package/src/components/icon/icon.svelte +14 -14
  115. package/src/components/language-flag/language-flag.svelte +3 -1
  116. package/src/components/list-page/detail-empty.svelte +34 -34
  117. package/src/components/list-page/detail-header.svelte +55 -47
  118. package/src/components/list-page/detail-panel.svelte +51 -59
  119. package/src/components/list-page/detail-row.svelte +19 -19
  120. package/src/components/list-page/detail-stack.svelte +21 -21
  121. package/src/components/list-page/empty-state.svelte +30 -30
  122. package/src/components/list-page/filter-item.svelte +20 -20
  123. package/src/components/list-page/filter-popover.svelte +60 -55
  124. package/src/components/list-page/grouped-list.svelte +4 -2
  125. package/src/components/list-page/index.ts +5 -1
  126. package/src/components/list-page/list-page-shell.svelte +2 -5
  127. package/src/components/list-page/master-detail.svelte +87 -88
  128. package/src/components/list-page/notice-bar.svelte +38 -38
  129. package/src/components/list-page/prev-next.svelte +63 -63
  130. package/src/components/list-page/rail-body.svelte +18 -18
  131. package/src/components/list-page/rail-empty.svelte +23 -30
  132. package/src/components/list-page/rail-head.svelte +24 -24
  133. package/src/components/list-page/rail-row.svelte +77 -73
  134. package/src/components/list-page/section-host.ts +2 -3
  135. package/src/components/locale-switch/locale-switch.svelte +41 -41
  136. package/src/components/mark/mark.svelte +34 -38
  137. package/src/components/open-in/index.ts +1 -5
  138. package/src/components/open-in/open-in.svelte +40 -40
  139. package/src/components/picker/mark-picker.svelte +49 -49
  140. package/src/components/picker/picker.svelte +5 -3
  141. package/src/components/section-header/section-header.test.ts +7 -1
  142. package/src/components/section-header/section-label.svelte +20 -20
  143. package/src/components/segmented/animated-tabs.svelte +31 -2
  144. package/src/components/segmented/collapse.test.ts +2 -1
  145. package/src/components/segmented/index.ts +2 -0
  146. package/src/components/segmented/segment.svelte +52 -49
  147. package/src/components/segmented/segmented-filter.svelte +79 -80
  148. package/src/components/segmented/segmented-nav.svelte +34 -40
  149. package/src/components/segmented/segmented-overflow.svelte +53 -50
  150. package/src/components/segmented/segmented-track.svelte +62 -46
  151. package/src/components/segmented/segmented.ts +2 -3
  152. package/src/components/star-rating-input/star-rating-input.svelte +103 -103
  153. package/src/components/svg/svg.svelte +4 -4
  154. package/src/components/table-view/table-view.svelte +17 -2
  155. package/src/components/table-view/table-view.test.ts +7 -1
  156. package/src/components/table-view/types.ts +11 -11
  157. package/src/components/tags-input/index.ts +13 -0
  158. package/src/components/tags-input/tags-input.svelte +305 -0
  159. package/src/components/tags-input/tags.test.ts +64 -0
  160. package/src/components/tags-input/tags.ts +142 -0
  161. package/src/components/theme/index.ts +2 -4
  162. package/src/components/theme/theme-provider.svelte +1 -3
  163. package/src/components/toolbar/toolbar-search.svelte +25 -25
  164. package/src/components/toolbar/toolbar.svelte +11 -7
  165. package/src/components/toolbar/toolbar.test.ts +10 -1
  166. package/src/design/index.css +21 -78
  167. package/src/design/polish.css +26 -35
  168. package/src/editor/editor-toolbar.svelte +104 -99
  169. package/src/editor/editor.svelte +296 -266
  170. package/src/editor/image-toolbar.svelte +20 -22
  171. package/src/editor/index.ts +1 -4
  172. package/src/editor/inline-editor.svelte +280 -238
  173. package/src/editor/markdown.ts +15 -15
  174. package/src/editor/renderer.svelte +71 -71
  175. package/src/editor/slash-menu.svelte +24 -27
  176. package/src/effects/animated-beam/diagrams/BeamSvg.svelte +1 -1
  177. package/src/effects/image-comparison/ImageComparison.svelte +11 -1
  178. package/src/effects/interactive-hover-button/InteractiveHoverButton.svelte +59 -46
  179. package/src/effects/magic-card/MagicCard.css +14 -7
  180. package/src/effects/marquee/Marquee.svelte +3 -5
  181. package/src/effects/reveal/observe.ts +0 -3
  182. package/src/effects/spotlight/Spotlight.svelte +1 -1
  183. package/src/effects/theme-toggle/ThemeToggle.css +25 -22
  184. package/src/effects/theme-toggle/ThemeToggle.svelte +6 -7
  185. package/src/gallery.test.ts +14 -4
  186. package/src/inbox/README.md +1 -3
  187. package/src/inbox/inbox.test.ts +181 -138
  188. package/src/inbox/index.ts +20 -20
  189. package/src/inbox/logic.ts +114 -111
  190. package/src/inbox/thread-inbox.svelte +227 -151
  191. package/src/inbox/types.ts +130 -108
  192. package/src/lib/theme-transition.ts +2 -4
  193. package/src/lib/tone.ts +2 -4
  194. package/src/lib/utils.ts +2 -2
  195. package/src/modules.test.ts +79 -69
  196. package/src/onboarding/define-tours.test.ts +55 -40
  197. package/src/onboarding/define-tours.ts +23 -23
  198. package/src/onboarding/demo.svelte +24 -5
  199. package/src/onboarding/index.ts +1 -8
  200. package/src/onboarding/onboarding-provider.svelte +19 -7
  201. package/src/onboarding/onboarding.svelte.ts +16 -16
  202. package/src/onboarding/pick.test.ts +61 -52
  203. package/src/onboarding/pick.ts +16 -13
  204. package/src/onboarding/stores.test.ts +80 -80
  205. package/src/onboarding/stores.ts +41 -39
  206. package/src/onboarding/tour-card.svelte +30 -5
  207. package/src/onboarding/tour-runner.svelte +12 -3
  208. package/src/onboarding/types.ts +20 -12
  209. package/src/onboarding/wait.ts +22 -22
  210. package/src/panel/side-panel.svelte +94 -93
  211. package/src/record/demo-data.ts +291 -225
  212. package/src/record/field-section.svelte +38 -34
  213. package/src/record/fields.ts +148 -96
  214. package/src/record/format.ts +63 -56
  215. package/src/record/index.ts +49 -35
  216. package/src/record/info-list.svelte +145 -114
  217. package/src/record/kinds.ts +31 -31
  218. package/src/record/logic.ts +216 -214
  219. package/src/record/next-up.svelte +45 -34
  220. package/src/record/project-list.svelte +56 -50
  221. package/src/record/record-demo.svelte +130 -110
  222. package/src/record/record-header.svelte +37 -28
  223. package/src/record/record-shell.svelte +54 -54
  224. package/src/record/record-stats.svelte +25 -25
  225. package/src/record/record-timeline.svelte +134 -114
  226. package/src/record/record.test.ts +430 -309
  227. package/src/record/steps-rail.svelte +225 -216
  228. package/src/record/types.ts +234 -215
  229. package/src/record/where-we-are.svelte +62 -58
  230. package/src/shadcn/button/button.svelte +1 -1
  231. package/src/shadcn/card/card-title.svelte +1 -5
  232. package/src/shadcn/hooks/use-highlighted-code.svelte.ts +2 -13
  233. package/src/shadcn/sheet/sheet-content.svelte +2 -4
  234. package/src/shadcn/table/table-cell.svelte +2 -3
  235. package/src/shadcn/utilities.css +3 -8
  236. package/src/slides/Controls.svelte +150 -124
  237. package/src/slides/Deck.svelte +243 -238
  238. package/src/slides/Fragment.svelte +64 -57
  239. package/src/slides/Overview.svelte +106 -101
  240. package/src/slides/Presenter.svelte +300 -266
  241. package/src/slides/Slide.svelte +185 -170
  242. package/src/slides/SlideList.svelte +16 -12
  243. package/src/slides/ambient.d.ts +1 -1
  244. package/src/slides/bridges.ts +42 -23
  245. package/src/slides/deck-controller.svelte.ts +47 -35
  246. package/src/slides/demo-slides.svelte +330 -306
  247. package/src/slides/demo.svelte +164 -160
  248. package/src/slides/export/capture.ts +20 -16
  249. package/src/slides/export/ffmpeg.ts +43 -38
  250. package/src/slides/export/index.ts +5 -5
  251. package/src/slides/export/launch.ts +3 -1
  252. package/src/slides/export/pdf.ts +30 -27
  253. package/src/slides/export/types.ts +10 -8
  254. package/src/slides/index.ts +11 -11
  255. package/src/slides/labels.ts +22 -19
  256. package/src/slides/layout.ts +9 -2
  257. package/src/slides/model.ts +4 -4
  258. package/src/slides/navigation.ts +43 -31
  259. package/src/slides/theme.css +5 -7
  260. package/src/slides/types.ts +9 -10
  261. package/src/status/define-statuses.ts +17 -24
  262. package/src/status/index.ts +5 -5
  263. package/src/status/status-badge.svelte +3 -1
  264. package/src/status/status-dot.svelte +1 -3
  265. package/src/status/status-icon.svelte +1 -1
  266. package/src/status/status-types.ts +21 -21
  267. package/src/tasks/board-order.ts +2 -4
  268. package/src/tasks/demo.svelte +189 -31
  269. package/src/tasks/detail/detail-demo.svelte +73 -13
  270. package/src/tasks/detail/detail-types.ts +45 -45
  271. package/src/tasks/detail/task-detail.svelte +40 -10
  272. package/src/tasks/detail/task-property.svelte +8 -2
  273. package/src/tasks/detail/task-thread.svelte +53 -12
  274. package/src/tasks/gantt/gantt-bar.svelte +17 -6
  275. package/src/tasks/gantt/gantt-grid.svelte +40 -9
  276. package/src/tasks/gantt/gantt-group-row.svelte +8 -2
  277. package/src/tasks/gantt/gantt-header.svelte +9 -3
  278. package/src/tasks/gantt/gantt-task-row.svelte +26 -6
  279. package/src/tasks/gantt/gantt.css +2 -1
  280. package/src/tasks/gantt/gantt.test.ts +3 -1
  281. package/src/tasks/gantt/logic.test.ts +110 -20
  282. package/src/tasks/gantt/logic.ts +42 -6
  283. package/src/tasks/inbox/inbox-card.svelte +13 -5
  284. package/src/tasks/inbox/inbox-draft.svelte +1 -1
  285. package/src/tasks/inbox/inbox-logic.ts +6 -40
  286. package/src/tasks/inbox/inbox-statuses.ts +23 -23
  287. package/src/tasks/inbox/inbox-trace.svelte +9 -3
  288. package/src/tasks/inbox/inbox-types.ts +54 -58
  289. package/src/tasks/inbox/index.ts +22 -21
  290. package/src/tasks/index.ts +49 -52
  291. package/src/tasks/kanban-board.svelte +16 -12
  292. package/src/tasks/kanban-types.ts +2 -3
  293. package/src/tasks/presets/sales.ts +6 -6
  294. package/src/tasks/task-gantt.svelte +28 -8
  295. package/src/tasks/task-list.svelte +41 -17
  296. package/src/tasks/time/duration.ts +40 -40
  297. package/src/tasks/time/time-chip.svelte +9 -2
  298. package/src/tasks/time/time-dot.svelte +1 -1
  299. package/src/tasks/time/time-ring.svelte +11 -3
  300. package/src/tasks/time/time-tracker.svelte +57 -15
  301. package/src/tasks/time/time-types.ts +57 -57
  302. package/src/tasks/use-kanban-dnd.svelte.ts +2 -3
  303. package/src/thread/index.ts +7 -7
  304. package/src/thread/logic.ts +36 -33
  305. package/src/thread/thread.svelte +82 -16
  306. package/src/thread/thread.test.ts +58 -47
  307. package/src/thread/types.ts +68 -68
  308. package/src/tokens/craft.css +17 -53
  309. package/src/tokens/index.css +3 -13
  310. package/src/tokens/palette.css +13 -11
  311. package/src/tokens/primitives.css +10 -23
  312. 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 {