@widgetic/editor 3.11.4 → 4.0.1

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 (497) hide show
  1. package/README.md +245 -0
  2. package/dist/assets/fonts.json +331 -0
  3. package/dist/assets/icons/ic-browser/audio.svg +7 -0
  4. package/dist/assets/icons/ic-browser/document.svg +11 -0
  5. package/dist/assets/icons/ic-browser/error.svg +5 -0
  6. package/dist/assets/icons/ic-browser/folder.svg +28 -0
  7. package/dist/assets/icons/ic-browser/player-pause.svg +1 -0
  8. package/dist/assets/icons/ic-browser/player-play.svg +1 -0
  9. package/dist/assets/icons/ic-browser/trash.svg +1 -0
  10. package/dist/assets/icons/ic-fixed-position/background-deselected.svg +3 -0
  11. package/dist/assets/icons/ic-fixed-position/background-selected.svg +3 -0
  12. package/dist/assets/icons/ic-fixed-position/bottom-left-background-deselected.svg +3 -0
  13. package/dist/assets/icons/ic-fixed-position/bottom-left-background-selected.svg +3 -0
  14. package/dist/assets/icons/ic-fixed-position/bottom-left-icon.svg +4 -0
  15. package/dist/assets/icons/ic-fixed-position/bottom-right-background-deselected.svg +3 -0
  16. package/dist/assets/icons/ic-fixed-position/bottom-right-background-selected.svg +3 -0
  17. package/dist/assets/icons/ic-fixed-position/bottom-right-icon.svg +4 -0
  18. package/dist/assets/icons/ic-fixed-position/center-bottom-icon.svg +5 -0
  19. package/dist/assets/icons/ic-fixed-position/center-center-icon.svg +3 -0
  20. package/dist/assets/icons/ic-fixed-position/center-left-icon.svg +5 -0
  21. package/dist/assets/icons/ic-fixed-position/center-right-icon.svg +5 -0
  22. package/dist/assets/icons/ic-fixed-position/center-top-icon.svg +5 -0
  23. package/dist/assets/icons/ic-fixed-position/top-left-background-deselected.svg +3 -0
  24. package/dist/assets/icons/ic-fixed-position/top-left-background-selected.svg +3 -0
  25. package/dist/assets/icons/ic-fixed-position/top-left-icon.svg +4 -0
  26. package/dist/assets/icons/ic-fixed-position/top-right-background-deselected.svg +3 -0
  27. package/dist/assets/icons/ic-fixed-position/top-right-background-selected.svg +3 -0
  28. package/dist/assets/icons/ic-fixed-position/top-right-icon.svg +4 -0
  29. package/dist/assets/icons/trash.svg +1 -0
  30. package/dist/components/MockWidget.svelte +108 -0
  31. package/dist/components/MockWidget.svelte.d.ts +22 -0
  32. package/dist/components/WidgetPreview.svelte +852 -0
  33. package/dist/components/WidgetPreview.svelte.d.ts +100 -0
  34. package/dist/components/color-select/ColorSelect.svelte +258 -0
  35. package/dist/components/color-select/ColorSelect.svelte.d.ts +42 -0
  36. package/dist/components/color-select/color.d.ts +25 -0
  37. package/dist/components/color-select/color.js +21 -0
  38. package/dist/components/color-select/constants.d.ts +5 -0
  39. package/dist/components/color-select/constants.js +5 -0
  40. package/dist/components/color-select/render.d.ts +5 -0
  41. package/dist/components/color-select/render.js +77 -0
  42. package/dist/components/color-select/shaders.d.ts +3 -0
  43. package/dist/components/color-select/shaders.js +8 -0
  44. package/dist/components/editors/EmbedEditor.svelte +700 -0
  45. package/dist/components/editors/EmbedEditor.svelte.d.ts +50 -0
  46. package/dist/components/editors/PropsEditor.svelte +3952 -0
  47. package/dist/components/editors/PropsEditor.svelte.d.ts +62 -0
  48. package/dist/components/editors/props-editor.css +73 -0
  49. package/dist/components/ic/AngleInputController.svelte +276 -0
  50. package/dist/components/ic/AngleInputController.svelte.d.ts +26 -0
  51. package/dist/components/ic/BrowserInputController.svelte +724 -0
  52. package/dist/components/ic/BrowserInputController.svelte.d.ts +39 -0
  53. package/dist/components/ic/CheckboxInputController.svelte +203 -0
  54. package/dist/components/ic/CheckboxInputController.svelte.d.ts +41 -0
  55. package/dist/components/ic/ColorPickerInputController.svelte +1584 -0
  56. package/dist/components/ic/ColorPickerInputController.svelte.d.ts +50 -0
  57. package/dist/components/ic/DateFieldInputController.svelte +453 -0
  58. package/dist/components/ic/DateFieldInputController.svelte.d.ts +35 -0
  59. package/dist/components/ic/DatePickerInputController.svelte +364 -0
  60. package/dist/components/ic/DatePickerInputController.svelte.d.ts +28 -0
  61. package/dist/components/ic/DropdownInputController.svelte +226 -0
  62. package/dist/components/ic/DropdownInputController.svelte.d.ts +43 -0
  63. package/dist/components/ic/FixedPositionInputController.svelte +353 -0
  64. package/dist/components/ic/FixedPositionInputController.svelte.d.ts +23 -0
  65. package/dist/components/ic/FontInputController.svelte +2062 -0
  66. package/dist/components/ic/FontInputController.svelte.d.ts +47 -0
  67. package/dist/components/ic/InputControllerHeader.svelte +147 -0
  68. package/dist/components/ic/InputControllerHeader.svelte.d.ts +23 -0
  69. package/dist/components/ic/NumberControls.svelte +299 -0
  70. package/dist/components/ic/NumberControls.svelte.d.ts +30 -0
  71. package/dist/components/ic/NumberInputController.svelte +397 -0
  72. package/dist/components/ic/NumberInputController.svelte.d.ts +30 -0
  73. package/dist/components/ic/PositionInputController.svelte +252 -0
  74. package/dist/components/ic/PositionInputController.svelte.d.ts +37 -0
  75. package/dist/components/ic/RadioGroupInputController.svelte +162 -0
  76. package/dist/components/ic/RadioGroupInputController.svelte.d.ts +38 -0
  77. package/dist/components/ic/RangeCalendarInputController.svelte +262 -0
  78. package/dist/components/ic/RangeCalendarInputController.svelte.d.ts +32 -0
  79. package/dist/components/ic/RoundedCornersInputController.svelte +469 -0
  80. package/dist/components/ic/RoundedCornersInputController.svelte.d.ts +37 -0
  81. package/dist/components/ic/SliderInputController.svelte +411 -0
  82. package/dist/components/ic/SliderInputController.svelte.d.ts +46 -0
  83. package/dist/components/ic/SliderRangeInputController.svelte +155 -0
  84. package/dist/components/ic/SliderRangeInputController.svelte.d.ts +33 -0
  85. package/dist/components/ic/SwitchInputController.svelte +107 -0
  86. package/dist/components/ic/SwitchInputController.svelte.d.ts +24 -0
  87. package/dist/components/ic/TagsInputController.svelte +125 -0
  88. package/dist/components/ic/TagsInputController.svelte.d.ts +27 -0
  89. package/dist/components/ic/TextAreaInputController.svelte +1804 -0
  90. package/dist/components/ic/TextAreaInputController.svelte.d.ts +13 -0
  91. package/dist/components/ic/TextAreaPreviewInputController.svelte +476 -0
  92. package/dist/components/ic/TextAreaPreviewInputController.svelte.d.ts +28 -0
  93. package/dist/components/ic/TextInputController.svelte +70 -0
  94. package/dist/components/ic/TextInputController.svelte.d.ts +30 -0
  95. package/dist/components/ic/TimeInputController.svelte +434 -0
  96. package/dist/components/ic/TimeInputController.svelte.d.ts +25 -0
  97. package/dist/components/ic/VectorInputController.svelte +1826 -0
  98. package/dist/components/ic/VectorInputController.svelte.d.ts +47 -0
  99. package/dist/components/ic/index.d.ts +39 -0
  100. package/dist/components/ic/index.js +24 -0
  101. package/dist/components/ic/shared/CategoryTabs.svelte +40 -0
  102. package/dist/components/ic/shared/CategoryTabs.svelte.d.ts +25 -0
  103. package/dist/components/ic/shared/ItemGrid.svelte +181 -0
  104. package/dist/components/ic/shared/ItemGrid.svelte.d.ts +35 -0
  105. package/dist/components/ic/shared/RecentItemsSection.svelte +260 -0
  106. package/dist/components/ic/shared/RecentItemsSection.svelte.d.ts +35 -0
  107. package/dist/components/ic/shared/index.d.ts +8 -0
  108. package/dist/components/ic/shared/index.js +9 -0
  109. package/dist/components/icons/addnew.svelte +21 -0
  110. package/dist/components/icons/addnew.svelte.d.ts +20 -0
  111. package/dist/components/icons/drag.svelte +29 -0
  112. package/dist/components/icons/drag.svelte.d.ts +20 -0
  113. package/dist/components/icons/drag.svg +1 -0
  114. package/dist/components/icons/locked.svelte +4 -0
  115. package/dist/components/icons/locked.svelte.d.ts +26 -0
  116. package/dist/components/icons/locked.svg +4 -0
  117. package/dist/components/icons/ok.svelte +20 -0
  118. package/dist/components/icons/ok.svelte.d.ts +20 -0
  119. package/dist/components/icons/ok.svg +11 -0
  120. package/dist/components/icons/unlocked.svelte +4 -0
  121. package/dist/components/icons/unlocked.svelte.d.ts +26 -0
  122. package/dist/components/icons/unlocked.svg +4 -0
  123. package/dist/components/icons/x.svelte +14 -0
  124. package/dist/components/icons/x.svelte.d.ts +20 -0
  125. package/dist/components/icons/x.svg +5 -0
  126. package/dist/components/ui/arrows/OpacitySliderArrows.svelte +43 -0
  127. package/dist/components/ui/arrows/OpacitySliderArrows.svelte.d.ts +23 -0
  128. package/dist/components/ui/checkerboard-background/CheckerboardBackground.svelte +15 -0
  129. package/dist/components/ui/checkerboard-background/CheckerboardBackground.svelte.d.ts +20 -0
  130. package/dist/config.d.ts +5 -0
  131. package/dist/config.js +19 -0
  132. package/dist/index.d.ts +4 -0
  133. package/dist/index.js +8 -0
  134. package/dist/stores/userSession.d.ts +7 -0
  135. package/dist/stores/userSession.js +28 -0
  136. package/dist/utils/FontManager.d.ts +276 -0
  137. package/dist/utils/FontManager.js +1283 -0
  138. package/dist/utils/MediaManager.d.ts +35 -0
  139. package/dist/utils/MediaManager.js +59 -0
  140. package/dist/utils/VectorManager.d.ts +190 -0
  141. package/dist/utils/VectorManager.js +754 -0
  142. package/dist/utils/colorAutocomplete.d.ts +10 -0
  143. package/dist/utils/colorAutocomplete.js +28 -0
  144. package/dist/utils/colorNames.d.ts +3 -0
  145. package/dist/utils/colorNames.js +152 -0
  146. package/dist/utils/colorTransparency.d.ts +52 -0
  147. package/dist/utils/colorTransparency.js +114 -0
  148. package/dist/utils/fontUtils.d.ts +34 -0
  149. package/dist/utils/fontUtils.js +132 -0
  150. package/dist/utils/passiveEvents.js +20 -0
  151. package/dist/utils/recentColors.d.ts +10 -0
  152. package/dist/utils/recentColors.js +63 -0
  153. package/dist/utils/skipFonts.d.ts +3 -0
  154. package/dist/utils/skipFonts.js +49 -0
  155. package/dist/utils/vectorUtils.d.ts +34 -0
  156. package/dist/utils/vectorUtils.js +71 -0
  157. package/dist/utils.d.ts +11 -0
  158. package/dist/utils.js +38 -0
  159. package/package.json +134 -74
  160. package/.babelrc +0 -10
  161. package/.bowerrc +0 -4
  162. package/.nvmrc +0 -1
  163. package/Gruntfile.coffee +0 -69
  164. package/bower.json +0 -28
  165. package/css/browser/entries/activateable.styl +0 -29
  166. package/css/browser/entries/audio.styl +0 -39
  167. package/css/browser/entries/folder.styl +0 -79
  168. package/css/browser/entries/image.styl +0 -26
  169. package/css/browser/entries/index.styl +0 -66
  170. package/css/browser/entries/rss.styl +0 -11
  171. package/css/browser/entries/video.styl +0 -11
  172. package/css/browser/index.styl +0 -86
  173. package/css/browser/intent/index.styl +0 -61
  174. package/css/browser/nav/index.styl +0 -73
  175. package/css/browser/social-browser.styl +0 -23
  176. package/css/controls/clock.styl +0 -110
  177. package/css/controls/config.styl +0 -6
  178. package/css/controls/control-audio-details.styl +0 -63
  179. package/css/controls/control-audio.styl +0 -28
  180. package/css/controls/control-browser.styl +0 -48
  181. package/css/controls/control-color.styl +0 -423
  182. package/css/controls/control-date_time.styl +0 -191
  183. package/css/controls/control-dropdown.styl +0 -79
  184. package/css/controls/control-font.styl +0 -147
  185. package/css/controls/control-image-details.styl +0 -35
  186. package/css/controls/control-image.styl +0 -43
  187. package/css/controls/control-order-picker.styl +0 -29
  188. package/css/controls/control-position.styl +0 -390
  189. package/css/controls/control-range.styl +0 -68
  190. package/css/controls/control-scale.styl +0 -30
  191. package/css/controls/control-slider.styl +0 -38
  192. package/css/controls/control-spinner.styl +0 -39
  193. package/css/controls/control-textarea.styl +0 -52
  194. package/css/controls/control-toggle.styl +0 -34
  195. package/css/controls/control-url.styl +0 -32
  196. package/css/controls/control-video-details.styl +0 -85
  197. package/css/controls/control-video.styl +0 -43
  198. package/css/controls/general.styl +0 -98
  199. package/css/controls/index.styl +0 -25
  200. package/css/editor/colors.styl +0 -7
  201. package/css/editor/content/directinput.styl +0 -47
  202. package/css/editor/content/index.styl +0 -111
  203. package/css/editor/content/message.styl +0 -53
  204. package/css/editor/content/tabs.styl +0 -75
  205. package/css/editor/copied-from-old-style.styl +0 -197
  206. package/css/editor/details/bulk-editor.styl +0 -104
  207. package/css/editor/details/index.styl +0 -21
  208. package/css/editor/details/item.styl +0 -153
  209. package/css/editor/embed.styl +0 -106
  210. package/css/editor/footer.styl +0 -180
  211. package/css/editor/full-editor.styl +0 -16
  212. package/css/editor/index.styl +0 -124
  213. package/css/editor/login-popup.styl +0 -13
  214. package/css/editor/preview.styl +0 -257
  215. package/css/editor/skin/editor/index.styl +0 -54
  216. package/css/editor/skin/editor/tabs.styl +0 -111
  217. package/css/editor/skin/index.styl +0 -138
  218. package/css/editor/skin/popup.styl +0 -152
  219. package/css/editor/skin/presets/dropdown.styl +0 -61
  220. package/css/editor/skin/presets/index.styl +0 -86
  221. package/css/editor/skin/presets/list.styl +0 -244
  222. package/css/editor/tooltips.styl +0 -85
  223. package/css/fonts.styl +0 -47
  224. package/css/icons/BlogvioEditor.dev.svg +0 -112
  225. package/css/icons.styl +0 -16
  226. package/css/img/arrow-right-rounded-v-small.png +0 -0
  227. package/css/img/browser-folder-separator.png +0 -0
  228. package/css/img/calendar-input-icons.png +0 -0
  229. package/css/img/check.png +0 -0
  230. package/css/img/close.png +0 -0
  231. package/css/img/composition/edit-icon.png +0 -0
  232. package/css/img/content/audio-icon.png +0 -0
  233. package/css/img/content/folder-icon.png +0 -0
  234. package/css/img/content/messages/error-background.png +0 -0
  235. package/css/img/content/messages/error-icon.png +0 -0
  236. package/css/img/content/messages/notification-background.png +0 -0
  237. package/css/img/content/messages/notification-icon.png +0 -0
  238. package/css/img/content/no-files-audio.png +0 -0
  239. package/css/img/content/no-files-image.png +0 -0
  240. package/css/img/content/no-files-video.png +0 -0
  241. package/css/img/content/no-files.png +0 -0
  242. package/css/img/content/rss-icon.png +0 -0
  243. package/css/img/content/selected.png +0 -0
  244. package/css/img/content/video-icon.png +0 -0
  245. package/css/img/content/with-control-text.png +0 -0
  246. package/css/img/control-dropdown-arrow.png +0 -0
  247. package/css/img/control-image-valid.png +0 -0
  248. package/css/img/control-position-icon.png +0 -0
  249. package/css/img/control-scale-lock-icon.png +0 -0
  250. package/css/img/control-slider-arrows-hover.png +0 -0
  251. package/css/img/control-slider-arrows.png +0 -0
  252. package/css/img/control-spinner-buttons-hover.png +0 -0
  253. package/css/img/control-spinner-buttons.png +0 -0
  254. package/css/img/control-textarea-bold-active.png +0 -0
  255. package/css/img/control-textarea-bold.png +0 -0
  256. package/css/img/control-textarea-italic-active.png +0 -0
  257. package/css/img/control-textarea-italic.png +0 -0
  258. package/css/img/control-textarea-underline-active.png +0 -0
  259. package/css/img/control-textarea-underline.png +0 -0
  260. package/css/img/custom-checkbox-sprite.png +0 -0
  261. package/css/img/details/item-collapse-icon.png +0 -0
  262. package/css/img/details/item-expand-icon.png +0 -0
  263. package/css/img/details/item-sort-icon.png +0 -0
  264. package/css/img/done-arrow.png +0 -0
  265. package/css/img/fb-logo-icon-w.png +0 -0
  266. package/css/img/grid-icons.png +0 -0
  267. package/css/img/icon-reference.png +0 -0
  268. package/css/img/indicator.png +0 -0
  269. package/css/img/loader.gif +0 -0
  270. package/css/img/loaders/audio-white.png +0 -0
  271. package/css/img/loaders/audio.png +0 -0
  272. package/css/img/loaders/photo-white.png +0 -0
  273. package/css/img/loaders/photo.png +0 -0
  274. package/css/img/loaders/util-white.png +0 -0
  275. package/css/img/loaders/util.png +0 -0
  276. package/css/img/loaders/video-white.png +0 -0
  277. package/css/img/loaders/video.png +0 -0
  278. package/css/img/maximize.png +0 -0
  279. package/css/img/minimize.png +0 -0
  280. package/css/img/next-btn.png +0 -0
  281. package/css/img/search-icon-sprite.png +0 -0
  282. package/css/img/services/blogvio.png +0 -0
  283. package/css/img/services/drive.png +0 -0
  284. package/css/img/services/dropbox-login.png +0 -0
  285. package/css/img/services/dropbox.png +0 -0
  286. package/css/img/services/facebook-login.png +0 -0
  287. package/css/img/services/facebook.png +0 -0
  288. package/css/img/services/flickr-login.png +0 -0
  289. package/css/img/services/flickr.png +0 -0
  290. package/css/img/services/instagram-login.png +0 -0
  291. package/css/img/services/instagram.png +0 -0
  292. package/css/img/services/text.png +0 -0
  293. package/css/img/services/uploads.png +0 -0
  294. package/css/img/services/uploads.svg +0 -689
  295. package/css/img/services/webaddress-bw.png +0 -0
  296. package/css/img/services/webaddress.png +0 -0
  297. package/css/img/services/webadress.png +0 -0
  298. package/css/img/services/websearch.png +0 -0
  299. package/css/img/services/wix.png +0 -0
  300. package/css/img/skin/item-active-indicator.png +0 -0
  301. package/css/img/skin/item-hover-icon.png +0 -0
  302. package/css/img/skin/overwrite.png +0 -0
  303. package/css/img/skin/popup-name-icon.png +0 -0
  304. package/css/img/skin/popup-save.png +0 -0
  305. package/css/img/skin/preset.png +0 -0
  306. package/css/img/skin/presets-arrow-purple.png +0 -0
  307. package/css/img/tw-icon-w.png +0 -0
  308. package/css/img/warning.png +0 -0
  309. package/css/img/white-rounded-check.png +0 -0
  310. package/css/index.styl +0 -9
  311. package/css/mixins.styl +0 -62
  312. package/css/scrollable.styl +0 -101
  313. package/karma.conf.js +0 -73
  314. package/lib/control.js +0 -1
  315. package/lib/controls.js +0 -1
  316. package/lib/core.editor.js +0 -4
  317. package/lib/core.js +0 -1
  318. package/lib/core.vendor.js +0 -3
  319. package/lib/dev/control.js +0 -845
  320. package/lib/dev/controls.js +0 -7774
  321. package/lib/dev/core.editor.js +0 -6439
  322. package/lib/dev/core.js +0 -1486
  323. package/lib/dev/core.vendor.js +0 -10708
  324. package/lib/dev/editor.css +0 -166
  325. package/lib/dev/editor.js +0 -29710
  326. package/lib/dev/editor.vendor.js +0 -15119
  327. package/lib/dev/jquery.nanoscroller.js.map +0 -10
  328. package/lib/editor.css +0 -167
  329. package/lib/editor.js +0 -49
  330. package/lib/editor.vendor.js +0 -11
  331. package/src/api/index.coffee +0 -23
  332. package/src/api/mixin.coffee +0 -89
  333. package/src/browser/controller/entry/audio.coffee +0 -38
  334. package/src/browser/controller/entry/button.coffee +0 -16
  335. package/src/browser/controller/entry/file.coffee +0 -35
  336. package/src/browser/controller/entry/folder.coffee +0 -14
  337. package/src/browser/controller/entry/image.coffee +0 -24
  338. package/src/browser/controller/entry/index.coffee +0 -18
  339. package/src/browser/controller/entry/rss.coffee +0 -5
  340. package/src/browser/controller/entry/video.coffee +0 -4
  341. package/src/browser/controller/index.coffee +0 -289
  342. package/src/browser/controller/instagram.coffee +0 -129
  343. package/src/browser/controller/navbar.coffee +0 -48
  344. package/src/browser/controller/social.coffee +0 -213
  345. package/src/browser/model/entry.coffee +0 -18
  346. package/src/browser/model/index.coffee +0 -34
  347. package/src/core/controller/font-loader.coffee +0 -65
  348. package/src/core/controller/lazyload.coffee +0 -14
  349. package/src/core/controller/overlay.coffee +0 -146
  350. package/src/core/controller/popup.coffee +0 -36
  351. package/src/core/controller/scrollable.coffee +0 -68
  352. package/src/core/controls/audio.coffee +0 -56
  353. package/src/core/controls/browser.coffee +0 -271
  354. package/src/core/controls/color.coffee +0 -121
  355. package/src/core/controls/date-time/calendar/index.coffee +0 -99
  356. package/src/core/controls/date-time/calendar/months.coffee +0 -52
  357. package/src/core/controls/date-time/calendar/years.coffee +0 -58
  358. package/src/core/controls/date-time/clock.coffee +0 -62
  359. package/src/core/controls/date-time/helper.coffee +0 -31
  360. package/src/core/controls/date-time/index.coffee +0 -62
  361. package/src/core/controls/date-time/popup.coffee +0 -56
  362. package/src/core/controls/details/audio.coffee +0 -33
  363. package/src/core/controls/details/image.coffee +0 -19
  364. package/src/core/controls/details/video.coffee +0 -14
  365. package/src/core/controls/dropdown.coffee +0 -100
  366. package/src/core/controls/font/font-item.coffee +0 -22
  367. package/src/core/controls/font/font-list.coffee +0 -108
  368. package/src/core/controls/font/fonts-popup.coffee +0 -91
  369. package/src/core/controls/font/index.coffee +0 -66
  370. package/src/core/controls/image.coffee +0 -37
  371. package/src/core/controls/index.coffee +0 -88
  372. package/src/core/controls/orderPicker.coffee +0 -93
  373. package/src/core/controls/position/grid-panel.coffee +0 -75
  374. package/src/core/controls/position/index.coffee +0 -98
  375. package/src/core/controls/position/panel.coffee +0 -68
  376. package/src/core/controls/position/pixel-panel.coffee +0 -54
  377. package/src/core/controls/position/popup.coffee +0 -63
  378. package/src/core/controls/position/reference-panel.coffee +0 -26
  379. package/src/core/controls/range.coffee +0 -191
  380. package/src/core/controls/rss.coffee +0 -16
  381. package/src/core/controls/scale.coffee +0 -78
  382. package/src/core/controls/slider.coffee +0 -122
  383. package/src/core/controls/stepper.coffee +0 -97
  384. package/src/core/controls/text.coffee +0 -54
  385. package/src/core/controls/textarea.coffee +0 -78
  386. package/src/core/controls/toggle.coffee +0 -29
  387. package/src/core/controls/url.coffee +0 -88
  388. package/src/core/controls/video.coffee +0 -9
  389. package/src/core/embed/api.js +0 -178
  390. package/src/core/embed/controller.js +0 -331
  391. package/src/core/models/composition.coffee +0 -178
  392. package/src/core/models/content.coffee +0 -79
  393. package/src/core/models/skin.coffee +0 -91
  394. package/src/core/models/user.coffee +0 -6
  395. package/src/core/models/widget.coffee +0 -88
  396. package/src/core/router/index.coffee +0 -44
  397. package/src/core/services/container/index.js +0 -18
  398. package/src/core/services/container/instance.js +0 -3
  399. package/src/core/services/content.js +0 -80
  400. package/src/core/services/instagram.coffee +0 -47
  401. package/src/core/services/mixin/events.js +0 -17
  402. package/src/core/services/service.js +0 -1
  403. package/src/core/services/sounds.js +0 -81
  404. package/src/core/services/uploads.js +0 -299
  405. package/src/core/utils/popup.coffee +0 -67
  406. package/src/core/utils/should-show.coffee +0 -12
  407. package/src/core/views/audio-details.pug +0 -9
  408. package/src/core/views/audio.pug +0 -7
  409. package/src/core/views/browser.pug +0 -4
  410. package/src/core/views/color.pug +0 -8
  411. package/src/core/views/date-time/calendar.pug +0 -3
  412. package/src/core/views/date-time/clock.pug +0 -12
  413. package/src/core/views/date-time/grid.pug +0 -6
  414. package/src/core/views/date-time/input.pug +0 -4
  415. package/src/core/views/date-time/table.pug +0 -6
  416. package/src/core/views/dropdown.pug +0 -9
  417. package/src/core/views/font/font-item.pug +0 -3
  418. package/src/core/views/font/fonts-list.pug +0 -7
  419. package/src/core/views/font/fonts-popup.pug +0 -5
  420. package/src/core/views/font/index.pug +0 -7
  421. package/src/core/views/image-details.pug +0 -6
  422. package/src/core/views/image.pug +0 -5
  423. package/src/core/views/input-layout.pug +0 -15
  424. package/src/core/views/order-items.pug +0 -7
  425. package/src/core/views/order-picker.pug +0 -7
  426. package/src/core/views/pixelpanel.pug +0 -69
  427. package/src/core/views/position.pug +0 -10
  428. package/src/core/views/range.pug +0 -9
  429. package/src/core/views/scale.pug +0 -7
  430. package/src/core/views/slider.pug +0 -6
  431. package/src/core/views/stepper.pug +0 -6
  432. package/src/core/views/text.pug +0 -4
  433. package/src/core/views/textarea.pug +0 -8
  434. package/src/core/views/toggle.pug +0 -5
  435. package/src/core/views/video-details.pug +0 -7
  436. package/src/core/views/video.pug +0 -4
  437. package/src/core/widget/index.coffee +0 -66
  438. package/src/editor/content/browser/dropzone.html +0 -268
  439. package/src/editor/content/browser/file.html +0 -124
  440. package/src/editor/content/browser/scroll-pane.html +0 -32
  441. package/src/editor/content/browser/service.html +0 -53
  442. package/src/editor/content/browser/upload.coffee +0 -44
  443. package/src/editor/content/browser/upload.html +0 -439
  444. package/src/editor/content/index.coffee +0 -294
  445. package/src/editor/content/input/field.coffee +0 -45
  446. package/src/editor/content/input/index.coffee +0 -115
  447. package/src/editor/content/service/base/photoitem.coffee +0 -49
  448. package/src/editor/content/service/cache.coffee +0 -20
  449. package/src/editor/content/service/default.coffee +0 -59
  450. package/src/editor/content/service/dropbox.coffee +0 -71
  451. package/src/editor/content/service/facebook.coffee +0 -143
  452. package/src/editor/content/service/flickr.coffee +0 -85
  453. package/src/editor/content/service/google-drive-sdk.coffee +0 -18
  454. package/src/editor/content/service/google-drive.coffee +0 -144
  455. package/src/editor/content/service/instagram-social.coffee +0 -84
  456. package/src/editor/content/service/instagram.coffee +0 -168
  457. package/src/editor/content/service/share-popup.coffee +0 -15
  458. package/src/editor/content/service/soundcloud.coffee +0 -163
  459. package/src/editor/content/tab.coffee +0 -27
  460. package/src/editor/details/bulk-editor/attributes-panel.coffee +0 -11
  461. package/src/editor/details/bulk-editor/index.coffee +0 -86
  462. package/src/editor/details/index.coffee +0 -156
  463. package/src/editor/details/item/attributes.coffee +0 -45
  464. package/src/editor/details/item/index.coffee +0 -82
  465. package/src/editor/editor.coffee +0 -274
  466. package/src/editor/footer/breadcrumb.coffee +0 -17
  467. package/src/editor/footer/index.coffee +0 -36
  468. package/src/editor/index.coffee +0 -79
  469. package/src/editor/listeners/embed-listener.coffee +0 -143
  470. package/src/editor/login-popup.coffee +0 -29
  471. package/src/editor/notification-controller.coffee +0 -35
  472. package/src/editor/popup.coffee +0 -68
  473. package/src/editor/remote-preview.coffee +0 -68
  474. package/src/editor/save/index.coffee +0 -62
  475. package/src/editor/skin/delete.coffee +0 -33
  476. package/src/editor/skin/editor/index.coffee +0 -188
  477. package/src/editor/skin/editor/panel.coffee +0 -37
  478. package/src/editor/skin/index.coffee +0 -126
  479. package/src/editor/skin/save.coffee +0 -58
  480. package/src/editor/skin/selector/index.coffee +0 -59
  481. package/src/editor/skin/selector/item.coffee +0 -123
  482. package/src/editor/skin/selector/list.coffee +0 -97
  483. package/src/editor/views/embed.pug +0 -10
  484. package/src/editor/views/loginbutton.pug +0 -8
  485. package/src/editor/views/popup.pug +0 -17
  486. package/src/editor/views/presets.pug +0 -8
  487. package/src/editor/views/presetslist-header.pug +0 -8
  488. package/src/editor/views/skinitem.pug +0 -13
  489. package/src/modules/control.js +0 -3
  490. package/src/modules/controls.js +0 -29
  491. package/src/modules/core.js +0 -19
  492. package/src/modules/editor.js +0 -80
  493. package/test/config.js +0 -3
  494. package/test/core/models/skin-spec.js +0 -209
  495. package/webpack.config.js +0 -46
  496. package/webpack.config.prod.js +0 -9
  497. /package/{src/core/views/date-time/popup.pug → dist/utils/passiveEvents.d.ts} +0 -0
