frappe-ui 1.0.0-beta.2 → 1.0.0-beta.21

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 (692) hide show
  1. package/experimental/FloatingWindow/FloatingWindow.cy.ts +328 -0
  2. package/experimental/FloatingWindow/FloatingWindow.vue +303 -0
  3. package/experimental/FloatingWindow/index.ts +8 -0
  4. package/experimental/FloatingWindow/types.ts +31 -0
  5. package/experimental/FloatingWindow/useFloatingWindow.ts +236 -0
  6. package/experimental/MultiEmailInput/MultiEmailInput.cy.ts +155 -0
  7. package/experimental/MultiEmailInput/MultiEmailInput.vue +472 -0
  8. package/experimental/MultiEmailInput/index.ts +17 -0
  9. package/experimental/MultiEmailInput/stories/AsyncSuggestions.vue +54 -0
  10. package/experimental/MultiEmailInput/stories/Basic.vue +18 -0
  11. package/experimental/MultiEmailInput/stories/CustomChip.vue +39 -0
  12. package/experimental/MultiEmailInput/stories/Labeling.vue +28 -0
  13. package/experimental/MultiEmailInput/types.ts +138 -0
  14. package/experimental/MultiEmailInput/utils.ts +60 -0
  15. package/experimental.ts +12 -0
  16. package/frappe/Billing/SignupBanner.vue +1 -1
  17. package/frappe/Billing/TrialBanner.vue +1 -1
  18. package/frappe/DataImport/DataImport.vue +1 -1
  19. package/frappe/DataImport/DataImportList.vue +1 -1
  20. package/frappe/DataImport/ImportSteps.vue +6 -6
  21. package/frappe/DataImport/MappingStep.vue +1 -1
  22. package/frappe/DataImport/PreviewStep.vue +9 -9
  23. package/frappe/DataImport/TemplateModal.vue +1 -1
  24. package/frappe/DataImport/UploadStep.vue +5 -5
  25. package/frappe/Filter/Filter.vue +1 -1
  26. package/frappe/Help/HelpModal.vue +1 -1
  27. package/frappe/Link/stories/Suffix.vue +2 -2
  28. package/frappe/Onboarding/GettingStartedBanner.vue +1 -1
  29. package/frappe/drive/components/AppSwitcher.vue +1 -1
  30. package/frappe/drive/components/InfoDialog.vue +3 -3
  31. package/frappe/drive/components/MoveDialog.vue +2 -2
  32. package/frappe/drive/components/Select/Select.vue +2 -2
  33. package/frappe/drive/components/ShareDialog.vue +1 -1
  34. package/frappe/telemetry/index.ts +88 -14
  35. package/frappe/telemetry/pulse.ts +78 -164
  36. package/frappe/telemetry/telemetry.test.ts +13 -79
  37. package/icons/IconPicker.vue +1 -1
  38. package/package.json +104 -34
  39. package/src/components/Alert/Alert.md +4 -0
  40. package/src/components/Alert/Alert.vue +4 -4
  41. package/src/components/Autocomplete/Autocomplete.vue +6 -6
  42. package/src/components/Avatar/Avatar.md +4 -0
  43. package/src/components/Avatar/Avatar.vue +23 -4
  44. package/src/components/Badge/Badge.api.md +1 -1
  45. package/src/components/Badge/Badge.cy.ts +34 -16
  46. package/src/components/Badge/Badge.md +20 -4
  47. package/src/components/Badge/Badge.vue +60 -45
  48. package/src/components/Badge/stories/CallMeta.vue +35 -0
  49. package/src/components/Badge/stories/EventStatus.vue +16 -0
  50. package/src/components/Badge/stories/ListStatus.vue +91 -0
  51. package/src/components/Badge/stories/Reactions.vue +33 -0
  52. package/src/components/Badge/stories/RowTags.vue +39 -0
  53. package/src/components/Badge/types.ts +1 -2
  54. package/src/components/BottomSheet/BottomSheet.api.md +57 -0
  55. package/src/components/BottomSheet/BottomSheet.vue +232 -0
  56. package/src/components/BottomSheet/index.ts +6 -0
  57. package/src/components/BottomSheet/types.ts +23 -0
  58. package/src/components/Breadcrumbs/Breadcrumbs.md +4 -0
  59. package/src/components/Breadcrumbs/Breadcrumbs.vue +3 -3
  60. package/src/components/Button/Button.api.md +37 -32
  61. package/src/components/Button/Button.cy.ts +18 -2
  62. package/src/components/Button/Button.md +20 -8
  63. package/src/components/Button/Button.vue +304 -284
  64. package/src/components/Button/stories/InlineActions.vue +10 -0
  65. package/src/components/Button/stories/LiveClassCard.vue +34 -0
  66. package/src/components/Button/stories/SectionAction.vue +17 -0
  67. package/src/components/Button/stories/SectionControls.vue +28 -0
  68. package/src/components/Button/stories/SelectionToolbar.vue +20 -0
  69. package/src/components/Button/stories/StackedActions.vue +10 -0
  70. package/src/components/Button/types.ts +38 -32
  71. package/src/components/Calendar/Calendar.api.md +16 -16
  72. package/src/components/Calendar/Calendar.vue +100 -73
  73. package/src/components/Calendar/CalendarDaily.vue +21 -20
  74. package/src/components/Calendar/CalendarMonthEvent.vue +30 -9
  75. package/src/components/Calendar/CalendarMonthly.vue +60 -39
  76. package/src/components/Calendar/CalendarTimeMarker.vue +9 -8
  77. package/src/components/Calendar/CalendarWeekDayEvent.vue +98 -42
  78. package/src/components/Calendar/CalendarWeekly.vue +77 -41
  79. package/src/components/Calendar/EventModalContent.vue +32 -16
  80. package/src/components/Calendar/NewEventModal.vue +19 -16
  81. package/src/components/Calendar/ShowMoreCalendarEvent.vue +11 -17
  82. package/src/components/Calendar/{calendarUtils.js → calendarUtils.ts} +76 -52
  83. package/src/components/Calendar/composables/{useCalendarData.js → useCalendarData.ts} +23 -11
  84. package/src/components/Calendar/composables/{useEventModal.js → useEventModal.ts} +16 -12
  85. package/src/components/Calendar/index.ts +12 -2
  86. package/src/components/Calendar/stories/CustomHeader.vue +1 -1
  87. package/src/components/Calendar/types.ts +106 -0
  88. package/src/components/Calendar/{useEventBase.js → useEventBase.ts} +40 -26
  89. package/src/components/Card.vue +2 -2
  90. package/src/components/Charts/ECharts.vue +1 -1
  91. package/src/components/Charts/NumberChart.vue +7 -7
  92. package/src/components/Charts/eChartOptions.ts +28 -13
  93. package/src/components/Checkbox/Checkbox.api.md +6 -0
  94. package/src/components/Checkbox/Checkbox.cy.ts +10 -2
  95. package/src/components/Checkbox/Checkbox.md +28 -0
  96. package/src/components/Checkbox/Checkbox.vue +9 -22
  97. package/src/components/Checkbox/stories/Group.vue +27 -0
  98. package/src/components/Checkbox/stories/HorizontalGroup.vue +24 -0
  99. package/src/components/Checkbox/stories/SettingRow.vue +23 -0
  100. package/src/components/Checkbox/stories/WithDescription.vue +27 -0
  101. package/src/components/Checkbox/types.ts +7 -0
  102. package/src/components/CodeEditor/CodeEditor.api.md +139 -0
  103. package/src/components/CodeEditor/CodeEditor.cy.ts +160 -0
  104. package/src/components/CodeEditor/CodeEditor.md +104 -0
  105. package/src/components/CodeEditor/CodeEditor.vue +556 -0
  106. package/src/components/CodeEditor/CodePreview.test.ts +49 -0
  107. package/src/components/CodeEditor/CodePreview.vue +45 -0
  108. package/src/components/CodeEditor/index.ts +17 -0
  109. package/src/components/CodeEditor/languages.ts +63 -0
  110. package/src/components/CodeEditor/stories/Default.vue +16 -0
  111. package/src/components/CodeEditor/stories/Labeling.vue +25 -0
  112. package/src/components/CodeEditor/stories/MaxHeight.vue +24 -0
  113. package/src/components/CodeEditor/stories/Preview.vue +36 -0
  114. package/src/components/CodeEditor/stories/Sizes.vue +15 -0
  115. package/src/components/CodeEditor/stories/Variants.vue +13 -0
  116. package/src/components/CodeEditor/theme.ts +272 -0
  117. package/src/components/CodeEditor/types.ts +69 -0
  118. package/src/components/Combobox/Combobox.api.md +19 -20
  119. package/src/components/Combobox/Combobox.cy.ts +54 -14
  120. package/src/components/Combobox/Combobox.md +4 -0
  121. package/src/components/Combobox/Combobox.vue +233 -247
  122. package/src/components/Combobox/ComboboxResults.vue +1 -1
  123. package/src/components/Combobox/stories/Clearable.vue +2 -2
  124. package/src/components/Combobox/stories/CreateNew.vue +1 -1
  125. package/src/components/Combobox/stories/Footer.vue +10 -2
  126. package/src/components/Combobox/stories/InDialog.vue +1 -1
  127. package/src/components/Combobox/types.ts +8 -29
  128. package/src/components/CommandPalette/CommandPaletteItem.vue +1 -1
  129. package/src/components/ContextMenu/ContextMenu.api.md +52 -0
  130. package/src/components/ContextMenu/ContextMenu.cy.ts +191 -0
  131. package/src/components/ContextMenu/ContextMenu.md +25 -0
  132. package/src/components/ContextMenu/ContextMenu.vue +106 -0
  133. package/src/components/ContextMenu/index.ts +2 -0
  134. package/src/components/ContextMenu/stories/FileList.vue +105 -0
  135. package/src/components/ContextMenu/stories/Groups.vue +95 -0
  136. package/src/components/ContextMenu/stories/Simple.vue +72 -0
  137. package/src/components/ContextMenu/types.ts +39 -0
  138. package/src/components/DatePicker/CalendarPanel.vue +128 -86
  139. package/src/components/DatePicker/DatePicker.vue +3 -9
  140. package/src/components/DatePicker/DateRangePicker.vue +4 -8
  141. package/src/components/DatePicker/DateTimePicker.cy.ts +4 -2
  142. package/src/components/DatePicker/DateTimePicker.vue +3 -9
  143. package/src/components/DatePicker/composables.ts +15 -37
  144. package/src/components/DatePicker/stories/Range.vue +1 -1
  145. package/src/components/DatePicker/types.ts +1 -1
  146. package/src/components/DesktopShell/DesktopShell.api.md +40 -0
  147. package/src/components/DesktopShell/DesktopShell.cy.ts +39 -0
  148. package/src/components/DesktopShell/DesktopShell.md +31 -0
  149. package/src/components/DesktopShell/DesktopShell.vue +81 -0
  150. package/src/components/DesktopShell/index.ts +3 -0
  151. package/src/components/DesktopShell/stories/Default.vue +97 -0
  152. package/src/components/DesktopShell/types.ts +2 -0
  153. package/src/components/Dialog/Dialog.api.md +7 -8
  154. package/src/components/Dialog/Dialog.md +4 -0
  155. package/src/components/Dialog/Dialog.vue +8 -7
  156. package/src/components/Dialog/stories/CommandPalette.vue +1 -1
  157. package/src/components/Dialog/stories/Wizard.vue +1 -1
  158. package/src/components/Dialogs.vue +11 -9
  159. package/src/components/Divider/Divider.api.md +1 -1
  160. package/src/components/Divider/Divider.md +4 -0
  161. package/src/components/Dropdown/Dropdown.api.md +10 -35
  162. package/src/components/Dropdown/Dropdown.md +10 -6
  163. package/src/components/Dropdown/Dropdown.vue +33 -47
  164. package/src/components/Dropdown/stories/{05_KebabMenu.vue → KebabMenu.vue} +3 -1
  165. package/src/components/Dropdown/stories/{06_UserMenu.vue → UserMenu.vue} +1 -1
  166. package/src/components/Dropdown/types.ts +25 -189
  167. package/src/components/Duration/Duration.api.md +92 -0
  168. package/src/components/Duration/Duration.cy.ts +183 -0
  169. package/src/components/Duration/Duration.md +98 -0
  170. package/src/components/Duration/Duration.vue +114 -0
  171. package/src/components/Duration/duration.ts +185 -0
  172. package/src/components/Duration/index.ts +8 -0
  173. package/src/components/Duration/stories/Default.vue +10 -0
  174. package/src/components/Duration/stories/Formats.vue +16 -0
  175. package/src/components/Duration/stories/RealWorld.vue +50 -0
  176. package/src/components/Duration/stories/Sizes.vue +15 -0
  177. package/src/components/Duration/stories/States.vue +21 -0
  178. package/src/components/Duration/types.ts +52 -0
  179. package/src/components/ErrorMessage/ErrorMessage.cy.ts +1 -1
  180. package/src/components/ErrorMessage/ErrorMessage.md +4 -0
  181. package/src/components/ErrorMessage/ErrorMessage.vue +1 -1
  182. package/src/components/FileUploader/FileUploader.api.md +36 -11
  183. package/src/components/FileUploader/FileUploader.cy.ts +66 -0
  184. package/src/components/FileUploader/FileUploader.md +4 -1
  185. package/src/components/FileUploader/FileUploader.vue +147 -106
  186. package/src/components/FileUploader/index.ts +6 -0
  187. package/src/components/FileUploader/types.ts +43 -0
  188. package/src/components/FormControl/FormControl.api.md +3 -4
  189. package/src/components/FormControl/FormControl.md +4 -0
  190. package/src/components/FormControl/stories/RealForm.vue +3 -3
  191. package/src/components/FormLabel.vue +5 -8
  192. package/src/components/HoverCard/HoverCard.api.md +100 -0
  193. package/src/components/HoverCard/HoverCard.cy.ts +103 -0
  194. package/src/components/HoverCard/HoverCard.md +52 -0
  195. package/src/components/HoverCard/HoverCard.vue +91 -0
  196. package/src/components/HoverCard/index.ts +2 -0
  197. package/src/components/HoverCard/stories/Arrow.vue +28 -0
  198. package/src/components/HoverCard/stories/Delays.vue +16 -0
  199. package/src/components/HoverCard/stories/Example.vue +28 -0
  200. package/src/components/HoverCard/types.ts +47 -0
  201. package/src/components/Icon/Icon.cy.ts +78 -0
  202. package/src/components/Icon/Icon.vue +50 -0
  203. package/src/components/Icon/index.ts +1 -0
  204. package/src/components/InputLabeling/InputDescription.vue +1 -1
  205. package/src/components/InputLabeling/InputError.vue +1 -1
  206. package/src/components/InputLabeling/InputLabel.vue +21 -7
  207. package/src/components/InputLabeling/RequiredIndicator.vue +1 -1
  208. package/src/components/ItemListRow/ItemListRow.vue +1 -1
  209. package/src/components/ItemListRow/stories/RowStates.vue +1 -1
  210. package/src/components/KeyboardShortcut.vue +4 -8
  211. package/src/components/KeyboardShortcutsModal/KeyboardShortcutsModal.api.md +22 -14
  212. package/src/components/KeyboardShortcutsModal/KeyboardShortcutsModal.vue +2 -2
  213. package/src/components/ListFilter/ListFilter.api.md +5 -6
  214. package/src/components/ListFilter/ListFilter.vue +161 -57
  215. package/src/components/ListFilter/NestedPopover.vue +17 -10
  216. package/src/components/ListFilter/SearchComplete.vue +31 -38
  217. package/src/components/ListItem.vue +1 -1
  218. package/src/components/ListView/ListEmptyState.vue +1 -1
  219. package/src/components/ListView/ListGroupHeader.vue +2 -2
  220. package/src/components/ListView/ListRow.vue +1 -1
  221. package/src/components/ListView/ListSelectBanner.vue +1 -1
  222. package/src/components/ListView/ListView.api.md +322 -6
  223. package/src/components/ListView/index.ts +25 -0
  224. package/src/components/ListView/stories/CustomList.vue +1 -1
  225. package/src/components/ListView/stories/GroupedRows.vue +1 -1
  226. package/src/components/LoadingIndicator.vue +13 -22
  227. package/src/components/LoadingText.vue +3 -13
  228. package/src/components/{Dropdown/DropdownMenuList.vue → Menu/Menu.vue} +110 -75
  229. package/src/components/{Dropdown/DropdownMenuItemContent.vue → Menu/MenuItemContent.vue} +23 -33
  230. package/src/components/{Dropdown/DropdownRenderContent.vue → Menu/MenuRenderContent.vue} +1 -1
  231. package/src/components/Menu/types.ts +225 -0
  232. package/src/components/{Dropdown → Menu}/utils.ts +36 -40
  233. package/src/components/MobileNav/MobileNav.api.md +70 -0
  234. package/src/components/MobileNav/MobileNav.cy.ts +89 -0
  235. package/src/components/MobileNav/MobileNav.md +34 -0
  236. package/src/components/MobileNav/MobileNav.vue +15 -0
  237. package/src/components/MobileNav/MobileNavItem.vue +125 -0
  238. package/src/components/MobileNav/index.ts +4 -0
  239. package/src/components/MobileNav/stories/Default.vue +40 -0
  240. package/src/components/MobileNav/types.ts +33 -0
  241. package/src/components/MobileShell/MobileShell.api.md +23 -0
  242. package/src/components/MobileShell/MobileShell.cy.ts +25 -0
  243. package/src/components/MobileShell/MobileShell.md +24 -0
  244. package/src/components/MobileShell/MobileShell.vue +41 -0
  245. package/src/components/MobileShell/index.ts +3 -0
  246. package/src/components/MobileShell/stories/Default.vue +75 -0
  247. package/src/components/MobileShell/types.ts +7 -0
  248. package/src/components/MonthPicker/MonthPicker.cy.ts +16 -0
  249. package/src/components/MonthPicker/MonthPicker.vue +19 -8
  250. package/src/components/MultiSelect/MultiSelect.api.md +3 -4
  251. package/src/components/MultiSelect/MultiSelect.md +42 -10
  252. package/src/components/MultiSelect/MultiSelect.vue +199 -227
  253. package/src/components/MultiSelect/MultiSelectResults.vue +1 -1
  254. package/src/components/MultiSelect/stories/Members.vue +1 -1
  255. package/src/components/MultiSelect/stories/TagsTrigger.vue +3 -3
  256. package/src/components/MultiSelect/types.ts +3 -3
  257. package/src/components/PageHeader/PageHeader.api.md +112 -0
  258. package/src/components/PageHeader/PageHeader.cy.ts +68 -0
  259. package/src/components/PageHeader/PageHeader.md +25 -0
  260. package/src/components/PageHeader/PageHeader.vue +13 -0
  261. package/src/components/PageHeader/PageHeaderBackButton.vue +29 -0
  262. package/src/components/PageHeader/PageHeaderBase.vue +51 -0
  263. package/src/components/PageHeader/PageHeaderMobile.vue +68 -0
  264. package/src/components/PageHeader/PageHeaderMobileTitle.vue +16 -0
  265. package/src/components/PageHeader/PageHeaderTarget.vue +13 -0
  266. package/src/components/PageHeader/PageHeaderTitle.vue +12 -0
  267. package/src/components/PageHeader/index.ts +7 -0
  268. package/src/components/PageHeader/stories/Default.vue +19 -0
  269. package/src/components/PageHeader/stories/Mobile.vue +24 -0
  270. package/src/components/PageHeader/target.ts +19 -0
  271. package/src/components/PageHeader/types.ts +19 -0
  272. package/src/components/Password/Password.md +4 -0
  273. package/src/components/Password/Password.vue +2 -2
  274. package/src/components/Popover/Popover.api.md +126 -42
  275. package/src/components/Popover/Popover.cy.ts +425 -41
  276. package/src/components/Popover/Popover.md +99 -4
  277. package/src/components/Popover/Popover.vue +288 -215
  278. package/src/components/Popover/index.ts +8 -1
  279. package/src/components/Popover/stories/Arrow.vue +16 -0
  280. package/src/components/Popover/stories/Bare.vue +20 -0
  281. package/src/components/Popover/stories/Click.vue +7 -4
  282. package/src/components/Popover/stories/Controlled.vue +22 -0
  283. package/src/components/Popover/stories/Dismissible.vue +19 -0
  284. package/src/components/Popover/stories/MatchTriggerWidth.vue +16 -0
  285. package/src/components/Popover/stories/SideAlign.vue +17 -0
  286. package/src/components/Popover/types.ts +101 -23
  287. package/src/components/Progress/Progress.cy.ts +2 -2
  288. package/src/components/Progress/Progress.md +4 -0
  289. package/src/components/Progress/Progress.vue +4 -4
  290. package/src/components/Rail/Rail.api.md +91 -0
  291. package/src/components/Rail/Rail.cy.ts +84 -0
  292. package/src/components/Rail/Rail.md +30 -0
  293. package/src/components/Rail/Rail.vue +16 -0
  294. package/src/components/Rail/RailItem.vue +134 -0
  295. package/src/components/Rail/RailItemBadge.vue +64 -0
  296. package/src/components/Rail/index.ts +2 -0
  297. package/src/components/Rail/stories/Default.vue +44 -0
  298. package/src/components/Rail/types.ts +37 -0
  299. package/src/components/Rating/Rating.md +4 -0
  300. package/src/components/Rating/Rating.vue +5 -8
  301. package/src/components/Rating/stories/CustomSlot.vue +2 -2
  302. package/src/components/ScrollArea/ScrollArea.api.md +59 -0
  303. package/src/components/ScrollArea/ScrollArea.vue +46 -0
  304. package/src/components/ScrollArea/ScrollBar.vue +101 -0
  305. package/src/components/ScrollArea/index.ts +2 -0
  306. package/src/components/Select/Select.cy.ts +16 -15
  307. package/src/components/Select/Select.md +4 -0
  308. package/src/components/Select/Select.vue +217 -203
  309. package/src/components/Select/stories/Example.vue +4 -3
  310. package/src/components/Select/stories/States.vue +1 -1
  311. package/src/components/Select/stories/TriggerSlots.vue +2 -2
  312. package/src/components/Select/utils.ts +4 -3
  313. package/src/components/SettingsDialog/SettingsBody.vue +23 -0
  314. package/src/components/SettingsDialog/SettingsContent.vue +7 -0
  315. package/src/components/SettingsDialog/SettingsDialog.api.md +273 -0
  316. package/src/components/SettingsDialog/SettingsDialog.cy.ts +126 -0
  317. package/src/components/SettingsDialog/SettingsDialog.md +43 -0
  318. package/src/components/SettingsDialog/SettingsDialog.vue +76 -0
  319. package/src/components/SettingsDialog/SettingsHeader.vue +36 -0
  320. package/src/components/SettingsDialog/SettingsNavGroup.vue +20 -0
  321. package/src/components/SettingsDialog/SettingsNavItem.vue +33 -0
  322. package/src/components/SettingsDialog/SettingsPanel.cy.ts +77 -0
  323. package/src/components/SettingsDialog/SettingsPanel.vue +26 -0
  324. package/src/components/SettingsDialog/SettingsRow.vue +52 -0
  325. package/src/components/SettingsDialog/SettingsSidebar.vue +16 -0
  326. package/src/components/SettingsDialog/index.ts +10 -0
  327. package/src/components/SettingsDialog/stories/Default.vue +99 -0
  328. package/src/components/SettingsDialog/stories/PanelBasic.vue +52 -0
  329. package/src/components/SettingsDialog/stories/panels/NotificationsPanel.vue +57 -0
  330. package/src/components/SettingsDialog/stories/panels/PreferencesPanel.vue +57 -0
  331. package/src/components/SettingsDialog/stories/panels/ProfilePanel.vue +38 -0
  332. package/src/components/SettingsDialog/stories/panels/UsersPanel.vue +83 -0
  333. package/src/components/SettingsDialog/types.ts +21 -0
  334. package/src/components/Sidebar/Sidebar.api.md +124 -65
  335. package/src/components/Sidebar/Sidebar.cy.ts +127 -76
  336. package/src/components/Sidebar/Sidebar.md +47 -3
  337. package/src/components/Sidebar/Sidebar.vue +64 -56
  338. package/src/components/Sidebar/SidebarCollapseToggle.vue +23 -0
  339. package/src/components/Sidebar/SidebarHeader.vue +76 -52
  340. package/src/components/Sidebar/SidebarItem.vue +159 -95
  341. package/src/components/Sidebar/SidebarItemIcon.vue +25 -0
  342. package/src/components/Sidebar/SidebarLabel.vue +34 -0
  343. package/src/components/Sidebar/SidebarSection.vue +29 -14
  344. package/src/components/Sidebar/index.ts +13 -2
  345. package/src/components/Sidebar/stories/Collapsed.vue +40 -0
  346. package/src/components/Sidebar/stories/Default.vue +153 -0
  347. package/src/components/Sidebar/stories/Legacy.vue +36 -0
  348. package/src/components/Sidebar/types.ts +105 -20
  349. package/src/components/Skeleton/Skeleton.cy.ts +21 -0
  350. package/src/components/Skeleton/Skeleton.md +21 -0
  351. package/src/components/Skeleton/Skeleton.vue +6 -0
  352. package/src/components/Skeleton/index.ts +1 -0
  353. package/src/components/Skeleton/stories/Card.vue +12 -0
  354. package/src/components/Skeleton/stories/List.vue +15 -0
  355. package/src/components/Skeleton/stories/Text.vue +11 -0
  356. package/src/components/Slider/Slider.api.md +1 -1
  357. package/src/components/Slider/Slider.cy.ts +29 -0
  358. package/src/components/Slider/Slider.md +10 -0
  359. package/src/components/Slider/Slider.vue +37 -9
  360. package/src/components/Slider/stories/NegativeValues.vue +12 -0
  361. package/src/components/Slider/types.ts +1 -1
  362. package/src/components/Spinner/Spinner.api.md +32 -0
  363. package/src/components/Spinner/Spinner.cy.ts +87 -0
  364. package/src/components/Spinner/Spinner.md +21 -0
  365. package/src/components/Spinner/Spinner.vue +168 -0
  366. package/src/components/Spinner/index.ts +2 -0
  367. package/src/components/Spinner/stories/InContext.vue +49 -0
  368. package/src/components/Spinner/stories/Sizes.vue +12 -0
  369. package/src/components/Spinner/stories/Themes.vue +10 -0
  370. package/src/components/Spinner/stories/Track.vue +12 -0
  371. package/src/components/Spinner/types.ts +14 -0
  372. package/src/components/Switch/Switch.md +4 -0
  373. package/src/components/Switch/Switch.vue +6 -23
  374. package/src/components/TabButtons/Pill.vue +161 -0
  375. package/src/components/TabButtons/TabButtons.api.md +52 -2
  376. package/src/components/TabButtons/TabButtons.cy.ts +18 -5
  377. package/src/components/TabButtons/TabButtons.md +49 -0
  378. package/src/components/TabButtons/TabButtons.vue +207 -109
  379. package/src/components/TabButtons/index.ts +11 -1
  380. package/src/components/TabButtons/pillTypes.ts +43 -0
  381. package/src/components/TabButtons/stories/PrefixSuffix.vue +65 -0
  382. package/src/components/TabButtons/stories/Sizes.vue +26 -0
  383. package/src/components/TabButtons/stories/Variants.vue +40 -0
  384. package/src/components/TabButtons/stories/Vertical.vue +64 -0
  385. package/src/components/TabButtons/types.ts +49 -0
  386. package/src/components/Tabs/Tabs.md +4 -0
  387. package/src/components/Tabs/Tabs.vue +1 -1
  388. package/src/components/TextEditor/TextEditor.cy.ts +1 -1
  389. package/src/components/TextEditor/TextEditor.md +3 -0
  390. package/src/components/TextEditor/TextEditor.vue +7 -4
  391. package/src/components/TextEditor/components/CodeBlockComponent.vue +4 -194
  392. package/src/components/TextEditor/components/FontColor.vue +1 -1
  393. package/src/components/TextEditor/components/LinkPopup.vue +1 -1
  394. package/src/components/TextEditor/components/MediaNodeView.vue +17 -17
  395. package/src/components/TextEditor/components/MentionList.vue +1 -1
  396. package/src/components/TextEditor/components/TextEditorBubbleMenu.vue +14 -5
  397. package/src/components/TextEditor/components/TextEditorFixedMenu.vue +3 -3
  398. package/src/components/TextEditor/components/{Menu.vue → TextEditorMenu.vue} +3 -3
  399. package/src/components/TextEditor/extensions/iframe/InsertIframe.vue +2 -2
  400. package/src/components/TextEditor/extensions/iframe/iframe-extension.ts +62 -19
  401. package/src/components/TextEditor/extensions/image-group/ImageGroupNodeView.vue +1 -1
  402. package/src/components/TextEditor/extensions/image-group/ImageGroupUploadDialog.vue +4 -4
  403. package/src/components/TextEditor/extensions/suggestion/SuggestionList.vue +1 -1
  404. package/src/components/TextEditor/extensions/toc-node/TocNodeView.vue +1 -1
  405. package/src/components/TextEditor/hljs-github.css +197 -0
  406. package/src/components/TextEditor/index.ts +13 -0
  407. package/src/components/TextInput/TextInput.cy.ts +8 -3
  408. package/src/components/TextInput/TextInput.md +4 -0
  409. package/src/components/TextInput/TextInput.vue +11 -15
  410. package/src/components/TextInput/stories/Slots.vue +1 -1
  411. package/src/components/Textarea/Textarea.cy.ts +1 -1
  412. package/src/components/Textarea/Textarea.md +4 -0
  413. package/src/components/Textarea/Textarea.vue +11 -15
  414. package/src/components/ThemeSwitcher/ThemePreview.vue +74 -0
  415. package/src/components/ThemeSwitcher/ThemeSwitcher.api.md +82 -0
  416. package/src/components/ThemeSwitcher/ThemeSwitcher.cy.ts +101 -0
  417. package/src/components/ThemeSwitcher/ThemeSwitcher.md +70 -0
  418. package/src/components/ThemeSwitcher/ThemeSwitcher.vue +145 -0
  419. package/src/components/ThemeSwitcher/index.ts +2 -0
  420. package/src/components/ThemeSwitcher/previews/PreviewWindow.vue +74 -0
  421. package/src/components/ThemeSwitcher/stories/Branded.vue +12 -0
  422. package/src/components/ThemeSwitcher/stories/Default.vue +7 -0
  423. package/src/components/ThemeSwitcher/stories/Menu.vue +53 -0
  424. package/src/components/ThemeSwitcher/stories/Toggle.vue +25 -0
  425. package/src/components/ThemeSwitcher/types.ts +35 -0
  426. package/src/components/TimePicker/TimePicker.api.md +67 -15
  427. package/src/components/TimePicker/TimePicker.cy.ts +130 -14
  428. package/src/components/TimePicker/TimePicker.md +6 -0
  429. package/src/components/TimePicker/TimePicker.vue +74 -19
  430. package/src/components/TimePicker/stories/Labeling.vue +21 -0
  431. package/src/components/TimePicker/stories/Range.vue +1 -1
  432. package/src/components/TimePicker/stories/SizesAndVariants.vue +23 -0
  433. package/src/components/TimePicker/stories/TwentyFour.vue +1 -1
  434. package/src/components/TimePicker/types.ts +15 -3
  435. package/src/components/TimePicker/utils.ts +82 -21
  436. package/src/components/Toast/Toast.md +1 -1
  437. package/src/components/Toast/Toast.sanitize.test.ts +50 -0
  438. package/src/components/Toast/Toast.test.ts +5 -0
  439. package/src/components/Toast/Toast.vue +7 -7
  440. package/src/components/Toast/ToastProvider.vue +18 -9
  441. package/src/components/Toast/stories/CustomIcon.vue +1 -1
  442. package/src/components/Toast/toast.ts +32 -8
  443. package/src/components/Tooltip/Tooltip.api.md +43 -5
  444. package/src/components/Tooltip/Tooltip.cy.ts +48 -0
  445. package/src/components/Tooltip/Tooltip.md +10 -0
  446. package/src/components/Tooltip/Tooltip.vue +20 -5
  447. package/src/components/Tooltip/TooltipBubble.vue +2 -2
  448. package/src/components/Tooltip/TooltipProvider.vue +53 -0
  449. package/src/components/Tooltip/index.ts +1 -0
  450. package/src/components/Tooltip/stories/Group.vue +22 -0
  451. package/src/components/Tooltip/stories/Slots.vue +2 -2
  452. package/src/components/Tooltip/types.ts +1 -1
  453. package/src/components/Tree/Tree.api.md +76 -18
  454. package/src/components/Tree/Tree.md +144 -2
  455. package/src/components/Tree/Tree.vue +276 -97
  456. package/src/components/Tree/TreeItem.vue +263 -0
  457. package/src/components/Tree/index.ts +9 -1
  458. package/src/components/Tree/stories/DragDrop.vue +94 -0
  459. package/src/components/Tree/stories/Example.vue +11 -34
  460. package/src/components/Tree/stories/ExpandAll.vue +48 -0
  461. package/src/components/Tree/stories/Guides.vue +49 -0
  462. package/src/components/Tree/stories/ListView.vue +74 -0
  463. package/src/components/Tree/tree.cy.ts +218 -90
  464. package/src/components/Tree/types.ts +114 -24
  465. package/src/components/Tree/useTreeDragDrop.ts +203 -0
  466. package/src/components/Tree/useTreeKeyboard.ts +109 -0
  467. package/src/components/VueGridLayout/Layout.vue +1 -1
  468. package/src/components/shared/picker/PickerShell.vue +20 -15
  469. package/src/components/shared/popover/PopoverPanel.vue +67 -0
  470. package/src/components/shared/popover/popoverPanel.css +102 -0
  471. package/src/components/shared/selection/utils.ts +6 -6
  472. package/src/composables/useAutofocusOnOpen.ts +2 -0
  473. package/src/composables/useScreenSize.ts +46 -0
  474. package/src/composables/useScrollContainer.ts +108 -0
  475. package/src/composables/useShortcut.ts +3 -0
  476. package/src/data-fetching/docStore.test.ts +74 -0
  477. package/src/data-fetching/docStore.ts +18 -7
  478. package/src/data-fetching/useCall/types.ts +1 -0
  479. package/src/data-fetching/useCall/useCall.test.ts +43 -0
  480. package/src/data-fetching/useCall/useCall.ts +17 -4
  481. package/src/data-fetching/useDoc/useDoc.test.ts +61 -2
  482. package/src/data-fetching/useDoc/useDoc.ts +35 -15
  483. package/src/data-fetching/useDoctype/useDoctype.ts +6 -5
  484. package/src/data-fetching/useFrappeFetch.ts +17 -4
  485. package/src/data-fetching/useList/types.ts +1 -0
  486. package/src/data-fetching/useList/useList.test.ts +40 -0
  487. package/src/data-fetching/useList/useList.ts +20 -13
  488. package/src/data-fetching/useNewDoc/useNewDoc.ts +9 -5
  489. package/src/directives/focus.ts +3 -3
  490. package/src/index.ts +105 -74
  491. package/src/mocks/handlers.ts +8 -0
  492. package/src/molecules/editor/Editor.cy.ts +6 -1
  493. package/src/molecules/editor/EditorBubbleMenu.vue +1 -1
  494. package/src/molecules/editor/EditorContent.vue +50 -9
  495. package/src/molecules/editor/EditorFixedMenu.vue +2 -1
  496. package/src/molecules/editor/EditorFloatingMenu.vue +1 -1
  497. package/src/molecules/editor/EditorTableMenu.vue +303 -0
  498. package/src/molecules/editor/MenuItems.vue +81 -69
  499. package/src/molecules/editor/commands.ts +10 -2
  500. package/src/molecules/editor/components/AttachmentNodeView.vue +115 -0
  501. package/src/molecules/editor/components/EditorDropZone.vue +105 -0
  502. package/src/molecules/editor/components/EditorPopover.vue +89 -0
  503. package/src/molecules/editor/components/MediaNodeView.vue +156 -49
  504. package/src/molecules/editor/components/MediaResizeHandles.vue +38 -0
  505. package/src/molecules/editor/components/MediaToolbar.vue +108 -54
  506. package/src/molecules/editor/components/TableContextMenu.vue +144 -0
  507. package/src/molecules/editor/components/UploadProgressIndicator.vue +33 -0
  508. package/src/molecules/editor/components/VideoControls.vue +208 -0
  509. package/src/molecules/editor/components/font-color/ColorSwatchGrid.vue +3 -5
  510. package/src/molecules/editor/components/font-color/fontColorController.ts +28 -23
  511. package/src/molecules/editor/components/font-color/swatches.ts +1 -1
  512. package/src/molecules/editor/components/media-node-view-controller.ts +4 -4
  513. package/src/molecules/editor/components/media-node-view-utils.test.ts +0 -13
  514. package/src/molecules/editor/components/media-node-view-utils.ts +0 -10
  515. package/src/molecules/editor/components/table-color/tableCellColorController.ts +80 -0
  516. package/src/molecules/editor/components/table-size-picker/TableSizePicker.vue +92 -0
  517. package/src/molecules/editor/components/table-size-picker/tableSizePickerController.ts +40 -0
  518. package/src/molecules/editor/composables/useEditorFileDrop.ts +117 -0
  519. package/src/molecules/editor/composables/useFloatingPopup.ts +46 -2
  520. package/src/molecules/editor/composables/useNodeViewResize.test.ts +60 -11
  521. package/src/molecules/editor/composables/useNodeViewResize.ts +80 -21
  522. package/src/molecules/editor/composables/useTableCellColorState.ts +78 -0
  523. package/src/molecules/editor/composables/useWindowFileDragging.ts +68 -0
  524. package/src/molecules/editor/extensions/attachment/attachment-engine.ts +250 -0
  525. package/src/molecules/editor/extensions/attachment/attachment-extension.ts +248 -0
  526. package/src/molecules/editor/extensions/attachment/attachment.flow.test.ts +80 -0
  527. package/src/molecules/editor/extensions/attachment/attachment.roundtrip.test.ts +50 -0
  528. package/src/molecules/editor/extensions/attachment/index.ts +5 -0
  529. package/src/molecules/editor/extensions/code-block/CodeBlockComponent.css +16 -26
  530. package/src/molecules/editor/extensions/code-block/CodeBlockComponent.vue +107 -22
  531. package/src/molecules/editor/extensions/content-paste/content-paste-extension.ts +5 -3
  532. package/src/molecules/editor/extensions/iframe/IframeInsertDialog.vue +34 -7
  533. package/src/molecules/editor/extensions/iframe/IframeNodeView.vue +54 -48
  534. package/src/molecules/editor/extensions/iframe/iframe-allowlist.ts +33 -4
  535. package/src/molecules/editor/extensions/iframe/iframe-commands.ts +49 -5
  536. package/src/molecules/editor/extensions/iframe/iframe-embed-utils.ts +30 -0
  537. package/src/molecules/editor/extensions/iframe/iframe-extension.ts +4 -4
  538. package/src/molecules/editor/extensions/iframe/iframeInsertDialogController.ts +12 -1
  539. package/src/molecules/editor/extensions/iframe/index.ts +4 -4
  540. package/src/molecules/editor/extensions/iframe/useIframeDialog.ts +25 -13
  541. package/src/molecules/editor/extensions/image/image-extension.ts +29 -11
  542. package/src/molecules/editor/extensions/image-group/ImageGroupGrid.vue +27 -11
  543. package/src/molecules/editor/extensions/image-group/ImageGroupGridCell.vue +55 -11
  544. package/src/molecules/editor/extensions/image-group/ImageGroupNodeView.vue +64 -31
  545. package/src/molecules/editor/extensions/image-group/ImageGroupUploadDialog.vue +84 -40
  546. package/src/molecules/editor/extensions/image-group/image-group-extension.ts +4 -3
  547. package/src/molecules/editor/extensions/image-group/useImageGroupDialog.ts +116 -17
  548. package/src/molecules/editor/extensions/link/LinkEditorPopup.vue +214 -71
  549. package/src/molecules/editor/extensions/link/link-click-plugin.ts +10 -15
  550. package/src/molecules/editor/extensions/link/link-commands.ts +24 -3
  551. package/src/molecules/editor/extensions/link/link-extension.ts +11 -2
  552. package/src/molecules/editor/extensions/link/link-popup-controller.test.ts +49 -0
  553. package/src/molecules/editor/extensions/link/link-popup-controller.ts +67 -2
  554. package/src/molecules/editor/extensions/link/link-shortcut-plugin.ts +7 -2
  555. package/src/molecules/editor/extensions/media-drop/media-drop-extension.ts +177 -0
  556. package/src/molecules/editor/extensions/shared/media-dimensions.ts +36 -3
  557. package/src/molecules/editor/extensions/shared/media-node-ops.ts +17 -0
  558. package/src/molecules/editor/extensions/shared/media-plugin.test.ts +60 -0
  559. package/src/molecules/editor/extensions/shared/media-plugin.ts +7 -37
  560. package/src/molecules/editor/extensions/shared/media-upload-engine.test.ts +39 -2
  561. package/src/molecules/editor/extensions/shared/media-upload-engine.ts +104 -10
  562. package/src/molecules/editor/extensions/shared/media-upload-state.ts +55 -0
  563. package/src/molecules/editor/extensions/shared/media-upload-types.ts +52 -1
  564. package/src/molecules/editor/extensions/shared/suggestion-helpers.ts +20 -0
  565. package/src/molecules/editor/extensions/shared/suggestion-types.ts +7 -0
  566. package/src/molecules/editor/extensions/shared/upload-types.ts +1 -0
  567. package/src/molecules/editor/extensions/slash-commands/slash-commands-extension.test.ts +38 -0
  568. package/src/molecules/editor/extensions/slash-commands/slash-commands-extension.ts +119 -55
  569. package/src/molecules/editor/extensions/suggestion/SuggestionList.vue +62 -22
  570. package/src/molecules/editor/extensions/suggestion/SuggestionListItem.vue +1 -1
  571. package/src/molecules/editor/extensions/suggestion/createSuggestionExtension.ts +4 -0
  572. package/src/molecules/editor/extensions/table/drag-scroll.ts +154 -0
  573. package/src/molecules/editor/extensions/table/table-cell-color.ts +188 -0
  574. package/src/molecules/editor/extensions/table/table-navigation.ts +560 -0
  575. package/src/molecules/editor/extensions/table/table-selection-overlay.ts +137 -0
  576. package/src/molecules/editor/extensions/toc-node/TocNodeView.vue +1 -1
  577. package/src/molecules/editor/extensions/toc-node/toc-render.ts +7 -3
  578. package/src/molecules/editor/extensions/video/video-extension.ts +72 -5
  579. package/src/molecules/editor/extensions.ts +176 -8
  580. package/src/molecules/editor/index.ts +9 -4
  581. package/src/molecules/editor/kits.test.ts +21 -1
  582. package/src/molecules/editor/kits.ts +56 -33
  583. package/src/molecules/editor/menu.test.ts +3 -1
  584. package/src/molecules/editor/menu.ts +180 -18
  585. package/src/molecules/editor/stories/Comment.vue +4 -4
  586. package/src/molecules/editor/stories/Inline.vue +1 -1
  587. package/src/molecules/editor/stories/Primitives.vue +1 -1
  588. package/src/molecules/editor/stories/RichText.vue +1 -1
  589. package/src/molecules/editor/style.css +80 -3
  590. package/src/molecules/editor/useEditor.test.ts +4 -0
  591. package/src/molecules/editor/useEditor.ts +7 -3
  592. package/src/molecules/list/List.cy.ts +626 -0
  593. package/src/molecules/list/List.vue +93 -0
  594. package/src/molecules/list/ListCell.vue +15 -0
  595. package/src/molecules/list/ListGroup.vue +40 -0
  596. package/src/molecules/list/ListHeader.vue +62 -0
  597. package/src/molecules/list/ListHeaderCell.vue +26 -0
  598. package/src/molecules/list/ListHeaderCellSort.vue +106 -0
  599. package/src/molecules/list/ListRow.vue +55 -0
  600. package/src/molecules/list/ListRowBase.vue +103 -0
  601. package/src/molecules/list/ListRows.vue +120 -0
  602. package/src/molecules/list/index.ts +24 -0
  603. package/src/molecules/list/list-context.ts +44 -0
  604. package/src/molecules/list/list.api.md +276 -0
  605. package/src/molecules/list/list.md +119 -0
  606. package/src/molecules/list/stories/Columns.vue +103 -0
  607. package/src/molecules/list/stories/Feed.vue +105 -0
  608. package/src/molecules/list/stories/Virtual.vue +31 -0
  609. package/src/molecules/list/style.css +173 -0
  610. package/src/molecules/list/types.ts +82 -0
  611. package/src/molecules/list/useVirtualRows.ts +80 -0
  612. package/src/resources/documentResource.d.ts +28 -0
  613. package/src/resources/documentResource.js +32 -4
  614. package/src/resources/documentResource.test.ts +111 -0
  615. package/src/resources/listResource.d.ts +33 -0
  616. package/src/resources/plugin.d.ts +7 -0
  617. package/src/resources/resources.d.ts +35 -0
  618. package/src/utils/{call.js → call.ts} +50 -9
  619. package/src/utils/config.ts +16 -1
  620. package/src/utils/confirmDialog.d.ts +17 -0
  621. package/src/utils/debounce.ts +6 -7
  622. package/src/utils/dialog.cy.ts +9 -9
  623. package/src/utils/fileSize.ts +36 -0
  624. package/src/utils/fileUploadHandler.ts +46 -6
  625. package/src/utils/frappeRequest.test.ts +79 -0
  626. package/src/utils/{frappeRequest.js → frappeRequest.ts} +76 -13
  627. package/src/utils/pageMeta.ts +1 -1
  628. package/src/utils/plugin.ts +58 -0
  629. package/src/utils/{request.js → request.ts} +28 -3
  630. package/src/utils/{socketio.js → socketio.ts} +11 -1
  631. package/src/utils/theme.ts +48 -57
  632. package/src/utils/useFileUpload.ts +80 -23
  633. package/tailwind/audit-token-drift.cjs +176 -0
  634. package/tailwind/colorPalette.js +69 -8
  635. package/tailwind/colors.json +1115 -397
  636. package/tailwind/figma-tokens-to-theme.js +364 -0
  637. package/tailwind/generated/colors.json +1217 -0
  638. package/tailwind/generated/effects.json +41 -0
  639. package/tailwind/generated/radius.json +20 -0
  640. package/tailwind/generated/typography.json +479 -0
  641. package/tailwind/migrate-tokens-v2.js +453 -0
  642. package/tailwind/migrate-tokens-v2.test.js +55 -0
  643. package/tailwind/plugin.js +220 -15
  644. package/tailwind/preset.js +15 -0
  645. package/tailwind/tokens.js +35 -37
  646. package/tsconfig.base.json +2 -2
  647. package/vitepress/components/Docs/CommandPalette.vue +214 -0
  648. package/vitepress/components/Docs/Demo.vue +60 -0
  649. package/vitepress/components/Docs/EmitsTable.vue +99 -0
  650. package/vitepress/components/Docs/MobileNavSheet.vue +148 -0
  651. package/vitepress/components/Docs/OnThisPage.vue +74 -0
  652. package/vitepress/components/Docs/PrevNextBtns.vue +58 -0
  653. package/vitepress/components/Docs/PropsTable.vue +185 -0
  654. package/vitepress/components/Docs/Search.vue +34 -0
  655. package/vitepress/components/Docs/Sidebar.vue +91 -0
  656. package/vitepress/components/Docs/SlotsTable.vue +99 -0
  657. package/vitepress/components/Docs/index.ts +13 -0
  658. package/vitepress/components/Docs/sidebarList.ts +33 -0
  659. package/vitepress/components/Layout.vue +58 -0
  660. package/vitepress/components/Navbar.vue +126 -0
  661. package/vitepress/components/index.ts +3 -0
  662. package/vitepress/composables/index.ts +3 -0
  663. package/vitepress/composables/useTheme.ts +39 -0
  664. package/vitepress/css/fonts/Inter-Italic.var.woff2 +0 -0
  665. package/vitepress/css/fonts/Inter.var.woff2 +0 -0
  666. package/vitepress/css/style.css +198 -0
  667. package/vitepress/index.d.ts +23 -0
  668. package/vitepress/index.node.ts +175 -0
  669. package/vitepress/index.ts +17 -0
  670. package/vitepress/plugins/colocatedComponentDocs.ts +164 -0
  671. package/vitepress/plugins/componentTransformer.ts +286 -0
  672. package/vitepress/plugins/index.ts +12 -0
  673. package/vitepress/state.ts +10 -0
  674. package/vitepress/theme/index.ts +21 -0
  675. package/frappe/telemetry/utils.ts +0 -15
  676. package/src/components/Badge/stories/Themes.vue +0 -10
  677. package/src/components/Badge/stories/Variants.vue +0 -10
  678. package/src/components/Button/stories/Icons.vue +0 -27
  679. package/src/components/Button/stories/Sizes.vue +0 -11
  680. package/src/components/Button/stories/Themes.vue +0 -10
  681. package/src/components/Button/stories/Variants.vue +0 -10
  682. package/src/components/Popover/stories/Hover.vue +0 -14
  683. package/src/components/Resource.vue +0 -21
  684. package/src/components/Sidebar/stories/Example.vue +0 -76
  685. package/src/components/Spinner.vue +0 -3
  686. package/src/components/TabButtons/TabButtons.story.vue +0 -22
  687. package/src/utils/plugin.js +0 -24
  688. /package/src/components/Dropdown/stories/{02_Shortcuts.vue → Shortcuts.vue} +0 -0
  689. /package/src/components/Dropdown/stories/{01_Simple.vue → Simple.vue} +0 -0
  690. /package/src/components/Dropdown/stories/{03_Submenus.vue → Submenus.vue} +0 -0
  691. /package/src/components/Dropdown/stories/{04_Switches.vue → Switches.vue} +0 -0
  692. /package/src/components/{Dropdown/DropdownRenderContentAsChild.vue → Menu/MenuRenderContentAsChild.vue} +0 -0
