@openreachtech/furo-vue 1.3.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 (310) hide show
  1. package/LICENSE +9 -0
  2. package/README.ja.md +334 -0
  3. package/README.md +333 -0
  4. package/index.js +11 -0
  5. package/lib/assets/css/furo/0010.variables-palette-color-scale.css +298 -0
  6. package/lib/assets/css/furo/0020.variables-z-index.css +10 -0
  7. package/lib/assets/css/furo/0030.variables-semantic-color.css +178 -0
  8. package/lib/assets/css/furo/0035.variables-semantic-dimension.css +104 -0
  9. package/lib/assets/css/furo/0040.variables-component-furo.css +28 -0
  10. package/lib/assets/css/furo/0050.furo-editor-content.css +22 -0
  11. package/lib/assets/css/furo.css +15 -0
  12. package/lib/components/atoms/FuroAvatar/FuroAvatar.vue +135 -0
  13. package/lib/components/atoms/FuroAvatar/FuroAvatarContext.js +147 -0
  14. package/lib/components/atoms/FuroBadge/FuroBadge.vue +240 -0
  15. package/lib/components/atoms/FuroBadge/FuroBadgeContext.js +298 -0
  16. package/lib/components/atoms/FuroButton/ButtonEmitPayload.js +44 -0
  17. package/lib/components/atoms/FuroButton/FuroButton.vue +301 -0
  18. package/lib/components/atoms/FuroButton/FuroButtonContext.js +328 -0
  19. package/lib/components/atoms/FuroCheckbox/CheckboxEmitPayload.js +106 -0
  20. package/lib/components/atoms/FuroCheckbox/FuroCheckbox.vue +173 -0
  21. package/lib/components/atoms/FuroCheckbox/FuroCheckboxContext.js +682 -0
  22. package/lib/components/atoms/FuroEmailField/EmailFieldEmitPayload.js +74 -0
  23. package/lib/components/atoms/FuroEmailField/FuroEmailField.vue +119 -0
  24. package/lib/components/atoms/FuroEmailField/FuroEmailFieldContext.js +311 -0
  25. package/lib/components/atoms/FuroFileField/FileFieldEmitPayload.js +80 -0
  26. package/lib/components/atoms/FuroFileField/FuroFileField.vue +128 -0
  27. package/lib/components/atoms/FuroFileField/FuroFileFieldContext.js +195 -0
  28. package/lib/components/atoms/FuroIcon/FuroIcon.vue +74 -0
  29. package/lib/components/atoms/FuroIcon/FuroIconContext.js +293 -0
  30. package/lib/components/atoms/FuroIcon/FuroIconRegistry.js +617 -0
  31. package/lib/components/atoms/FuroIcon/FuroIconifyRendererContext.js +101 -0
  32. package/lib/components/atoms/FuroIcon/furoIconData.js +164 -0
  33. package/lib/components/atoms/FuroIcon/iconRenderers.js +62 -0
  34. package/lib/components/atoms/FuroIcon/icons.js +7 -0
  35. package/lib/components/atoms/FuroNumberField/FuroNumberField.vue +213 -0
  36. package/lib/components/atoms/FuroNumberField/FuroNumberFieldContext.js +442 -0
  37. package/lib/components/atoms/FuroNumberField/NumberFieldEmitPayload.js +162 -0
  38. package/lib/components/atoms/FuroPasswordField/FuroPasswordField.vue +119 -0
  39. package/lib/components/atoms/FuroPasswordField/FuroPasswordFieldContext.js +311 -0
  40. package/lib/components/atoms/FuroPasswordField/PasswordFieldEmitPayload.js +74 -0
  41. package/lib/components/atoms/FuroRadio/FuroRadio.vue +193 -0
  42. package/lib/components/atoms/FuroRadio/FuroRadioContext.js +557 -0
  43. package/lib/components/atoms/FuroRadio/RadioEmitPayload.js +72 -0
  44. package/lib/components/atoms/FuroSwitch/FuroSwitch.vue +165 -0
  45. package/lib/components/atoms/FuroSwitch/FuroSwitchContext.js +440 -0
  46. package/lib/components/atoms/FuroSwitch/SwitchEmitPayload.js +86 -0
  47. package/lib/components/atoms/FuroTextField/FuroTextField.vue +119 -0
  48. package/lib/components/atoms/FuroTextField/FuroTextFieldContext.js +311 -0
  49. package/lib/components/atoms/FuroTextField/TextFieldEmitPayload.js +83 -0
  50. package/lib/components/atoms/FuroTextarea/FuroTextarea.vue +121 -0
  51. package/lib/components/atoms/FuroTextarea/FuroTextareaContext.js +322 -0
  52. package/lib/components/atoms/FuroTextarea/TextareaEmitPayload.js +83 -0
  53. package/lib/components/atoms/FuroToggle/FuroToggle.vue +172 -0
  54. package/lib/components/atoms/FuroToggle/FuroToggleContext.js +410 -0
  55. package/lib/components/atoms/FuroToggle/ToggleEmitPayload.js +86 -0
  56. package/lib/components/molecules/FuroAccordion/AccordionEmitPayload.js +80 -0
  57. package/lib/components/molecules/FuroAccordion/FuroAccordion.vue +219 -0
  58. package/lib/components/molecules/FuroAccordion/FuroAccordionContext.js +547 -0
  59. package/lib/components/molecules/FuroAttachment/FuroAttachment.vue +376 -0
  60. package/lib/components/molecules/FuroAttachment/FuroAttachmentContext.js +628 -0
  61. package/lib/components/molecules/FuroAutocompleteField/AutocompleteEmitPayload.js +225 -0
  62. package/lib/components/molecules/FuroAutocompleteField/FuroAutocompleteField.vue +669 -0
  63. package/lib/components/molecules/FuroAutocompleteField/FuroAutocompleteFieldContext.js +1525 -0
  64. package/lib/components/molecules/FuroAvatarGroup/FuroAvatarGroup.vue +126 -0
  65. package/lib/components/molecules/FuroAvatarGroup/FuroAvatarGroupContext.js +245 -0
  66. package/lib/components/molecules/FuroBreadcrumb/FuroBreadcrumb.vue +219 -0
  67. package/lib/components/molecules/FuroBreadcrumb/FuroBreadcrumbContext.js +257 -0
  68. package/lib/components/molecules/FuroCollapsible/CollapsibleEmitPayload.js +69 -0
  69. package/lib/components/molecules/FuroCollapsible/FuroCollapsible.vue +177 -0
  70. package/lib/components/molecules/FuroCollapsible/FuroCollapsibleContext.js +389 -0
  71. package/lib/components/molecules/FuroControlBlock/FuroControlBlock.vue +149 -0
  72. package/lib/components/molecules/FuroControlBlock/FuroControlBlockContext.js +81 -0
  73. package/lib/components/molecules/FuroDatePicker/DatePickerEmitPayload.js +74 -0
  74. package/lib/components/molecules/FuroDatePicker/FuroDatePicker.vue +661 -0
  75. package/lib/components/molecules/FuroDatePicker/FuroDatePickerContext.js +1038 -0
  76. package/lib/components/molecules/FuroDateTimePicker/DateTimePickerEmitPayload.js +96 -0
  77. package/lib/components/molecules/FuroDateTimePicker/FuroDateTimePicker.vue +153 -0
  78. package/lib/components/molecules/FuroDateTimePicker/FuroDateTimePickerContext.js +840 -0
  79. package/lib/components/molecules/FuroDropdownMenu/DropdownMenuOptionSelectEmitPayload.js +100 -0
  80. package/lib/components/molecules/FuroDropdownMenu/FuroDropdownMenu.vue +348 -0
  81. package/lib/components/molecules/FuroDropdownMenu/FuroDropdownMenuContext.js +714 -0
  82. package/lib/components/molecules/FuroDropdownMenu/FuroDropdownMenuOptionRows.vue +222 -0
  83. package/lib/components/molecules/FuroEditableField/EditableEmitPayload.js +89 -0
  84. package/lib/components/molecules/FuroEditableField/FuroEditableField.vue +314 -0
  85. package/lib/components/molecules/FuroEditableField/FuroEditableFieldContext.js +1018 -0
  86. package/lib/components/molecules/FuroEditableField/processors/EditorCollisionProcessor.js +236 -0
  87. package/lib/components/molecules/FuroEditableField/processors/ObjectProcessor.js +101 -0
  88. package/lib/components/molecules/FuroEmptyState/FuroEmptyState.vue +124 -0
  89. package/lib/components/molecules/FuroEmptyState/FuroEmptyStateContext.js +42 -0
  90. package/lib/components/molecules/FuroErrorState/FuroErrorState.vue +125 -0
  91. package/lib/components/molecules/FuroErrorState/FuroErrorStateContext.js +42 -0
  92. package/lib/components/molecules/FuroPagination/FuroPagination.vue +246 -0
  93. package/lib/components/molecules/FuroPagination/FuroPaginationContext.js +489 -0
  94. package/lib/components/molecules/FuroPopover/FuroPopover.vue +171 -0
  95. package/lib/components/molecules/FuroPopover/FuroPopoverContext.js +279 -0
  96. package/lib/components/molecules/FuroScrollArea/FuroScrollArea.vue +181 -0
  97. package/lib/components/molecules/FuroScrollArea/FuroScrollAreaContext.js +149 -0
  98. package/lib/components/molecules/FuroSearchField/FuroSearchField.vue +155 -0
  99. package/lib/components/molecules/FuroSearchField/FuroSearchFieldContext.js +231 -0
  100. package/lib/components/molecules/FuroSectionHeader/FuroSectionHeader.vue +140 -0
  101. package/lib/components/molecules/FuroSectionHeader/FuroSectionHeaderContext.js +204 -0
  102. package/lib/components/molecules/FuroSelect/FuroSelect.vue +474 -0
  103. package/lib/components/molecules/FuroSelect/FuroSelectContext.js +743 -0
  104. package/lib/components/molecules/FuroSelect/SelectEmitPayload.js +181 -0
  105. package/lib/components/molecules/FuroSeparator/FuroSeparator.vue +88 -0
  106. package/lib/components/molecules/FuroSeparator/FuroSeparatorContext.js +40 -0
  107. package/lib/components/molecules/FuroSkeleton/FuroSkeleton.vue +101 -0
  108. package/lib/components/molecules/FuroSkeleton/FuroSkeletonContext.js +82 -0
  109. package/lib/components/molecules/FuroSplitter/FuroSplitter.vue +237 -0
  110. package/lib/components/molecules/FuroSplitter/FuroSplitterContext.js +429 -0
  111. package/lib/components/molecules/FuroSplitter/SplitterEmitPayload.js +69 -0
  112. package/lib/components/molecules/FuroTimeField/FuroTimeField.vue +251 -0
  113. package/lib/components/molecules/FuroTimeField/FuroTimeFieldContext.js +1138 -0
  114. package/lib/components/molecules/FuroTimeField/TimeFieldEmitPayload.js +74 -0
  115. package/lib/components/molecules/FuroToggleGroup/FuroToggleGroup.vue +211 -0
  116. package/lib/components/molecules/FuroToggleGroup/FuroToggleGroupContext.js +551 -0
  117. package/lib/components/molecules/FuroToggleGroup/ToggleGroupEmitPayload.js +80 -0
  118. package/lib/components/molecules/FuroToolBar/FuroToolBar.vue +91 -0
  119. package/lib/components/molecules/FuroToolBar/FuroToolBarContext.js +75 -0
  120. package/lib/components/molecules/FuroTooltip/FuroTooltip.vue +183 -0
  121. package/lib/components/molecules/FuroTooltip/FuroTooltipContext.js +290 -0
  122. package/lib/components/organisms/FuroAlertDialog/FuroAlertDialog.vue +324 -0
  123. package/lib/components/organisms/FuroAlertDialog/FuroAlertDialogContext.js +608 -0
  124. package/lib/components/organisms/FuroDialog/FuroDialog.vue +362 -0
  125. package/lib/components/organisms/FuroDialog/FuroDialogContext.js +535 -0
  126. package/lib/components/organisms/FuroDrawer/FuroDrawer.vue +618 -0
  127. package/lib/components/organisms/FuroDrawer/FuroDrawerContext.js +676 -0
  128. package/lib/components/organisms/FuroEditor/FuroEditor.vue +613 -0
  129. package/lib/components/organisms/FuroEditor/FuroEditorCommandContext.js +420 -0
  130. package/lib/components/organisms/FuroEditor/FuroEditorContext.js +2048 -0
  131. package/lib/components/organisms/FuroEditor/FuroEditorMentionMenu.vue +120 -0
  132. package/lib/components/organisms/FuroEditor/FuroEditorMentionMenuContext.js +261 -0
  133. package/lib/components/organisms/FuroEditor/PreserveNewlinesPasteExtension.js +267 -0
  134. package/lib/components/organisms/FuroEditor/build-editor-extensions.js +97 -0
  135. package/lib/components/organisms/FuroEditor/mention-suggestion.js +320 -0
  136. package/lib/components/organisms/FuroEditor/paste-link-segments.js +124 -0
  137. package/lib/components/organisms/FuroSidebar/FuroSidebar.vue +473 -0
  138. package/lib/components/organisms/FuroSidebar/FuroSidebarContext.js +1014 -0
  139. package/lib/components/organisms/FuroStepper/FuroStepper.vue +345 -0
  140. package/lib/components/organisms/FuroStepper/FuroStepperContext.js +481 -0
  141. package/lib/components/organisms/FuroTable/FuroTable.vue +707 -0
  142. package/lib/components/organisms/FuroTable/FuroTableContext.js +1965 -0
  143. package/lib/components/organisms/FuroTable/FuroTableRowItemContext.js +111 -0
  144. package/lib/components/organisms/FuroTabs/FuroTabs.vue +403 -0
  145. package/lib/components/organisms/FuroTabs/FuroTabsContext.js +846 -0
  146. package/lib/components/organisms/FuroToast/FuroToast.vue +624 -0
  147. package/lib/components/organisms/FuroToast/FuroToastContext.js +1089 -0
  148. package/lib/components/organisms/FuroToast/FuroToaster.vue +44 -0
  149. package/lib/components/organisms/FuroToast/FuroToasterContext.js +113 -0
  150. package/lib/components/organisms/FuroToast/ToastQueue.js +325 -0
  151. package/lib/components/organisms/FuroToast/toast.js +64 -0
  152. package/lib/contexts/BaseFuroContext.js +318 -0
  153. package/lib/contexts/BaseFuroContextAccessor.js +47 -0
  154. package/lib/emit-payload/BaseEmitPayload.js +91 -0
  155. package/lib/index.components.js +61 -0
  156. package/lib/index.core.js +18 -0
  157. package/lib/tools/TimerClerk.js +75 -0
  158. package/nuxt/index.js +109 -0
  159. package/nuxt/runtime/furo-icon-renderer.js +24 -0
  160. package/package.json +137 -0
  161. package/public/furo-vue/components.json +4619 -0
  162. package/public/furo-vue/llms.txt +66 -0
  163. package/types/furo-nuxt.d.ts +48 -0
  164. package/types/generated/components/atoms/FuroAvatar/FuroAvatar.vue.d.ts +74 -0
  165. package/types/generated/components/atoms/FuroAvatar/FuroAvatarContext.d.ts +82 -0
  166. package/types/generated/components/atoms/FuroBadge/FuroBadge.vue.d.ts +12 -0
  167. package/types/generated/components/atoms/FuroBadge/FuroBadgeContext.d.ts +160 -0
  168. package/types/generated/components/atoms/FuroButton/ButtonEmitPayload.d.ts +19 -0
  169. package/types/generated/components/atoms/FuroButton/FuroButton.vue.d.ts +53 -0
  170. package/types/generated/components/atoms/FuroButton/FuroButtonContext.d.ts +156 -0
  171. package/types/generated/components/atoms/FuroCheckbox/CheckboxEmitPayload.d.ts +55 -0
  172. package/types/generated/components/atoms/FuroCheckbox/FuroCheckbox.vue.d.ts +87 -0
  173. package/types/generated/components/atoms/FuroCheckbox/FuroCheckboxContext.d.ts +342 -0
  174. package/types/generated/components/atoms/FuroEmailField/EmailFieldEmitPayload.d.ts +46 -0
  175. package/types/generated/components/atoms/FuroEmailField/FuroEmailField.vue.d.ts +458 -0
  176. package/types/generated/components/atoms/FuroEmailField/FuroEmailFieldContext.d.ts +161 -0
  177. package/types/generated/components/atoms/FuroFileField/FileFieldEmitPayload.d.ts +46 -0
  178. package/types/generated/components/atoms/FuroFileField/FuroFileField.vue.d.ts +454 -0
  179. package/types/generated/components/atoms/FuroFileField/FuroFileFieldContext.d.ts +109 -0
  180. package/types/generated/components/atoms/FuroIcon/FuroIcon.vue.d.ts +14 -0
  181. package/types/generated/components/atoms/FuroIcon/FuroIconContext.d.ts +152 -0
  182. package/types/generated/components/atoms/FuroIcon/FuroIconRegistry.d.ts +457 -0
  183. package/types/generated/components/atoms/FuroIcon/FuroIconifyRendererContext.d.ts +65 -0
  184. package/types/generated/components/atoms/FuroIcon/furoIconData.d.ts +154 -0
  185. package/types/generated/components/atoms/FuroIcon/iconRenderers.d.ts +6 -0
  186. package/types/generated/components/atoms/FuroIcon/icons.d.ts +6 -0
  187. package/types/generated/components/atoms/FuroNumberField/FuroNumberField.vue.d.ts +500 -0
  188. package/types/generated/components/atoms/FuroNumberField/FuroNumberFieldContext.d.ts +243 -0
  189. package/types/generated/components/atoms/FuroNumberField/NumberFieldEmitPayload.d.ts +74 -0
  190. package/types/generated/components/atoms/FuroPasswordField/FuroPasswordField.vue.d.ts +458 -0
  191. package/types/generated/components/atoms/FuroPasswordField/FuroPasswordFieldContext.d.ts +161 -0
  192. package/types/generated/components/atoms/FuroPasswordField/PasswordFieldEmitPayload.d.ts +46 -0
  193. package/types/generated/components/atoms/FuroRadio/FuroRadio.vue.d.ts +96 -0
  194. package/types/generated/components/atoms/FuroRadio/FuroRadioContext.d.ts +290 -0
  195. package/types/generated/components/atoms/FuroRadio/RadioEmitPayload.d.ts +39 -0
  196. package/types/generated/components/atoms/FuroSwitch/FuroSwitch.vue.d.ts +74 -0
  197. package/types/generated/components/atoms/FuroSwitch/FuroSwitchContext.d.ts +218 -0
  198. package/types/generated/components/atoms/FuroSwitch/SwitchEmitPayload.d.ts +42 -0
  199. package/types/generated/components/atoms/FuroTextField/FuroTextField.vue.d.ts +458 -0
  200. package/types/generated/components/atoms/FuroTextField/FuroTextFieldContext.d.ts +161 -0
  201. package/types/generated/components/atoms/FuroTextField/TextFieldEmitPayload.d.ts +52 -0
  202. package/types/generated/components/atoms/FuroTextarea/FuroTextarea.vue.d.ts +422 -0
  203. package/types/generated/components/atoms/FuroTextarea/FuroTextareaContext.d.ts +161 -0
  204. package/types/generated/components/atoms/FuroTextarea/TextareaEmitPayload.d.ts +52 -0
  205. package/types/generated/components/atoms/FuroToggle/FuroToggle.vue.d.ts +51 -0
  206. package/types/generated/components/atoms/FuroToggle/FuroToggleContext.d.ts +191 -0
  207. package/types/generated/components/atoms/FuroToggle/ToggleEmitPayload.d.ts +42 -0
  208. package/types/generated/components/molecules/FuroAccordion/AccordionEmitPayload.d.ts +41 -0
  209. package/types/generated/components/molecules/FuroAccordion/FuroAccordion.vue.d.ts +133 -0
  210. package/types/generated/components/molecules/FuroAccordion/FuroAccordionContext.d.ts +279 -0
  211. package/types/generated/components/molecules/FuroAttachment/FuroAttachment.vue.d.ts +79 -0
  212. package/types/generated/components/molecules/FuroAttachment/FuroAttachmentContext.d.ts +363 -0
  213. package/types/generated/components/molecules/FuroAutocompleteField/AutocompleteEmitPayload.d.ts +129 -0
  214. package/types/generated/components/molecules/FuroAutocompleteField/FuroAutocompleteField.vue.d.ts +751 -0
  215. package/types/generated/components/molecules/FuroAutocompleteField/FuroAutocompleteFieldContext.d.ts +722 -0
  216. package/types/generated/components/molecules/FuroAvatarGroup/FuroAvatarGroup.vue.d.ts +84 -0
  217. package/types/generated/components/molecules/FuroAvatarGroup/FuroAvatarGroupContext.d.ts +150 -0
  218. package/types/generated/components/molecules/FuroBreadcrumb/FuroBreadcrumb.vue.d.ts +24 -0
  219. package/types/generated/components/molecules/FuroBreadcrumb/FuroBreadcrumbContext.d.ts +152 -0
  220. package/types/generated/components/molecules/FuroCollapsible/CollapsibleEmitPayload.d.ts +36 -0
  221. package/types/generated/components/molecules/FuroCollapsible/FuroCollapsible.vue.d.ts +98 -0
  222. package/types/generated/components/molecules/FuroCollapsible/FuroCollapsibleContext.d.ts +191 -0
  223. package/types/generated/components/molecules/FuroControlBlock/FuroControlBlock.vue.d.ts +30 -0
  224. package/types/generated/components/molecules/FuroControlBlock/FuroControlBlockContext.d.ts +57 -0
  225. package/types/generated/components/molecules/FuroDatePicker/DatePickerEmitPayload.d.ts +41 -0
  226. package/types/generated/components/molecules/FuroDatePicker/FuroDatePicker.vue.d.ts +456 -0
  227. package/types/generated/components/molecules/FuroDatePicker/FuroDatePickerContext.d.ts +516 -0
  228. package/types/generated/components/molecules/FuroDateTimePicker/DateTimePickerEmitPayload.d.ts +55 -0
  229. package/types/generated/components/molecules/FuroDateTimePicker/FuroDateTimePicker.vue.d.ts +1365 -0
  230. package/types/generated/components/molecules/FuroDateTimePicker/FuroDateTimePickerContext.d.ts +435 -0
  231. package/types/generated/components/molecules/FuroDropdownMenu/DropdownMenuOptionSelectEmitPayload.d.ts +48 -0
  232. package/types/generated/components/molecules/FuroDropdownMenu/FuroDropdownMenu.vue.d.ts +773 -0
  233. package/types/generated/components/molecules/FuroDropdownMenu/FuroDropdownMenuContext.d.ts +380 -0
  234. package/types/generated/components/molecules/FuroDropdownMenu/FuroDropdownMenuOptionRows.vue.d.ts +261 -0
  235. package/types/generated/components/molecules/FuroEditableField/EditableEmitPayload.d.ts +39 -0
  236. package/types/generated/components/molecules/FuroEditableField/FuroEditableField.vue.d.ts +646 -0
  237. package/types/generated/components/molecules/FuroEditableField/FuroEditableFieldContext.d.ts +477 -0
  238. package/types/generated/components/molecules/FuroEditableField/processors/EditorCollisionProcessor.d.ts +121 -0
  239. package/types/generated/components/molecules/FuroEditableField/processors/ObjectProcessor.d.ts +45 -0
  240. package/types/generated/components/molecules/FuroEmptyState/FuroEmptyState.vue.d.ts +21 -0
  241. package/types/generated/components/molecules/FuroEmptyState/FuroEmptyStateContext.d.ts +32 -0
  242. package/types/generated/components/molecules/FuroErrorState/FuroErrorState.vue.d.ts +21 -0
  243. package/types/generated/components/molecules/FuroErrorState/FuroErrorStateContext.d.ts +32 -0
  244. package/types/generated/components/molecules/FuroPagination/FuroPagination.vue.d.ts +534 -0
  245. package/types/generated/components/molecules/FuroPagination/FuroPaginationContext.d.ts +271 -0
  246. package/types/generated/components/molecules/FuroPopover/FuroPopover.vue.d.ts +513 -0
  247. package/types/generated/components/molecules/FuroPopover/FuroPopoverContext.d.ts +160 -0
  248. package/types/generated/components/molecules/FuroScrollArea/FuroScrollArea.vue.d.ts +124 -0
  249. package/types/generated/components/molecules/FuroScrollArea/FuroScrollAreaContext.d.ts +86 -0
  250. package/types/generated/components/molecules/FuroSearchField/FuroSearchField.vue.d.ts +487 -0
  251. package/types/generated/components/molecules/FuroSearchField/FuroSearchFieldContext.d.ts +137 -0
  252. package/types/generated/components/molecules/FuroSectionHeader/FuroSectionHeader.vue.d.ts +49 -0
  253. package/types/generated/components/molecules/FuroSectionHeader/FuroSectionHeaderContext.d.ts +121 -0
  254. package/types/generated/components/molecules/FuroSelect/FuroSelect.vue.d.ts +719 -0
  255. package/types/generated/components/molecules/FuroSelect/FuroSelectContext.d.ts +402 -0
  256. package/types/generated/components/molecules/FuroSelect/SelectEmitPayload.d.ts +102 -0
  257. package/types/generated/components/molecules/FuroSeparator/FuroSeparator.vue.d.ts +39 -0
  258. package/types/generated/components/molecules/FuroSeparator/FuroSeparatorContext.d.ts +31 -0
  259. package/types/generated/components/molecules/FuroSkeleton/FuroSkeleton.vue.d.ts +27 -0
  260. package/types/generated/components/molecules/FuroSkeleton/FuroSkeletonContext.d.ts +61 -0
  261. package/types/generated/components/molecules/FuroSplitter/FuroSplitter.vue.d.ts +139 -0
  262. package/types/generated/components/molecules/FuroSplitter/FuroSplitterContext.d.ts +226 -0
  263. package/types/generated/components/molecules/FuroSplitter/SplitterEmitPayload.d.ts +41 -0
  264. package/types/generated/components/molecules/FuroTimeField/FuroTimeField.vue.d.ts +498 -0
  265. package/types/generated/components/molecules/FuroTimeField/FuroTimeFieldContext.d.ts +555 -0
  266. package/types/generated/components/molecules/FuroTimeField/TimeFieldEmitPayload.d.ts +41 -0
  267. package/types/generated/components/molecules/FuroToggleGroup/FuroToggleGroup.vue.d.ts +70 -0
  268. package/types/generated/components/molecules/FuroToggleGroup/FuroToggleGroupContext.d.ts +270 -0
  269. package/types/generated/components/molecules/FuroToggleGroup/ToggleGroupEmitPayload.d.ts +41 -0
  270. package/types/generated/components/molecules/FuroToolBar/FuroToolBar.vue.d.ts +30 -0
  271. package/types/generated/components/molecules/FuroToolBar/FuroToolBarContext.d.ts +48 -0
  272. package/types/generated/components/molecules/FuroTooltip/FuroTooltip.vue.d.ts +524 -0
  273. package/types/generated/components/molecules/FuroTooltip/FuroTooltipContext.d.ts +166 -0
  274. package/types/generated/components/organisms/FuroAlertDialog/FuroAlertDialog.vue.d.ts +651 -0
  275. package/types/generated/components/organisms/FuroAlertDialog/FuroAlertDialogContext.d.ts +299 -0
  276. package/types/generated/components/organisms/FuroDialog/FuroDialog.vue.d.ts +583 -0
  277. package/types/generated/components/organisms/FuroDialog/FuroDialogContext.d.ts +264 -0
  278. package/types/generated/components/organisms/FuroDrawer/FuroDrawer.vue.d.ts +635 -0
  279. package/types/generated/components/organisms/FuroDrawer/FuroDrawerContext.d.ts +347 -0
  280. package/types/generated/components/organisms/FuroEditor/FuroEditor.vue.d.ts +2487 -0
  281. package/types/generated/components/organisms/FuroEditor/FuroEditorCommandContext.d.ts +192 -0
  282. package/types/generated/components/organisms/FuroEditor/FuroEditorContext.d.ts +904 -0
  283. package/types/generated/components/organisms/FuroEditor/FuroEditorMentionMenu.vue.d.ts +43 -0
  284. package/types/generated/components/organisms/FuroEditor/FuroEditorMentionMenuContext.d.ts +159 -0
  285. package/types/generated/components/organisms/FuroEditor/PreserveNewlinesPasteExtension.d.ts +43 -0
  286. package/types/generated/components/organisms/FuroEditor/build-editor-extensions.d.ts +22 -0
  287. package/types/generated/components/organisms/FuroEditor/mention-suggestion.d.ts +25 -0
  288. package/types/generated/components/organisms/FuroEditor/paste-link-segments.d.ts +31 -0
  289. package/types/generated/components/organisms/FuroSidebar/FuroSidebar.vue.d.ts +898 -0
  290. package/types/generated/components/organisms/FuroSidebar/FuroSidebarContext.d.ts +596 -0
  291. package/types/generated/components/organisms/FuroStepper/FuroStepper.vue.d.ts +589 -0
  292. package/types/generated/components/organisms/FuroStepper/FuroStepperContext.d.ts +266 -0
  293. package/types/generated/components/organisms/FuroTable/FuroTable.vue.d.ts +528 -0
  294. package/types/generated/components/organisms/FuroTable/FuroTableContext.d.ts +1035 -0
  295. package/types/generated/components/organisms/FuroTable/FuroTableRowItemContext.d.ts +62 -0
  296. package/types/generated/components/organisms/FuroTabs/FuroTabs.vue.d.ts +515 -0
  297. package/types/generated/components/organisms/FuroTabs/FuroTabsContext.d.ts +438 -0
  298. package/types/generated/components/organisms/FuroToast/FuroToast.vue.d.ts +725 -0
  299. package/types/generated/components/organisms/FuroToast/FuroToastContext.d.ts +543 -0
  300. package/types/generated/components/organisms/FuroToast/FuroToaster.vue.d.ts +729 -0
  301. package/types/generated/components/organisms/FuroToast/FuroToasterContext.d.ts +62 -0
  302. package/types/generated/components/organisms/FuroToast/ToastQueue.d.ts +201 -0
  303. package/types/generated/components/organisms/FuroToast/toast.d.ts +23 -0
  304. package/types/generated/contexts/BaseFuroContext.d.ts +207 -0
  305. package/types/generated/contexts/BaseFuroContextAccessor.d.ts +28 -0
  306. package/types/generated/emit-payload/BaseEmitPayload.d.ts +62 -0
  307. package/types/generated/index.components.d.ts +50 -0
  308. package/types/generated/index.core.d.ts +6 -0
  309. package/types/generated/tools/TimerClerk.d.ts +50 -0
  310. package/types/index.d.ts +12 -0
