@placeholderco/placeholder-ui 1.0.12 → 2.0.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 (276) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +134 -130
  3. package/dist/app.css +178 -0
  4. package/dist/bootstrap.min.css +3600 -0
  5. package/dist/cards/GitCard.svelte +115 -0
  6. package/dist/cards/GitCard.svelte.d.ts +22 -0
  7. package/dist/display/Avatar.svelte +29 -17
  8. package/dist/display/Avatar.svelte.d.ts +12 -2
  9. package/dist/display/LinkCollection.svelte +40 -18
  10. package/dist/display/LinkCollection.svelte.d.ts +9 -2
  11. package/dist/display/Paper.svelte +31 -19
  12. package/dist/display/Paper.svelte.d.ts +12 -3
  13. package/dist/display/alert/Alert.svelte +129 -0
  14. package/dist/display/alert/Alert.svelte.d.ts +15 -0
  15. package/dist/display/alert/AlertVariant.d.ts +1 -0
  16. package/dist/display/alert/AlertVariant.js +1 -0
  17. package/dist/display/filetree/FileTree.svelte +259 -0
  18. package/dist/display/filetree/FileTree.svelte.d.ts +76 -0
  19. package/dist/display/filetree/FileTreeItem.svelte +396 -0
  20. package/dist/display/filetree/FileTreeItem.svelte.d.ts +40 -0
  21. package/dist/display/filetree/FileTreeNode.d.ts +46 -0
  22. package/dist/display/filetree/FileTreeNode.js +115 -0
  23. package/dist/display/filetree/index.d.ts +5 -0
  24. package/dist/display/filetree/index.js +3 -0
  25. package/dist/editors/CodeMirror.svelte +306 -0
  26. package/dist/editors/CodeMirror.svelte.d.ts +31 -0
  27. package/dist/editors/tiptap/LinkActionMenu.svelte +307 -0
  28. package/dist/editors/tiptap/LinkActionMenu.svelte.d.ts +8 -0
  29. package/dist/editors/tiptap/TipTap.svelte +856 -0
  30. package/dist/editors/tiptap/TipTap.svelte.d.ts +54 -0
  31. package/dist/editors/tiptap/TipTapBubble.svelte +790 -0
  32. package/dist/editors/tiptap/TipTapBubble.svelte.d.ts +44 -0
  33. package/dist/editors/tiptap/collaboration.d.ts +55 -0
  34. package/dist/editors/tiptap/collaboration.js +1 -0
  35. package/dist/editors/tiptap/extensions/Alert.d.ts +18 -0
  36. package/dist/editors/tiptap/extensions/Alert.js +72 -0
  37. package/dist/editors/tiptap/extensions/CodeBlockEditor.d.ts +21 -0
  38. package/dist/editors/tiptap/extensions/CodeBlockEditor.js +326 -0
  39. package/dist/editors/tiptap/extensions/Div.d.ts +19 -0
  40. package/dist/editors/tiptap/extensions/Div.js +89 -0
  41. package/dist/editors/tiptap/extensions/Span.d.ts +5 -0
  42. package/dist/editors/tiptap/extensions/Span.js +56 -0
  43. package/dist/editors/tiptap/extensions/codeBlockEditor.css +236 -0
  44. package/dist/editors/tiptap/extensions/codeBlockLanguages.d.ts +17 -0
  45. package/dist/editors/tiptap/extensions/codeBlockLanguages.js +124 -0
  46. package/dist/editors/tiptap/extensions/index.d.ts +7 -0
  47. package/dist/editors/tiptap/extensions/index.js +5 -0
  48. package/dist/editors/tiptap/toolbar/AlertButton.svelte +261 -0
  49. package/dist/editors/tiptap/toolbar/AlertButton.svelte.d.ts +7 -0
  50. package/dist/editors/tiptap/toolbar/AlignCenterButton.svelte +18 -0
  51. package/dist/editors/tiptap/toolbar/AlignCenterButton.svelte.d.ts +7 -0
  52. package/dist/editors/tiptap/toolbar/AlignJustifyButton.svelte +18 -0
  53. package/dist/editors/tiptap/toolbar/AlignJustifyButton.svelte.d.ts +7 -0
  54. package/dist/editors/tiptap/toolbar/AlignLeftButton.svelte +18 -0
  55. package/dist/editors/tiptap/toolbar/AlignLeftButton.svelte.d.ts +7 -0
  56. package/dist/editors/tiptap/toolbar/AlignRightButton.svelte +18 -0
  57. package/dist/editors/tiptap/toolbar/AlignRightButton.svelte.d.ts +7 -0
  58. package/dist/editors/tiptap/toolbar/BoldButton.svelte +18 -0
  59. package/dist/editors/tiptap/toolbar/BoldButton.svelte.d.ts +7 -0
  60. package/dist/editors/tiptap/toolbar/BulletListButton.svelte +18 -0
  61. package/dist/editors/tiptap/toolbar/BulletListButton.svelte.d.ts +7 -0
  62. package/dist/editors/tiptap/toolbar/CodeBlockButton.svelte +18 -0
  63. package/dist/editors/tiptap/toolbar/CodeBlockButton.svelte.d.ts +7 -0
  64. package/dist/editors/tiptap/toolbar/CodeButton.svelte +18 -0
  65. package/dist/editors/tiptap/toolbar/CodeButton.svelte.d.ts +7 -0
  66. package/dist/editors/tiptap/toolbar/ColorButton.svelte +299 -0
  67. package/dist/editors/tiptap/toolbar/ColorButton.svelte.d.ts +20 -0
  68. package/dist/editors/tiptap/toolbar/DocumentButton.svelte +58 -0
  69. package/dist/editors/tiptap/toolbar/DocumentButton.svelte.d.ts +9 -0
  70. package/dist/editors/tiptap/toolbar/HeadingButton.svelte +30 -0
  71. package/dist/editors/tiptap/toolbar/HeadingButton.svelte.d.ts +8 -0
  72. package/dist/editors/tiptap/toolbar/HtmlButton.svelte +68 -0
  73. package/dist/editors/tiptap/toolbar/HtmlButton.svelte.d.ts +7 -0
  74. package/dist/editors/tiptap/toolbar/ImageButton.svelte +54 -0
  75. package/dist/editors/tiptap/toolbar/ImageButton.svelte.d.ts +8 -0
  76. package/dist/editors/tiptap/toolbar/ItalicButton.svelte +18 -0
  77. package/dist/editors/tiptap/toolbar/ItalicButton.svelte.d.ts +7 -0
  78. package/dist/editors/tiptap/toolbar/LinkButton.svelte +257 -0
  79. package/dist/editors/tiptap/toolbar/LinkButton.svelte.d.ts +7 -0
  80. package/dist/editors/tiptap/toolbar/OrderedListButton.svelte +18 -0
  81. package/dist/editors/tiptap/toolbar/OrderedListButton.svelte.d.ts +7 -0
  82. package/dist/editors/tiptap/toolbar/RedoButton.svelte +18 -0
  83. package/dist/editors/tiptap/toolbar/RedoButton.svelte.d.ts +7 -0
  84. package/dist/editors/tiptap/toolbar/StrikethroughButton.svelte +18 -0
  85. package/dist/editors/tiptap/toolbar/StrikethroughButton.svelte.d.ts +7 -0
  86. package/dist/editors/tiptap/toolbar/SubscriptButton.svelte +18 -0
  87. package/dist/editors/tiptap/toolbar/SubscriptButton.svelte.d.ts +7 -0
  88. package/dist/editors/tiptap/toolbar/SuperscriptButton.svelte +18 -0
  89. package/dist/editors/tiptap/toolbar/SuperscriptButton.svelte.d.ts +7 -0
  90. package/dist/editors/tiptap/toolbar/TableButton.svelte +258 -0
  91. package/dist/editors/tiptap/toolbar/TableButton.svelte.d.ts +7 -0
  92. package/dist/editors/tiptap/toolbar/ToolbarButton.svelte +84 -0
  93. package/dist/editors/tiptap/toolbar/ToolbarButton.svelte.d.ts +10 -0
  94. package/dist/editors/tiptap/toolbar/UnderlineButton.svelte +18 -0
  95. package/dist/editors/tiptap/toolbar/UnderlineButton.svelte.d.ts +7 -0
  96. package/dist/editors/tiptap/toolbar/UndoButton.svelte +18 -0
  97. package/dist/editors/tiptap/toolbar/UndoButton.svelte.d.ts +7 -0
  98. package/dist/form/Autocomplete.svelte +86 -135
  99. package/dist/form/Autocomplete.svelte.d.ts +6 -8
  100. package/dist/form/AutocompleteMulti.svelte +385 -227
  101. package/dist/form/AutocompleteMulti.svelte.d.ts +59 -10
  102. package/dist/form/Checkbox.svelte +59 -54
  103. package/dist/form/Checkbox.svelte.d.ts +14 -5
  104. package/dist/form/Chips.svelte +29 -13
  105. package/dist/form/Chips.svelte.d.ts +16 -2
  106. package/dist/form/ComboBox.svelte +30 -20
  107. package/dist/form/ComboBox.svelte.d.ts +1 -1
  108. package/dist/form/ComboBoxItemBuilder.svelte +161 -188
  109. package/dist/form/ComboBoxItemBuilder.svelte.d.ts +19 -2
  110. package/dist/form/ComboBoxMulti.svelte +38 -24
  111. package/dist/form/ComboBoxMulti.svelte.d.ts +1 -1
  112. package/dist/form/CronBuilder.svelte +84 -67
  113. package/dist/form/CronBuilder.svelte.d.ts +16 -3
  114. package/dist/form/DatePicker.svelte +148 -195
  115. package/dist/form/DateRangePicker.svelte +101 -98
  116. package/dist/form/DateTimePicker.svelte +167 -208
  117. package/dist/form/DateTimePicker.svelte.d.ts +10 -2
  118. package/dist/form/FormGroup.svelte +33 -10
  119. package/dist/form/FormGroup.svelte.d.ts +12 -2
  120. package/dist/form/Number.svelte +70 -47
  121. package/dist/form/Number.svelte.d.ts +36 -6
  122. package/dist/form/Radio.svelte +18 -18
  123. package/dist/form/RadioGroup.svelte +23 -12
  124. package/dist/form/RadioGroup.svelte.d.ts +13 -2
  125. package/dist/form/SegmentedControl.svelte +76 -56
  126. package/dist/form/SegmentedControl.svelte.d.ts +9 -4
  127. package/dist/form/Select.svelte +150 -233
  128. package/dist/form/Select.svelte.d.ts +29 -5
  129. package/dist/form/SelectMulti.svelte +163 -277
  130. package/dist/form/SelectMulti.svelte.d.ts +36 -7
  131. package/dist/form/Slider.svelte +183 -269
  132. package/dist/form/Slider.svelte.d.ts +32 -17
  133. package/dist/form/StringArrayBuilder.svelte +249 -0
  134. package/dist/form/StringArrayBuilder.svelte.d.ts +15 -0
  135. package/dist/form/Switch.svelte +164 -106
  136. package/dist/form/Switch.svelte.d.ts +22 -3
  137. package/dist/form/TextArea.svelte +49 -29
  138. package/dist/form/TextArea.svelte.d.ts +23 -3
  139. package/dist/form/Textbox.svelte +69 -68
  140. package/dist/form/Textbox.svelte.d.ts +32 -4
  141. package/dist/form/TimePicker.svelte +42 -47
  142. package/dist/form/TimePicker.svelte.d.ts +12 -2
  143. package/dist/form/comboBoxCore.svelte.d.ts +99 -0
  144. package/dist/form/comboBoxCore.svelte.js +243 -0
  145. package/dist/icon/Icon.svelte +8 -3
  146. package/dist/icon/Icon.svelte.d.ts +8 -3
  147. package/dist/icon/file-type-bmp.svg +1 -0
  148. package/dist/icon/file-type-csv.svg +1 -0
  149. package/dist/icon/file-type-doc.svg +1 -0
  150. package/dist/icon/file-type-docx.svg +1 -0
  151. package/dist/icon/file-type-jpg.svg +1 -0
  152. package/dist/icon/file-type-pdf.svg +1 -0
  153. package/dist/icon/file-type-png.svg +1 -0
  154. package/dist/icon/file-type-ppt.svg +1 -0
  155. package/dist/icon/file-type-svg.svg +1 -0
  156. package/dist/icon/file-type-txt.svg +1 -0
  157. package/dist/icon/file-type-xls.svg +1 -0
  158. package/dist/icon/file-type-zip.svg +1 -0
  159. package/dist/icon/index.d.ts +166 -151
  160. package/dist/icon/index.js +166 -152
  161. package/dist/icon/logo-placeholder.svg +6 -0
  162. package/dist/icon/source-code.svg +1 -0
  163. package/dist/icon/sun-moon.svg +1 -0
  164. package/dist/icon/table.svg +6 -0
  165. package/dist/index.d.ts +87 -26
  166. package/dist/index.js +25 -28
  167. package/dist/layout/AppShell.svelte +12 -162
  168. package/dist/layout/AppShell.svelte.d.ts +6 -27
  169. package/dist/layout/CustomNavbar.svelte +9 -7
  170. package/dist/layout/CustomNavbar.svelte.d.ts +4 -2
  171. package/dist/layout/Navbar.svelte +161 -129
  172. package/dist/layout/Navbar.svelte.d.ts +29 -15
  173. package/dist/layout/NavbarItemDisplay.svelte +2 -2
  174. package/dist/layout/NavbarItemView.svelte +182 -0
  175. package/dist/layout/NavbarItemView.svelte.d.ts +10 -0
  176. package/dist/layout/Sidenav.svelte +280 -341
  177. package/dist/layout/Sidenav.svelte.d.ts +36 -4
  178. package/dist/models/ComboBoxItem.d.ts +2 -0
  179. package/dist/models/Employee.d.ts +53 -0
  180. package/dist/models/Employee.js +23 -0
  181. package/dist/models/Hyperlink.d.ts +3 -1
  182. package/dist/models/LinkCollectionItem.d.ts +3 -1
  183. package/dist/models/NavbarItem.d.ts +6 -2
  184. package/dist/theme.svelte.d.ts +0 -1
  185. package/dist/theme.svelte.js +46 -58
  186. package/dist/ui/Accordion.svelte +81 -242
  187. package/dist/ui/Accordion.svelte.d.ts +37 -14
  188. package/dist/ui/AccordionItem.svelte +303 -0
  189. package/dist/ui/AccordionItem.svelte.d.ts +21 -0
  190. package/dist/ui/ActionIcon.svelte +25 -21
  191. package/dist/ui/ActionIcon.svelte.d.ts +15 -2
  192. package/dist/ui/Badge.svelte +163 -171
  193. package/dist/ui/Badge.svelte.d.ts +13 -5
  194. package/dist/ui/Button.svelte +178 -205
  195. package/dist/ui/Button.svelte.d.ts +19 -2
  196. package/dist/ui/ButtonVariant.d.ts +15 -1
  197. package/dist/ui/ContextMenu.svelte +146 -0
  198. package/dist/ui/ContextMenu.svelte.d.ts +37 -0
  199. package/dist/ui/ContextMenuList.svelte +188 -0
  200. package/dist/ui/ContextMenuList.svelte.d.ts +12 -0
  201. package/dist/ui/Dialog.svelte +183 -111
  202. package/dist/ui/Dialog.svelte.d.ts +21 -4
  203. package/dist/ui/Drawer.svelte +237 -184
  204. package/dist/ui/Drawer.svelte.d.ts +20 -2
  205. package/dist/ui/Dropdown.svelte +24 -14
  206. package/dist/ui/Dropdown.svelte.d.ts +11 -2
  207. package/dist/ui/Dropzone.svelte +159 -57
  208. package/dist/ui/Dropzone.svelte.d.ts +29 -2
  209. package/dist/ui/Link.svelte +12 -4
  210. package/dist/ui/Link.svelte.d.ts +8 -2
  211. package/dist/ui/Loader.svelte +23 -18
  212. package/dist/ui/Loader.svelte.d.ts +5 -2
  213. package/dist/ui/Logo.svelte +37 -0
  214. package/dist/ui/Logo.svelte.d.ts +16 -0
  215. package/dist/ui/MultiSortable.svelte +287 -0
  216. package/dist/ui/MultiSortable.svelte.d.ts +50 -0
  217. package/dist/ui/Pagination.svelte +15 -9
  218. package/dist/ui/Pagination.svelte.d.ts +6 -2
  219. package/dist/ui/Popover.svelte +43 -65
  220. package/dist/ui/Popover.svelte.d.ts +14 -5
  221. package/dist/ui/ProgressBar.svelte +28 -30
  222. package/dist/ui/ProgressBarVariant.d.ts +7 -1
  223. package/dist/ui/SegmentedControl.svelte +0 -0
  224. package/dist/ui/SegmentedControl.svelte.d.ts +26 -0
  225. package/dist/ui/Sortable.svelte +163 -0
  226. package/dist/ui/Sortable.svelte.d.ts +47 -0
  227. package/dist/ui/Table.svelte +383 -80
  228. package/dist/ui/Table.svelte.d.ts +43 -2
  229. package/dist/ui/Tabs.svelte +22 -18
  230. package/dist/ui/Tabs.svelte.d.ts +6 -2
  231. package/dist/ui/ThemeSwitcher.svelte +26 -7
  232. package/dist/ui/ThemeSwitcher.svelte.d.ts +4 -2
  233. package/dist/ui/Tooltip.svelte +312 -180
  234. package/dist/ui/Tooltip.svelte.d.ts +4 -3
  235. package/dist/uiTheme.svelte.d.ts +86 -0
  236. package/dist/uiTheme.svelte.js +214 -0
  237. package/dist/util/DateFunctions.js +1 -1
  238. package/dist/util/DragDrop.d.ts +50 -0
  239. package/dist/util/DragDrop.js +154 -0
  240. package/dist/util/NavigateTo.js +2 -2
  241. package/dist/util/dayjs.d.ts +4 -0
  242. package/dist/util/dayjs.js +15 -0
  243. package/package.json +66 -26
  244. package/dist/display/Alert.svelte +0 -179
  245. package/dist/display/Alert.svelte.d.ts +0 -13
  246. package/dist/form/FileInput.svelte +0 -235
  247. package/dist/form/FileInput.svelte.d.ts +0 -23
  248. package/dist/form/PasswordInput.svelte +0 -253
  249. package/dist/form/PasswordInput.svelte.d.ts +0 -34
  250. package/dist/form/Rating.svelte +0 -235
  251. package/dist/form/Rating.svelte.d.ts +0 -21
  252. package/dist/styles/components.css +0 -199
  253. package/dist/styles/dark.css +0 -146
  254. package/dist/styles/index.css +0 -116
  255. package/dist/styles/reset.css +0 -110
  256. package/dist/styles/semantic.css +0 -86
  257. package/dist/styles/tokens.css +0 -203
  258. package/dist/styles/utilities.css +0 -523
  259. package/dist/ui/Breadcrumbs.svelte +0 -131
  260. package/dist/ui/Breadcrumbs.svelte.d.ts +0 -16
  261. package/dist/ui/LoadingOverlay.svelte +0 -53
  262. package/dist/ui/LoadingOverlay.svelte.d.ts +0 -11
  263. package/dist/ui/Progress.svelte +0 -191
  264. package/dist/ui/Progress.svelte.d.ts +0 -15
  265. package/dist/ui/RingProgress.svelte +0 -141
  266. package/dist/ui/RingProgress.svelte.d.ts +0 -18
  267. package/dist/ui/Skeleton.svelte +0 -85
  268. package/dist/ui/Skeleton.svelte.d.ts +0 -12
  269. package/dist/ui/Stepper.svelte +0 -355
  270. package/dist/ui/Stepper.svelte.d.ts +0 -24
  271. package/dist/ui/Timeline.svelte +0 -225
  272. package/dist/ui/Timeline.svelte.d.ts +0 -23
  273. /package/dist/{actions → util}/ClickOutside.d.ts +0 -0
  274. /package/dist/{actions → util}/ClickOutside.js +0 -0
  275. /package/dist/{transitions → util}/Transitions.d.ts +0 -0
  276. /package/dist/{transitions → util}/Transitions.js +0 -0