@@ -0,0 +1,1804 @@
1
+ <!--
2
+ TextAreaInputController
3
+
4
+ A rich text editor component with advanced formatting capabilities:
5
+ - Text styling (bold, italic, underline)
6
+ - Headings and lists
7
+ - Color picker integration
8
+ - Link insertion
9
+ - Dynamic height adjustment
10
+
11
+ Key Features:
12
+ - Maintains editor state across format changes
13
+ - Handles complex selection scenarios
14
+ - Manages toolbar state based on text formatting
15
+ - Implements smooth height transitions
16
+ - Preserves cursor position during operations
17
+
18
+ Edge Cases:
19
+ - Handles empty selections gracefully
20
+ - Manages focus state during dropdown interactions
21
+ - Prevents recursive updates during state changes
22
+ - Maintains formatting consistency across operations
23
+ -->
24
+
25
+ <script lang="ts">
26
+ // Svelte imports
27
+ import { onMount, onDestroy } from 'svelte';
28
+ import { writable } from 'svelte/store';
29
+ import type { ComponentEvents } from 'svelte';
30
+
31
+ // Component config
32
+ import type { InputControllerConfig } from './index';
33
+
34
+ // Import Shadcn UI components
35
+ import { Button, ToggleGroup, Popover, Command } from '@widgetic/design-system/components';
36
+
37
+ // Import Lucide Icons
38
+ import {
39
+ IconBrush,
40
+ IconBold,
41
+ IconItalic,
42
+ IconUnderline,
43
+ IconLink,
44
+ ChevronDown,
45
+ X,
46
+ IconPilcrow,
47
+ IconList,
48
+ IconListNumbers,
49
+ IconListCheck
50
+ } from '@widgetic/design-system/icons';
51
+
52
+
53
+ // Import Shad Editor
54
+ import { ShadEditor, type TipTapEditor, type TipTapEditorLevel } from '@widgetic/design-system/shad-editor';
55
+
56
+ // Add local ColorPickerInputController import
57
+ import ColorPickerInputController from './ColorPickerInputController.svelte';
58
+
59
+ // Add this constant for the decoration type
60
+ const TRANSITION_DURATION = 250;
61
+
62
+ /**
63
+ * Component props
64
+ */
65
+ let {
66
+ class: className = '',
67
+ label,
68
+ value = $bindable(''),
69
+ onChange,
70
+ config = {},
71
+ description = undefined,
72
+ onFocus = () => {}
73
+ } = $props<{
74
+ class: string;
75
+ label: string;
76
+ value: string;
77
+ onChange: (newValue: string) => void;
78
+ config?: InputControllerConfig;
79
+ description?: string;
80
+ onFocus?: () => void;
81
+ }>();
82
+
83
+ // Use config values with fallbacks
84
+ const textColor = config.textColor || '#1a1a1a';
85
+
86
+ /**
87
+ * State Variables
88
+ */
89
+ let isInitialMount = $state(false); // Tracks initial component mounting to prevent unnecessary updates
90
+ let editorInstance: TipTapEditor | null = null; // Reference to TipTap editor instance
91
+ let textStyleDropdownOpen = $state(false); // Controls visibility of text style dropdown
92
+ let listStyleDropdownOpen = $state(false);
93
+ /**
94
+ * Format Tracking
95
+ * Maintains active format states for toolbar UI:
96
+ * - Prevents invalid format combinations
97
+ * - Updates reactively with selection changes
98
+ * - Syncs with editor commands
99
+ */
100
+ // Add type definition for formats
101
+ type FormatType = 'bold' | 'italic' | 'underline' | 'link';
102
+ type ActiveFormats = Record<FormatType, boolean>;
103
+ type FormatMapping = Record<FormatType, { value: string; icon: any }>;
104
+
105
+ let activeFormats = $state<ActiveFormats>({
106
+ bold: false,
107
+ italic: false,
108
+ underline: false,
109
+ link: false
110
+ });
111
+
112
+ const formatMapping: FormatMapping = {
113
+ bold: { value: 'b', icon: IconBold },
114
+ italic: { value: 'i', icon: IconItalic },
115
+ underline: { value: 'u', icon: IconUnderline },
116
+ link: { value: 'link', icon: IconLink }
117
+ };
118
+
119
+ let currentTextStyle = $state('text'); // Track current text style
120
+
121
+ let showColorPicker = $state(false);
122
+ let currentColor = $state(textColor);
123
+ let selectedTextRange: { from: number, to: number } | null = $state(null);
124
+
125
+ const textOptions = [
126
+ { value: 'text', label: 'Paragraph', icon: IconPilcrow },
127
+ { value: 'h1', label: 'H1' },
128
+ { value: 'h2', label: 'H2' },
129
+ { value: 'h3', label: 'H3' }
130
+ ];
131
+
132
+ const listOptions = [
133
+ { value: 'bulletList', label: 'Bullet List', icon: IconList },
134
+ { value: 'orderedList', label: 'Numbered List', icon: IconListNumbers },
135
+ { value: 'checkList', label: 'Check List', icon: IconListCheck }
136
+ ];
137
+
138
+ const content = writable(value);
139
+
140
+ // Create a store for managing dropdowns
141
+ const openDropdowns = writable<Set<string>>(new Set());
142
+
143
+ function toggleDropdown(id: string) {
144
+ openDropdowns.update(set => {
145
+ if (set.has(id)) {
146
+ set.delete(id);
147
+ } else {
148
+ set.add(id);
149
+ }
150
+ return set;
151
+ });
152
+ }
153
+
154
+ // Add these variables to track focus state
155
+ let editorHasFocus = $state(false);
156
+ let toolbarRef: HTMLDivElement;
157
+
158
+
159
+
160
+ // Update to use $derived for reactive statement
161
+ const editorStyle = $derived(`
162
+ --editor-min-height: auto;
163
+ --editor-max-height: none;
164
+ --transition-duration: 0ms;
165
+ --transition-timing: ease-out;
166
+ `);
167
+
168
+ // Add this with other state declarations
169
+ let editorClass = $derived(`border-none overflow-auto editor-height ${''}`);
170
+
171
+ // Add state to track if any dropdown is open
172
+ let isDropdownOpen = $state(false);
173
+
174
+ /**
175
+ * Editor Mount Handler
176
+ *
177
+ * Initializes the editor instance and sets up event listeners:
178
+ * 1. Sets initial content if provided
179
+ * 2. Configures editor options (autofocus, editability)
180
+ * 3. Sets up selection tracking for format updates
181
+ * 4. Manages focus/blur behavior and height transitions
182
+ * 5. Handles initial mount state
183
+ *
184
+ * Edge Cases:
185
+ * - Prevents height changes when dropdowns are open
186
+ * - Manages focus state during toolbar interactions
187
+ * - Handles selection preservation during format changes
188
+ */
189
+ // Add these new state variables after the other state declarations
190
+ let lastKnownContent = $state('');
191
+ let wasVisible = $state(true);
192
+
193
+ function handleEditorMount(e: any) {
194
+ editorInstance = e.detail.editor;
195
+
196
+ if (!editorInstance) {
197
+ console.error('Failed to initialize editor instance');
198
+ return;
199
+ }
200
+
201
+ // Restore content if we were previously hidden
202
+ if (!wasVisible && lastKnownContent) {
203
+ try {
204
+ const coloredContent = `<span style="color: ${textColor}">${lastKnownContent}</span>`;
205
+ editorInstance.commands.setContent(coloredContent);
206
+ } catch (error) {
207
+ console.error('Error restoring content after visibility change:', error);
208
+ }
209
+ } else if (value) {
210
+ try {
211
+ const coloredContent = `<span style="color: ${textColor}">${value}</span>`;
212
+ editorInstance.commands.setContent(coloredContent);
213
+
214
+ // Set caret to start after setting content
215
+ editorInstance.commands.setTextSelection(0);
216
+ } catch (error) {
217
+ console.error('Error setting initial content and color:', error);
218
+ }
219
+ }
220
+
221
+ // Disable auto-focus behavior
222
+ editorInstance.setOptions({
223
+ autofocus: false,
224
+ editable: true
225
+ });
226
+
227
+ // Add selection change listener
228
+ editorInstance.on('selectionUpdate', () => {
229
+ handleSelectionChange();
230
+ updateActiveFormats();
231
+ });
232
+
233
+ // Track focus state and handle height changes
234
+ editorInstance.on('focus', () => {
235
+ onFocus();
236
+ editorHasFocus = true;
237
+
238
+ // When focusing, ensure caret is at start if this is initial mount
239
+ if (isInitialMount && editorInstance) {
240
+ editorInstance.commands.setTextSelection(0);
241
+ }
242
+ });
243
+
244
+ // Update the blur handler inside handleEditorMount
245
+ editorInstance.on('blur', ({ event }) => {
246
+ const relatedTarget = (event as FocusEvent).relatedTarget as Node | null;
247
+ handleEditorBlur(relatedTarget);
248
+ });
249
+
250
+ // Set initial mount flag
251
+ isInitialMount = true;
252
+ setTimeout(() => {
253
+ isInitialMount = false;
254
+ // Check overflow only once after initial mount
255
+ setTimeout(() => {
256
+ checkContentOverflow();
257
+ }, 300); // Give it a bit more time to ensure content is fully rendered
258
+ }, 100);
259
+
260
+ updateActiveFormats();
261
+
262
+ // Helper function to check if content overflows viewport - only called once on mount
263
+ function checkContentOverflow() {
264
+ if (!editorInstance) return;
265
+ try {
266
+ const editorDom = editorInstance.view.dom;
267
+ const contentArea = editorDom.closest('.editor-content-area') as HTMLElement;
268
+
269
+ if (!contentArea) {
270
+ console.error('Could not find .editor-content-area parent element');
271
+ return;
272
+ }
273
+
274
+ const proseMirror = contentArea.querySelector('.ProseMirror') as HTMLElement;
275
+ if (!proseMirror) {
276
+ console.error('Could not find .ProseMirror element inside content area');
277
+ return;
278
+ }
279
+
280
+ // Get the actual content height (including padding)
281
+ const contentHeight = proseMirror.scrollHeight;
282
+ // Get the available container height
283
+ const containerHeight = contentArea.clientHeight;
284
+ const isOverflowing = contentHeight > containerHeight;
285
+
286
+ console.log('Initial overflow check:', {
287
+ contentHeight,
288
+ containerHeight,
289
+ difference: contentHeight - containerHeight,
290
+ isOverflowing
291
+ });
292
+
293
+ if (isOverflowing) {
294
+ proseMirror.style.backgroundColor = 'rgba(255, 0, 0, 0.1)';
295
+ }
296
+ } catch (error) {
297
+ console.error('Error checking content overflow:', error);
298
+ }
299
+ }
300
+
301
+ // Helper function to force caret and scroll to beginning
302
+ function forceCaretToStart() {
303
+ if (!editorInstance) return;
304
+ try {
305
+ // Only force caret to start if this is the initial mount
306
+ if (isInitialMount) {
307
+ // Force the caret (text selection) to the beginning
308
+ editorInstance.chain().setTextSelection({ from: 0, to: 0 }).run();
309
+ }
310
+ } catch (error) {
311
+ console.error('Error forcing caret to beginning:', error);
312
+ }
313
+ }
314
+
315
+ // Only call forceCaretToStart once during initial mount
316
+ if (isInitialMount) {
317
+ setTimeout(forceCaretToStart, 300);
318
+ }
319
+ }
320
+
321
+ function handleEditorUpdate(e: any) {
322
+ if (!editorInstance) return;
323
+
324
+ const newContent = e.detail.html;
325
+ if (newContent !== value) {
326
+ content.set(newContent);
327
+ onChange(newContent);
328
+ updateActiveFormats();
329
+ currentTextStyle = getCurrentTextStyle();
330
+ }
331
+ }
332
+
333
+ /**
334
+ * Format Management System
335
+ *
336
+ * Handles text formatting operations and state:
337
+ * - Updates toolbar UI based on current selection
338
+ * - Manages format toggles (bold, italic, etc.)
339
+ * - Handles text style changes (headings, lists)
340
+ * - Preserves selection during format operations
341
+ *
342
+ * Selection Handling:
343
+ * - Tracks active formats only when text is selected
344
+ * - Resets formats when no selection exists
345
+ * - Maintains format state during dropdown operations
346
+ */
347
+ function updateActiveFormats() {
348
+ if (!editorInstance) return;
349
+
350
+ // Update activeFormats based on editor state
351
+ const newFormats = {
352
+ bold: editorInstance.isActive('bold'),
353
+ italic: editorInstance.isActive('italic'),
354
+ underline: editorInstance.isActive('underline'),
355
+ link: editorInstance.isActive('link')
356
+ };
357
+
358
+ // Only update if there's a change to prevent unnecessary rerenders
359
+ if (JSON.stringify(newFormats) !== JSON.stringify($state.snapshot(activeFormats))) {
360
+ activeFormats = newFormats;
361
+ console.log('Active formats updated:', $state.snapshot(activeFormats));
362
+ }
363
+ }
364
+
365
+ function applyFormat(format: FormatType, isAdding: boolean = true) {
366
+ if (!editorInstance) return;
367
+
368
+ try {
369
+ // Always focus first and start a chain
370
+ const chain = editorInstance.chain().focus() as any;
371
+
372
+ switch (format) {
373
+ case 'bold':
374
+ chain.toggleBold().run();
375
+ break;
376
+ case 'italic':
377
+ chain.toggleItalic().run();
378
+ break;
379
+ case 'underline':
380
+ chain.toggleUnderline().run();
381
+ break;
382
+ case 'link':
383
+ if (isAdding) {
384
+ const href = window.prompt('Enter URL');
385
+ if (href) {
386
+ chain.setLink({ href }).run();
387
+ }
388
+ } else {
389
+ chain.unsetLink().run();
390
+ }
391
+ break;
392
+ }
393
+
394
+ // Update formats after applying changes
395
+ setTimeout(() => {
396
+ updateActiveFormats();
397
+ }, 0);
398
+ } catch (error) {
399
+ console.error('Error applying format:', format, error);
400
+ }
401
+ }
402
+
403
+ function handleToggleChange(newValues: string[] | undefined): void {
404
+ if (!editorInstance) {
405
+ console.warn('No editor instance available');
406
+ return;
407
+ }
408
+
409
+ if (!newValues) {
410
+ console.warn('No new values provided');
411
+ return;
412
+ }
413
+
414
+ // Use $state.snapshot() for logging
415
+ const currentValues = $state.snapshot(toggleValues);
416
+ console.log('Toggle change:', { newValues, currentValues });
417
+
418
+ // Find which format was toggled by comparing the arrays
419
+ const added = newValues.filter(v => !currentValues.includes(v));
420
+ const removed = currentValues.filter(v => !newValues.includes(v));
421
+
422
+ // Handle the change
423
+ if (added.length > 0) {
424
+ // Find the format type for the added value
425
+ const format = Object.entries(formatMapping).find(([_, m]) => m.value === added[0])?.[0] as FormatType;
426
+ if (format) {
427
+ console.log('Applying format:', format);
428
+ applyFormat(format, true);
429
+ }
430
+ } else if (removed.length > 0) {
431
+ // Find the format type for the removed value
432
+ const format = Object.entries(formatMapping).find(([_, m]) => m.value === removed[0])?.[0] as FormatType;
433
+ if (format) {
434
+ console.log('Removing format:', format);
435
+ applyFormat(format, false);
436
+ }
437
+ }
438
+ }
439
+
440
+ // Update derived toggleValues with proper typing
441
+ const toggleValues = $derived(Object.entries(activeFormats)
442
+ .filter(([_, isActive]) => isActive)
443
+ .map(([format]) => formatMapping[format as FormatType].value)
444
+ );
445
+
446
+ // Restore handleTextStyleChange for other style changes
447
+ function handleTextStyleChange(value: string) {
448
+ if (!editorInstance) return;
449
+
450
+ try {
451
+ const { from, to } = editorInstance.state.selection;
452
+ const selectedText = editorInstance.state.doc.textBetween(from, to);
453
+
454
+ console.log('Changing style for text "%s" to:', (selectedText || 'No text selected'), value);
455
+
456
+ // Store the current scroll position and selection
457
+ const editorDom = editorInstance.view.dom;
458
+ const contentArea = editorDom.closest('.editor-content-area') as HTMLElement;
459
+ const scrollTop = contentArea ? contentArea.scrollTop : editorDom.scrollTop;
460
+ const selection = { from, to };
461
+
462
+ const chain = editorInstance.chain().focus() as any;
463
+
464
+ switch (value) {
465
+ case 'text':
466
+ chain.setNode('paragraph').run();
467
+ currentTextStyle = value;
468
+ break;
469
+ case 'h1':
470
+ case 'h2':
471
+ case 'h3':
472
+ const level = parseInt(value.charAt(1)) as TipTapEditorLevel;
473
+ chain.toggleHeading({ level }).run();
474
+ currentTextStyle = editorInstance.isActive('heading', { level }) ? value : 'text';
475
+ break;
476
+ case 'bulletList':
477
+ chain.toggleBulletList().run();
478
+ currentTextStyle = value;
479
+ break;
480
+ case 'orderedList':
481
+ chain.toggleOrderedList().run();
482
+ currentTextStyle = value;
483
+ break;
484
+ case 'checkList':
485
+ chain.toggleTaskList().run();
486
+ currentTextStyle = value;
487
+ break;
488
+ case 'blockquote':
489
+ chain.toggleBlockquote().run();
490
+ currentTextStyle = value;
491
+ break;
492
+ case 'code':
493
+ chain.toggleCode().run();
494
+ currentTextStyle = value;
495
+ break;
496
+ case 'strike':
497
+ chain.toggleStrike().run();
498
+ currentTextStyle = value;
499
+ break;
500
+ }
501
+
502
+ // Restore the scroll position and selection after the style change
503
+ requestAnimationFrame(() => {
504
+ if (contentArea) {
505
+ contentArea.scrollTop = scrollTop;
506
+ } else if (editorDom) {
507
+ editorDom.scrollTop = scrollTop;
508
+ }
509
+ // Restore selection
510
+ editorInstance?.chain()
511
+ .setTextSelection(selection)
512
+ .run();
513
+ });
514
+
515
+ updateActiveFormats();
516
+ textStyleDropdownOpen = false;
517
+ } catch (error) {
518
+ console.error('Error changing text style:', error);
519
+ }
520
+ }
521
+
522
+ function getCurrentTextStyle() {
523
+ if (!editorInstance) return 'text';
524
+
525
+ // Check for headings first
526
+ if (editorInstance.isActive('heading', { level: 1 })) return 'h1';
527
+ if (editorInstance.isActive('heading', { level: 2 })) return 'h2';
528
+ if (editorInstance.isActive('heading', { level: 3 })) return 'h3';
529
+
530
+ // Check for lists and other block-level formats
531
+ if (editorInstance.isActive('bulletList')) return 'bulletList';
532
+ if (editorInstance.isActive('orderedList')) return 'orderedList';
533
+ if (editorInstance.isActive('checkList')) return 'checkList';
534
+ if (editorInstance.isActive('blockquote')) return 'blockquote';
535
+ if (editorInstance.isActive('code')) return 'code';
536
+ if (editorInstance.isActive('strike')) return 'strike';
537
+
538
+ // If no block-level format is active, return the current text style
539
+ const marks = editorInstance.state.selection.$head.marks();
540
+ if (marks.length > 0) {
541
+ // Keep the current text style if there are inline marks
542
+ return currentTextStyle;
543
+ }
544
+
545
+ return 'text';
546
+ }
547
+ // Function to store text selection
548
+ function storeSelection() {
549
+ if (!editorInstance) return null;
550
+ const { from, to } = editorInstance.state.selection;
551
+ if (from === to) return null;
552
+ return { from, to };
553
+ }
554
+
555
+ // Function to restore text selection
556
+ function restoreSelection() {
557
+ if (!editorInstance || !selectedTextRange) return;
558
+ editorInstance.chain()
559
+ .setTextSelection({ from: selectedTextRange.from, to: selectedTextRange.to })
560
+ .run();
561
+ }
562
+
563
+ // Helper function to convert RGB to Hex
564
+ function rgbToHex(r: number, g: number, b: number): string {
565
+ const toHex = (n: number) => {
566
+ const hex = Math.round(n).toString(16);
567
+ return hex.length === 1 ? '0' + hex : hex;
568
+ };
569
+ return '#' + toHex(r) + toHex(g) + toHex(b);
570
+ }
571
+
572
+ // Update handleColorChange to use current selection if available
573
+ function handleColorChange(color: string) {
574
+ if (!editorInstance) return;
575
+
576
+ try {
577
+ // Get current selection
578
+ const { from, to } = editorInstance.state.selection;
579
+ const hasSelection = from !== to;
580
+
581
+ if (hasSelection) {
582
+ // Apply color to selection using the correct command
583
+ editorInstance.chain()
584
+ .setTextSelection({ from, to })
585
+ .setMark('textStyle', { color })
586
+ .run();
587
+
588
+ // Remove any highlight marks and restore selection visibility
589
+ editorInstance.chain()
590
+ .unsetMark('highlight')
591
+ .run();
592
+
593
+ // Remove hide-selection class
594
+ editorInstance.view.dom.classList.remove('hide-selection');
595
+
596
+ // Ensure editor has focus
597
+ editorInstance.commands.focus();
598
+ }
599
+ } catch (error) {
600
+ console.error('Error applying color:', error);
601
+ }
602
+ }
603
+
604
+ // $effect(() => {
605
+ // if (value !== $content && editorInstance) {
606
+ // console.log('External value changed:', value);
607
+ // content.set(value);
608
+ // editorInstance.commands.setContent(value);
609
+ // }
610
+ // });
611
+
612
+ // Add colorPickerOpen state
613
+ let colorPickerOpen = $state(false);
614
+
615
+ // Add function to handle color picker open/close
616
+ function handleColorPickerToggle() {
617
+ colorPickerOpen = !colorPickerOpen;
618
+ if (colorPickerOpen && selectedTextRange) {
619
+ restoreSelection();
620
+ }
621
+ }
622
+
623
+ // Update getSelectedTextColor to ensure hex format with 100% opacity
624
+ function getSelectedTextColor(): string {
625
+ if (!editorInstance) return currentColor;
626
+
627
+ const { from, to } = editorInstance.state.selection;
628
+ if (from === to) return currentColor;
629
+
630
+ // Get the color mark from the selection
631
+ const marks = editorInstance.state.doc.nodeAt(from)?.marks;
632
+ const colorMark = marks?.find(mark => mark.type.name === 'textStyle' && mark.attrs.color);
633
+
634
+ let color = colorMark?.attrs.color || currentColor;
635
+
636
+ // Convert RGB to Hex if needed
637
+ if (color.startsWith('rgb')) {
638
+ const matches = color.match(/\d+/g);
639
+ if (matches && matches.length >= 3) {
640
+ color = rgbToHex(
641
+ parseInt(matches[0]),
642
+ parseInt(matches[1]),
643
+ parseInt(matches[2])
644
+ );
645
+ }
646
+ }
647
+
648
+ // Ensure color is in hex format and has 100% opacity
649
+ if (color.length === 9) { // If it's a hex with alpha
650
+ color = color.substring(0, 7); // Remove alpha channel
651
+ }
652
+
653
+ return color;
654
+ }
655
+
656
+ // Update handleSelectionChange to properly handle color conversion
657
+ function handleSelectionChange() {
658
+ if (!editorInstance) return;
659
+
660
+ const { from, to } = editorInstance.state.selection;
661
+ const hasSelection = from !== to;
662
+
663
+ if (hasSelection) {
664
+ selectedRange = { from, to };
665
+
666
+ // Get the selected text and check for color mark
667
+ const marks = editorInstance.state.doc.nodeAt(from)?.marks || [];
668
+ const colorMark = marks.find(mark =>
669
+ mark.type.name === 'textStyle' && mark.attrs.color
670
+ );
671
+
672
+ // Update currentColor if a color mark is found
673
+ if (colorMark?.attrs.color) {
674
+ let color = colorMark.attrs.color;
675
+
676
+ // Convert RGB to Hex if needed
677
+ if (color.startsWith('rgb')) {
678
+ const matches = color.match(/\d+/g);
679
+ if (matches && matches.length >= 3) {
680
+ color = rgbToHex(
681
+ parseInt(matches[0]),
682
+ parseInt(matches[1]),
683
+ parseInt(matches[2])
684
+ );
685
+ }
686
+ }
687
+
688
+ // Remove alpha channel if present
689
+ if (color.length === 9) {
690
+ color = color.substring(0, 7);
691
+ }
692
+
693
+ currentColor = color;
694
+ }
695
+
696
+ console.log('Selection updated:', {
697
+ text: editorInstance.state.doc.textBetween(from, to),
698
+ range: { from, to },
699
+ style: currentTextStyle,
700
+ format: [
701
+ ...(editorInstance.isActive('bold') ? ['b'] : []),
702
+ ...(editorInstance.isActive('italic') ? ['i'] : []),
703
+ ...(editorInstance.isActive('underline') ? ['u'] : []),
704
+ ...(editorInstance.isActive('link') ? ['link'] : [])
705
+ ],
706
+ color: currentColor
707
+ });
708
+ } else {
709
+ selectedRange = null;
710
+ currentColor = textColor; // Reset to default color when deselecting
711
+ }
712
+ }
713
+
714
+ // Add this state to track animation
715
+ let isColorPickerAnimating = $state(false);
716
+
717
+ // Update handleColorButtonClick to ensure hex format
718
+ function handleColorButtonClick() {
719
+ if (!editorInstance) return;
720
+
721
+ const { from, to } = editorInstance.state.selection;
722
+ if (from !== to) {
723
+ selectedRange = { from, to };
724
+ isSelectionActive = true;
725
+
726
+ // Get and convert color to hex format
727
+ let selectedColor = getSelectedTextColor();
728
+ // Ensure we convert any rgb colors to hex
729
+ if (selectedColor.startsWith('rgb')) {
730
+ const matches = selectedColor.match(/\d+/g);
731
+ if (matches && matches.length >= 3) {
732
+ selectedColor = rgbToHex(
733
+ parseInt(matches[0]),
734
+ parseInt(matches[1]),
735
+ parseInt(matches[2])
736
+ );
737
+ }
738
+ }
739
+ // Remove any alpha channel
740
+ if (selectedColor.length === 9) {
741
+ selectedColor = selectedColor.substring(0, 7);
742
+ }
743
+
744
+ currentColor = selectedColor;
745
+
746
+ // Set focus to editor
747
+ editorHasFocus = true;
748
+ // editorInstance.commands.focus();
749
+
750
+ } else {
751
+ selectedRange = null;
752
+ isSelectionActive = false;
753
+ }
754
+
755
+ showColorPicker = true;
756
+ isColorPickerAnimating = true;
757
+
758
+ // console.log('Color picker toolbar opened with selection:', {
759
+ // range: selectedRange,
760
+ // color: currentColor,
761
+ // hasFocus: editorHasFocus,
762
+ // isActive
763
+ // });
764
+ }
765
+
766
+ // Update the event handler function to handle CustomEvent
767
+ function handleColorPickerInteraction(event: CustomEvent<any>) {
768
+ // Get the original MouseEvent from the detail if available
769
+ const mouseEvent = event.detail?.originalEvent as MouseEvent;
770
+ if (mouseEvent) {
771
+ // Prevent the default behavior that might clear the selection
772
+ mouseEvent.preventDefault();
773
+ mouseEvent.stopPropagation();
774
+ }
775
+
776
+ // Reapply the selection if it exists
777
+ if (selectedRange && editorInstance) {
778
+ editorInstance.chain()
779
+ .focus()
780
+ .setTextSelection(selectedRange)
781
+ .run();
782
+ }
783
+ }
784
+
785
+ // Update handleColorPickerClose function
786
+ function handleColorPickerClose() {
787
+ showColorPicker = false;
788
+ isColorPickerAnimating = false;
789
+ isSelectionActive = false;
790
+
791
+ if (editorInstance) {
792
+ // Create a chain to handle all operations
793
+ const chain = editorInstance.chain();
794
+
795
+ // Remove highlight if it exists
796
+ chain.unsetMark('highlight');
797
+
798
+ // Store current selection before cleanup
799
+ const currentSelection = editorInstance.state.selection;
800
+ const hasSelection = currentSelection.from !== currentSelection.to;
801
+
802
+ // Run the chain
803
+ chain.run();
804
+
805
+ // Remove hide-selection class
806
+ editorInstance.view.dom.classList.remove('hide-selection');
807
+
808
+ // Always focus the editor first
809
+ editorInstance.view.focus();
810
+
811
+ // Then reapply selection if it existed
812
+ if (hasSelection) {
813
+ editorInstance.commands.setTextSelection({
814
+ from: currentSelection.from,
815
+ to: currentSelection.to
816
+ });
817
+ }
818
+ }
819
+ }
820
+
821
+ // Add effect to handle clicks outside Color Select
822
+ $effect(() => {
823
+ const handleClickOutside = (event: MouseEvent) => {
824
+ const colorSelectModal = document.querySelector('[data-radix-popper-content-wrapper]');
825
+ const isClickingColorSelect = colorSelectModal?.contains(event.target as Node);
826
+
827
+ // Don't do anything if clicking within the color select modal
828
+ if (isClickingColorSelect) {
829
+ // Prevent any focus changes
830
+ event.preventDefault();
831
+ event.stopPropagation();
832
+ return;
833
+ }
834
+ };
835
+
836
+ if (isColorSelectOpen) {
837
+ document.addEventListener('mousedown', handleClickOutside);
838
+ return () => document.removeEventListener('mousedown', handleClickOutside);
839
+ }
840
+ });
841
+
842
+ /**
843
+ * Color Management System
844
+ *
845
+ * Handles text color operations:
846
+ * - Stores and restores text selection during color picker use
847
+ * - Manages color picker visibility state
848
+ * - Applies colors to selected text
849
+ * - Retrieves current text color for selected range
850
+ *
851
+ * Selection Preservation:
852
+ * - Stores selection before opening color picker
853
+ * - Restores selection after color picker closes
854
+ * - Handles edge cases when no text is selected
855
+ */
856
+
857
+ // Add this with the other function declarations in the script section
858
+ function handleToolbarInteraction(event: MouseEvent) {
859
+ const target = event.target as HTMLElement;
860
+
861
+ // If clicking toolbar elements or color picker, maintain expanded state
862
+ if (target.matches('input, button, [role="button"], [tabindex="0"]') ||
863
+ target.closest('.color-picker-container')) {
864
+ // Keep toolbar enabled since we're interacting with it
865
+ editorHasFocus = true;
866
+ return;
867
+ }
868
+
869
+ event.preventDefault();
870
+ event.stopPropagation();
871
+
872
+ // When clicking toolbar itself, maintain focus and enabled state
873
+ if (editorInstance) {
874
+ editorInstance.commands.focus();
875
+ editorHasFocus = true;
876
+ }
877
+ }
878
+
879
+ // Add this function to handle keyboard interactions
880
+ function handleToolbarKeyDown(event: KeyboardEvent) {
881
+ // Don't handle keyboard events for inputs and interactive elements
882
+ const target = event.target as HTMLElement;
883
+ if (target.matches('input, button, [role="button"], [tabindex="0"]')) {
884
+ return;
885
+ }
886
+
887
+ // Handle keyboard interactions for toolbar
888
+ if (event.key === 'Enter' || event.key === ' ') {
889
+ event.preventDefault();
890
+ handleToolbarInteraction(event as unknown as MouseEvent);
891
+ }
892
+ }
893
+
894
+ // Update the effect that maintains selection during color picker interactions
895
+ $effect(() => {
896
+ if (showColorPicker && selectedTextRange && editorInstance) {
897
+ const { from, to } = selectedTextRange;
898
+ editorInstance.chain()
899
+ .setTextSelection({ from, to })
900
+ .run();
901
+ }
902
+ });
903
+
904
+ // Add these near the top of the script section with other state declarations
905
+ let isColorSelectOpen = $state(false);
906
+
907
+ // Add handleKeyDown function
908
+ function handleKeyDown(event: KeyboardEvent) {
909
+ if (event.key === 'Escape') {
910
+ if (showColorPicker) {
911
+ event.preventDefault();
912
+ handleColorPickerClose();
913
+ }
914
+ }
915
+ }
916
+
917
+ // Update handleEditorBlur to maintain toolbar state
918
+ function handleEditorBlur(relatedTarget: Node | null) {
919
+ if (!editorInstance) return;
920
+
921
+ const colorSelectModal = document.querySelector('[data-radix-popper-content-wrapper]');
922
+ const popoverContent = document.querySelector('[role="dialog"]');
923
+ const commandMenu = document.querySelector('[cmdk-root]');
924
+
925
+ const isClickingInside = toolbarRef?.contains(relatedTarget) ||
926
+ editorInstance.view.dom.contains(relatedTarget as Node) ||
927
+ colorSelectModal?.contains(relatedTarget as Node) ||
928
+ popoverContent?.contains(relatedTarget as Node) ||
929
+ commandMenu?.contains(relatedTarget as Node);
930
+
931
+ if (!isClickingInside) {
932
+ editorHasFocus = false;
933
+ }
934
+ }
935
+
936
+ // Add this logging helper
937
+ function logTransitionState(source: string) {
938
+ // console.log(`Transition State [${source}]:`, {
939
+ // isTransitioning,
940
+ // editorHeight,
941
+ // isActive,
942
+ // editorHasFocus,
943
+ // isIdle: editorHeight === IDLE_HEIGHT
944
+ // });
945
+ }
946
+
947
+ // Add this function to handle popover state changes
948
+ function handlePopoverOpenChange(isOpen: boolean) {
949
+ textStyleDropdownOpen = isOpen;
950
+
951
+ // Ensure editor maintains focus when popover opens/closes
952
+ if (editorInstance) {
953
+ editorHasFocus = true;
954
+ editorInstance.commands.focus();
955
+ }
956
+ }
957
+
958
+ // Add this function to handle command item selection
959
+ function handleCommandSelect(value: string) {
960
+ if (!editorInstance) return;
961
+
962
+ // Change the text style to the selected value
963
+ handleTextStyleChange(value);
964
+
965
+ // Restore editor focus after command selection
966
+ setTimeout(() => {
967
+ if (!editorInstance) return;
968
+ editorHasFocus = true;
969
+ editorInstance.commands.focus();
970
+ }, 0);
971
+ }
972
+
973
+ // Add these variables to track button position
974
+ let triggerButtonRect = $state<DOMRect | null>(null);
975
+ let triggerButton: any;
976
+ let triggerButtonElement: HTMLButtonElement;
977
+
978
+ // Add function to update position using the button element directly
979
+ function updateTriggerPosition(buttonElement: HTMLButtonElement) {
980
+ triggerButtonRect = buttonElement.getBoundingClientRect();
981
+ }
982
+
983
+ // Add these state variables near the top with other state declarations
984
+ let selectedRange: { from: number, to: number } | null = $state(null);
985
+ let isSelectionActive = $state(false);
986
+
987
+ // Add this near the top of the script section with other constants
988
+ const STYLE_DROPDOWN_ID = 'text-style-dropdown';
989
+
990
+ // Add these functions near the top with other event handlers
991
+ function closeTextStyleDropdown() {
992
+ if (textStyleDropdownOpen) {
993
+ textStyleDropdownOpen = false;
994
+ // Restore focus to editor
995
+ if (!editorInstance) return;
996
+
997
+ setTimeout(() => {
998
+ if (!editorInstance) return;
999
+ editorHasFocus = true;
1000
+ editorInstance.commands.focus();
1001
+ }, 0);
1002
+ }
1003
+ }
1004
+
1005
+ function handleScroll() {
1006
+ closeTextStyleDropdown();
1007
+ }
1008
+
1009
+ // Add to lifecycle management
1010
+ onMount(() => {
1011
+ window.addEventListener('scroll', handleScroll, true);
1012
+ });
1013
+
1014
+ onDestroy(() => {
1015
+ window.removeEventListener('scroll', handleScroll, true);
1016
+ });
1017
+
1018
+ // Add cleanup function
1019
+ onDestroy(() => {
1020
+ if (editorInstance) {
1021
+ // Store the current content before unmounting
1022
+ lastKnownContent = editorInstance.getHTML();
1023
+ wasVisible = false;
1024
+
1025
+ // Clean up editor instance
1026
+ editorInstance.destroy();
1027
+ editorInstance = null;
1028
+ }
1029
+ });
1030
+
1031
+ // Update mousedown handler to remove height changes
1032
+ function handleMouseDown(e: MouseEvent) {
1033
+ if (editorInstance) {
1034
+ const target = e.target as HTMLElement;
1035
+ const isInteractive = target.matches('button, input, [role="button"], [tabindex="0"]') ||
1036
+ target.closest('button, input, [role="button"], [tabindex="0"]');
1037
+
1038
+ if (!isInteractive) {
1039
+ e.preventDefault();
1040
+ e.stopPropagation();
1041
+
1042
+ // Just focus the editor
1043
+ editorHasFocus = true;
1044
+ editorInstance.commands.focus();
1045
+ }
1046
+ }
1047
+ }
1048
+
1049
+ // Update hit zone handler to remove transitions
1050
+ function handleHitZone(e: MouseEvent) {
1051
+ e.preventDefault();
1052
+ e.stopPropagation();
1053
+ if (editorInstance) {
1054
+ editorInstance.commands.focus();
1055
+ editorHasFocus = true;
1056
+ }
1057
+ }
1058
+ </script>
1059
+
1060
+ <!-- svelte-ignore a11y_no_static_element_interactions -->
1061
+ <!-- svelte-ignore event_directive_deprecated -->
1062
+ <div
1063
+ class="text-area-input-controller {className ? className + ' ' : ''}flex flex-col"
1064
+ on:keydown={handleKeyDown}
1065
+ tabindex="-1"
1066
+ >
1067
+ <!-- Label section -->
1068
+ <div class="label-container flex items-center justify-between">
1069
+ <!-- svelte-ignore a11y_label_has_associated_control -->
1070
+ <label class="subtext text-grey5 cursor-default select-none truncate">{label}</label>
1071
+ </div>
1072
+
1073
+ <!-- Main editor container -->
1074
+ <div
1075
+ class="main-editor-container group relative w-full h-full rounded-small widgetic-colors bg-grey1 flex flex-col overflow-hidden"
1076
+ style={editorStyle}
1077
+ on:mousedown={handleMouseDown}
1078
+ >
1079
+ <!-- Toolbar at the top -->
1080
+ <div
1081
+ bind:this={toolbarRef}
1082
+ class="toolbar-container sticky top-0 left-0 right-0 h-[56px] bg-grey1 z-10 border-b border-grey2 overflow-hidden select-none"
1083
+ on:mousedown={handleToolbarInteraction}
1084
+ on:click={handleToolbarInteraction}
1085
+ on:keydown={handleToolbarKeyDown}
1086
+ role="toolbar"
1087
+ aria-label="Text formatting toolbar"
1088
+ tabindex="-1"
1089
+ >
1090
+ <div class="flex items-center flex-1 relative h-8">
1091
+ <!-- Normal toolbar items -->
1092
+ <div class={`toolbar-items-container flex items-center flex-1 absolute inset-0 transition-transform duration-&lsqb;${TRANSITION_DURATION}ms&rsqb; ease-out ${
1093
+ isColorPickerAnimating ? 'translate-y-[-200%]' :
1094
+ showColorPicker ? 'translate-y-[-200%]' :
1095
+ 'translate-y-0'
1096
+ }`}>
1097
+ <!-- Color Picker Preview Button -->
1098
+ <Button
1099
+ variant="outline"
1100
+ size="sm"
1101
+ class="color-picker-preview-button toolbar-button h-9 w-9 p-0 relative overflow-hidden mr-2"
1102
+ onclick={(e) => {
1103
+ e.stopPropagation();
1104
+ e.preventDefault();
1105
+ const button = e.currentTarget as HTMLButtonElement;
1106
+ button.blur();
1107
+ handleColorButtonClick();
1108
+ }}
1109
+ >
1110
+ <div class="toolbar-button-content flex h-full w-full items-center justify-center">
1111
+ <IconBrush
1112
+ class="!text-grey5 hover:!text-grey7"
1113
+ style="width: 20px !important; height: 20px !important; min-width: 20px !important; min-height: 20px !important; max-width: 20px !important; max-height: 20px !important; {selectedRange ? `color: ${currentColor} !important;` : ''}"
1114
+ />
1115
+ </div>
1116
+ </Button>
1117
+
1118
+ <!-- Text Formatting Toggle Group -->
1119
+ <ToggleGroup.Root
1120
+ type="multiple"
1121
+ size="sm"
1122
+ variant="outline"
1123
+ class="text-formatting-toggle-group inline-flex h-8"
1124
+ value={toggleValues}
1125
+ onValueChange={(value) => {
1126
+ if (!editorInstance) return;
1127
+ if (value === undefined) return;
1128
+ handleToggleChange(value);
1129
+ }}
1130
+ >
1131
+ {#each Object.entries(formatMapping) as [format, { value, icon: Icon }]}
1132
+ <ToggleGroup.Item
1133
+ {value}
1134
+ aria-label={`Toggle ${format}`}
1135
+ data-state={activeFormats[format as FormatType] ? 'on' : 'off'}
1136
+ class="toolbar-button {activeFormats[format as FormatType] ? 'bg-muted' : ''} {format !== 'link' ? 'mr-2' : ''}"
1137
+ >
1138
+ <div class="toolbar-button-content flex h-full w-full items-center justify-center">
1139
+ <Icon
1140
+ class="!text-grey5 hover:!text-grey7"
1141
+ style="width: 20px !important; height: 20px !important; min-width: 20px !important; min-height: 20px !important; max-width: 20px !important; max-height: 20px !important;"
1142
+ />
1143
+ </div>
1144
+ </ToggleGroup.Item>
1145
+ {/each}
1146
+ </ToggleGroup.Root>
1147
+
1148
+ <!-- Text Style Dropdown -->
1149
+ <div class="pl-2">
1150
+ <Popover.Root
1151
+ bind:open={textStyleDropdownOpen}
1152
+ onOpenChange={(e: any) => {
1153
+ handlePopoverOpenChange(e);
1154
+ textStyleDropdownOpen = e;
1155
+ }}
1156
+ >
1157
+ <Popover.Trigger>
1158
+ <Button
1159
+ bind:this={triggerButton}
1160
+ id="text-style-trigger"
1161
+ variant="outline"
1162
+ size="sm"
1163
+ class="toolbar-button h-8 w-[54px] justify-between"
1164
+ onclick={(e) => {
1165
+ e.stopPropagation();
1166
+ e.preventDefault();
1167
+
1168
+ const buttonElement = e.currentTarget as HTMLButtonElement;
1169
+ updateTriggerPosition(buttonElement);
1170
+ buttonElement.blur();
1171
+
1172
+ textStyleDropdownOpen = !textStyleDropdownOpen;
1173
+ }}
1174
+ >
1175
+ <div class="toolbar-button-content flex h-full w-full items-center justify-between px-2">
1176
+ {#if currentTextStyle === 'text'}
1177
+ <IconPilcrow class="h-4 w-4" />
1178
+ {:else}
1179
+ <span>{textOptions.find(opt => opt.value === currentTextStyle)?.label || 'Text'}</span>
1180
+ {/if}
1181
+ <ChevronDown class="h-4 w-4" />
1182
+ </div>
1183
+ </Button>
1184
+ </Popover.Trigger>
1185
+
1186
+ <Popover.Content
1187
+ class="w-[200px] p-0"
1188
+ >
1189
+ <Command.Root>
1190
+ <Command.List>
1191
+ {#each textOptions as option}
1192
+ <Command.Item
1193
+ value={option.value}
1194
+ onclick={(e: any) => {
1195
+ e.stopPropagation();
1196
+ e.preventDefault();
1197
+ handleCommandSelect(option.value);
1198
+ }}
1199
+ class="flex items-center justify-between px-2 py-1.5 text-sm hover:bg-muted cursor-pointer"
1200
+ >
1201
+ {#if option.icon}
1202
+ <div class="flex items-center gap-2">
1203
+ {#if option.icon}
1204
+ <option.icon class="h-4 w-4" />
1205
+ {/if}
1206
+ <span>{option.label}</span>
1207
+ </div>
1208
+ {:else}
1209
+ <span>{option.label}</span>
1210
+ {/if}
1211
+ {#if option.value === currentTextStyle}
1212
+ <div class="h-4 w-4">✓</div>
1213
+ {/if}
1214
+ </Command.Item>
1215
+ {/each}
1216
+ </Command.List>
1217
+ </Command.Root>
1218
+ </Popover.Content>
1219
+ </Popover.Root>
1220
+ </div>
1221
+
1222
+ <!-- List Style Dropdown -->
1223
+ <div class="pl-2">
1224
+ <Popover.Root
1225
+ bind:open={listStyleDropdownOpen}
1226
+ onOpenChange={(e: any) => {
1227
+ handlePopoverOpenChange(e);
1228
+ listStyleDropdownOpen = e;
1229
+ }}
1230
+ >
1231
+ <Popover.Trigger>
1232
+ <Button
1233
+ variant="outline"
1234
+ size="sm"
1235
+ class="toolbar-button h-8 w-[54px] justify-between"
1236
+ onclick={(e) => {
1237
+ e.stopPropagation();
1238
+ e.preventDefault();
1239
+ const buttonElement = e.currentTarget as HTMLButtonElement;
1240
+ buttonElement.blur();
1241
+ listStyleDropdownOpen = !listStyleDropdownOpen;
1242
+ }}
1243
+ >
1244
+ <div class="toolbar-button-content flex h-full w-full items-center justify-between px-2">
1245
+ {#if currentTextStyle && currentTextStyle.includes('List')}
1246
+ {@const activeListOption = listOptions.find(opt => opt.value === currentTextStyle)}
1247
+ {#if activeListOption?.icon}
1248
+ <activeListOption.icon class="h-4 w-4" />
1249
+ {/if}
1250
+ {:else}
1251
+ <IconList class="h-4 w-4" />
1252
+ {/if}
1253
+ <ChevronDown class="h-4 w-4" />
1254
+ </div>
1255
+ </Button>
1256
+ </Popover.Trigger>
1257
+
1258
+ <Popover.Content
1259
+ class="style-dropdown-container w-[300px] p-0"
1260
+ >
1261
+ <Command.Root>
1262
+ <Command.List>
1263
+ {#each listOptions as option}
1264
+ <Command.Item
1265
+ value={option.value}
1266
+ onclick={(e: any) => {
1267
+ e.stopPropagation();
1268
+ e.preventDefault();
1269
+ handleCommandSelect(option.value);
1270
+ }}
1271
+ class="flex items-center justify-between px-2 py-1.5 text-sm hover:bg-muted cursor-pointer"
1272
+ >
1273
+ <div class="flex items-center gap-2">
1274
+ {#if option.icon}
1275
+ <option.icon class="h-4 w-4" />
1276
+ {/if}
1277
+ <span>{option.label}</span>
1278
+ </div>
1279
+ {#if option.value === currentTextStyle}
1280
+ <div class="h-4 w-4">✓</div>
1281
+ {/if}
1282
+ </Command.Item>
1283
+ {/each}
1284
+ </Command.List>
1285
+ </Command.Root>
1286
+ </Popover.Content>
1287
+ </Popover.Root>
1288
+ </div>
1289
+ </div>
1290
+
1291
+ <!-- Color picker panel -->
1292
+ <div class={`color-picker-parent flex items-center gap-2 absolute inset-0 transition-transform duration-&lsqb;${TRANSITION_DURATION}ms&rsqb; ease-out ${
1293
+ isColorPickerAnimating ? 'translate-y-0' :
1294
+ showColorPicker ? 'translate-y-0' :
1295
+ 'translate-y-[200%]'
1296
+ }`}>
1297
+ <!-- Close Button First -->
1298
+ <Button
1299
+ variant="outline"
1300
+ size="sm"
1301
+ class="color-picker-close-button toolbar-button h-9 w-9 p-0 ml-4"
1302
+ onclick={(e) => {
1303
+ e.stopPropagation();
1304
+ e.preventDefault();
1305
+ const button = e.currentTarget as HTMLButtonElement;
1306
+ button.blur();
1307
+ handleColorPickerClose();
1308
+ }}
1309
+ >
1310
+ <div class="toolbar-button-content flex h-full w-full items-center justify-center">
1311
+ <X class="h-4 w-4" />
1312
+ </div>
1313
+ </Button>
1314
+
1315
+ <!-- Color Picker Container -->
1316
+ <div class="flex-1">
1317
+ <ColorPickerInputController
1318
+ bind:value={currentColor}
1319
+ onChange={(color) => {
1320
+ currentColor = color;
1321
+ handleColorChange(color);
1322
+ }}
1323
+ controllerId="text-color-picker"
1324
+ {openDropdowns}
1325
+ {toggleDropdown}
1326
+ saveRecentColors={true}
1327
+ showOpacityControls={false}
1328
+ showRing={false}
1329
+ portalTarget="text-area-portal"
1330
+ on:mousedown={handleColorPickerInteraction}
1331
+ on:mousemove={handleColorPickerInteraction}
1332
+ on:focus={() => {
1333
+ if (editorInstance && selectedRange) {
1334
+ editorInstance.chain()
1335
+ .setTextSelection(selectedRange)
1336
+ .setMark('highlight', { color: 'var(--text-background-color)' })
1337
+ .run();
1338
+ editorInstance.view.dom.classList.add('hide-selection');
1339
+ }
1340
+ }}
1341
+ on:blur={() => {
1342
+ if (editorInstance) {
1343
+ editorInstance.chain().unsetMark('highlight').run();
1344
+ }
1345
+ }}
1346
+ />
1347
+ </div>
1348
+ </div>
1349
+ </div>
1350
+ </div>
1351
+
1352
+ <!-- Scrollable content area -->
1353
+ <div class="editor-content-area h-[calc(100%-56px)] overflow-auto">
1354
+ <ShadEditor
1355
+ bind:content={value}
1356
+ showToolbar={false}
1357
+ class="shad-editor subtext {editorClass} {showColorPicker || textStyleDropdownOpen ? 'color-picking-active' : ''} {editorHasFocus ? 'editor-focused' : ''} {isSelectionActive ? 'selection-active' : ''}"
1358
+ on:mount={handleEditorMount}
1359
+ on:update={handleEditorUpdate}
1360
+ />
1361
+ </div>
1362
+ </div>
1363
+
1364
+ {#if description}
1365
+ <p class="text-sm text-muted-foreground mt-2">{description}</p>
1366
+ {/if}
1367
+ </div>
1368
+
1369
+ <style>
1370
+
1371
+ /* Update editor styles */
1372
+ :global(.ProseMirror) {
1373
+ outline: none !important;
1374
+ width: 100%;
1375
+ height: calc(100% - 56px);
1376
+ padding: var(--editor-padding-top, 0.35rem) var(--editor-padding-right, 0.35rem)
1377
+ var(--editor-padding-bottom, 0.35rem) var(--editor-padding-left, 0.35rem);
1378
+ background: transparent !important;
1379
+ border: none !important;
1380
+ border-radius: inherit;
1381
+ overflow-y: auto;
1382
+ margin-top: 56px;
1383
+ scrollbar-width: thin;
1384
+ scrollbar-color: transparent transparent;
1385
+ }
1386
+
1387
+ :global(.ProseMirror p) {
1388
+ margin: 0;
1389
+ }
1390
+
1391
+ /* Remove height management styles */
1392
+ :global(.editor-height) {
1393
+ border: none;
1394
+ overflow: auto;
1395
+ }
1396
+
1397
+ /* Keep scrollbar styles */
1398
+ :global(.ProseMirror::-webkit-scrollbar) {
1399
+ width: 6px;
1400
+ margin-right: 2px;
1401
+ }
1402
+
1403
+ :global(.ProseMirror::-webkit-scrollbar-track) {
1404
+ background-color: transparent;
1405
+ margin-right: 2px;
1406
+ }
1407
+
1408
+ :global(.ProseMirror::-webkit-scrollbar-thumb) {
1409
+ background-color: hsl(var(--border)) !important;
1410
+ border-radius: 3px;
1411
+ margin-right: 2px;
1412
+ }
1413
+
1414
+ :global(.ProseMirror::-webkit-scrollbar-thumb:hover) {
1415
+ background-color: hsl(var(--muted-foreground) / 0.6) !important;
1416
+ }
1417
+
1418
+ /* Keep typography styles */
1419
+ :global(.ProseMirror p),
1420
+ :global(.ProseMirror h1),
1421
+ :global(.ProseMirror h2),
1422
+ :global(.ProseMirror h3),
1423
+ :global(.ProseMirror li p),
1424
+ :global(.ProseMirror [data-type="taskItem"] p) {
1425
+ margin: 0;
1426
+ line-height: 1.4;
1427
+ }
1428
+
1429
+ /* Heading specific styles */
1430
+ :global(.ProseMirror h1) { font-size: 2em; font-weight: 700; }
1431
+ :global(.ProseMirror h2) { font-size: 1.5em; font-weight: 700; }
1432
+ :global(.ProseMirror h3) { font-size: 1.17em; font-weight: 700; }
1433
+
1434
+ /* List styles - Consolidated */
1435
+ :global(.ProseMirror ul),
1436
+ :global(.ProseMirror ol),
1437
+ :global(.ProseMirror [data-type="taskList"]) {
1438
+ padding-left: 1.5rem;
1439
+ margin: 0;
1440
+ line-height: 1.4;
1441
+ }
1442
+
1443
+ :global(.ProseMirror li),
1444
+ :global(.ProseMirror [data-type="taskItem"]) {
1445
+ position: relative;
1446
+ padding-left: 0.5rem;
1447
+ margin: 0;
1448
+ }
1449
+
1450
+ /* Task list specific styles */
1451
+ :global(.ProseMirror [data-type="taskList"]) {
1452
+ list-style: none;
1453
+ padding-left: 0rem !important;
1454
+ }
1455
+
1456
+ :global(.ProseMirror [data-type="taskItem"]) {
1457
+ display: flex;
1458
+ align-items: flex-start;
1459
+ padding-left: 0 !important;
1460
+ margin-left: 0 !important;
1461
+ gap: 0.5rem;
1462
+ }
1463
+
1464
+ :global(.ProseMirror [data-type="taskItem"] label) {
1465
+ display: flex;
1466
+ align-items: center;
1467
+ margin: 0;
1468
+ padding: 0;
1469
+ }
1470
+
1471
+ :global(.ProseMirror [data-type="taskItem"] input[type="checkbox"]) {
1472
+ margin: 0;
1473
+ margin-top: 0.4em;
1474
+ }
1475
+
1476
+ /* Focus states - Consolidated */
1477
+ :global(.ProseMirror),
1478
+ :global(.ProseMirror:hover),
1479
+ :global(.ProseMirror:focus),
1480
+ :global(.ProseMirror:focus-visible),
1481
+ :global(.ProseMirror:focus-within),
1482
+ :global(.ProseMirror *),
1483
+ :global(.ProseMirror * *),
1484
+ :global(.ProseMirror-focused) {
1485
+ border-color: transparent !important;
1486
+ outline: none !important;
1487
+ box-shadow: none !important;
1488
+ }
1489
+
1490
+ /* Container focus styles */
1491
+ :global(.group:focus-within) {
1492
+ outline: none !important;
1493
+ }
1494
+
1495
+ /* Toolbar styles - Consolidated */
1496
+ :global(.toolbar-button) {
1497
+ position: relative !important;
1498
+ border: 1px solid hsl(var(--border)) !important;
1499
+ transition: border-color 0.2s ease-in-out, background-color 0.2s ease-in-out !important;
1500
+ }
1501
+
1502
+ :global(.toolbar-button:hover) {
1503
+ border-color: hsl(var(--grey3)) !important;
1504
+ background-color: hsl(var(--grey1)) !important;
1505
+ }
1506
+
1507
+ :global(.toolbar-button svg) {
1508
+ transition: color 0.2s ease-in-out !important;
1509
+ }
1510
+
1511
+ :global(.toolbar-button:hover svg) {
1512
+ color: hsl(var(--grey7)) !important;
1513
+ }
1514
+
1515
+ :global(.toolbar-button-content) {
1516
+ position: relative !important;
1517
+ z-index: 2 !important;
1518
+ }
1519
+
1520
+ /* Input styles */
1521
+ :global(input.h-8),
1522
+ :global(.input-container) {
1523
+ height: 2rem !important;
1524
+ min-height: 2rem !important;
1525
+ line-height: 2rem !important;
1526
+ padding-top: 0 !important;
1527
+ padding-bottom: 0 !important;
1528
+ font-size: 0.875rem !important;
1529
+ }
1530
+
1531
+ /* Color picker styles */
1532
+ :global(.color-picker-container) {
1533
+ display: flex !important;
1534
+ align-items: center !important;
1535
+ gap: 8px !important;
1536
+ }
1537
+
1538
+ :global(.color-picker-container input[type="text"]) {
1539
+ width: 162px !important;
1540
+ min-width: 162px !important;
1541
+ }
1542
+
1543
+ :global(.color-picker-container:focus-within) {
1544
+ z-index: 10;
1545
+ }
1546
+
1547
+ :global(.color-picker-container input[type="text"]),
1548
+ :global(.color-picker-container input[type="text"]:focus),
1549
+ :global(.color-picker-container input[type="text"]:focus-visible),
1550
+ :global(.color-picker-container input[type="text"]:focus-within),
1551
+ :global(.color-picker-container input[type="text"]:hover) {
1552
+ outline: none !important;
1553
+ border: 1px solid hsl(var(--border)) !important;
1554
+ box-shadow: none !important;
1555
+ --tw-ring-offset-width: 0 !important;
1556
+ --tw-ring-offset-color: transparent !important;
1557
+ --tw-ring-color: transparent !important;
1558
+ --tw-ring-offset-shadow: none !important;
1559
+ --tw-ring-shadow: none !important;
1560
+ }
1561
+
1562
+ /* Add these styles to ensure proper positioning */
1563
+ :global(.fixed) {
1564
+ position: fixed;
1565
+ }
1566
+
1567
+ :global(.z-50) {
1568
+ z-index: 50;
1569
+ }
1570
+
1571
+ /* Update existing styles to handle portaled content */
1572
+ :global([cmdk-root]) {
1573
+ position: relative;
1574
+ background: white;
1575
+ border-radius: 0.375rem;
1576
+ overflow: hidden;
1577
+ }
1578
+
1579
+ :global([cmdk-list]) {
1580
+ max-height: 300px;
1581
+ overflow-y: auto;
1582
+ }
1583
+
1584
+ :global([cmdk-item]) {
1585
+ cursor: pointer;
1586
+ padding: 0.5rem 1rem;
1587
+ }
1588
+
1589
+ :global([cmdk-item]:hover) {
1590
+ background-color: hsl(var(--muted));
1591
+ }
1592
+
1593
+ /* Add these styles to handle the portaled color suggestions */
1594
+ :global(.color-suggestions-portal) {
1595
+ position: fixed;
1596
+ z-index: 100;
1597
+ background: white;
1598
+ border-radius: 0.375rem;
1599
+ box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
1600
+ border: 1px solid hsl(var(--border));
1601
+ }
1602
+
1603
+ /* Selection Styles */
1604
+ :global(.ProseMirror mark[data-color="#adcff5"]) {
1605
+ background-color: #b4d5fe !important;
1606
+ color: inherit !important;
1607
+ border-radius: 2px !important;
1608
+ padding: 2px 0 !important;
1609
+ margin: -2px 0 !important;
1610
+ }
1611
+
1612
+ /* Selection Styles */
1613
+ :global(.ProseMirror) {
1614
+ --selection-color: #b4d5fe;
1615
+ --text-background-color: #b4d5fe;
1616
+ }
1617
+
1618
+ /* Default selection style */
1619
+ :global(.ProseMirror ::selection) {
1620
+ background-color: var(--selection-color) !important;
1621
+ color: inherit !important;
1622
+ }
1623
+
1624
+ /* Selection style during color picker */
1625
+ :global(.ProseMirror.color-picking-active ::selection) {
1626
+ background-color: var(--selection-color) !important;
1627
+ color: inherit !important;
1628
+ }
1629
+
1630
+ /* Hide selection when text background is active */
1631
+ :global(.ProseMirror.hide-selection ::selection) {
1632
+ background-color: transparent !important;
1633
+ }
1634
+
1635
+ /* Text background highlight */
1636
+ :global(.ProseMirror mark[data-highlight]) {
1637
+ background-color: var(--text-background-color) !important;
1638
+ color: inherit !important;
1639
+ }
1640
+
1641
+ :global(.text-area) {
1642
+ height: 100% !important;
1643
+ width: 100% !important;
1644
+ border-radius: var(--radius-small) !important;
1645
+ overflow: hidden !important;
1646
+ background-color: hsl(var(--grey1)) !important;
1647
+ border: 1px solid hsl(var(--grey2));
1648
+ transition: border-color 0.2s ease !important;
1649
+ }
1650
+
1651
+ :global(.text-area .ProseMirror) {
1652
+ margin-top: 0 !important;
1653
+ padding: 8px 16px 24px 16px !important;
1654
+ background-color: transparent !important;
1655
+ cursor: text !important;
1656
+ border: none !important;
1657
+ }
1658
+
1659
+ /* Add portal border radius */
1660
+ :global(.portal-contents) {
1661
+ border-radius: var(--radius-small) !important;
1662
+ overflow: hidden !important;
1663
+ }
1664
+
1665
+ :global(.text-area .toolbar-container) {
1666
+ padding: 10px 0 !important;
1667
+ display: flex !important;
1668
+ gap: 0 !important;
1669
+ border-bottom: 1px solid hsl(var(--grey2)) !important;
1670
+ background-color: hsl(var(--grey1)) !important;
1671
+ }
1672
+
1673
+ :global(.text-area .toolbar-container button) {
1674
+ width: 36px !important;
1675
+ height: 36px !important;
1676
+ background-color: white !important;
1677
+ border: 1px solid hsl(var(--grey2)) !important;
1678
+ border-radius: var(--radius) !important;
1679
+ display: flex !important;
1680
+ align-items: center !important;
1681
+ justify-content: center !important;
1682
+ transition: all 0.2s !important;
1683
+ }
1684
+
1685
+ /* Color the brush icon based on selected color */
1686
+ :global(.text-area .color-picker-preview-button svg) {
1687
+ color: var(--selected-color, currentColor) !important;
1688
+ }
1689
+
1690
+ :global(.text-area .color-picker-preview-button:hover svg) {
1691
+ color: var(--selected-color, hsl(var(--grey7))) !important;
1692
+ }
1693
+ /* Button spacing */
1694
+ :global(.text-area .color-picker-preview-button) {
1695
+ margin-left: 16px !important;
1696
+ margin-right: 8px !important;
1697
+ }
1698
+
1699
+ :global(.text-area .text-formatting-toggle-group) {
1700
+ margin-right: 8px !important;
1701
+ display: flex !important;
1702
+ gap: 0 !important;
1703
+ }
1704
+
1705
+ :global(.text-area .text-formatting-toggle-group button) {
1706
+ margin: 0 !important;
1707
+ }
1708
+
1709
+ :global(.text-area .text-formatting-toggle-group button:not(:last-child)) {
1710
+ margin-right: 8px !important;
1711
+ }
1712
+
1713
+ :global(.text-area #text-style-trigger) {
1714
+ width: 54px !important;
1715
+ }
1716
+
1717
+ :global(.text-area .toolbar-container button:hover) {
1718
+ border-color: hsl(var(--grey2)) !important;
1719
+ }
1720
+
1721
+ :global(.text-area .toolbar-container button:active) {
1722
+ transform: scale(0.95) !important;
1723
+ }
1724
+
1725
+ :global(.text-area .toolbar-container button svg) {
1726
+ width: 20px !important;
1727
+ height: 20px !important;
1728
+ color: hsl(var(--grey5)) !important;
1729
+ transition: color 0.2s !important;
1730
+ }
1731
+
1732
+ :global(.text-area .toolbar-container button:hover svg) {
1733
+ color: hsl(var(--grey7)) !important;
1734
+ }
1735
+
1736
+ /* Style for active state */
1737
+ :global(.text-area .toolbar-container button[data-state="on"]) {
1738
+ background-color: hsl(var(--sage-green)) !important;
1739
+ border-color: hsl(var(--grey1)) !important;
1740
+ }
1741
+
1742
+ :global(.text-area .toolbar-container button[data-state="on"] svg) {
1743
+ color: white !important;
1744
+ }
1745
+
1746
+ /* Update the color picker button to show selected color */
1747
+ :global(.text-area .color-picker-preview-button) {
1748
+ position: relative !important;
1749
+ }
1750
+
1751
+ :global(.text-area .color-picker-preview-button::after) {
1752
+ display: none !important;
1753
+ }
1754
+
1755
+ /* Update icon color based on selection */
1756
+ :global(.text-area .color-picker-preview-button .toolbar-button-content svg) {
1757
+ transition: color 0.2s ease !important;
1758
+ }
1759
+
1760
+ :global(.text-area .color-picker-preview-button:hover .toolbar-button-content svg) {
1761
+ color: currentColor !important;
1762
+ }
1763
+
1764
+ /* Remove hit zone since we want clicks everywhere */
1765
+ .input-hit-zone {
1766
+ display: none;
1767
+ }
1768
+
1769
+ /* Make the main container clickable */
1770
+ .main-editor-container {
1771
+ cursor: text;
1772
+ }
1773
+
1774
+ /* Add width for list style dropdown button */
1775
+ :global(.text-area .toolbar-button.h-8) {
1776
+ width: 54px !important;
1777
+ }
1778
+
1779
+ /* Update color picker container styles */
1780
+ :global(.color-picker-parent) {
1781
+ position: absolute;
1782
+ inset: 0;
1783
+ padding: 0;
1784
+ display: flex;
1785
+ align-items: center;
1786
+ gap: 8px;
1787
+ }
1788
+
1789
+ :global(.color-picker-parent input[type="text"]) {
1790
+ width: 162px !important;
1791
+ min-width: 162px !important;
1792
+ height: 36px !important;
1793
+ min-height: 36px !important;
1794
+ }
1795
+
1796
+ :global(.color-picker-close-button) {
1797
+ width: 36px !important;
1798
+ height: 36px !important;
1799
+ min-width: 36px !important;
1800
+ min-height: 36px !important;
1801
+ }
1802
+ </style>
1803
+
1804
+