@@ -0,0 +1,2048 @@
1
+ import {
2
+ reactive,
3
+ nextTick,
4
+ onMounted,
5
+ } from 'vue'
6
+
7
+ import {
8
+ marked,
9
+ } from 'marked'
10
+
11
+ import BaseFuroContext from '../../../contexts/BaseFuroContext.js'
12
+
13
+ import FuroEditorCommandContext from './FuroEditorCommandContext.js'
14
+
15
+ const DEFAULT_LABELS = {
16
+ normalText: 'Normal text',
17
+ heading1: 'Heading 1',
18
+ heading2: 'Heading 2',
19
+ heading3: 'Heading 3',
20
+ heading4: 'Heading 4',
21
+ colorDefault: 'Default',
22
+ colorRed: 'Red',
23
+ colorBlue: 'Blue',
24
+ colorGreen: 'Green',
25
+ colorOrange: 'Orange',
26
+ colorPurple: 'Purple',
27
+ alignLeft: 'Align left',
28
+ alignCenter: 'Align center',
29
+ alignRight: 'Align right',
30
+ alignJustify: 'Justify',
31
+ heading: 'Text style',
32
+ textColor: 'Text color',
33
+ textAlign: 'Text alignment',
34
+ bold: 'Bold',
35
+ italic: 'Italic',
36
+ underline: 'Underline',
37
+ strike: 'Strikethrough',
38
+ bullets: 'Bullet list',
39
+ numbers: 'Numbered list',
40
+ blockquote: 'Quote',
41
+ link: 'Link',
42
+ mention: 'Mention',
43
+ image: 'Insert image',
44
+ overflow: 'More options',
45
+ linkDialogTitle: 'Add link',
46
+ linkUrlLabel: 'Link',
47
+ linkDisplayTextLabel: 'Display text (optional)',
48
+ linkSave: 'Save',
49
+ linkCancel: 'Cancel',
50
+ }
51
+
52
+ const OVERFLOW_TOOLBAR_NAME = 'overflow'
53
+
54
+ const FEATURE_TOOLBAR_NAMES = [
55
+ 'image',
56
+ 'mention',
57
+ ]
58
+
59
+ const COLOR_OPTION_PREFIX = 'color:'
60
+
61
+ const COLOR_VALUES = {
62
+ red: '#ef4444',
63
+ blue: '#3b82f6',
64
+ green: '#10b981',
65
+ orange: '#f97316',
66
+ purple: '#8b5cf6',
67
+ }
68
+
69
+ const MARKDOWN_PATTERNS = [
70
+ /^#{1,6}\s/u,
71
+ /\*\*.*\*\*/u,
72
+ /\*.*\*/u,
73
+ /\[.*\]\(.*\)/u,
74
+ /^-\s/u,
75
+ /```.*```/su,
76
+ ]
77
+
78
+ /**
79
+ * Context for the FuroEditor organism.
80
+ *
81
+ * Owns the editor lifecycle, the value/attachment surface, the formatting toolbar
82
+ * model, and the link dialog. Selection and command execution are delegated to a
83
+ * {@link FuroEditorCommandContext} sub-context. Paste behavior (markdown detection,
84
+ * font-style stripping, newline preservation) is wired onto the editor on mount.
85
+ *
86
+ * Domain concerns (mention search, attachment uploads) are exposed as adapter
87
+ * callbacks and slots — this component never touches GraphQL or domain schemas.
88
+ *
89
+ * @extends {BaseFuroContext<null, FuroEditorContextParams['props'], string>}
90
+ */
91
+ export default class FuroEditorContext extends BaseFuroContext {
92
+ /**
93
+ * Constructor.
94
+ *
95
+ * @param {FuroEditorContextParams} params - Parameters of this constructor.
96
+ */
97
+ constructor ({
98
+ props,
99
+ componentContext,
100
+
101
+ reactiveHash,
102
+ editorRef,
103
+ }) {
104
+ super({
105
+ props,
106
+ componentContext,
107
+ })
108
+
109
+ this.reactiveHash = reactiveHash
110
+ this.editorRef = editorRef
111
+ this.commandContext = FuroEditorCommandContext.create({
112
+ editorRef,
113
+ })
114
+
115
+ /** @type {{ from: number, to: number } | null} */
116
+ this.linkRange = null
117
+ }
118
+
119
+ /**
120
+ * Factory method to create a new instance of this class.
121
+ *
122
+ * @template {X extends typeof FuroEditorContext ? X : never} T, X
123
+ * @override
124
+ * @param {FuroEditorContextFactoryParams} params - Parameters of this factory method.
125
+ * @returns {InstanceType<T>} - New instance of this class.
126
+ * @this {T}
127
+ */
128
+ static create ({
129
+ props,
130
+ componentContext,
131
+
132
+ editorRef,
133
+ reactiveHash = this.buildReactiveHash(),
134
+ }) {
135
+ return /** @type {InstanceType<T>} */ (
136
+ new this({
137
+ props,
138
+ componentContext,
139
+
140
+ reactiveHash,
141
+ editorRef,
142
+ })
143
+ )
144
+ }
145
+
146
+ /** @override */
147
+ static get EMIT_EVENT_NAME () {
148
+ return {
149
+ UPDATE_VALUE: 'update:value',
150
+ CHANGE_VALUE: 'change-value',
151
+ COMMIT_VALUE: 'commit-value',
152
+ UPDATE_ATTACHED_FILES: 'update:attachedFiles',
153
+ ADD_MENTION: 'mention:add',
154
+ ADD_FILES: 'files:add',
155
+ IMAGE_ERROR: 'image:error',
156
+ }
157
+ }
158
+
159
+ /**
160
+ * Build reactive hash.
161
+ *
162
+ * @override
163
+ * @returns {FuroEditorContextParams['reactiveHash']}
164
+ */
165
+ static buildReactiveHash () {
166
+ return {
167
+ statusReactive: reactive({
168
+ isLinkDialogOpen: false,
169
+ }),
170
+ addLinkReactive: reactive({
171
+ url: '',
172
+ displayText: '',
173
+ }),
174
+ }
175
+ }
176
+
177
+ /**
178
+ * get: default toolbar labels.
179
+ *
180
+ * @returns {typeof DEFAULT_LABELS} - Default labels.
181
+ */
182
+ static get DEFAULT_LABELS () {
183
+ return DEFAULT_LABELS
184
+ }
185
+
186
+ /**
187
+ * Build the option object for the `useEditor()` composable.
188
+ *
189
+ * Keeps all editor-assembly logic out of the component's `setup()`: the SFC only
190
+ * passes the result to `useEditor()` and hands the ref to {@link create}.
191
+ *
192
+ * The Tiptap builders are injected (not imported) so this context module — and the
193
+ * unit tests that load it — stay free of the editor runtime.
194
+ *
195
+ * @param {{
196
+ * props: { parcel?: FuroEditorParcel | null }
197
+ * componentContext: import('vue').SetupContext<*>
198
+ * buildExtensions: typeof import('./build-editor-extensions.js').buildEditorExtensions
199
+ * createMention: typeof import('./mention-suggestion.js').createMentionExtension
200
+ * }} params - Parameters of this method.
201
+ * @returns {Partial<import('@tiptap/core').EditorOptions>} - useEditor() options.
202
+ */
203
+ static buildEditorOptions ({
204
+ props,
205
+ componentContext,
206
+ buildExtensions,
207
+ createMention,
208
+ }) {
209
+ const parcel = props.parcel
210
+ ?? {}
211
+
212
+ const mentionExtension = this.buildMentionExtension({
213
+ parcel,
214
+ componentContext,
215
+ createMention,
216
+ })
217
+
218
+ const placeholder = parcel.placeholder
219
+ ?? ''
220
+ const content = parcel.value
221
+ ?? ''
222
+
223
+ const extensions = buildExtensions({
224
+ placeholder,
225
+ enableImage: Boolean(parcel.image?.uploadImage),
226
+ mentionExtension,
227
+ })
228
+
229
+ const attributes = parcel.contentAttributes
230
+ ?? {}
231
+
232
+ return {
233
+ editable: !parcel.readOnly && parcel.appearance !== 'plain',
234
+ content,
235
+ autofocus: parcel.autofocus ?? false,
236
+ extensions,
237
+ editorProps: {
238
+ attributes,
239
+ },
240
+ }
241
+ }
242
+
243
+ /**
244
+ * Build the mention extension via the injected factory.
245
+ *
246
+ * Always registers the mention node so saved chips render in any editor (including
247
+ * read-only / display). The live `@` popup is wired only when a search adapter is
248
+ * supplied.
249
+ *
250
+ * @param {{
251
+ * parcel: FuroEditorParcel
252
+ * componentContext: import('vue').SetupContext<*>
253
+ * createMention: typeof import('./mention-suggestion.js').createMentionExtension
254
+ * }} params - Parameters of this method.
255
+ * @returns {import('@tiptap/core').AnyExtension} - Mention extension.
256
+ */
257
+ static buildMentionExtension ({
258
+ parcel,
259
+ componentContext,
260
+ createMention,
261
+ }) {
262
+ const searchMentions = parcel.mention?.searchMentions
263
+
264
+ if (!searchMentions) {
265
+ return createMention()
266
+ }
267
+
268
+ return createMention({
269
+ searchMentions,
270
+ onMentionAdd: mention => componentContext.emit(
271
+ this.EMIT_EVENT_NAME.ADD_MENTION,
272
+ mention
273
+ ),
274
+ })
275
+ }
276
+
277
+ /**
278
+ * get: live editor instance, or null before it mounts.
279
+ *
280
+ * @returns {import('@tiptap/vue-3').Editor | null} - Editor instance.
281
+ */
282
+ get editor () {
283
+ return this.editorRef.value
284
+ ?? null
285
+ }
286
+
287
+ /**
288
+ * get: the command sub-context.
289
+ *
290
+ * @returns {FuroEditorCommandContext} - Command facade.
291
+ */
292
+ get command () {
293
+ return this.commandContext
294
+ }
295
+
296
+ /**
297
+ * get: status reactive (link dialog open state).
298
+ *
299
+ * @returns {{ isLinkDialogOpen: boolean }} - Status reactive.
300
+ */
301
+ get statusReactive () {
302
+ return this.reactiveHash.statusReactive
303
+ }
304
+
305
+ /**
306
+ * get: add-link reactive (url + display text).
307
+ *
308
+ * @returns {{ url: string, displayText: string }} - Link reactive.
309
+ */
310
+ get addLinkReactive () {
311
+ return this.reactiveHash.addLinkReactive
312
+ }
313
+
314
+ /**
315
+ * get: current HTML value from parcel.
316
+ *
317
+ * @returns {string} - HTML content.
318
+ */
319
+ get value () {
320
+ return this.parcel?.value
321
+ ?? ''
322
+ }
323
+
324
+ /**
325
+ * get: placeholder text.
326
+ *
327
+ * @returns {string} - Placeholder.
328
+ */
329
+ get placeholder () {
330
+ return this.parcel?.placeholder
331
+ ?? ''
332
+ }
333
+
334
+ /**
335
+ * get: attached files passed in by the consumer.
336
+ *
337
+ * @returns {Array<*>} - Attached files.
338
+ */
339
+ get attachedFiles () {
340
+ return this.parcel?.attachedFiles
341
+ ?? []
342
+ }
343
+
344
+ /**
345
+ * Whether the editor is read-only.
346
+ *
347
+ * @returns {boolean} - true when read-only.
348
+ */
349
+ isReadOnly () {
350
+ return Boolean(this.parcel?.readOnly)
351
+ }
352
+
353
+ /**
354
+ * Whether the editor renders as plain text (no chrome: toolbar, border, padding).
355
+ *
356
+ * @returns {boolean} - true in plain appearance.
357
+ */
358
+ isPlain () {
359
+ return this.parcel?.appearance === 'plain'
360
+ }
361
+
362
+ /**
363
+ * Whether the editor is display-only (read-only or plain) — never editable.
364
+ *
365
+ * @returns {boolean} - true when display-only.
366
+ */
367
+ isDisplayOnly () {
368
+ return this.isReadOnly()
369
+ || this.isPlain()
370
+ }
371
+
372
+ /**
373
+ * Whether the editor instance is currently editable.
374
+ *
375
+ * @returns {boolean} - true when editable.
376
+ */
377
+ isEditorEditable () {
378
+ return this.editor?.isEditable
379
+ ?? false
380
+ }
381
+
382
+ /**
383
+ * Whether the mention feature is enabled (a search adapter was supplied).
384
+ *
385
+ * @returns {boolean} - true when mentions are available.
386
+ */
387
+ hasMentionFeature () {
388
+ return Boolean(this.parcel?.mention?.searchMentions)
389
+ }
390
+
391
+ /**
392
+ * Whether inline images are enabled (an upload adapter was supplied).
393
+ *
394
+ * @returns {boolean} - true when inline images are available.
395
+ */
396
+ hasImageFeature () {
397
+ return Boolean(this.parcel?.image?.uploadImage)
398
+ }
399
+
400
+ /**
401
+ * Whether the link dialog is open.
402
+ *
403
+ * @returns {boolean} - true when open.
404
+ */
405
+ isLinkDialogOpen () {
406
+ return this.statusReactive.isLinkDialogOpen
407
+ }
408
+
409
+ /**
410
+ * Merge consumer label overrides over the defaults.
411
+ *
412
+ * @returns {typeof DEFAULT_LABELS} - Effective labels.
413
+ */
414
+ extractLabels () {
415
+ return {
416
+ ...DEFAULT_LABELS,
417
+ ...this.parcel?.labels,
418
+ }
419
+ }
420
+
421
+ /**
422
+ * Setup component context: bind editor events, paste handlers, and value/readonly sync.
423
+ *
424
+ * @template {X extends FuroEditorContext ? X : never} T, X
425
+ * @override
426
+ * @returns {T} - For method chaining.
427
+ * @this {T}
428
+ */
429
+ setupComponent () {
430
+ onMounted(() => {
431
+ this.bindEditorEvents()
432
+ this.bindPasteHandlers()
433
+ })
434
+
435
+ this.watch(
436
+ () => this.value,
437
+ () => this.syncValueIntoEditor(),
438
+ {
439
+ immediate: true,
440
+ }
441
+ )
442
+
443
+ this.watch(
444
+ () => this.isDisplayOnly(),
445
+ () => this.syncEditableState(),
446
+ {
447
+ immediate: true,
448
+ }
449
+ )
450
+
451
+ // useEditor() from @tiptap/vue-3 destroys the editor on unmount automatically.
452
+
453
+ return this
454
+ }
455
+
456
+ /**
457
+ * Bind editor update/blur events to emits.
458
+ *
459
+ * @returns {void}
460
+ */
461
+ bindEditorEvents () {
462
+ if (!this.editor) {
463
+ return
464
+ }
465
+
466
+ this.editor.on('update', ({
467
+ editor,
468
+ }) => {
469
+ const html = editor.getHTML()
470
+
471
+ this.emit(this.EMIT_EVENT_NAME.UPDATE_VALUE, html)
472
+ this.emit(this.EMIT_EVENT_NAME.CHANGE_VALUE, {
473
+ value: html,
474
+ })
475
+ })
476
+
477
+ this.editor.on('blur', ({
478
+ editor,
479
+ }) => {
480
+ this.emit(this.EMIT_EVENT_NAME.COMMIT_VALUE, {
481
+ value: editor.getHTML(),
482
+ })
483
+ })
484
+ }
485
+
486
+ /**
487
+ * Install paste transforms (markdown detection, font-style stripping, normalization).
488
+ *
489
+ * @returns {void}
490
+ */
491
+ bindPasteHandlers () {
492
+ if (!this.editor) {
493
+ return
494
+ }
495
+
496
+ this.editor.setOptions({
497
+ editorProps: {
498
+ ...this.editor.options.editorProps,
499
+ transformPastedText: text => this.normalizePastedText({
500
+ text,
501
+ }),
502
+ transformPastedHTML: html => this.stripPastedHtmlFontStyles({
503
+ html,
504
+ }),
505
+ handlePaste: (_, clipboardEvent) => this.handleEditorPaste({
506
+ clipboardEvent,
507
+ }),
508
+ handleDrop: (view, dragEvent) => this.handleEditorDrop({
509
+ view,
510
+ dragEvent,
511
+ }),
512
+ },
513
+ })
514
+ }
515
+
516
+ /**
517
+ * Handle a paste: inline images first, then markdown plain text.
518
+ *
519
+ * @param {{
520
+ * clipboardEvent: ClipboardEvent
521
+ * }} params - Parameters of this method.
522
+ * @returns {boolean} - true when a handler consumed the paste.
523
+ */
524
+ handleEditorPaste ({
525
+ clipboardEvent,
526
+ }) {
527
+ this.emitPastedFiles({ clipboardEvent })
528
+
529
+ if (this.handleImagePaste({ clipboardEvent })) {
530
+ return true
531
+ }
532
+
533
+ return this.handleMarkdownPaste({ clipboardEvent })
534
+ }
535
+
536
+ /**
537
+ * Emit the pasted files the editor does not insert itself.
538
+ *
539
+ * @param {{
540
+ * clipboardEvent: ClipboardEvent
541
+ * }} params - Parameters of this method.
542
+ * @returns {void}
543
+ */
544
+ emitPastedFiles ({
545
+ clipboardEvent,
546
+ }) {
547
+ const clipboard = clipboardEvent?.['clipboardData']
548
+ const files = this.extractUnconsumedFiles({
549
+ fileSource: clipboard?.files ?? null,
550
+ })
551
+
552
+ this.emitAddedFiles({ files })
553
+ }
554
+
555
+ /**
556
+ * Emit files handed to the consumer, unless there are none.
557
+ *
558
+ * @param {{
559
+ * files: Array<File>
560
+ * }} params - Parameters of this method.
561
+ * @returns {void}
562
+ */
563
+ emitAddedFiles ({
564
+ files,
565
+ }) {
566
+ if (files.length === 0) {
567
+ return
568
+ }
569
+
570
+ this.emit(this.EMIT_EVENT_NAME.ADD_FILES, {
571
+ files,
572
+ })
573
+ }
574
+
575
+ /**
576
+ * Upload and insert any image files present on the clipboard.
577
+ *
578
+ * @param {{
579
+ * clipboardEvent: ClipboardEvent
580
+ * }} params - Parameters of this method.
581
+ * @returns {boolean} - true when image files were consumed.
582
+ */
583
+ handleImagePaste ({
584
+ clipboardEvent,
585
+ }) {
586
+ if (!this.hasImageFeature()) {
587
+ return false
588
+ }
589
+
590
+ const clipboard = clipboardEvent?.['clipboardData']
591
+ const imageFiles = this.extractImageFiles({
592
+ fileSource: clipboard?.files ?? null,
593
+ })
594
+
595
+ if (imageFiles.length === 0) {
596
+ return false
597
+ }
598
+
599
+ this.uploadAndInsertImages({
600
+ files: imageFiles,
601
+ position: null,
602
+ })
603
+
604
+ return true
605
+ }
606
+
607
+ /**
608
+ * Insert dropped images at the drop position, and hand the other files to the consumer.
609
+ *
610
+ * @param {{
611
+ * view: import('@tiptap/pm/view').EditorView
612
+ * dragEvent: DragEvent
613
+ * }} params - Parameters of this method.
614
+ * @returns {boolean} - true when the dropped files were taken.
615
+ */
616
+ handleEditorDrop ({
617
+ view,
618
+ dragEvent,
619
+ }) {
620
+ const fileSource = dragEvent?.dataTransfer?.files
621
+ ?? null
622
+ const imageFiles = this.extractInsertableImageFiles({ fileSource })
623
+ const droppedFiles = this.extractUnconsumedFiles({ fileSource })
624
+
625
+ if (imageFiles.length === 0 && droppedFiles.length === 0) {
626
+ return false
627
+ }
628
+
629
+ dragEvent.preventDefault()
630
+
631
+ this.emitAddedFiles({ files: droppedFiles })
632
+
633
+ const position = this.extractDropPosition({
634
+ view,
635
+ dragEvent,
636
+ })
637
+
638
+ this.uploadAndInsertImages({
639
+ files: imageFiles,
640
+ position,
641
+ })
642
+
643
+ return true
644
+ }
645
+
646
+ /**
647
+ * Extract the document position a drag event points at.
648
+ *
649
+ * @param {{
650
+ * view: import('@tiptap/pm/view').EditorView
651
+ * dragEvent: DragEvent
652
+ * }} params - Parameters of this method.
653
+ * @returns {number | null} - Document position.
654
+ */
655
+ extractDropPosition ({
656
+ view,
657
+ dragEvent,
658
+ }) {
659
+ const coordinates = view.posAtCoords({
660
+ left: dragEvent.clientX,
661
+ top: dragEvent.clientY,
662
+ })
663
+
664
+ return coordinates?.pos
665
+ ?? null
666
+ }
667
+
668
+ /**
669
+ * Push the parcel value into the editor when it diverges from the current HTML.
670
+ *
671
+ * @returns {void}
672
+ */
673
+ syncValueIntoEditor () {
674
+ if (!this.editor) {
675
+ return
676
+ }
677
+
678
+ const currentHtml = this.editor.getHTML()
679
+
680
+ if (this.value === currentHtml) {
681
+ return
682
+ }
683
+
684
+ this.editor
685
+ .commands
686
+ .setContent(this.value)
687
+ }
688
+
689
+ /**
690
+ * Reflect the read-only flag onto the editor.
691
+ *
692
+ * @returns {void}
693
+ */
694
+ syncEditableState () {
695
+ if (!this.editor) {
696
+ return
697
+ }
698
+
699
+ this.editor.setEditable(!this.isDisplayOnly())
700
+ }
701
+
702
+ /**
703
+ * Convert pasted markdown plain text into HTML and insert it.
704
+ *
705
+ * @param {{
706
+ * clipboardEvent: ClipboardEvent
707
+ * }} params - Parameters of this method.
708
+ * @returns {boolean} - true when this handler consumed the paste.
709
+ */
710
+ handleMarkdownPaste ({
711
+ clipboardEvent,
712
+ }) {
713
+ // Bracket access avoids the Data-suffix identifier lint on native ClipboardEvent APIs.
714
+ const clipboard = clipboardEvent?.['clipboardData']
715
+ const rawText = clipboard?.['getData']?.('text/plain')
716
+
717
+ if (!rawText) {
718
+ return false
719
+ }
720
+
721
+ if (!this.isMarkdown({ text: rawText })) {
722
+ return false
723
+ }
724
+
725
+ this.insertMarkdownAsHtml({
726
+ rawText,
727
+ })
728
+
729
+ return true
730
+ }
731
+
732
+ /**
733
+ * Render markdown to HTML asynchronously and insert it at the cursor.
734
+ *
735
+ * @param {{
736
+ * rawText: string
737
+ * }} params - Parameters of this method.
738
+ * @returns {void}
739
+ */
740
+ insertMarkdownAsHtml ({
741
+ rawText,
742
+ }) {
743
+ Promise.resolve(marked(rawText))
744
+ .then(html => {
745
+ this.editor
746
+ ?.commands
747
+ .insertContent(html)
748
+ })
749
+ }
750
+
751
+ /**
752
+ * Normalize pasted plain-text line breaks.
753
+ *
754
+ * @param {{
755
+ * text: string
756
+ * }} params - Parameters of this method.
757
+ * @returns {string} - Normalized text.
758
+ */
759
+ normalizePastedText ({
760
+ text,
761
+ }) {
762
+ return text
763
+ .replace(/\r\n/gu, '\n')
764
+ .replace(/\r/gu, '\n')
765
+ .replace(/\n{3,}/gu, '\n\n')
766
+ .trim()
767
+ }
768
+
769
+ /**
770
+ * Strip font-family / font-size declarations from pasted HTML so external
771
+ * typography does not override the editor's.
772
+ *
773
+ * @param {{
774
+ * html: string
775
+ * }} params - Parameters of this method.
776
+ * @returns {string} - Sanitized HTML.
777
+ */
778
+ stripPastedHtmlFontStyles ({
779
+ html,
780
+ }) {
781
+ return html
782
+ .replace(/font-family:\s*[^;"]+;?/giu, '')
783
+ .replace(/font-size:\s*[^;"]+;?/giu, '')
784
+ .replace(/\s*style="\s*"/giu, '')
785
+ }
786
+
787
+ /**
788
+ * Whether pasted text looks like markdown.
789
+ *
790
+ * @param {{
791
+ * text: string
792
+ * }} params - Parameters of this method.
793
+ * @returns {boolean} - true when markdown patterns match.
794
+ */
795
+ isMarkdown ({
796
+ text,
797
+ }) {
798
+ return MARKDOWN_PATTERNS.some(pattern => pattern.test(text))
799
+ }
800
+
801
+ /**
802
+ * Build the inline-mark controls (bold / italic / underline / strikethrough).
803
+ *
804
+ * @returns {Array<FuroEditorControl>} - Mark controls.
805
+ */
806
+ generateMarkControls () {
807
+ const labels = this.extractLabels()
808
+
809
+ return /** @type {Array<FuroEditorControl>} */ ([
810
+ {
811
+ name: 'bold',
812
+ kind: 'toggle',
813
+ command: 'toggleBold',
814
+ iconName: 'ph:text-b',
815
+ activeIconName: 'ph:text-b-bold',
816
+ tooltip: labels.bold,
817
+ },
818
+ {
819
+ name: 'italic',
820
+ kind: 'toggle',
821
+ command: 'toggleItalic',
822
+ iconName: 'ph:text-italic',
823
+ activeIconName: 'ph:text-italic-bold',
824
+ tooltip: labels.italic,
825
+ },
826
+ {
827
+ name: 'underline',
828
+ kind: 'toggle',
829
+ command: 'toggleUnderline',
830
+ iconName: 'ph:text-underline',
831
+ activeIconName: 'ph:text-underline-bold',
832
+ tooltip: labels.underline,
833
+ },
834
+ {
835
+ name: 'strike',
836
+ kind: 'toggle',
837
+ command: 'toggleStrike',
838
+ iconName: 'ph:text-strikethrough',
839
+ activeIconName: 'ph:text-strikethrough-bold',
840
+ tooltip: labels.strike,
841
+ },
842
+ ])
843
+ }
844
+
845
+ /**
846
+ * Build the block controls (bullets / numbers / blockquote).
847
+ *
848
+ * @returns {Array<FuroEditorControl>} - Block controls.
849
+ */
850
+ generateBlockControls () {
851
+ const labels = this.extractLabels()
852
+
853
+ return /** @type {Array<FuroEditorControl>} */ ([
854
+ {
855
+ name: 'bulletList',
856
+ kind: 'toggle',
857
+ command: 'toggleBulletList',
858
+ iconName: 'ph:list-bullets',
859
+ activeIconName: 'ph:list-bullets-bold',
860
+ tooltip: labels.bullets,
861
+ },
862
+ {
863
+ name: 'orderedList',
864
+ kind: 'toggle',
865
+ command: 'toggleOrderedList',
866
+ iconName: 'ph:list-numbers',
867
+ activeIconName: 'ph:list-numbers-bold',
868
+ tooltip: labels.numbers,
869
+ },
870
+ {
871
+ name: 'blockquote',
872
+ kind: 'toggle',
873
+ command: 'toggleBlockquote',
874
+ iconName: 'ph:quotes',
875
+ activeIconName: 'ph:quotes-bold',
876
+ tooltip: labels.blockquote,
877
+ },
878
+ ])
879
+ }
880
+
881
+ /**
882
+ * Build the action controls (link, plus image / mention when enabled).
883
+ *
884
+ * @returns {Array<FuroEditorControl>} - Action controls.
885
+ */
886
+ generateActionControls () {
887
+ const labels = this.extractLabels()
888
+
889
+ const linkControls = [
890
+ {
891
+ name: 'link',
892
+ kind: 'link',
893
+ command: '',
894
+ iconName: 'ph:link',
895
+ activeIconName: 'ph:link-bold',
896
+ tooltip: labels.link,
897
+ },
898
+ ]
899
+
900
+ const imageControls = this.hasImageFeature()
901
+ ? [
902
+ {
903
+ name: 'image',
904
+ kind: 'image',
905
+ command: '',
906
+ iconName: 'ph:image',
907
+ activeIconName: 'ph:image-fill',
908
+ tooltip: labels.image,
909
+ },
910
+ ]
911
+ : []
912
+
913
+ const mentionControls = this.hasMentionFeature()
914
+ ? [
915
+ {
916
+ name: 'mention',
917
+ kind: 'mention',
918
+ command: '',
919
+ iconName: 'ph:at',
920
+ activeIconName: 'ph:at-bold',
921
+ tooltip: labels.mention,
922
+ },
923
+ ]
924
+ : []
925
+
926
+ return /** @type {Array<FuroEditorControl>} */ ([
927
+ ...linkControls,
928
+ ...imageControls,
929
+ ...mentionControls,
930
+ ])
931
+ }
932
+
933
+ /**
934
+ * Flatten all toolbar controls into a single list.
935
+ *
936
+ * @returns {Array<FuroEditorControl>} - Toolbar controls.
937
+ */
938
+ generateFormattingControls () {
939
+ return [
940
+ ...this.generateMarkControls(),
941
+ ...this.generateBlockControls(),
942
+ ...this.generateActionControls(),
943
+ ]
944
+ }
945
+
946
+ /**
947
+ * Build a lookup of control descriptors keyed by control name.
948
+ *
949
+ * @returns {Record<string, FuroEditorControl>} - Controls keyed by name.
950
+ */
951
+ generateControlHash () {
952
+ const entries = this.generateFormattingControls()
953
+ .map(control => /** @type {[string, FuroEditorControl]} */ ([
954
+ control.name,
955
+ control,
956
+ ]))
957
+
958
+ return Object.fromEntries(entries)
959
+ }
960
+
961
+ /**
962
+ * Build a lookup of dropdown descriptors keyed by name.
963
+ *
964
+ * @returns {Record<string, FuroEditorToolbarEntry>} - Dropdown entries keyed by name.
965
+ */
966
+ generateDropdownDescriptorHash () {
967
+ const labels = this.extractLabels()
968
+
969
+ return /** @type {Record<string, FuroEditorToolbarEntry>} */ ({
970
+ heading: {
971
+ type: 'dropdown',
972
+ key: 'heading',
973
+ label: labels.heading,
974
+ iconName: 'ph:text-aa',
975
+ dropdownKind: 'heading',
976
+ options: this.generateHeadingOptions(),
977
+ },
978
+ color: {
979
+ type: 'dropdown',
980
+ key: 'color',
981
+ label: labels.textColor,
982
+ iconName: 'ph:text-t',
983
+ dropdownKind: 'textStyle',
984
+ hasSwatch: true,
985
+ options: this.generateColorOptions(),
986
+ },
987
+ align: {
988
+ type: 'dropdown',
989
+ key: 'align',
990
+ label: labels.textAlign,
991
+ iconName: 'ph:text-align-left',
992
+ dropdownKind: 'textAlign',
993
+ options: this.generateAlignOptions(),
994
+ },
995
+ })
996
+ }
997
+
998
+ /**
999
+ * The default toolbar layout: an array of groups, separators sit between groups.
1000
+ *
1001
+ * @returns {Array<Array<string>>} - Groups of item names.
1002
+ */
1003
+ generateDefaultToolbarGroups () {
1004
+ const imageActions = this.hasImageFeature()
1005
+ ? [
1006
+ 'image',
1007
+ ]
1008
+ : []
1009
+
1010
+ const mentionActions = this.hasMentionFeature()
1011
+ ? [
1012
+ 'mention',
1013
+ ]
1014
+ : []
1015
+
1016
+ const actionGroup = [
1017
+ 'link',
1018
+ ...imageActions,
1019
+ ...mentionActions,
1020
+ ]
1021
+
1022
+ return [
1023
+ [
1024
+ 'heading',
1025
+ 'color',
1026
+ 'align',
1027
+ ],
1028
+ [
1029
+ 'bold',
1030
+ 'italic',
1031
+ 'underline',
1032
+ 'strike',
1033
+ ],
1034
+ [
1035
+ 'bulletList',
1036
+ 'orderedList',
1037
+ 'blockquote',
1038
+ ],
1039
+ actionGroup,
1040
+ ]
1041
+ }
1042
+
1043
+ /**
1044
+ * The effective toolbar groups (consumer override or the default).
1045
+ *
1046
+ * @returns {Array<Array<string>>} - Groups of item names.
1047
+ */
1048
+ generateToolbarGroups () {
1049
+ return this.parcel?.toolbar
1050
+ ?? this.generateDefaultToolbarGroups()
1051
+ }
1052
+
1053
+ /**
1054
+ * Resolve a name to a dropdown or control descriptor, leaving `overflow` unhandled.
1055
+ *
1056
+ * @param {{
1057
+ * name: string
1058
+ * dropdownHash: Record<string, FuroEditorToolbarEntry>
1059
+ * controlHash: Record<string, FuroEditorControl>
1060
+ * }} params - Parameters of this method.
1061
+ * @returns {FuroEditorToolbarEntry | null} - Render descriptor.
1062
+ */
1063
+ resolveDropdownOrControlEntry ({
1064
+ name,
1065
+ dropdownHash,
1066
+ controlHash,
1067
+ }) {
1068
+ const dropdownEntry = dropdownHash[name]
1069
+
1070
+ if (dropdownEntry) {
1071
+ return dropdownEntry
1072
+ }
1073
+
1074
+ const control = controlHash[name]
1075
+
1076
+ if (!control) {
1077
+ return null
1078
+ }
1079
+
1080
+ return /** @type {FuroEditorToolbarEntry} */ ({
1081
+ type: 'control',
1082
+ key: name,
1083
+ control,
1084
+ })
1085
+ }
1086
+
1087
+ /**
1088
+ * Resolve a toolbar item name to a render descriptor, or null when unavailable.
1089
+ *
1090
+ * @param {{
1091
+ * name: string
1092
+ * dropdownHash: Record<string, FuroEditorToolbarEntry>
1093
+ * controlHash: Record<string, FuroEditorControl>
1094
+ * }} params - Parameters of this method.
1095
+ * @returns {FuroEditorToolbarEntry | null} - Render descriptor.
1096
+ */
1097
+ resolveToolbarEntry ({
1098
+ name,
1099
+ dropdownHash,
1100
+ controlHash,
1101
+ }) {
1102
+ const entry = this.resolveDropdownOrControlEntry({
1103
+ name,
1104
+ dropdownHash,
1105
+ controlHash,
1106
+ })
1107
+
1108
+ if (entry) {
1109
+ return entry
1110
+ }
1111
+
1112
+ if (name === OVERFLOW_TOOLBAR_NAME) {
1113
+ return this.resolveOverflowEntry()
1114
+ }
1115
+
1116
+ this.throwOnUnknownToolbarName({
1117
+ name,
1118
+ })
1119
+
1120
+ return null
1121
+ }
1122
+
1123
+ /**
1124
+ * Resolve the overflow menu, or null when no action is left to put in it.
1125
+ *
1126
+ * @returns {FuroEditorToolbarEntry | null} - Render descriptor.
1127
+ */
1128
+ resolveOverflowEntry () {
1129
+ if (this.generateOverflowEntryNames().length === 0) {
1130
+ return null
1131
+ }
1132
+
1133
+ return /** @type {FuroEditorToolbarEntry} */ ({
1134
+ type: 'overflow',
1135
+ key: OVERFLOW_TOOLBAR_NAME,
1136
+ })
1137
+ }
1138
+
1139
+ /**
1140
+ * List the toolbar names this editor accepts, feature-gated names included.
1141
+ *
1142
+ * @returns {Array<string>} - Accepted names.
1143
+ */
1144
+ generateKnownToolbarNames () {
1145
+ const controlNames = this.generateFormattingControls()
1146
+ .map(control => control.name)
1147
+
1148
+ const gatedNames = FEATURE_TOOLBAR_NAMES
1149
+ .filter(name => !controlNames.includes(name))
1150
+
1151
+ return [
1152
+ ...Object.keys(this.generateDropdownDescriptorHash()),
1153
+ ...controlNames,
1154
+ ...gatedNames,
1155
+ OVERFLOW_TOOLBAR_NAME,
1156
+ ]
1157
+ }
1158
+
1159
+ /**
1160
+ * Whether a toolbar name lies outside the vocabulary. A gated feature's own name
1161
+ * stays known, so switching the feature off never reads as a typo.
1162
+ *
1163
+ * @param {{
1164
+ * name: string
1165
+ * }} params - Parameters of this method.
1166
+ * @returns {boolean} - true when nothing accepts the name.
1167
+ */
1168
+ isUnknownToolbarName ({
1169
+ name,
1170
+ }) {
1171
+ return !this.generateKnownToolbarNames()
1172
+ .includes(name)
1173
+ }
1174
+
1175
+ /**
1176
+ * Fail on a `parcel.toolbar` name nothing can resolve, which would otherwise ship
1177
+ * as a quietly shorter toolbar. Production keeps dropping the name instead.
1178
+ *
1179
+ * @param {{
1180
+ * name: string
1181
+ * }} params - Parameters of this method.
1182
+ * @returns {void}
1183
+ * @throws {Error} When the name lies outside the toolbar vocabulary.
1184
+ */
1185
+ throwOnUnknownToolbarName ({
1186
+ name,
1187
+ }) {
1188
+ if (process.env.NODE_ENV === 'production') {
1189
+ return
1190
+ }
1191
+
1192
+ if (!this.isUnknownToolbarName({
1193
+ name,
1194
+ })) {
1195
+ return
1196
+ }
1197
+
1198
+ const acceptedNames = this.generateKnownToolbarNames()
1199
+ .join(', ')
1200
+
1201
+ throw new Error(`[FuroEditor] \`parcel.toolbar\` names "${name}", which is not a toolbar action. Accepted names: ${acceptedNames}.`)
1202
+ }
1203
+
1204
+ /**
1205
+ * List the default action names no toolbar group named.
1206
+ *
1207
+ * @returns {Array<string>} - Hidden action names.
1208
+ */
1209
+ generateOverflowEntryNames () {
1210
+ const namedNames = this.generateToolbarGroups()
1211
+ .flat()
1212
+
1213
+ return this.generateDefaultToolbarGroups()
1214
+ .flat()
1215
+ .filter(name => !namedNames.includes(name))
1216
+ }
1217
+
1218
+ /**
1219
+ * Build the render descriptors the overflow menu holds.
1220
+ *
1221
+ * @returns {Array<FuroEditorToolbarEntry>} - Render descriptors.
1222
+ */
1223
+ generateOverflowEntries () {
1224
+ const dropdownHash = this.generateDropdownDescriptorHash()
1225
+ const controlHash = this.generateControlHash()
1226
+
1227
+ return this.generateOverflowEntryNames()
1228
+ .map(name => this.resolveDropdownOrControlEntry({
1229
+ name,
1230
+ dropdownHash,
1231
+ controlHash,
1232
+ }))
1233
+ .filter(entry => entry !== null)
1234
+ }
1235
+
1236
+ /**
1237
+ * Build the overflow menu options, one per hidden action.
1238
+ *
1239
+ * @returns {Array<import('../../molecules/FuroDropdownMenu/DropdownMenuOptionSelectEmitPayload.js').FuroDropdownMenuOption>} - Options.
1240
+ */
1241
+ generateOverflowOptions () {
1242
+ return this.generateOverflowEntries()
1243
+ .map(entry => this.buildOverflowOption({
1244
+ entry,
1245
+ }))
1246
+ }
1247
+
1248
+ /**
1249
+ * Build one overflow menu option, nesting a hidden dropdown as a submenu.
1250
+ *
1251
+ * @param {{
1252
+ * entry: FuroEditorToolbarEntry
1253
+ * }} params - Parameters of this method.
1254
+ * @returns {import('../../molecules/FuroDropdownMenu/DropdownMenuOptionSelectEmitPayload.js').FuroDropdownMenuOption} - Option.
1255
+ */
1256
+ buildOverflowOption ({
1257
+ entry,
1258
+ }) {
1259
+ if (entry.type === 'dropdown') {
1260
+ return {
1261
+ label: entry.label,
1262
+ value: entry.key,
1263
+ type: 'submenu',
1264
+ options: entry.options,
1265
+ }
1266
+ }
1267
+
1268
+ if (entry.control.kind !== 'toggle') {
1269
+ return {
1270
+ label: entry.control.tooltip,
1271
+ value: entry.control.name,
1272
+ }
1273
+ }
1274
+
1275
+ return {
1276
+ label: entry.control.tooltip,
1277
+ value: entry.control.name,
1278
+ type: 'checkbox',
1279
+ checked: this.isControlActive({
1280
+ control: entry.control,
1281
+ }),
1282
+ }
1283
+ }
1284
+
1285
+ /**
1286
+ * Extract the icon an overflow row draws, mirroring the on-bar active swap.
1287
+ *
1288
+ * @param {{
1289
+ * option: import('../../molecules/FuroDropdownMenu/DropdownMenuOptionSelectEmitPayload.js').FuroDropdownMenuOption
1290
+ * }} params - Parameters of this method.
1291
+ * @returns {string | null} - Icon name, or null when the row draws none.
1292
+ */
1293
+ extractOverflowIconName ({
1294
+ option,
1295
+ }) {
1296
+ const name = option.value
1297
+ ?? ''
1298
+ const dropdownEntry = this.generateDropdownDescriptorHash()[name]
1299
+
1300
+ if (dropdownEntry) {
1301
+ return dropdownEntry.iconName
1302
+ }
1303
+
1304
+ const control = this.generateControlHash()[name]
1305
+
1306
+ if (!control) {
1307
+ return null
1308
+ }
1309
+
1310
+ return this.isControlActive({
1311
+ control,
1312
+ })
1313
+ ? control.activeIconName
1314
+ : control.iconName
1315
+ }
1316
+
1317
+ /**
1318
+ * Whether an overflow row draws a colour swatch beside its label.
1319
+ *
1320
+ * @param {{
1321
+ * option: import('../../molecules/FuroDropdownMenu/DropdownMenuOptionSelectEmitPayload.js').FuroDropdownMenuOption
1322
+ * }} params - Parameters of this method.
1323
+ * @returns {boolean} - true when the row is a colour option.
1324
+ */
1325
+ hasOverflowSwatch ({
1326
+ option,
1327
+ }) {
1328
+ return (option.value ?? '')
1329
+ .startsWith(COLOR_OPTION_PREFIX)
1330
+ }
1331
+
1332
+ /**
1333
+ * React to an overflow menu selection, routing to the handler the value belongs to.
1334
+ *
1335
+ * @param {{
1336
+ * value?: string
1337
+ * }} params - Parameters of this method.
1338
+ * @returns {void}
1339
+ */
1340
+ onOverflowSelect ({
1341
+ value = '',
1342
+ }) {
1343
+ const control = this.generateControlHash()[value]
1344
+
1345
+ if (!control) {
1346
+ this.onDropdownSelect({
1347
+ value,
1348
+ })
1349
+
1350
+ return
1351
+ }
1352
+
1353
+ this.onControlClick({
1354
+ control,
1355
+ })
1356
+ }
1357
+
1358
+ /**
1359
+ * Build the flat toolbar layout (dropdowns, controls, separators between groups).
1360
+ *
1361
+ * @returns {Array<FuroEditorToolbarEntry>} - Ordered render descriptors.
1362
+ */
1363
+ generateToolbarLayout () {
1364
+ const dropdownHash = this.generateDropdownDescriptorHash()
1365
+ const controlHash = this.generateControlHash()
1366
+
1367
+ const resolvedGroups = this.generateToolbarGroups()
1368
+ .map(group =>
1369
+ group
1370
+ .map(name => this.resolveToolbarEntry({
1371
+ name,
1372
+ dropdownHash,
1373
+ controlHash,
1374
+ }))
1375
+ .filter(entry => entry !== null)
1376
+ )
1377
+ .filter(group => group.length > 0)
1378
+
1379
+ return resolvedGroups.flatMap((group, index) => {
1380
+ const separator = index === 0
1381
+ ? []
1382
+ : [
1383
+ /** @type {FuroEditorToolbarEntry} */ ({
1384
+ type: 'separator',
1385
+ key: `separator-${index}`,
1386
+ }),
1387
+ ]
1388
+
1389
+ return [
1390
+ ...separator,
1391
+ ...group,
1392
+ ]
1393
+ })
1394
+ }
1395
+
1396
+ /**
1397
+ * Build the heading dropdown options.
1398
+ *
1399
+ * @returns {Array<import('../../molecules/FuroDropdownMenu/DropdownMenuOptionSelectEmitPayload.js').FuroDropdownMenuOption>} - Options.
1400
+ */
1401
+ generateHeadingOptions () {
1402
+ const labels = this.extractLabels()
1403
+
1404
+ return [
1405
+ {
1406
+ label: labels.normalText,
1407
+ value: 'paragraph',
1408
+ },
1409
+ {
1410
+ label: labels.heading1,
1411
+ value: 'heading:1',
1412
+ },
1413
+ {
1414
+ label: labels.heading2,
1415
+ value: 'heading:2',
1416
+ },
1417
+ {
1418
+ label: labels.heading3,
1419
+ value: 'heading:3',
1420
+ },
1421
+ {
1422
+ label: labels.heading4,
1423
+ value: 'heading:4',
1424
+ },
1425
+ ]
1426
+ }
1427
+
1428
+ /**
1429
+ * Build the text-color dropdown options.
1430
+ *
1431
+ * @returns {Array<import('../../molecules/FuroDropdownMenu/DropdownMenuOptionSelectEmitPayload.js').FuroDropdownMenuOption>} - Options.
1432
+ */
1433
+ generateColorOptions () {
1434
+ const labels = this.extractLabels()
1435
+
1436
+ return [
1437
+ {
1438
+ label: labels.colorDefault,
1439
+ value: 'color:',
1440
+ },
1441
+ {
1442
+ label: labels.colorRed,
1443
+ value: `color:${COLOR_VALUES.red}`,
1444
+ },
1445
+ {
1446
+ label: labels.colorBlue,
1447
+ value: `color:${COLOR_VALUES.blue}`,
1448
+ },
1449
+ {
1450
+ label: labels.colorGreen,
1451
+ value: `color:${COLOR_VALUES.green}`,
1452
+ },
1453
+ {
1454
+ label: labels.colorOrange,
1455
+ value: `color:${COLOR_VALUES.orange}`,
1456
+ },
1457
+ {
1458
+ label: labels.colorPurple,
1459
+ value: `color:${COLOR_VALUES.purple}`,
1460
+ },
1461
+ ]
1462
+ }
1463
+
1464
+ /**
1465
+ * Resolve the swatch color for a color dropdown option value.
1466
+ *
1467
+ * @param {{
1468
+ * value?: string
1469
+ * }} params - Parameters of this method.
1470
+ * @returns {string} - CSS color for the swatch dot.
1471
+ */
1472
+ extractColorSwatch ({
1473
+ value,
1474
+ }) {
1475
+ const rawValue = value
1476
+ ?? ''
1477
+
1478
+ const swatchColor = rawValue.replace(/^color:/u, '')
1479
+
1480
+ return swatchColor === ''
1481
+ ? 'var(--color-foreground)'
1482
+ : swatchColor
1483
+ }
1484
+
1485
+ /**
1486
+ * Build the text-align dropdown options.
1487
+ *
1488
+ * @returns {Array<import('../../molecules/FuroDropdownMenu/DropdownMenuOptionSelectEmitPayload.js').FuroDropdownMenuOption>} - Options.
1489
+ */
1490
+ generateAlignOptions () {
1491
+ const labels = this.extractLabels()
1492
+
1493
+ return [
1494
+ {
1495
+ label: labels.alignLeft,
1496
+ value: 'align:left',
1497
+ },
1498
+ {
1499
+ label: labels.alignCenter,
1500
+ value: 'align:center',
1501
+ },
1502
+ {
1503
+ label: labels.alignRight,
1504
+ value: 'align:right',
1505
+ },
1506
+ {
1507
+ label: labels.alignJustify,
1508
+ value: 'align:justify',
1509
+ },
1510
+ ]
1511
+ }
1512
+
1513
+ /**
1514
+ * React to a formatting control click.
1515
+ *
1516
+ * @param {{
1517
+ * control: FuroEditorControl
1518
+ * }} params - Parameters of this method.
1519
+ * @returns {void}
1520
+ */
1521
+ onControlClick ({
1522
+ control,
1523
+ }) {
1524
+ const override = this.parcel?.actionOverrides?.[control.name]
1525
+
1526
+ if (override) {
1527
+ override({
1528
+ control,
1529
+ command: this.commandContext,
1530
+ editor: this.editor,
1531
+ })
1532
+
1533
+ return
1534
+ }
1535
+
1536
+ const actionHash = {
1537
+ toggle: () => this.commandContext.toggleNodeOrMark({
1538
+ command: control.command,
1539
+ }),
1540
+ link: () => this.openLinkDialog(),
1541
+ image: () => this.openImagePicker(),
1542
+ mention: () => this.commandContext.insertText({
1543
+ text: '@',
1544
+ }),
1545
+ }
1546
+
1547
+ actionHash[control.kind]?.()
1548
+ }
1549
+
1550
+ /**
1551
+ * Open a native file picker for images and insert the chosen files inline.
1552
+ *
1553
+ * @returns {void}
1554
+ */
1555
+ openImagePicker () {
1556
+ if (!this.hasImageFeature()) {
1557
+ return
1558
+ }
1559
+
1560
+ const input = this.createImageFileInput()
1561
+
1562
+ input.addEventListener('change', () => {
1563
+ this.uploadAndInsertImages({
1564
+ files: input.files,
1565
+ position: null,
1566
+ })
1567
+ })
1568
+
1569
+ input.click()
1570
+ }
1571
+
1572
+ /**
1573
+ * Create a hidden multi-image file input element.
1574
+ *
1575
+ * @returns {HTMLInputElement} - The file input.
1576
+ */
1577
+ createImageFileInput () {
1578
+ const input = document.createElement('input')
1579
+
1580
+ input.type = 'file'
1581
+ input.accept = 'image/*'
1582
+ input.multiple = true
1583
+
1584
+ return input
1585
+ }
1586
+
1587
+ /**
1588
+ * Extract image files from a FileList-like value.
1589
+ *
1590
+ * @param {{
1591
+ * fileSource: FileList | null
1592
+ * }} params - Parameters of this method.
1593
+ * @returns {Array<File>} - Image files only.
1594
+ */
1595
+ extractImageFiles ({
1596
+ fileSource,
1597
+ }) {
1598
+ if (!fileSource) {
1599
+ return []
1600
+ }
1601
+
1602
+ return Array.from(fileSource)
1603
+ .filter(file => file.type.startsWith('image/'))
1604
+ }
1605
+
1606
+ /**
1607
+ * Extract the image files the editor embeds itself, empty without an upload adapter.
1608
+ *
1609
+ * @param {{
1610
+ * fileSource: FileList | null
1611
+ * }} params - Parameters of this method.
1612
+ * @returns {Array<File>} - Image files the editor takes.
1613
+ */
1614
+ extractInsertableImageFiles ({
1615
+ fileSource,
1616
+ }) {
1617
+ if (!this.hasImageFeature()) {
1618
+ return []
1619
+ }
1620
+
1621
+ return this.extractImageFiles({ fileSource })
1622
+ }
1623
+
1624
+ /**
1625
+ * Extract the files the editor leaves for the consumer to store.
1626
+ *
1627
+ * @param {{
1628
+ * fileSource: FileList | null
1629
+ * }} params - Parameters of this method.
1630
+ * @returns {Array<File>} - Files the editor does not insert.
1631
+ */
1632
+ extractUnconsumedFiles ({
1633
+ fileSource,
1634
+ }) {
1635
+ if (!fileSource) {
1636
+ return []
1637
+ }
1638
+
1639
+ const insertableFiles = this.extractInsertableImageFiles({ fileSource })
1640
+
1641
+ return Array.from(fileSource)
1642
+ .filter(file => !insertableFiles.includes(file))
1643
+ }
1644
+
1645
+ /**
1646
+ * Upload image files through the consumer adapter and insert them inline.
1647
+ *
1648
+ * The first file lands at `position` (or the cursor when null); the rest follow
1649
+ * it in order. Uploads run sequentially to preserve document order.
1650
+ *
1651
+ * @param {{
1652
+ * files: FileList | Array<File> | null
1653
+ * position?: number | null
1654
+ * }} params - Parameters of this method.
1655
+ * @returns {void}
1656
+ */
1657
+ uploadAndInsertImages ({
1658
+ files,
1659
+ position = null,
1660
+ }) {
1661
+ const imageFiles = this.extractImageFiles({
1662
+ fileSource: /** @type {FileList | null} */ (files),
1663
+ })
1664
+
1665
+ imageFiles.reduce(
1666
+ async (previousPromise, file, index) => {
1667
+ await previousPromise
1668
+
1669
+ const insertPosition = index === 0
1670
+ ? position
1671
+ : null
1672
+
1673
+ await this.uploadAndInsertImage({
1674
+ file,
1675
+ position: insertPosition,
1676
+ })
1677
+ },
1678
+ Promise.resolve()
1679
+ )
1680
+ }
1681
+
1682
+ /**
1683
+ * Upload one image and insert it, reporting a refusal instead of ending the batch.
1684
+ *
1685
+ * @param {{
1686
+ * file: File
1687
+ * position: number | null
1688
+ * }} params - Parameters of this method.
1689
+ * @returns {Promise<void>} - Resolves once the file is inserted or reported.
1690
+ */
1691
+ async uploadAndInsertImage ({
1692
+ file,
1693
+ position,
1694
+ }) {
1695
+ const uploadImage = this.parcel?.image?.uploadImage
1696
+
1697
+ if (!uploadImage) {
1698
+ return
1699
+ }
1700
+
1701
+ try {
1702
+ const uploaded = await uploadImage({ file })
1703
+ const altText = uploaded.alt
1704
+ ?? ''
1705
+
1706
+ this.commandContext.insertImage({
1707
+ src: uploaded.src,
1708
+ alt: altText,
1709
+ position,
1710
+ })
1711
+ } catch (error) {
1712
+ this.emit(this.EMIT_EVENT_NAME.IMAGE_ERROR, {
1713
+ file,
1714
+ error,
1715
+ })
1716
+ }
1717
+ }
1718
+
1719
+ /**
1720
+ * React to a dropdown option selection.
1721
+ *
1722
+ * @param {{
1723
+ * value?: string
1724
+ * }} params - Parameters of this method (value is the option's encoded action).
1725
+ * @returns {void}
1726
+ */
1727
+ onDropdownSelect ({
1728
+ value = '',
1729
+ }) {
1730
+ const [
1731
+ kind,
1732
+ ...rest
1733
+ ] = value.split(':')
1734
+ const argument = rest.join(':')
1735
+
1736
+ const actionHash = {
1737
+ paragraph: () => this.commandContext.executeDropdownCommand({
1738
+ command: 'setParagraph',
1739
+ }),
1740
+ heading: () => this.commandContext.executeDropdownCommand({
1741
+ command: 'toggleHeading',
1742
+ level: Number(argument),
1743
+ }),
1744
+ color: () => this.commandContext.executeDropdownCommand({
1745
+ command: 'setColor',
1746
+ value: argument,
1747
+ }),
1748
+ align: () => this.commandContext.executeDropdownCommand({
1749
+ command: 'setTextAlign',
1750
+ value: argument,
1751
+ }),
1752
+ }
1753
+
1754
+ actionHash[/** @type {keyof typeof actionHash} */ (kind)]?.()
1755
+ }
1756
+
1757
+ /**
1758
+ * Whether a formatting control is currently active.
1759
+ *
1760
+ * @param {{
1761
+ * control: FuroEditorControl
1762
+ * }} params - Parameters of this method.
1763
+ * @returns {boolean} - true when active.
1764
+ */
1765
+ isControlActive ({
1766
+ control,
1767
+ }) {
1768
+ return this.commandContext.isActive({
1769
+ name: control.name,
1770
+ })
1771
+ }
1772
+
1773
+ /**
1774
+ * Extract the button variant for a control based on its active state.
1775
+ *
1776
+ * @param {{
1777
+ * control: FuroEditorControl
1778
+ * }} params - Parameters of this method.
1779
+ * @returns {'secondary' | 'ghost'} - Variant.
1780
+ */
1781
+ extractControlVariant ({
1782
+ control,
1783
+ }) {
1784
+ return this.isControlActive({
1785
+ control,
1786
+ })
1787
+ ? 'secondary'
1788
+ : 'ghost'
1789
+ }
1790
+
1791
+ /**
1792
+ * Extract the trigger variant for a dropdown group based on its active state.
1793
+ *
1794
+ * @param {{
1795
+ * kind: 'heading' | 'textStyle' | 'textAlign'
1796
+ * }} params - Parameters of this method.
1797
+ * @returns {'secondary' | 'ghost'} - Variant.
1798
+ */
1799
+ extractDropdownVariant ({
1800
+ kind,
1801
+ }) {
1802
+ return this.isDropdownActive({
1803
+ kind,
1804
+ })
1805
+ ? 'secondary'
1806
+ : 'ghost'
1807
+ }
1808
+
1809
+ /**
1810
+ * Whether a dropdown group (heading / color / align) is active.
1811
+ *
1812
+ * @param {{
1813
+ * kind: 'heading' | 'textStyle' | 'textAlign'
1814
+ * }} params - Parameters of this method.
1815
+ * @returns {boolean} - true when active.
1816
+ */
1817
+ isDropdownActive ({
1818
+ kind,
1819
+ }) {
1820
+ const activeHash = {
1821
+ heading: () => this.commandContext.isActive({
1822
+ name: 'heading',
1823
+ }),
1824
+ textStyle: () => this.commandContext.isActive({
1825
+ name: 'textStyle',
1826
+ }),
1827
+ textAlign: () => this.commandContext.hasActiveTextAlign({
1828
+ alignments: [
1829
+ 'left',
1830
+ 'center',
1831
+ 'right',
1832
+ 'justify',
1833
+ ],
1834
+ }),
1835
+ }
1836
+
1837
+ return activeHash[kind]?.()
1838
+ ?? false
1839
+ }
1840
+
1841
+ /**
1842
+ * Reflect the dialog open state from a FuroDialog open change.
1843
+ *
1844
+ * @param {{
1845
+ * isOpen: boolean
1846
+ * }} params - Parameters of this method.
1847
+ * @returns {void}
1848
+ */
1849
+ onLinkDialogOpenChange ({
1850
+ isOpen,
1851
+ }) {
1852
+ this.statusReactive.isLinkDialogOpen = isOpen
1853
+ }
1854
+
1855
+ /**
1856
+ * Open the link dialog, prefilling from the current selection.
1857
+ *
1858
+ * @returns {void}
1859
+ */
1860
+ openLinkDialog () {
1861
+ const selectionLink = this.commandContext.extractSelectionLink()
1862
+
1863
+ this.addLinkReactive.url = selectionLink.url
1864
+ this.addLinkReactive.displayText = selectionLink.displayText
1865
+ this.linkRange = selectionLink.range
1866
+
1867
+ this.statusReactive.isLinkDialogOpen = true
1868
+ }
1869
+
1870
+ /**
1871
+ * Close the link dialog.
1872
+ *
1873
+ * @returns {void}
1874
+ */
1875
+ closeLinkDialog () {
1876
+ this.statusReactive.isLinkDialogOpen = false
1877
+ }
1878
+
1879
+ /**
1880
+ * Apply the link from the dialog and close it.
1881
+ *
1882
+ * @returns {void}
1883
+ */
1884
+ saveLink () {
1885
+ const { url } = this.addLinkReactive
1886
+ const { displayText } = this.addLinkReactive
1887
+ const range = this.linkRange
1888
+
1889
+ this.closeLinkDialog()
1890
+
1891
+ this.addLinkReactive.url = ''
1892
+ this.addLinkReactive.displayText = ''
1893
+
1894
+ // Apply after the modal closes so focus returns to the editor before the edit.
1895
+ nextTick(() => {
1896
+ this.commandContext.applyLink({
1897
+ url,
1898
+ displayText,
1899
+ range,
1900
+ })
1901
+
1902
+ this.linkRange = null
1903
+ })
1904
+ }
1905
+
1906
+ /**
1907
+ * Update the dialog link URL.
1908
+ *
1909
+ * @param {{
1910
+ * url?: string | null
1911
+ * }} params - Parameters of this method.
1912
+ * @returns {void}
1913
+ */
1914
+ updateLinkUrl ({
1915
+ url = '',
1916
+ }) {
1917
+ this.addLinkReactive.url = url ?? ''
1918
+ }
1919
+
1920
+ /**
1921
+ * Update the dialog link display text.
1922
+ *
1923
+ * @param {{
1924
+ * displayText?: string | null
1925
+ * }} params - Parameters of this method.
1926
+ * @returns {void}
1927
+ */
1928
+ updateLinkDisplayText ({
1929
+ displayText = '',
1930
+ }) {
1931
+ this.addLinkReactive.displayText = displayText ?? ''
1932
+ }
1933
+
1934
+ /**
1935
+ * Remove an attached file by index and emit the updated array.
1936
+ *
1937
+ * @param {{
1938
+ * index: number
1939
+ * }} params - Parameters of this method.
1940
+ * @returns {void}
1941
+ */
1942
+ removeAttachedFile ({
1943
+ index,
1944
+ }) {
1945
+ const nextFiles = this.attachedFiles
1946
+ .filter((file, fileIndex) => fileIndex !== index)
1947
+
1948
+ this.emit(this.EMIT_EVENT_NAME.UPDATE_ATTACHED_FILES, nextFiles)
1949
+ }
1950
+
1951
+ /**
1952
+ * Whether the consumer provided an `attachments` slot.
1953
+ *
1954
+ * @returns {boolean} - true when the slot is present.
1955
+ */
1956
+ hasAttachmentsSlot () {
1957
+ return Boolean(this.slots?.attachments)
1958
+ }
1959
+ }
1960
+
1961
+ /**
1962
+ * @typedef {{
1963
+ * name: string
1964
+ * kind: 'toggle' | 'link' | 'image' | 'mention'
1965
+ * command: string
1966
+ * iconName: string
1967
+ * activeIconName: string
1968
+ * tooltip: string
1969
+ * }} FuroEditorControl
1970
+ */
1971
+
1972
+ /**
1973
+ * @typedef {{
1974
+ * type: 'separator'
1975
+ * key: string
1976
+ * } | {
1977
+ * type: 'dropdown'
1978
+ * key: string
1979
+ * label: string
1980
+ * iconName: string
1981
+ * dropdownKind: 'heading' | 'textStyle' | 'textAlign'
1982
+ * hasSwatch?: boolean
1983
+ * options: Array<import('../../molecules/FuroDropdownMenu/DropdownMenuOptionSelectEmitPayload.js').FuroDropdownMenuOption>
1984
+ * } | {
1985
+ * type: 'control'
1986
+ * key: string
1987
+ * control: FuroEditorControl
1988
+ * } | {
1989
+ * type: 'overflow'
1990
+ * key: string
1991
+ * }} FuroEditorToolbarEntry
1992
+ */
1993
+
1994
+ /**
1995
+ * @typedef {{
1996
+ * isLinkDialogOpen: boolean
1997
+ * }} FuroEditorStatusReactive
1998
+ */
1999
+
2000
+ /**
2001
+ * @typedef {{
2002
+ * url: string
2003
+ * displayText: string
2004
+ * }} FuroEditorAddLinkReactive
2005
+ */
2006
+
2007
+ /**
2008
+ * @typedef {import('../../../contexts/BaseFuroContext.js').BaseFuroContextParams<{
2009
+ * parcel?: FuroEditorParcel | null
2010
+ * }> & {
2011
+ * editorRef: ReturnType<typeof import('@tiptap/vue-3').useEditor>
2012
+ * reactiveHash: {
2013
+ * statusReactive: import('vue').Reactive<FuroEditorStatusReactive>
2014
+ * addLinkReactive: import('vue').Reactive<FuroEditorAddLinkReactive>
2015
+ * }
2016
+ * }} FuroEditorContextParams
2017
+ */
2018
+
2019
+ /**
2020
+ * @typedef {Omit<FuroEditorContextParams, 'reactiveHash'> & {
2021
+ * reactiveHash?: FuroEditorContextParams['reactiveHash']
2022
+ * }} FuroEditorContextFactoryParams
2023
+ */
2024
+
2025
+ /**
2026
+ * @typedef {{
2027
+ * value?: string
2028
+ * placeholder?: string
2029
+ * readOnly?: boolean
2030
+ * appearance?: 'editor' | 'plain'
2031
+ * autofocus?: boolean | 'start' | 'end' | 'all' | number
2032
+ * contentAttributes?: Record<string, string>
2033
+ * toolbar?: Array<Array<string>>
2034
+ * attachedFiles?: Array<*>
2035
+ * labels?: Partial<typeof DEFAULT_LABELS>
2036
+ * mention?: {
2037
+ * searchMentions: (params: { query: string }) => Promise<Array<{ id: string | number, label: string }>>
2038
+ * }
2039
+ * image?: {
2040
+ * uploadImage: (params: { file: File }) => Promise<{ src: string, alt?: string }>
2041
+ * }
2042
+ * actionOverrides?: Record<string, (params: {
2043
+ * control: FuroEditorControl
2044
+ * command: FuroEditorCommandContext
2045
+ * editor: import('@tiptap/vue-3').Editor | null
2046
+ * }) => void>
2047
+ * }} FuroEditorParcel
2048
+ */