@@ -0,0 +1,790 @@
1
+ <script lang="ts">
2
+ import { onMount, onDestroy } from 'svelte';
3
+ import { Editor, Extension, type AnyExtension } from '@tiptap/core';
4
+ import { StarterKit } from '@tiptap/starter-kit';
5
+ import { BubbleMenu } from '@tiptap/extension-bubble-menu';
6
+ import Underline from '@tiptap/extension-underline';
7
+ import TextAlign from '@tiptap/extension-text-align';
8
+ import Link from '@tiptap/extension-link';
9
+ import Subscript from '@tiptap/extension-subscript';
10
+ import Superscript from '@tiptap/extension-superscript';
11
+ import Image from '@tiptap/extension-image';
12
+ import { TableKit } from '@tiptap/extension-table';
13
+ import Color from '@tiptap/extension-color';
14
+ import Placeholder from '@tiptap/extension-placeholder';
15
+ import { TextStyle } from '@tiptap/extension-text-style';
16
+ import Collaboration from '@tiptap/extension-collaboration';
17
+ import { CollaborationCaret } from '@tiptap/extension-collaboration-caret';
18
+ import { Alert, CodeBlockEditor, Div, Span, type CodeBlocksOptions } from './extensions/index.js';
19
+ import type { CollaborationConfig } from './collaboration.js';
20
+ import FormGroup from '../../form/FormGroup.svelte';
21
+ import LinkActionMenu from './LinkActionMenu.svelte';
22
+ import BoldButton from './toolbar/BoldButton.svelte';
23
+ import ItalicButton from './toolbar/ItalicButton.svelte';
24
+ import StrikethroughButton from './toolbar/StrikethroughButton.svelte';
25
+ import CodeButton from './toolbar/CodeButton.svelte';
26
+ import UnderlineButton from './toolbar/UnderlineButton.svelte';
27
+ import ColorButton from './toolbar/ColorButton.svelte';
28
+ import LinkButton from './toolbar/LinkButton.svelte';
29
+ import SuperscriptButton from './toolbar/SuperscriptButton.svelte';
30
+ import SubscriptButton from './toolbar/SubscriptButton.svelte';
31
+ import HeadingButton from './toolbar/HeadingButton.svelte';
32
+ import TableButton from './toolbar/TableButton.svelte';
33
+ import CodeBlockButton from './toolbar/CodeBlockButton.svelte';
34
+
35
+ export interface TipTapBubbleProps {
36
+ /** HTML content value (bindable). Ignored when collaboration is enabled. */
37
+ value?: string;
38
+ /** Label text displayed above the editor */
39
+ label?: string;
40
+ /** Mark field as required (shows asterisk) */
41
+ required?: boolean;
42
+ /** CSS classes for the FormGroup wrapper */
43
+ groupClass?: string;
44
+ /** Tooltip text on hover */
45
+ tooltipText?: string;
46
+ /** Position of the tooltip */
47
+ tooltipLocation?: 'top' | 'bottom' | 'left' | 'right';
48
+ /** Enable editing mode */
49
+ editable?: boolean;
50
+ /** Placeholder text when editor is empty */
51
+ placeholder?: string;
52
+ /** Additional CSS classes */
53
+ class?: string;
54
+ /** Async handler for image uploads, returns URL or null */
55
+ onImageUpload?: (file: File) => Promise<string | null>;
56
+ /** Callback when content changes */
57
+ onUpdate?: (html: string) => void;
58
+ /** Opt-in real-time collaboration config. When provided, content syncs via Yjs. */
59
+ collaboration?: CollaborationConfig;
60
+ /** Opt-in rich code blocks: CodeMirror-powered editing with syntax highlighting,
61
+ * autocompletion and a per-block language picker. Pass true for all languages
62
+ * or a config to narrow the offered languages and set the default. */
63
+ codeBlocks?: boolean | CodeBlocksOptions;
64
+ /** Additional TipTap extensions to register with the editor */
65
+ extensions?: AnyExtension[];
66
+ }
67
+
68
+ let {
69
+ value = $bindable(''),
70
+ label = '',
71
+ required = false,
72
+ groupClass = '',
73
+ tooltipText = undefined,
74
+ tooltipLocation = 'top',
75
+ editable = true,
76
+ placeholder = 'Start typing...',
77
+ class: className = '',
78
+ onImageUpload,
79
+ onUpdate,
80
+ collaboration,
81
+ codeBlocks = false,
82
+ extensions: customExtensions = []
83
+ }: TipTapBubbleProps = $props();
84
+
85
+ let element: HTMLElement | undefined = $state();
86
+ let bubbleMenuElement: HTMLDivElement | undefined = $state();
87
+ let linkBubbleMenuElement: HTMLDivElement | undefined = $state();
88
+ let editorState: { editor: Editor | null } = $state({ editor: null });
89
+ let isDragging = $state(false);
90
+
91
+ // Handle dropped files
92
+ async function handleDrop(event: DragEvent) {
93
+ if (!onImageUpload || !editorState.editor) return;
94
+
95
+ event.preventDefault();
96
+ isDragging = false;
97
+
98
+ const files = event.dataTransfer?.files;
99
+ if (!files?.length) return;
100
+
101
+ for (const file of files) {
102
+ if (file.type.startsWith('image/')) {
103
+ const imageUrl = await onImageUpload(file);
104
+ if (imageUrl) {
105
+ editorState.editor.chain().focus().setImage({ src: imageUrl }).run();
106
+ }
107
+ }
108
+ }
109
+ }
110
+
111
+ // Handle drag over
112
+ function handleDragOver(event: DragEvent) {
113
+ if (!onImageUpload) return;
114
+ event.preventDefault();
115
+ isDragging = true;
116
+ }
117
+
118
+ // Handle drag leave
119
+ function handleDragLeave(event: DragEvent) {
120
+ if (!onImageUpload) return;
121
+ const relatedTarget = event.relatedTarget as Node | null;
122
+ if (!element?.contains(relatedTarget)) {
123
+ isDragging = false;
124
+ }
125
+ }
126
+
127
+ // Handle paste events
128
+ async function handlePaste(event: ClipboardEvent) {
129
+ if (!onImageUpload || !editorState.editor) return;
130
+
131
+ const items = event.clipboardData?.items;
132
+ if (!items) return;
133
+
134
+ for (const item of items) {
135
+ if (item.type.startsWith('image/')) {
136
+ event.preventDefault();
137
+ const file = item.getAsFile();
138
+ if (file) {
139
+ const imageUrl = await onImageUpload(file);
140
+ if (imageUrl) {
141
+ editorState.editor.chain().focus().setImage({ src: imageUrl }).run();
142
+ }
143
+ }
144
+ break;
145
+ }
146
+ }
147
+ }
148
+
149
+ // Strip color declarations from inline styles in pasted HTML so pasted text
150
+ // inherits the editor's colours instead of carrying over the source colours.
151
+ function stripPastedColors(html: string): string {
152
+ if (typeof window === 'undefined') return html;
153
+
154
+ const container = document.createElement('div');
155
+ container.innerHTML = html;
156
+
157
+ container.querySelectorAll<HTMLElement>('[style]').forEach((el) => {
158
+ const cleaned = el.style.cssText
159
+ .split(';')
160
+ .map((decl) => decl.trim())
161
+ .filter((decl) => {
162
+ if (!decl) return false;
163
+ const prop = decl.slice(0, decl.indexOf(':')).trim().toLowerCase();
164
+ return prop !== 'color' && prop !== '-webkit-text-fill-color';
165
+ })
166
+ .join('; ');
167
+
168
+ if (cleaned) {
169
+ el.setAttribute('style', cleaned);
170
+ } else {
171
+ el.removeAttribute('style');
172
+ }
173
+ });
174
+
175
+ // Drop legacy <font color> attributes as well.
176
+ container.querySelectorAll('font[color]').forEach((el) => el.removeAttribute('color'));
177
+
178
+ return container.innerHTML;
179
+ }
180
+
181
+ function handleEditorClick(event: MouseEvent) {
182
+ const target = event.target as HTMLElement | null;
183
+ const link = target?.closest('a');
184
+
185
+ if (!link || !element?.contains(link)) {
186
+ return;
187
+ }
188
+
189
+ event.preventDefault();
190
+
191
+ const position = editorState.editor?.view.posAtCoords({
192
+ left: event.clientX,
193
+ top: event.clientY
194
+ });
195
+
196
+ if (position) {
197
+ editorState.editor?.chain().setTextSelection(position.pos).extendMarkRange('link').run();
198
+ }
199
+ }
200
+
201
+ // Custom Tab Handler Extension
202
+ const TabHandler = Extension.create({
203
+ name: 'tabHandler',
204
+
205
+ addKeyboardShortcuts() {
206
+ return {
207
+ Tab: ({ editor }) => {
208
+ // Rich code blocks handle Tab inside CodeMirror; never replace a
209
+ // selected code block with spaces
210
+ if (codeBlocks && editor.isActive('codeBlock')) {
211
+ return false;
212
+ }
213
+ if (editor.isActive('listItem')) {
214
+ return editor.commands.sinkListItem('listItem');
215
+ }
216
+ if (editor.isActive('table')) {
217
+ return editor.commands.goToNextCell();
218
+ }
219
+ return editor.commands.insertContent(' ');
220
+ },
221
+ 'Shift-Tab': ({ editor }) => {
222
+ if (editor.isActive('listItem')) {
223
+ return editor.commands.liftListItem('listItem');
224
+ }
225
+ if (editor.isActive('table')) {
226
+ return editor.commands.goToPreviousCell();
227
+ }
228
+ return false;
229
+ }
230
+ };
231
+ }
232
+ });
233
+
234
+ onMount(() => {
235
+ const extensions = [
236
+ Div,
237
+ Span,
238
+ StarterKit.configure({
239
+ undoRedo: collaboration ? false : undefined,
240
+ link: false,
241
+ underline: false,
242
+ codeBlock: codeBlocks ? false : undefined
243
+ }),
244
+ Underline,
245
+ TextStyle,
246
+ Color,
247
+ Placeholder.configure({
248
+ placeholder: placeholder
249
+ }),
250
+ TextAlign.configure({
251
+ types: ['heading', 'paragraph', 'div', 'span']
252
+ }),
253
+ Link.configure({
254
+ openOnClick: false,
255
+ enableClickSelection: true,
256
+ HTMLAttributes: {
257
+ class: 'editor-link'
258
+ }
259
+ }),
260
+ Subscript,
261
+ Superscript,
262
+ Image,
263
+ Alert,
264
+ TabHandler,
265
+ TableKit.configure({ table: { resizable: true } }),
266
+ ...customExtensions
267
+ ];
268
+
269
+ if (codeBlocks) {
270
+ const codeBlocksConfig = typeof codeBlocks === 'object' ? codeBlocks : {};
271
+ extensions.push(
272
+ CodeBlockEditor.configure({
273
+ languages: codeBlocksConfig.languages ?? null,
274
+ defaultLanguage: codeBlocksConfig.defaultLanguage ?? 'javascript'
275
+ })
276
+ );
277
+ }
278
+
279
+ if (bubbleMenuElement && editable) {
280
+ extensions.push(
281
+ BubbleMenu.configure({
282
+ pluginKey: 'formatBubbleMenu',
283
+ element: bubbleMenuElement,
284
+ shouldShow: ({ editor, state }) => {
285
+ const { from, to } = state.selection;
286
+ return from !== to && !editor.isActive('image') && !editor.isActive('link');
287
+ }
288
+ })
289
+ );
290
+ }
291
+
292
+ if (linkBubbleMenuElement) {
293
+ extensions.push(
294
+ BubbleMenu.configure({
295
+ pluginKey: 'linkActionBubbleMenu',
296
+ element: linkBubbleMenuElement,
297
+ shouldShow: ({ editor }) => Boolean(editor.getAttributes('link').href),
298
+ options: {
299
+ placement: 'top',
300
+ offset: 8
301
+ }
302
+ })
303
+ );
304
+ }
305
+
306
+ if (collaboration) {
307
+ extensions.push(
308
+ Collaboration.configure({ fragment: collaboration.fragment }),
309
+ CollaborationCaret.configure({
310
+ provider: collaboration.provider,
311
+ user: collaboration.user
312
+ })
313
+ );
314
+ }
315
+
316
+ editorState.editor = new Editor({
317
+ element: element,
318
+ extensions,
319
+ content: collaboration ? undefined : value,
320
+ editable: editable,
321
+ editorProps: {
322
+ transformPastedHTML: stripPastedColors
323
+ },
324
+ onTransaction: ({ editor }) => {
325
+ editorState = { editor };
326
+ },
327
+ onUpdate: ({ editor }) => {
328
+ const html = editor.getHTML();
329
+ value = html;
330
+ if (onUpdate) {
331
+ onUpdate(html);
332
+ }
333
+ }
334
+ });
335
+
336
+ if (element) {
337
+ element.addEventListener('click', handleEditorClick, true);
338
+ element.addEventListener('paste', handlePaste as unknown as EventListener);
339
+ }
340
+ });
341
+
342
+ onDestroy(() => {
343
+ element?.removeEventListener('click', handleEditorClick, true);
344
+ element?.removeEventListener('paste', handlePaste as unknown as EventListener);
345
+ editorState.editor?.destroy();
346
+ });
347
+
348
+ // Public API methods
349
+ export function getHTML(): string {
350
+ const editor = editorState.editor;
351
+ if (!editor || editor.isDestroyed) return '';
352
+ return editor.getHTML();
353
+ }
354
+
355
+ export function getJSON(): object {
356
+ const editor = editorState.editor;
357
+ if (!editor || editor.isDestroyed) return {};
358
+ return editor.getJSON();
359
+ }
360
+
361
+ export function setContent(html: string) {
362
+ const editor = editorState.editor;
363
+ if (!editor || editor.isDestroyed) return;
364
+ editor.commands.setContent(html);
365
+ }
366
+
367
+ export function clear() {
368
+ const editor = editorState.editor;
369
+ if (!editor || editor.isDestroyed) return;
370
+ editor.commands.clearContent();
371
+ }
372
+
373
+ export function focus() {
374
+ const editor = editorState.editor;
375
+ if (!editor || editor.isDestroyed) return;
376
+ editor.commands.focus();
377
+ }
378
+ </script>
379
+
380
+ <FormGroup {label} {required} class={groupClass} {tooltipLocation} {tooltipText}>
381
+ <!-- svelte-ignore a11y_no_static_element_interactions -->
382
+ <div
383
+ class="editor-container bubble-editor {className}"
384
+ class:dragging={isDragging}
385
+ ondragover={handleDragOver}
386
+ ondragleave={handleDragLeave}
387
+ ondrop={handleDrop}
388
+ >
389
+ {#if editable}
390
+ <div
391
+ bind:this={bubbleMenuElement}
392
+ class="bubble-menu"
393
+ style="visibility: hidden; opacity: 0; position: absolute;"
394
+ >
395
+ {#if editorState.editor}
396
+ <div class="bubble-menu-group">
397
+ <HeadingButton editor={editorState.editor} level={1} />
398
+ <HeadingButton editor={editorState.editor} level={2} />
399
+ <HeadingButton editor={editorState.editor} level={3} />
400
+ </div>
401
+
402
+ <div class="bubble-menu-divider"></div>
403
+
404
+ <div class="bubble-menu-group">
405
+ <BoldButton editor={editorState.editor} />
406
+ <ItalicButton editor={editorState.editor} />
407
+ <StrikethroughButton editor={editorState.editor} />
408
+ <UnderlineButton editor={editorState.editor} />
409
+ <CodeButton editor={editorState.editor} />
410
+ {#if codeBlocks}
411
+ <CodeBlockButton editor={editorState.editor} />
412
+ {/if}
413
+ <ColorButton editor={editorState.editor} />
414
+ </div>
415
+
416
+ <div class="bubble-menu-divider"></div>
417
+
418
+ <div class="bubble-menu-group">
419
+ <LinkButton editor={editorState.editor} />
420
+ <SuperscriptButton editor={editorState.editor} />
421
+ <SubscriptButton editor={editorState.editor} />
422
+ </div>
423
+
424
+ <div class="bubble-menu-divider"></div>
425
+
426
+ <div class="bubble-menu-group">
427
+ <TableButton editor={editorState.editor} />
428
+ </div>
429
+ {/if}
430
+ </div>
431
+ {/if}
432
+
433
+ <div
434
+ bind:this={linkBubbleMenuElement}
435
+ class="link-bubble-menu"
436
+ style="visibility: hidden; opacity: 0; position: absolute;"
437
+ >
438
+ {#if editorState.editor}
439
+ <LinkActionMenu editor={editorState.editor} {editable} />
440
+ {/if}
441
+ </div>
442
+
443
+ <div class="editor-content" bind:this={element}></div>
444
+ </div>
445
+ </FormGroup>
446
+
447
+ <style>
448
+ .editor-container {
449
+ position: relative;
450
+ border: 1px solid var(--border-color);
451
+ border-radius: 6px;
452
+ background: var(--paper-body-bg);
453
+ font-family: var(--ui-font-family);
454
+ transition: border-color 0.2s ease;
455
+ }
456
+
457
+ .editor-container.dragging {
458
+ border-color: var(--ui-tertiary);
459
+ border-style: dashed;
460
+ }
461
+
462
+ .bubble-menu {
463
+ display: flex;
464
+ align-items: center;
465
+ gap: 4px;
466
+ padding: 6px;
467
+ background: var(--paper-title-bg);
468
+ border: 1px solid var(--border-color);
469
+ border-radius: 6px;
470
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
471
+ flex-wrap: nowrap;
472
+ }
473
+
474
+ :global(.dark) .bubble-menu {
475
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.4);
476
+ }
477
+
478
+ .bubble-menu-group {
479
+ display: flex;
480
+ align-items: center;
481
+ gap: 2px;
482
+ }
483
+
484
+ .bubble-menu-divider {
485
+ width: 1px;
486
+ height: 24px;
487
+ background: var(--border-color);
488
+ margin: 0 4px;
489
+ }
490
+
491
+ .link-bubble-menu {
492
+ z-index: 1100;
493
+ }
494
+
495
+ .editor-content {
496
+ padding: 16px;
497
+ min-height: 200px;
498
+ color: var(--text-color);
499
+ }
500
+
501
+ /* TipTap Editor Styles */
502
+ .editor-content :global(.ProseMirror) {
503
+ outline: none;
504
+ }
505
+
506
+ .editor-content :global(.ProseMirror p) {
507
+ margin: 0 0 1em 0;
508
+ }
509
+
510
+ .editor-content :global(.ProseMirror h1) {
511
+ font-size: 2em;
512
+ font-weight: bold;
513
+ margin: 0.67em 0;
514
+ }
515
+
516
+ .editor-content :global(.ProseMirror h2) {
517
+ font-size: 1.5em;
518
+ font-weight: bold;
519
+ margin: 0.75em 0;
520
+ }
521
+
522
+ .editor-content :global(.ProseMirror h3) {
523
+ font-size: 1.17em;
524
+ font-weight: bold;
525
+ margin: 0.83em 0;
526
+ }
527
+
528
+ .editor-content :global(.ProseMirror ul),
529
+ .editor-content :global(.ProseMirror ol) {
530
+ padding-left: 1.5em;
531
+ margin: 1em 0;
532
+ }
533
+
534
+ .editor-content :global(.ProseMirror code) {
535
+ background: var(--page-bg);
536
+ padding: 2px 6px;
537
+ border-radius: 3px;
538
+ font-family: 'Monaco', 'Courier New', monospace;
539
+ font-size: 0.9em;
540
+ color: var(--ui-accent-dark);
541
+ }
542
+
543
+ :global(.dark) .editor-content :global(.ProseMirror code) {
544
+ color: var(--ui-accent);
545
+ }
546
+
547
+ .editor-content :global(.ProseMirror a) {
548
+ color: var(--ui-tertiary-dark);
549
+ text-decoration: underline;
550
+ cursor: pointer;
551
+ pointer-events: auto;
552
+ }
553
+
554
+ :global(.dark) .editor-content :global(.ProseMirror a) {
555
+ color: var(--ui-tertiary);
556
+ }
557
+
558
+ .editor-content :global(.ProseMirror a.editor-link) {
559
+ cursor: pointer;
560
+ pointer-events: auto;
561
+ }
562
+
563
+ .editor-content :global(.ProseMirror a.editor-link:hover) {
564
+ background: rgba(var(--ui-primary-rgbc), 0.1);
565
+ }
566
+
567
+ :global(.dark) .editor-content :global(.ProseMirror a.editor-link:hover) {
568
+ background: rgba(var(--ui-accent-rgbc), 0.1);
569
+ }
570
+
571
+ .editor-content :global(.ProseMirror) {
572
+ pointer-events: auto;
573
+ }
574
+
575
+ .editor-content :global(.ProseMirror img) {
576
+ max-width: 100%;
577
+ height: auto;
578
+ }
579
+
580
+ /* Table styles */
581
+ .editor-content :global(.ProseMirror .tableWrapper) {
582
+ overflow-x: auto;
583
+ margin: 1em 0;
584
+ }
585
+
586
+ .editor-content :global(.ProseMirror table) {
587
+ border-collapse: collapse;
588
+ table-layout: fixed;
589
+ width: 100%;
590
+ overflow: hidden;
591
+ }
592
+
593
+ .editor-content :global(.ProseMirror th),
594
+ .editor-content :global(.ProseMirror td) {
595
+ position: relative;
596
+ border: 1px solid var(--border-color);
597
+ padding: 6px 10px;
598
+ vertical-align: top;
599
+ min-width: 1em;
600
+ box-sizing: border-box;
601
+ }
602
+
603
+ .editor-content :global(.ProseMirror th) {
604
+ background: var(--paper-title-bg);
605
+ font-weight: 600;
606
+ text-align: left;
607
+ }
608
+
609
+ .editor-content :global(.ProseMirror table p) {
610
+ margin: 0;
611
+ }
612
+
613
+ .editor-content :global(.ProseMirror .selectedCell::after) {
614
+ content: '';
615
+ position: absolute;
616
+ inset: 0;
617
+ background: rgba(var(--ui-primary-rgbc), 0.08);
618
+ pointer-events: none;
619
+ z-index: 2;
620
+ }
621
+
622
+ :global(.dark) .editor-content :global(.ProseMirror .selectedCell::after) {
623
+ background: rgba(var(--ui-accent-rgbc), 0.12);
624
+ }
625
+
626
+ .editor-content :global(.ProseMirror .column-resize-handle) {
627
+ position: absolute;
628
+ top: 0;
629
+ bottom: -2px;
630
+ right: -2px;
631
+ width: 4px;
632
+ background: var(--ui-tertiary);
633
+ pointer-events: none;
634
+ z-index: 20;
635
+ }
636
+
637
+ .editor-content :global(.ProseMirror.resize-cursor) {
638
+ cursor: col-resize;
639
+ }
640
+
641
+ .editor-content :global(.ProseMirror [style*='text-align: left']) {
642
+ text-align: left;
643
+ }
644
+
645
+ .editor-content :global(.ProseMirror [style*='text-align: center']) {
646
+ text-align: center;
647
+ }
648
+
649
+ .editor-content :global(.ProseMirror [style*='text-align: right']) {
650
+ text-align: right;
651
+ }
652
+
653
+ .editor-content :global(.ProseMirror [style*='text-align: justify']) {
654
+ text-align: justify;
655
+ }
656
+
657
+ /* Placeholder styles */
658
+ .editor-content :global(.ProseMirror p.is-editor-empty:first-child::before) {
659
+ content: attr(data-placeholder);
660
+ float: left;
661
+ color: var(--placeholder-color);
662
+ pointer-events: none;
663
+ height: 0;
664
+ }
665
+
666
+ /* Alert styles */
667
+ .editor-content :global(.ProseMirror .alert) {
668
+ display: flex;
669
+ flex-direction: column;
670
+ border-radius: 0.25rem;
671
+ padding: 0.5rem;
672
+ margin: 0.5rem 0;
673
+ }
674
+
675
+ .editor-content :global(.ProseMirror .alert-title) {
676
+ margin-bottom: 0.25rem;
677
+ font-weight: 700;
678
+ }
679
+
680
+ .editor-content :global(.ProseMirror .alert-title::before) {
681
+ content: attr(data-title-element);
682
+ display: none;
683
+ }
684
+
685
+ .editor-content :global(.ProseMirror .alert-content) {
686
+ line-height: 1.5;
687
+ }
688
+
689
+ .editor-content :global(.ProseMirror .alert-default) {
690
+ background: rgba(var(--ui-accent-rgbc), 0.3);
691
+ border: 1px solid var(--ui-accent);
692
+ color: var(--text-color);
693
+ }
694
+
695
+ .editor-content :global(.ProseMirror .alert-default .alert-title) {
696
+ color: var(--ui-primary);
697
+ }
698
+
699
+ .editor-content :global(.ProseMirror .alert-danger) {
700
+ background: #f8d7da;
701
+ color: #58151c;
702
+ border: 1px solid #f1aeb5;
703
+ }
704
+
705
+ .editor-content :global(.ProseMirror .alert-danger .alert-title) {
706
+ color: #58151c;
707
+ }
708
+
709
+ .editor-content :global(.ProseMirror .alert-warning) {
710
+ background: #fff3cd;
711
+ color: #664d03;
712
+ border: 1px solid #ffe69c;
713
+ }
714
+
715
+ .editor-content :global(.ProseMirror .alert-warning .alert-title) {
716
+ color: #664d03;
717
+ }
718
+
719
+ :global(.dark) .editor-content :global(.ProseMirror .alert-default) {
720
+ background: rgba(var(--ui-primary-rgbc), 0.3);
721
+ border-color: var(--ui-primary);
722
+ }
723
+
724
+ :global(.dark) .editor-content :global(.ProseMirror .alert-default .alert-title) {
725
+ color: var(--white);
726
+ }
727
+
728
+ :global(.dark) .editor-content :global(.ProseMirror .alert-danger) {
729
+ background: #2c0b0e;
730
+ color: #ea868f;
731
+ border-color: #842029;
732
+ }
733
+
734
+ :global(.dark) .editor-content :global(.ProseMirror .alert-danger .alert-title) {
735
+ color: #ea868f;
736
+ }
737
+
738
+ :global(.dark) .editor-content :global(.ProseMirror .alert-warning) {
739
+ background: #332701;
740
+ color: #fff3cd;
741
+ border-color: #997404;
742
+ }
743
+
744
+ :global(.dark) .editor-content :global(.ProseMirror .alert-warning .alert-title) {
745
+ color: #fff3cd;
746
+ }
747
+
748
+ .editor-content :global(.alert p:last-child) {
749
+ margin-bottom: 0;
750
+ }
751
+
752
+ .editor-content :global(.tiptap p:last-child) {
753
+ margin-bottom: 0;
754
+ }
755
+
756
+ .editor-content :global(.alert) {
757
+ margin-bottom: 1rem !important;
758
+ }
759
+
760
+ /* Collaboration caret styles */
761
+ .editor-content :global(.collaboration-carets__caret) {
762
+ position: relative;
763
+ display: inline;
764
+ border-left: 2px solid;
765
+ margin-left: -1px;
766
+ margin-right: -1px;
767
+ pointer-events: none;
768
+ word-break: normal;
769
+ }
770
+
771
+ .editor-content :global(.collaboration-carets__label) {
772
+ position: absolute;
773
+ top: -1.4em;
774
+ left: -1px;
775
+ font-size: 12px;
776
+ font-weight: 600;
777
+ line-height: normal;
778
+ padding: 0.1rem 0.3rem;
779
+ border-radius: 3px 3px 3px 0;
780
+ color: #fff;
781
+ white-space: nowrap;
782
+ user-select: none;
783
+ pointer-events: none;
784
+ }
785
+
786
+ /* Collaboration selection highlight */
787
+ .editor-content :global(.ProseMirror-yjs-selection) {
788
+ display: inline;
789
+ }
790
+ </style>