@@ -0,0 +1,556 @@
1
+ <template>
2
+ <LabelingWrapper
3
+ :enabled="hasLabeling"
4
+ :wrapper-class="['space-y-1', attrs.class]"
5
+ :wrapper-style="attrs.style as any"
6
+ >
7
+ <InputLabel
8
+ v-if="props.label || $slots.label"
9
+ :id="labelId"
10
+ :label="props.label"
11
+ :required="props.required"
12
+ >
13
+ <template v-if="$slots.label" #default="slotProps">
14
+ <slot name="label" v-bind="slotProps" />
15
+ </template>
16
+ </InputLabel>
17
+ <div
18
+ ref="el"
19
+ class="code-editor"
20
+ data-slot="control"
21
+ :class="hasLabeling ? null : (attrs.class as any)"
22
+ :style="hasLabeling ? null : (attrs.style as any)"
23
+ v-bind="dataAttrs"
24
+ />
25
+ <InputDescription
26
+ v-if="showDescription || $slots.description"
27
+ :id="descriptionId"
28
+ :description="props.description"
29
+ >
30
+ <slot v-if="$slots.description" name="description" />
31
+ </InputDescription>
32
+ <InputError v-if="hasError" :id="errorMessageId" :lines="errorLines" />
33
+ </LabelingWrapper>
34
+ </template>
35
+
36
+ <script setup lang="ts">
37
+ // Writer primitive: a CodeMirror 6 editor, nothing else (no preview logic — that
38
+ // lives in the sibling `CodePreview`). CodeMirror is lazy-loaded in `onMounted`
39
+ // via dynamic `import()` so the static graph stays editor-free and each language
40
+ // tree-shakes into its own async chunk — apps that never render a code field pay
41
+ // no runtime cost (importing `frappe-ui/code-editor` pulls in no editor code
42
+ // until a field actually mounts).
43
+ import {
44
+ computed,
45
+ onBeforeUnmount,
46
+ onMounted,
47
+ ref,
48
+ useAttrs,
49
+ useSlots,
50
+ watch,
51
+ } from 'vue'
52
+ import type { EditorView } from '@codemirror/view'
53
+ import type { Compartment, Extension } from '@codemirror/state'
54
+ import { useInputLabeling } from '../../composables/useInputLabeling'
55
+ import InputLabel from '../InputLabeling/InputLabel.vue'
56
+ import InputDescription from '../InputLabeling/InputDescription.vue'
57
+ import InputError from '../InputLabeling/InputError.vue'
58
+ import LabelingWrapper from '../InputLabeling/LabelingWrapper.vue'
59
+ import { loadLanguage } from './languages'
60
+ import { buildBaseTheme, buildSyntaxHighlight } from './theme'
61
+ import type { CodeEditorEmits, CodeEditorProps } from './types'
62
+
63
+ defineOptions({ inheritAttrs: false })
64
+
65
+ const props = withDefaults(defineProps<CodeEditorProps>(), {
66
+ language: 'plain',
67
+ disabled: false,
68
+ placeholder: '',
69
+ variant: 'subtle',
70
+ size: 'md',
71
+ })
72
+ const emit = defineEmits<CodeEditorEmits>()
73
+ defineSlots<{
74
+ /** Overrides the rendered label content. Receives `{ required }`. */
75
+ label?: (props: { required: boolean }) => any
76
+ /** Overrides the rendered description content. */
77
+ description?: () => any
78
+ }>()
79
+
80
+ const attrs = useAttrs()
81
+ const slots = useSlots()
82
+
83
+ const {
84
+ labelId,
85
+ descriptionId,
86
+ errorMessageId,
87
+ labelledBy,
88
+ describedBy,
89
+ hasError,
90
+ errorLines,
91
+ showDescription,
92
+ dataAttrs,
93
+ } = useInputLabeling(props, {
94
+ size: () => props.size,
95
+ variant: () => props.variant,
96
+ disabled: () => props.disabled,
97
+ })
98
+
99
+ // Render the labeling chrome (and its wrapping div) only when something needs
100
+ // it — otherwise the editor mounts bare, preserving the primitive's footprint.
101
+ const hasLabeling = computed(() =>
102
+ Boolean(
103
+ props.label ||
104
+ slots.label ||
105
+ showDescription.value ||
106
+ slots.description ||
107
+ hasError.value,
108
+ ),
109
+ )
110
+
111
+ const el = ref<HTMLElement | null>(null)
112
+
113
+ let view: EditorView | null = null
114
+ let languageCompartment: Compartment | null = null
115
+ let disabledCompartment: Compartment | null = null
116
+ let variantCompartment: Compartment | null = null
117
+ let sizeCompartment: Compartment | null = null
118
+ let placeholderCompartment: Compartment | null = null
119
+ // Carries the labeling ARIA onto CodeMirror's `contentDOM` (the focusable
120
+ // contenteditable). Setting it on the outer wrapper would miss the element a
121
+ // screen reader actually lands on, so it goes through `contentAttributes`.
122
+ let a11yCompartment: Compartment | null = null
123
+ // Imported CodeMirror modules, held so the watchers can reconfigure without a
124
+ // second dynamic import.
125
+ let cmView: typeof import('@codemirror/view') | null = null
126
+ let cmState: typeof import('@codemirror/state') | null = null
127
+ // Guards the `modelValue` watcher against the editor's own `updateListener`: when
128
+ // we dispatch an external change (e.g. the wrapper's JSON pretty-print) we must
129
+ // not echo it straight back out as fresh input.
130
+ let syncingFromProp = false
131
+ // Tracks whether content overflows the height cap so the `overflow` emit only
132
+ // fires on transitions, not on every keystroke. The observer watches the
133
+ // scroller box. The cap itself is pure CSS — consumers set `--cm-max-height` on
134
+ // the root (e.g. `style="--cm-max-height: 13.5rem"`), which the base theme reads
135
+ // as the editor's `max-height` (P10: styling via CSS hooks, not a style prop).
136
+ // This emit is the one piece a consumer can't derive from CSS — measuring the
137
+ // crossing needs the ResizeObserver below — so it stays on the public surface.
138
+ let overflowObserver: ResizeObserver | null = null
139
+ let lastOverflow = false
140
+ // `onMounted` is async (it awaits CodeMirror's dynamic imports). If the component
141
+ // unmounts mid-load, `onBeforeUnmount` runs while `view` is still null and its
142
+ // cleanup no-ops — then the resumed `onMounted` would create an orphan view +
143
+ // observer that nothing destroys. This flag lets the resume bail instead.
144
+ let destroyed = false
145
+
146
+ // Drive the gutter's horizontal-scroll shadow (see theme.ts). Two things: flag
147
+ // `cm-scrolled-x` when the content is scrolled under the sticky gutter so the
148
+ // shadow fades in, and publish `--cm-text-height` (the bottom of the last line)
149
+ // so the shadow overlay is capped to the actual code and doesn't run down into
150
+ // the empty min-height area below it. The scroller's `scroll` event doesn't
151
+ // bubble, so the scroll half can't ride CodeMirror's `domEventHandlers`; it's a
152
+ // plain listener on `scrollDOM`. The height half is refreshed wherever geometry
153
+ // changes (mount, edits, reflow, size) alongside `measureOverflow`.
154
+ function syncGutterShadow() {
155
+ if (!view) return
156
+ view.dom.classList.toggle('cm-scrolled-x', view.scrollDOM.scrollLeft > 0)
157
+ const textHeight = view.lineBlockAt(view.state.doc.length).bottom
158
+ view.dom.style.setProperty('--cm-text-height', `${textHeight}px`)
159
+ }
160
+
161
+ // Emit when the scroller becomes (un)scrollable — i.e. content crosses the cap.
162
+ function measureOverflow() {
163
+ if (!view) return
164
+ const s = view.scrollDOM
165
+ const overflowing = s.scrollHeight > s.clientHeight + 1
166
+ if (overflowing === lastOverflow) return
167
+ lastOverflow = overflowing
168
+ emit('overflow', overflowing)
169
+ }
170
+
171
+ // Build the language `Compartment` contents for a key. For `json` we also wire
172
+ // the lint gutter + parse linter so invalid JSON is marked inline at its
173
+ // position — the field contract has no error channel, so validation surfaces
174
+ // here rather than in the field chrome.
175
+ async function buildLanguageExtension(key?: string): Promise<Extension> {
176
+ const exts: Extension[] = []
177
+ const lang = await loadLanguage(key)
178
+ if (lang) exts.push(lang)
179
+ if (key === 'json') {
180
+ const { jsonParseLinter } = await import('@codemirror/lang-json')
181
+ const { linter, lintGutter } = await import('@codemirror/lint')
182
+ exts.push(lintGutter(), linter(jsonParseLinter()))
183
+ }
184
+ return exts
185
+ }
186
+
187
+ // ARIA for the `contentDOM`, derived from the labeling props. Reconfigured by a
188
+ // watcher when the label/description/error wiring changes.
189
+ function buildA11y(): Extension {
190
+ if (!cmView) return []
191
+ const a: Record<string, string> = {}
192
+ if (labelledBy.value) a['aria-labelledby'] = labelledBy.value
193
+ if (describedBy.value) a['aria-describedby'] = describedBy.value
194
+ if (hasError.value) {
195
+ a['aria-invalid'] = 'true'
196
+ a['aria-errormessage'] = errorMessageId.value
197
+ }
198
+ if (props.required) a['aria-required'] = 'true'
199
+ return cmView.EditorView.contentAttributes.of(a)
200
+ }
201
+
202
+ // Placeholder shown when the doc is empty. Empty string → no extension (a bare
203
+ // `placeholder('')` would render an empty hint box). Held in a compartment so a
204
+ // consumer can change the text at runtime without recreating the view.
205
+ function buildPlaceholder(text?: string): Extension {
206
+ if (!cmView || !text) return []
207
+ return cmView.placeholder(text)
208
+ }
209
+
210
+ // CodeMirror side of `disabled`: make the doc read-only and the content DOM
211
+ // non-editable. The greyed-out surface is owned by `buildVariant` (the variant
212
+ // compartment is the sole source of surface chrome), so disabling reconfigures
213
+ // both compartments — see the `disabled` watcher.
214
+ function buildDisabled(disabled: boolean): Extension {
215
+ if (!cmState || !cmView) return []
216
+ return [
217
+ cmState.EditorState.readOnly.of(disabled),
218
+ cmView.EditorView.editable.of(!disabled),
219
+ ]
220
+ }
221
+
222
+ // Per-variant surface (background + border + focus treatment), mirroring
223
+ // frappe-ui's `subtle | outline` input variants so a code field sits
224
+ // flush with the TextInput/Textarea fields around it (`ghost` is intentionally
225
+ // not offered — a borderless code surface loses its editable affordance). The
226
+ // base `theme` owns
227
+ // everything else (typography, radius, gutters, tooltips) and deliberately does
228
+ // NOT set bg/border, so these fragments are the sole source of surface chrome —
229
+ // no precedence fight. Added after `theme` in the extensions array, so they win
230
+ // over `basicSetup`'s defaults the same way `theme` already does.
231
+ function buildVariant(v?: string, disabled?: boolean): Extension {
232
+ if (!cmView) return []
233
+ // Disabled surface, mirroring frappe-ui's TextInput/Textarea `disabled`
234
+ // variant: a flat greyed box with no hover/focus chrome, content dimmed so the
235
+ // whole field (including syntax-highlighted tokens) reads as inert. Keeps the
236
+ // base variant's border treatment (`outline` stays bordered, `subtle` goes
237
+ // transparent) so a disabled field keeps the footprint of its enabled self.
238
+ if (disabled) {
239
+ return cmView.EditorView.theme({
240
+ '&': {
241
+ backgroundColor: 'var(--surface-gray-1, #f8fafc)',
242
+ border:
243
+ v === 'outline'
244
+ ? '1px solid var(--outline-gray-2, #e2e8f0)'
245
+ : '1px solid transparent',
246
+ },
247
+ // Gutter shares the disabled surface so scrolled content can't bleed
248
+ // through behind it (the gutter is sticky over the scroller).
249
+ '.cm-gutters': { backgroundColor: 'var(--surface-gray-1, #f8fafc)' },
250
+ '.cm-content': { opacity: '0.6' },
251
+ })
252
+ }
253
+ const ring = '0 0 0 2px var(--outline-gray-3, #cbd5e1)'
254
+ // Each variant paints `.cm-gutters` to match its `&` surface in the same
255
+ // state — the gutter is sticky over the scroller, so a transparent one would
256
+ // let horizontally-scrolled code bleed through behind the line numbers.
257
+ const fragments: Record<string, Record<string, Record<string, string>>> = {
258
+ subtle: {
259
+ '&': {
260
+ backgroundColor: 'var(--surface-gray-2, #f1f5f9)',
261
+ border: '1px solid var(--surface-gray-2, #e2e8f0)',
262
+ },
263
+ '.cm-gutters': { backgroundColor: 'var(--surface-gray-2, #f1f5f9)' },
264
+ // Hover mirrors frappe-ui's TextInput/Textarea `subtle` variant
265
+ // (`hover:border-outline-elevation-2 hover:bg-surface-gray-3`) so a code
266
+ // field reacts to hover the same way the inputs around it do. Declared
267
+ // before `&.cm-focused` so the focused surface wins when both apply.
268
+ '&:hover': {
269
+ backgroundColor: 'var(--surface-gray-3, #e2e8f0)',
270
+ borderColor: 'var(--outline-elevation-2, #94a3b8)',
271
+ },
272
+ '&:hover .cm-gutters': {
273
+ backgroundColor: 'var(--surface-gray-3, #e2e8f0)',
274
+ },
275
+ '&.cm-focused': {
276
+ backgroundColor: 'var(--surface-base, white)',
277
+ borderColor: 'var(--outline-gray-4, #94a3b8)',
278
+ boxShadow: ring,
279
+ },
280
+ '&.cm-focused .cm-gutters': {
281
+ backgroundColor: 'var(--surface-base, white)',
282
+ },
283
+ },
284
+ outline: {
285
+ '&': {
286
+ backgroundColor: 'var(--surface-base, white)',
287
+ border: '1px solid var(--outline-gray-2, #e2e8f0)',
288
+ },
289
+ '.cm-gutters': { backgroundColor: 'var(--surface-base, white)' },
290
+ '&.cm-focused': {
291
+ borderColor: 'var(--outline-gray-4, #94a3b8)',
292
+ boxShadow: ring,
293
+ },
294
+ },
295
+ }
296
+ return cmView.EditorView.theme(fragments[v ?? 'subtle'] ?? fragments.subtle)
297
+ }
298
+
299
+ // Per-size font scale + content min-height, mirroring frappe-ui's `sm|md|lg|xl`
300
+ // input sizes. This compartment is the SOLE source of `&` font size and
301
+ // `.cm-content` min-height — the base `theme` deliberately omits both so there's
302
+ // no equal-specificity fight whose winner depends on StyleModule order.
303
+ function buildSize(s?: string): Extension {
304
+ if (!cmView) return []
305
+ const sizes: Record<string, { fontSize: string; minHeight: string }> = {
306
+ sm: { fontSize: '12px', minHeight: '3.5rem' },
307
+ md: { fontSize: '13px', minHeight: '4.5rem' },
308
+ lg: { fontSize: '14px', minHeight: '6rem' },
309
+ xl: { fontSize: '16px', minHeight: '7.5rem' },
310
+ }
311
+ const cfg = sizes[s ?? 'md'] ?? sizes.md
312
+ return cmView.EditorView.theme({
313
+ '&': { fontSize: cfg.fontSize },
314
+ '.cm-content': { minHeight: cfg.minHeight },
315
+ })
316
+ }
317
+
318
+ onMounted(async () => {
319
+ // SSR guard — no editor without a DOM.
320
+ if (typeof document === 'undefined' || !el.value) return
321
+
322
+ cmView = await import('@codemirror/view')
323
+ cmState = await import('@codemirror/state')
324
+ const { basicSetup } = await import('codemirror')
325
+ // `basicSetup` leaves Tab as a focus-move (CodeMirror's a11y default); bind it
326
+ // to indent/dedent so Tab behaves like an editor, not a form field. Escape
327
+ // still releases focus, so keyboard users can tab out via Esc-then-Tab.
328
+ const { indentWithTab } = await import('@codemirror/commands')
329
+ const { EditorView } = cmView
330
+ const { Compartment } = cmState
331
+
332
+ languageCompartment = new Compartment()
333
+ disabledCompartment = new Compartment()
334
+ variantCompartment = new Compartment()
335
+ sizeCompartment = new Compartment()
336
+ placeholderCompartment = new Compartment()
337
+ a11yCompartment = new Compartment()
338
+
339
+ // Base chrome theme + token-keyed syntax highlighting live in `./theme` (the
340
+ // ~200 lines of style literals would otherwise drown the editor logic). Both
341
+ // take the already-imported CodeMirror modules so no editor code leaks into the
342
+ // static graph.
343
+ const theme = buildBaseTheme(cmView)
344
+ const highlight = await buildSyntaxHighlight(cmState)
345
+
346
+ const updateListener = EditorView.updateListener.of((update) => {
347
+ if (update.docChanged && !syncingFromProp)
348
+ emit('update:modelValue', update.state.doc.toString())
349
+ // Content height may have changed — re-check the cap and the scroll-shadow
350
+ // overlay. A doc edit changes height, but so does folding/unfolding (a
351
+ // view-state effect, not a doc change) and any reflow. The capped/min-height
352
+ // scroller can keep its box size constant while content height shifts, so the
353
+ // ResizeObserver never fires for those — `geometryChanged` is what catches
354
+ // them (folding latching a stale Expand button was the bug this closes).
355
+ if (update.docChanged || update.geometryChanged) {
356
+ measureOverflow()
357
+ syncGutterShadow()
358
+ }
359
+ })
360
+
361
+ // Blur is the commit point (mirrors the typed-field contract).
362
+ const blurHandler = EditorView.domEventHandlers({
363
+ blur: () => {
364
+ if (view) emit('change', view.state.doc.toString())
365
+ },
366
+ })
367
+
368
+ // Escape releases focus. `indentWithTab` rebinds Tab to indent, which would
369
+ // otherwise trap keyboard users inside the editor with no way out (WCAG
370
+ // 2.1.2). Escape blurs the content DOM so they can Tab on to the next field —
371
+ // Esc-then-Tab, the standard CodeMirror keyboard-trap escape hatch.
372
+ const escapeHandler = EditorView.domEventHandlers({
373
+ keydown: (e, v) => {
374
+ if (e.key === 'Escape') {
375
+ v.contentDOM.blur()
376
+ return true
377
+ }
378
+ return false
379
+ },
380
+ })
381
+
382
+ const extensions: Extension[] = [
383
+ basicSetup,
384
+ cmView.keymap.of([indentWithTab]),
385
+ escapeHandler,
386
+ languageCompartment.of(await buildLanguageExtension(props.language)),
387
+ disabledCompartment.of(buildDisabled(!!props.disabled)),
388
+ a11yCompartment.of(buildA11y()),
389
+ updateListener,
390
+ blurHandler,
391
+ theme,
392
+ variantCompartment.of(buildVariant(props.variant, props.disabled)),
393
+ sizeCompartment.of(buildSize(props.size)),
394
+ placeholderCompartment.of(buildPlaceholder(props.placeholder)),
395
+ highlight,
396
+ ]
397
+
398
+ // Bail if the component unmounted while the dynamic imports above were in
399
+ // flight — otherwise we'd create a view + observer that `onBeforeUnmount`
400
+ // already ran past and nothing would ever destroy.
401
+ if (destroyed || !el.value) return
402
+
403
+ view = new EditorView({
404
+ doc: props.modelValue ?? '',
405
+ extensions,
406
+ parent: el.value,
407
+ })
408
+
409
+ measureOverflow()
410
+ // Shadow the gutter while the content is scrolled horizontally. Listen on the
411
+ // scroller (the event doesn't bubble) and seed the initial state.
412
+ view.scrollDOM.addEventListener('scroll', syncGutterShadow, { passive: true })
413
+ syncGutterShadow()
414
+ // Catches overflow changes the doc-edit path misses: wrapping reflow on width
415
+ // change, and the cap being applied/removed (a consumer toggling
416
+ // `--cm-max-height` resizes the scroller, which fires this). Fires once on
417
+ // observe for the initial measurement.
418
+ if (typeof ResizeObserver !== 'undefined') {
419
+ overflowObserver = new ResizeObserver(() => {
420
+ measureOverflow()
421
+ syncGutterShadow()
422
+ })
423
+ overflowObserver.observe(view.scrollDOM)
424
+ }
425
+ })
426
+
427
+ // Sync external `modelValue` changes into the view. This is also what makes the
428
+ // wrapper's JSON pretty-print visible: when it rewrites the value on commit, this
429
+ // dispatches the reformatted string back into the editor.
430
+ //
431
+ // Rather than replacing the whole doc (which would reset the caret to the end,
432
+ // collapse the selection, jump the scroll, and abort IME composition), we diff
433
+ // off the common prefix/suffix and change only the differing middle. CodeMirror
434
+ // then maps the existing selection through that change, so an unchanged caret
435
+ // stays put — important when a consumer transforms the value live, not just on
436
+ // the unfocused blur/pretty-print path.
437
+ watch(
438
+ () => props.modelValue,
439
+ (val) => {
440
+ if (!view) return
441
+ const current = view.state.doc.toString()
442
+ const next = val ?? ''
443
+ if (next === current) return
444
+
445
+ // Longest common prefix.
446
+ let from = 0
447
+ const max = Math.min(current.length, next.length)
448
+ while (from < max && current[from] === next[from]) from++
449
+ // Longest common suffix (not overlapping the prefix on either side).
450
+ let curEnd = current.length
451
+ let nextEnd = next.length
452
+ while (
453
+ curEnd > from &&
454
+ nextEnd > from &&
455
+ current[curEnd - 1] === next[nextEnd - 1]
456
+ ) {
457
+ curEnd--
458
+ nextEnd--
459
+ }
460
+
461
+ syncingFromProp = true
462
+ view.dispatch({
463
+ changes: { from, to: curEnd, insert: next.slice(from, nextEnd) },
464
+ })
465
+ syncingFromProp = false
466
+ },
467
+ )
468
+
469
+ // Swap the language (and JSON linter) without recreating the view. The extension
470
+ // build is async (it dynamic-imports the `@codemirror/lang-*` package), so a fast
471
+ // A→B switch races two builds; the monotonic `langSeq` token drops a stale build
472
+ // whose import resolves after a newer one, so the latest `props.language` always
473
+ // wins regardless of import-resolution order.
474
+ let langSeq = 0
475
+ watch(
476
+ () => props.language,
477
+ async (key) => {
478
+ const seq = ++langSeq
479
+ const ext = await buildLanguageExtension(key)
480
+ if (!view || !languageCompartment || seq !== langSeq) return
481
+ view.dispatch({
482
+ effects: languageCompartment.reconfigure(ext),
483
+ })
484
+ },
485
+ )
486
+
487
+ // Toggle disabled without recreating the view: reconfigure the read-only/editable
488
+ // extension and re-derive the surface (the variant compartment owns the greyed
489
+ // disabled chrome) in a single dispatch.
490
+ watch(
491
+ () => props.disabled,
492
+ (disabled) => {
493
+ if (!view || !disabledCompartment || !variantCompartment) return
494
+ view.dispatch({
495
+ effects: [
496
+ disabledCompartment.reconfigure(buildDisabled(!!disabled)),
497
+ variantCompartment.reconfigure(buildVariant(props.variant, disabled)),
498
+ ],
499
+ })
500
+ },
501
+ )
502
+
503
+ // Swap the surface variant without recreating the view.
504
+ watch(
505
+ () => props.variant,
506
+ (v) => {
507
+ if (!view || !variantCompartment) return
508
+ view.dispatch({
509
+ effects: variantCompartment.reconfigure(buildVariant(v, props.disabled)),
510
+ })
511
+ },
512
+ )
513
+
514
+ // Swap the size scale without recreating the view, then re-measure overflow
515
+ // (the new min-height/font size can change whether content overflows).
516
+ watch(
517
+ () => props.size,
518
+ (s) => {
519
+ if (!view || !sizeCompartment) return
520
+ view.dispatch({ effects: sizeCompartment.reconfigure(buildSize(s)) })
521
+ measureOverflow()
522
+ syncGutterShadow()
523
+ },
524
+ )
525
+
526
+ // Swap the placeholder text without recreating the view.
527
+ watch(
528
+ () => props.placeholder,
529
+ (text) => {
530
+ if (!view || !placeholderCompartment) return
531
+ view.dispatch({
532
+ effects: placeholderCompartment.reconfigure(buildPlaceholder(text)),
533
+ })
534
+ },
535
+ )
536
+
537
+ // Re-apply ARIA when the label/description/error wiring changes.
538
+ watch(
539
+ () => [labelledBy.value, describedBy.value, hasError.value, props.required],
540
+ () => {
541
+ if (!view || !a11yCompartment) return
542
+ view.dispatch({ effects: a11yCompartment.reconfigure(buildA11y()) })
543
+ },
544
+ )
545
+
546
+ onBeforeUnmount(() => {
547
+ destroyed = true
548
+ overflowObserver?.disconnect()
549
+ overflowObserver = null
550
+ if (view) {
551
+ view.scrollDOM.removeEventListener('scroll', syncGutterShadow)
552
+ view.destroy()
553
+ view = null
554
+ }
555
+ })
556
+ </script>
@@ -0,0 +1,49 @@
1
+ /**
2
+ * @vitest-environment jsdom
3
+ *
4
+ * CodePreview is the sanitize-before-render half of the pair, pure enough to
5
+ * mount in jsdom. The CodeEditor writer lazy-loads CodeMirror and measures
6
+ * layout on mount (which jsdom can't render), so its behaviour is covered in
7
+ * CodeEditor.cy.ts instead.
8
+ */
9
+ import { describe, expect, it } from 'vitest'
10
+ import { createApp } from 'vue'
11
+ import CodePreview from './CodePreview.vue'
12
+
13
+ describe('CodePreview', () => {
14
+ it('renders sanitized markdown', () => {
15
+ const host = document.createElement('div')
16
+ const app = createApp(CodePreview, {
17
+ modelValue: '# Title',
18
+ language: 'markdown',
19
+ })
20
+ app.mount(host)
21
+ expect(host.innerHTML).toContain('<h1')
22
+ expect(host.textContent).toContain('Title')
23
+ app.unmount()
24
+ })
25
+
26
+ it('renders sanitized html', () => {
27
+ const host = document.createElement('div')
28
+ const app = createApp(CodePreview, {
29
+ modelValue: '<p>hi</p><script>alert(1)</script>',
30
+ language: 'html',
31
+ })
32
+ app.mount(host)
33
+ expect(host.innerHTML).toContain('<p>hi</p>')
34
+ // The script tag is stripped by DOMPurify.
35
+ expect(host.innerHTML).not.toContain('alert(1)')
36
+ app.unmount()
37
+ })
38
+
39
+ it('renders nothing for non-preview languages', () => {
40
+ const host = document.createElement('div')
41
+ const app = createApp(CodePreview, {
42
+ modelValue: 'const a = 1;',
43
+ language: 'javascript',
44
+ })
45
+ app.mount(host)
46
+ expect(host.textContent).toBe('')
47
+ app.unmount()
48
+ })
49
+ })
@@ -0,0 +1,45 @@
1
+ <template>
2
+ <!-- Display-only, sanitized render. Mirrors the sanitize-before-render
3
+ convention (DOMPurify is a frappe-ui dep). The container always renders for
4
+ a preview-capable language (even when empty) so the consumer's
5
+ min-height/border holds instead of collapsing; non-preview languages render
6
+ nothing. -->
7
+ <!-- eslint-disable-next-line vue/no-v-html -->
8
+ <div
9
+ v-if="isPreviewLanguage"
10
+ class="prose prose-sm max-w-none text-ink-gray-8"
11
+ v-html="sanitized"
12
+ />
13
+ </template>
14
+
15
+ <script setup lang="ts">
16
+ // Preview primitive — separate from the writer. Renders read-only sanitized
17
+ // output for the two languages that have a meaningful preview:
18
+ // markdown → marked() → DOMPurify.sanitize → v-html
19
+ // html → DOMPurify.sanitize → v-html
20
+ // Any other language renders nothing (the wrapper only mounts this for fieldtypes
21
+ // that want a preview). No emit — display only.
22
+ import { computed } from 'vue'
23
+ import DOMPurify from 'dompurify'
24
+ import { marked } from 'marked'
25
+ import type { CodePreviewProps } from './types'
26
+
27
+ const props = defineProps<CodePreviewProps>()
28
+
29
+ const isPreviewLanguage = computed(
30
+ () => props.language === 'markdown' || props.language === 'html',
31
+ )
32
+
33
+ const sanitized = computed(() => {
34
+ const src = props.modelValue ?? ''
35
+ if (!src) return ''
36
+ if (props.language === 'markdown') {
37
+ // `marked.parse` is sync for string input (no async extensions here).
38
+ return DOMPurify.sanitize(marked.parse(src) as string)
39
+ }
40
+ if (props.language === 'html') {
41
+ return DOMPurify.sanitize(src)
42
+ }
43
+ return ''
44
+ })
45
+ </script>
@@ -0,0 +1,17 @@
1
+ // Code-editor primitives: a CodeMirror 6 writer (`CodeEditor`) and a sanitized
2
+ // read-only `CodePreview`, kept as two independent pieces. CodeMirror is
3
+ // lazy-loaded inside `CodeEditor`, so importing this barrel pulls in no editor
4
+ // code until a field actually mounts.
5
+ //
6
+ // Published from frappe-ui under its own `frappe-ui/code-editor` subpath (not the
7
+ // root barrel) so the CodeMirror dependency only loads for apps that opt in —
8
+ // the same isolation pattern as `frappe-ui/editor`.
9
+ export { default as CodeEditor } from './CodeEditor.vue'
10
+ export { default as CodePreview } from './CodePreview.vue'
11
+ export { loadLanguage } from './languages'
12
+ export type {
13
+ CodeLanguage,
14
+ CodeEditorProps,
15
+ CodeEditorEmits,
16
+ CodePreviewProps,
17
+ } from './types'