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.
- package/experimental/FloatingWindow/FloatingWindow.cy.ts +328 -0
- package/experimental/FloatingWindow/FloatingWindow.vue +303 -0
- package/experimental/FloatingWindow/index.ts +8 -0
- package/experimental/FloatingWindow/types.ts +31 -0
- package/experimental/FloatingWindow/useFloatingWindow.ts +236 -0
- package/experimental/MultiEmailInput/MultiEmailInput.cy.ts +155 -0
- package/experimental/MultiEmailInput/MultiEmailInput.vue +472 -0
- package/experimental/MultiEmailInput/index.ts +17 -0
- package/experimental/MultiEmailInput/stories/AsyncSuggestions.vue +54 -0
- package/experimental/MultiEmailInput/stories/Basic.vue +18 -0
- package/experimental/MultiEmailInput/stories/CustomChip.vue +39 -0
- package/experimental/MultiEmailInput/stories/Labeling.vue +28 -0
- package/experimental/MultiEmailInput/types.ts +138 -0
- package/experimental/MultiEmailInput/utils.ts +60 -0
- package/experimental.ts +12 -0
- package/frappe/Billing/SignupBanner.vue +1 -1
- package/frappe/Billing/TrialBanner.vue +1 -1
- package/frappe/DataImport/DataImport.vue +1 -1
- package/frappe/DataImport/DataImportList.vue +1 -1
- package/frappe/DataImport/ImportSteps.vue +6 -6
- package/frappe/DataImport/MappingStep.vue +1 -1
- package/frappe/DataImport/PreviewStep.vue +9 -9
- package/frappe/DataImport/TemplateModal.vue +1 -1
- package/frappe/DataImport/UploadStep.vue +5 -5
- package/frappe/Filter/Filter.vue +1 -1
- package/frappe/Help/HelpModal.vue +1 -1
- package/frappe/Link/stories/Suffix.vue +2 -2
- package/frappe/Onboarding/GettingStartedBanner.vue +1 -1
- package/frappe/drive/components/AppSwitcher.vue +1 -1
- package/frappe/drive/components/InfoDialog.vue +3 -3
- package/frappe/drive/components/MoveDialog.vue +2 -2
- package/frappe/drive/components/Select/Select.vue +2 -2
- package/frappe/drive/components/ShareDialog.vue +1 -1
- package/frappe/telemetry/index.ts +88 -14
- package/frappe/telemetry/pulse.ts +78 -164
- package/frappe/telemetry/telemetry.test.ts +13 -79
- package/icons/IconPicker.vue +1 -1
- package/package.json +104 -34
- package/src/components/Alert/Alert.md +4 -0
- package/src/components/Alert/Alert.vue +4 -4
- package/src/components/Autocomplete/Autocomplete.vue +6 -6
- package/src/components/Avatar/Avatar.md +4 -0
- package/src/components/Avatar/Avatar.vue +23 -4
- package/src/components/Badge/Badge.api.md +1 -1
- package/src/components/Badge/Badge.cy.ts +34 -16
- package/src/components/Badge/Badge.md +20 -4
- package/src/components/Badge/Badge.vue +60 -45
- package/src/components/Badge/stories/CallMeta.vue +35 -0
- package/src/components/Badge/stories/EventStatus.vue +16 -0
- package/src/components/Badge/stories/ListStatus.vue +91 -0
- package/src/components/Badge/stories/Reactions.vue +33 -0
- package/src/components/Badge/stories/RowTags.vue +39 -0
- package/src/components/Badge/types.ts +1 -2
- package/src/components/BottomSheet/BottomSheet.api.md +57 -0
- package/src/components/BottomSheet/BottomSheet.vue +232 -0
- package/src/components/BottomSheet/index.ts +6 -0
- package/src/components/BottomSheet/types.ts +23 -0
- package/src/components/Breadcrumbs/Breadcrumbs.md +4 -0
- package/src/components/Breadcrumbs/Breadcrumbs.vue +3 -3
- package/src/components/Button/Button.api.md +37 -32
- package/src/components/Button/Button.cy.ts +18 -2
- package/src/components/Button/Button.md +20 -8
- package/src/components/Button/Button.vue +304 -284
- package/src/components/Button/stories/InlineActions.vue +10 -0
- package/src/components/Button/stories/LiveClassCard.vue +34 -0
- package/src/components/Button/stories/SectionAction.vue +17 -0
- package/src/components/Button/stories/SectionControls.vue +28 -0
- package/src/components/Button/stories/SelectionToolbar.vue +20 -0
- package/src/components/Button/stories/StackedActions.vue +10 -0
- package/src/components/Button/types.ts +38 -32
- package/src/components/Calendar/Calendar.api.md +16 -16
- package/src/components/Calendar/Calendar.vue +100 -73
- package/src/components/Calendar/CalendarDaily.vue +21 -20
- package/src/components/Calendar/CalendarMonthEvent.vue +30 -9
- package/src/components/Calendar/CalendarMonthly.vue +60 -39
- package/src/components/Calendar/CalendarTimeMarker.vue +9 -8
- package/src/components/Calendar/CalendarWeekDayEvent.vue +98 -42
- package/src/components/Calendar/CalendarWeekly.vue +77 -41
- package/src/components/Calendar/EventModalContent.vue +32 -16
- package/src/components/Calendar/NewEventModal.vue +19 -16
- package/src/components/Calendar/ShowMoreCalendarEvent.vue +11 -17
- package/src/components/Calendar/{calendarUtils.js → calendarUtils.ts} +76 -52
- package/src/components/Calendar/composables/{useCalendarData.js → useCalendarData.ts} +23 -11
- package/src/components/Calendar/composables/{useEventModal.js → useEventModal.ts} +16 -12
- package/src/components/Calendar/index.ts +12 -2
- package/src/components/Calendar/stories/CustomHeader.vue +1 -1
- package/src/components/Calendar/types.ts +106 -0
- package/src/components/Calendar/{useEventBase.js → useEventBase.ts} +40 -26
- package/src/components/Card.vue +2 -2
- package/src/components/Charts/ECharts.vue +1 -1
- package/src/components/Charts/NumberChart.vue +7 -7
- package/src/components/Charts/eChartOptions.ts +28 -13
- package/src/components/Checkbox/Checkbox.api.md +6 -0
- package/src/components/Checkbox/Checkbox.cy.ts +10 -2
- package/src/components/Checkbox/Checkbox.md +28 -0
- package/src/components/Checkbox/Checkbox.vue +9 -22
- package/src/components/Checkbox/stories/Group.vue +27 -0
- package/src/components/Checkbox/stories/HorizontalGroup.vue +24 -0
- package/src/components/Checkbox/stories/SettingRow.vue +23 -0
- package/src/components/Checkbox/stories/WithDescription.vue +27 -0
- package/src/components/Checkbox/types.ts +7 -0
- package/src/components/CodeEditor/CodeEditor.api.md +139 -0
- package/src/components/CodeEditor/CodeEditor.cy.ts +160 -0
- package/src/components/CodeEditor/CodeEditor.md +104 -0
- package/src/components/CodeEditor/CodeEditor.vue +556 -0
- package/src/components/CodeEditor/CodePreview.test.ts +49 -0
- package/src/components/CodeEditor/CodePreview.vue +45 -0
- package/src/components/CodeEditor/index.ts +17 -0
- package/src/components/CodeEditor/languages.ts +63 -0
- package/src/components/CodeEditor/stories/Default.vue +16 -0
- package/src/components/CodeEditor/stories/Labeling.vue +25 -0
- package/src/components/CodeEditor/stories/MaxHeight.vue +24 -0
- package/src/components/CodeEditor/stories/Preview.vue +36 -0
- package/src/components/CodeEditor/stories/Sizes.vue +15 -0
- package/src/components/CodeEditor/stories/Variants.vue +13 -0
- package/src/components/CodeEditor/theme.ts +272 -0
- package/src/components/CodeEditor/types.ts +69 -0
- package/src/components/Combobox/Combobox.api.md +19 -20
- package/src/components/Combobox/Combobox.cy.ts +54 -14
- package/src/components/Combobox/Combobox.md +4 -0
- package/src/components/Combobox/Combobox.vue +233 -247
- package/src/components/Combobox/ComboboxResults.vue +1 -1
- package/src/components/Combobox/stories/Clearable.vue +2 -2
- package/src/components/Combobox/stories/CreateNew.vue +1 -1
- package/src/components/Combobox/stories/Footer.vue +10 -2
- package/src/components/Combobox/stories/InDialog.vue +1 -1
- package/src/components/Combobox/types.ts +8 -29
- package/src/components/CommandPalette/CommandPaletteItem.vue +1 -1
- package/src/components/ContextMenu/ContextMenu.api.md +52 -0
- package/src/components/ContextMenu/ContextMenu.cy.ts +191 -0
- package/src/components/ContextMenu/ContextMenu.md +25 -0
- package/src/components/ContextMenu/ContextMenu.vue +106 -0
- package/src/components/ContextMenu/index.ts +2 -0
- package/src/components/ContextMenu/stories/FileList.vue +105 -0
- package/src/components/ContextMenu/stories/Groups.vue +95 -0
- package/src/components/ContextMenu/stories/Simple.vue +72 -0
- package/src/components/ContextMenu/types.ts +39 -0
- package/src/components/DatePicker/CalendarPanel.vue +128 -86
- package/src/components/DatePicker/DatePicker.vue +3 -9
- package/src/components/DatePicker/DateRangePicker.vue +4 -8
- package/src/components/DatePicker/DateTimePicker.cy.ts +4 -2
- package/src/components/DatePicker/DateTimePicker.vue +3 -9
- package/src/components/DatePicker/composables.ts +15 -37
- package/src/components/DatePicker/stories/Range.vue +1 -1
- package/src/components/DatePicker/types.ts +1 -1
- package/src/components/DesktopShell/DesktopShell.api.md +40 -0
- package/src/components/DesktopShell/DesktopShell.cy.ts +39 -0
- package/src/components/DesktopShell/DesktopShell.md +31 -0
- package/src/components/DesktopShell/DesktopShell.vue +81 -0
- package/src/components/DesktopShell/index.ts +3 -0
- package/src/components/DesktopShell/stories/Default.vue +97 -0
- package/src/components/DesktopShell/types.ts +2 -0
- package/src/components/Dialog/Dialog.api.md +7 -8
- package/src/components/Dialog/Dialog.md +4 -0
- package/src/components/Dialog/Dialog.vue +8 -7
- package/src/components/Dialog/stories/CommandPalette.vue +1 -1
- package/src/components/Dialog/stories/Wizard.vue +1 -1
- package/src/components/Dialogs.vue +11 -9
- package/src/components/Divider/Divider.api.md +1 -1
- package/src/components/Divider/Divider.md +4 -0
- package/src/components/Dropdown/Dropdown.api.md +10 -35
- package/src/components/Dropdown/Dropdown.md +10 -6
- package/src/components/Dropdown/Dropdown.vue +33 -47
- package/src/components/Dropdown/stories/{05_KebabMenu.vue → KebabMenu.vue} +3 -1
- package/src/components/Dropdown/stories/{06_UserMenu.vue → UserMenu.vue} +1 -1
- package/src/components/Dropdown/types.ts +25 -189
- package/src/components/Duration/Duration.api.md +92 -0
- package/src/components/Duration/Duration.cy.ts +183 -0
- package/src/components/Duration/Duration.md +98 -0
- package/src/components/Duration/Duration.vue +114 -0
- package/src/components/Duration/duration.ts +185 -0
- package/src/components/Duration/index.ts +8 -0
- package/src/components/Duration/stories/Default.vue +10 -0
- package/src/components/Duration/stories/Formats.vue +16 -0
- package/src/components/Duration/stories/RealWorld.vue +50 -0
- package/src/components/Duration/stories/Sizes.vue +15 -0
- package/src/components/Duration/stories/States.vue +21 -0
- package/src/components/Duration/types.ts +52 -0
- package/src/components/ErrorMessage/ErrorMessage.cy.ts +1 -1
- package/src/components/ErrorMessage/ErrorMessage.md +4 -0
- package/src/components/ErrorMessage/ErrorMessage.vue +1 -1
- package/src/components/FileUploader/FileUploader.api.md +36 -11
- package/src/components/FileUploader/FileUploader.cy.ts +66 -0
- package/src/components/FileUploader/FileUploader.md +4 -1
- package/src/components/FileUploader/FileUploader.vue +147 -106
- package/src/components/FileUploader/index.ts +6 -0
- package/src/components/FileUploader/types.ts +43 -0
- package/src/components/FormControl/FormControl.api.md +3 -4
- package/src/components/FormControl/FormControl.md +4 -0
- package/src/components/FormControl/stories/RealForm.vue +3 -3
- package/src/components/FormLabel.vue +5 -8
- package/src/components/HoverCard/HoverCard.api.md +100 -0
- package/src/components/HoverCard/HoverCard.cy.ts +103 -0
- package/src/components/HoverCard/HoverCard.md +52 -0
- package/src/components/HoverCard/HoverCard.vue +91 -0
- package/src/components/HoverCard/index.ts +2 -0
- package/src/components/HoverCard/stories/Arrow.vue +28 -0
- package/src/components/HoverCard/stories/Delays.vue +16 -0
- package/src/components/HoverCard/stories/Example.vue +28 -0
- package/src/components/HoverCard/types.ts +47 -0
- package/src/components/Icon/Icon.cy.ts +78 -0
- package/src/components/Icon/Icon.vue +50 -0
- package/src/components/Icon/index.ts +1 -0
- package/src/components/InputLabeling/InputDescription.vue +1 -1
- package/src/components/InputLabeling/InputError.vue +1 -1
- package/src/components/InputLabeling/InputLabel.vue +21 -7
- package/src/components/InputLabeling/RequiredIndicator.vue +1 -1
- package/src/components/ItemListRow/ItemListRow.vue +1 -1
- package/src/components/ItemListRow/stories/RowStates.vue +1 -1
- package/src/components/KeyboardShortcut.vue +4 -8
- package/src/components/KeyboardShortcutsModal/KeyboardShortcutsModal.api.md +22 -14
- package/src/components/KeyboardShortcutsModal/KeyboardShortcutsModal.vue +2 -2
- package/src/components/ListFilter/ListFilter.api.md +5 -6
- package/src/components/ListFilter/ListFilter.vue +161 -57
- package/src/components/ListFilter/NestedPopover.vue +17 -10
- package/src/components/ListFilter/SearchComplete.vue +31 -38
- package/src/components/ListItem.vue +1 -1
- package/src/components/ListView/ListEmptyState.vue +1 -1
- package/src/components/ListView/ListGroupHeader.vue +2 -2
- package/src/components/ListView/ListRow.vue +1 -1
- package/src/components/ListView/ListSelectBanner.vue +1 -1
- package/src/components/ListView/ListView.api.md +322 -6
- package/src/components/ListView/index.ts +25 -0
- package/src/components/ListView/stories/CustomList.vue +1 -1
- package/src/components/ListView/stories/GroupedRows.vue +1 -1
- package/src/components/LoadingIndicator.vue +13 -22
- package/src/components/LoadingText.vue +3 -13
- package/src/components/{Dropdown/DropdownMenuList.vue → Menu/Menu.vue} +110 -75
- package/src/components/{Dropdown/DropdownMenuItemContent.vue → Menu/MenuItemContent.vue} +23 -33
- package/src/components/{Dropdown/DropdownRenderContent.vue → Menu/MenuRenderContent.vue} +1 -1
- package/src/components/Menu/types.ts +225 -0
- package/src/components/{Dropdown → Menu}/utils.ts +36 -40
- package/src/components/MobileNav/MobileNav.api.md +70 -0
- package/src/components/MobileNav/MobileNav.cy.ts +89 -0
- package/src/components/MobileNav/MobileNav.md +34 -0
- package/src/components/MobileNav/MobileNav.vue +15 -0
- package/src/components/MobileNav/MobileNavItem.vue +125 -0
- package/src/components/MobileNav/index.ts +4 -0
- package/src/components/MobileNav/stories/Default.vue +40 -0
- package/src/components/MobileNav/types.ts +33 -0
- package/src/components/MobileShell/MobileShell.api.md +23 -0
- package/src/components/MobileShell/MobileShell.cy.ts +25 -0
- package/src/components/MobileShell/MobileShell.md +24 -0
- package/src/components/MobileShell/MobileShell.vue +41 -0
- package/src/components/MobileShell/index.ts +3 -0
- package/src/components/MobileShell/stories/Default.vue +75 -0
- package/src/components/MobileShell/types.ts +7 -0
- package/src/components/MonthPicker/MonthPicker.cy.ts +16 -0
- package/src/components/MonthPicker/MonthPicker.vue +19 -8
- package/src/components/MultiSelect/MultiSelect.api.md +3 -4
- package/src/components/MultiSelect/MultiSelect.md +42 -10
- package/src/components/MultiSelect/MultiSelect.vue +199 -227
- package/src/components/MultiSelect/MultiSelectResults.vue +1 -1
- package/src/components/MultiSelect/stories/Members.vue +1 -1
- package/src/components/MultiSelect/stories/TagsTrigger.vue +3 -3
- package/src/components/MultiSelect/types.ts +3 -3
- package/src/components/PageHeader/PageHeader.api.md +112 -0
- package/src/components/PageHeader/PageHeader.cy.ts +68 -0
- package/src/components/PageHeader/PageHeader.md +25 -0
- package/src/components/PageHeader/PageHeader.vue +13 -0
- package/src/components/PageHeader/PageHeaderBackButton.vue +29 -0
- package/src/components/PageHeader/PageHeaderBase.vue +51 -0
- package/src/components/PageHeader/PageHeaderMobile.vue +68 -0
- package/src/components/PageHeader/PageHeaderMobileTitle.vue +16 -0
- package/src/components/PageHeader/PageHeaderTarget.vue +13 -0
- package/src/components/PageHeader/PageHeaderTitle.vue +12 -0
- package/src/components/PageHeader/index.ts +7 -0
- package/src/components/PageHeader/stories/Default.vue +19 -0
- package/src/components/PageHeader/stories/Mobile.vue +24 -0
- package/src/components/PageHeader/target.ts +19 -0
- package/src/components/PageHeader/types.ts +19 -0
- package/src/components/Password/Password.md +4 -0
- package/src/components/Password/Password.vue +2 -2
- package/src/components/Popover/Popover.api.md +126 -42
- package/src/components/Popover/Popover.cy.ts +425 -41
- package/src/components/Popover/Popover.md +99 -4
- package/src/components/Popover/Popover.vue +288 -215
- package/src/components/Popover/index.ts +8 -1
- package/src/components/Popover/stories/Arrow.vue +16 -0
- package/src/components/Popover/stories/Bare.vue +20 -0
- package/src/components/Popover/stories/Click.vue +7 -4
- package/src/components/Popover/stories/Controlled.vue +22 -0
- package/src/components/Popover/stories/Dismissible.vue +19 -0
- package/src/components/Popover/stories/MatchTriggerWidth.vue +16 -0
- package/src/components/Popover/stories/SideAlign.vue +17 -0
- package/src/components/Popover/types.ts +101 -23
- package/src/components/Progress/Progress.cy.ts +2 -2
- package/src/components/Progress/Progress.md +4 -0
- package/src/components/Progress/Progress.vue +4 -4
- package/src/components/Rail/Rail.api.md +91 -0
- package/src/components/Rail/Rail.cy.ts +84 -0
- package/src/components/Rail/Rail.md +30 -0
- package/src/components/Rail/Rail.vue +16 -0
- package/src/components/Rail/RailItem.vue +134 -0
- package/src/components/Rail/RailItemBadge.vue +64 -0
- package/src/components/Rail/index.ts +2 -0
- package/src/components/Rail/stories/Default.vue +44 -0
- package/src/components/Rail/types.ts +37 -0
- package/src/components/Rating/Rating.md +4 -0
- package/src/components/Rating/Rating.vue +5 -8
- package/src/components/Rating/stories/CustomSlot.vue +2 -2
- package/src/components/ScrollArea/ScrollArea.api.md +59 -0
- package/src/components/ScrollArea/ScrollArea.vue +46 -0
- package/src/components/ScrollArea/ScrollBar.vue +101 -0
- package/src/components/ScrollArea/index.ts +2 -0
- package/src/components/Select/Select.cy.ts +16 -15
- package/src/components/Select/Select.md +4 -0
- package/src/components/Select/Select.vue +217 -203
- package/src/components/Select/stories/Example.vue +4 -3
- package/src/components/Select/stories/States.vue +1 -1
- package/src/components/Select/stories/TriggerSlots.vue +2 -2
- package/src/components/Select/utils.ts +4 -3
- package/src/components/SettingsDialog/SettingsBody.vue +23 -0
- package/src/components/SettingsDialog/SettingsContent.vue +7 -0
- package/src/components/SettingsDialog/SettingsDialog.api.md +273 -0
- package/src/components/SettingsDialog/SettingsDialog.cy.ts +126 -0
- package/src/components/SettingsDialog/SettingsDialog.md +43 -0
- package/src/components/SettingsDialog/SettingsDialog.vue +76 -0
- package/src/components/SettingsDialog/SettingsHeader.vue +36 -0
- package/src/components/SettingsDialog/SettingsNavGroup.vue +20 -0
- package/src/components/SettingsDialog/SettingsNavItem.vue +33 -0
- package/src/components/SettingsDialog/SettingsPanel.cy.ts +77 -0
- package/src/components/SettingsDialog/SettingsPanel.vue +26 -0
- package/src/components/SettingsDialog/SettingsRow.vue +52 -0
- package/src/components/SettingsDialog/SettingsSidebar.vue +16 -0
- package/src/components/SettingsDialog/index.ts +10 -0
- package/src/components/SettingsDialog/stories/Default.vue +99 -0
- package/src/components/SettingsDialog/stories/PanelBasic.vue +52 -0
- package/src/components/SettingsDialog/stories/panels/NotificationsPanel.vue +57 -0
- package/src/components/SettingsDialog/stories/panels/PreferencesPanel.vue +57 -0
- package/src/components/SettingsDialog/stories/panels/ProfilePanel.vue +38 -0
- package/src/components/SettingsDialog/stories/panels/UsersPanel.vue +83 -0
- package/src/components/SettingsDialog/types.ts +21 -0
- package/src/components/Sidebar/Sidebar.api.md +124 -65
- package/src/components/Sidebar/Sidebar.cy.ts +127 -76
- package/src/components/Sidebar/Sidebar.md +47 -3
- package/src/components/Sidebar/Sidebar.vue +64 -56
- package/src/components/Sidebar/SidebarCollapseToggle.vue +23 -0
- package/src/components/Sidebar/SidebarHeader.vue +76 -52
- package/src/components/Sidebar/SidebarItem.vue +159 -95
- package/src/components/Sidebar/SidebarItemIcon.vue +25 -0
- package/src/components/Sidebar/SidebarLabel.vue +34 -0
- package/src/components/Sidebar/SidebarSection.vue +29 -14
- package/src/components/Sidebar/index.ts +13 -2
- package/src/components/Sidebar/stories/Collapsed.vue +40 -0
- package/src/components/Sidebar/stories/Default.vue +153 -0
- package/src/components/Sidebar/stories/Legacy.vue +36 -0
- package/src/components/Sidebar/types.ts +105 -20
- package/src/components/Skeleton/Skeleton.cy.ts +21 -0
- package/src/components/Skeleton/Skeleton.md +21 -0
- package/src/components/Skeleton/Skeleton.vue +6 -0
- package/src/components/Skeleton/index.ts +1 -0
- package/src/components/Skeleton/stories/Card.vue +12 -0
- package/src/components/Skeleton/stories/List.vue +15 -0
- package/src/components/Skeleton/stories/Text.vue +11 -0
- package/src/components/Slider/Slider.api.md +1 -1
- package/src/components/Slider/Slider.cy.ts +29 -0
- package/src/components/Slider/Slider.md +10 -0
- package/src/components/Slider/Slider.vue +37 -9
- package/src/components/Slider/stories/NegativeValues.vue +12 -0
- package/src/components/Slider/types.ts +1 -1
- package/src/components/Spinner/Spinner.api.md +32 -0
- package/src/components/Spinner/Spinner.cy.ts +87 -0
- package/src/components/Spinner/Spinner.md +21 -0
- package/src/components/Spinner/Spinner.vue +168 -0
- package/src/components/Spinner/index.ts +2 -0
- package/src/components/Spinner/stories/InContext.vue +49 -0
- package/src/components/Spinner/stories/Sizes.vue +12 -0
- package/src/components/Spinner/stories/Themes.vue +10 -0
- package/src/components/Spinner/stories/Track.vue +12 -0
- package/src/components/Spinner/types.ts +14 -0
- package/src/components/Switch/Switch.md +4 -0
- package/src/components/Switch/Switch.vue +6 -23
- package/src/components/TabButtons/Pill.vue +161 -0
- package/src/components/TabButtons/TabButtons.api.md +52 -2
- package/src/components/TabButtons/TabButtons.cy.ts +18 -5
- package/src/components/TabButtons/TabButtons.md +49 -0
- package/src/components/TabButtons/TabButtons.vue +207 -109
- package/src/components/TabButtons/index.ts +11 -1
- package/src/components/TabButtons/pillTypes.ts +43 -0
- package/src/components/TabButtons/stories/PrefixSuffix.vue +65 -0
- package/src/components/TabButtons/stories/Sizes.vue +26 -0
- package/src/components/TabButtons/stories/Variants.vue +40 -0
- package/src/components/TabButtons/stories/Vertical.vue +64 -0
- package/src/components/TabButtons/types.ts +49 -0
- package/src/components/Tabs/Tabs.md +4 -0
- package/src/components/Tabs/Tabs.vue +1 -1
- package/src/components/TextEditor/TextEditor.cy.ts +1 -1
- package/src/components/TextEditor/TextEditor.md +3 -0
- package/src/components/TextEditor/TextEditor.vue +7 -4
- package/src/components/TextEditor/components/CodeBlockComponent.vue +4 -194
- package/src/components/TextEditor/components/FontColor.vue +1 -1
- package/src/components/TextEditor/components/LinkPopup.vue +1 -1
- package/src/components/TextEditor/components/MediaNodeView.vue +17 -17
- package/src/components/TextEditor/components/MentionList.vue +1 -1
- package/src/components/TextEditor/components/TextEditorBubbleMenu.vue +14 -5
- package/src/components/TextEditor/components/TextEditorFixedMenu.vue +3 -3
- package/src/components/TextEditor/components/{Menu.vue → TextEditorMenu.vue} +3 -3
- package/src/components/TextEditor/extensions/iframe/InsertIframe.vue +2 -2
- package/src/components/TextEditor/extensions/iframe/iframe-extension.ts +62 -19
- package/src/components/TextEditor/extensions/image-group/ImageGroupNodeView.vue +1 -1
- package/src/components/TextEditor/extensions/image-group/ImageGroupUploadDialog.vue +4 -4
- package/src/components/TextEditor/extensions/suggestion/SuggestionList.vue +1 -1
- package/src/components/TextEditor/extensions/toc-node/TocNodeView.vue +1 -1
- package/src/components/TextEditor/hljs-github.css +197 -0
- package/src/components/TextEditor/index.ts +13 -0
- package/src/components/TextInput/TextInput.cy.ts +8 -3
- package/src/components/TextInput/TextInput.md +4 -0
- package/src/components/TextInput/TextInput.vue +11 -15
- package/src/components/TextInput/stories/Slots.vue +1 -1
- package/src/components/Textarea/Textarea.cy.ts +1 -1
- package/src/components/Textarea/Textarea.md +4 -0
- package/src/components/Textarea/Textarea.vue +11 -15
- package/src/components/ThemeSwitcher/ThemePreview.vue +74 -0
- package/src/components/ThemeSwitcher/ThemeSwitcher.api.md +82 -0
- package/src/components/ThemeSwitcher/ThemeSwitcher.cy.ts +101 -0
- package/src/components/ThemeSwitcher/ThemeSwitcher.md +70 -0
- package/src/components/ThemeSwitcher/ThemeSwitcher.vue +145 -0
- package/src/components/ThemeSwitcher/index.ts +2 -0
- package/src/components/ThemeSwitcher/previews/PreviewWindow.vue +74 -0
- package/src/components/ThemeSwitcher/stories/Branded.vue +12 -0
- package/src/components/ThemeSwitcher/stories/Default.vue +7 -0
- package/src/components/ThemeSwitcher/stories/Menu.vue +53 -0
- package/src/components/ThemeSwitcher/stories/Toggle.vue +25 -0
- package/src/components/ThemeSwitcher/types.ts +35 -0
- package/src/components/TimePicker/TimePicker.api.md +67 -15
- package/src/components/TimePicker/TimePicker.cy.ts +130 -14
- package/src/components/TimePicker/TimePicker.md +6 -0
- package/src/components/TimePicker/TimePicker.vue +74 -19
- package/src/components/TimePicker/stories/Labeling.vue +21 -0
- package/src/components/TimePicker/stories/Range.vue +1 -1
- package/src/components/TimePicker/stories/SizesAndVariants.vue +23 -0
- package/src/components/TimePicker/stories/TwentyFour.vue +1 -1
- package/src/components/TimePicker/types.ts +15 -3
- package/src/components/TimePicker/utils.ts +82 -21
- package/src/components/Toast/Toast.md +1 -1
- package/src/components/Toast/Toast.sanitize.test.ts +50 -0
- package/src/components/Toast/Toast.test.ts +5 -0
- package/src/components/Toast/Toast.vue +7 -7
- package/src/components/Toast/ToastProvider.vue +18 -9
- package/src/components/Toast/stories/CustomIcon.vue +1 -1
- package/src/components/Toast/toast.ts +32 -8
- package/src/components/Tooltip/Tooltip.api.md +43 -5
- package/src/components/Tooltip/Tooltip.cy.ts +48 -0
- package/src/components/Tooltip/Tooltip.md +10 -0
- package/src/components/Tooltip/Tooltip.vue +20 -5
- package/src/components/Tooltip/TooltipBubble.vue +2 -2
- package/src/components/Tooltip/TooltipProvider.vue +53 -0
- package/src/components/Tooltip/index.ts +1 -0
- package/src/components/Tooltip/stories/Group.vue +22 -0
- package/src/components/Tooltip/stories/Slots.vue +2 -2
- package/src/components/Tooltip/types.ts +1 -1
- package/src/components/Tree/Tree.api.md +76 -18
- package/src/components/Tree/Tree.md +144 -2
- package/src/components/Tree/Tree.vue +276 -97
- package/src/components/Tree/TreeItem.vue +263 -0
- package/src/components/Tree/index.ts +9 -1
- package/src/components/Tree/stories/DragDrop.vue +94 -0
- package/src/components/Tree/stories/Example.vue +11 -34
- package/src/components/Tree/stories/ExpandAll.vue +48 -0
- package/src/components/Tree/stories/Guides.vue +49 -0
- package/src/components/Tree/stories/ListView.vue +74 -0
- package/src/components/Tree/tree.cy.ts +218 -90
- package/src/components/Tree/types.ts +114 -24
- package/src/components/Tree/useTreeDragDrop.ts +203 -0
- package/src/components/Tree/useTreeKeyboard.ts +109 -0
- package/src/components/VueGridLayout/Layout.vue +1 -1
- package/src/components/shared/picker/PickerShell.vue +20 -15
- package/src/components/shared/popover/PopoverPanel.vue +67 -0
- package/src/components/shared/popover/popoverPanel.css +102 -0
- package/src/components/shared/selection/utils.ts +6 -6
- package/src/composables/useAutofocusOnOpen.ts +2 -0
- package/src/composables/useScreenSize.ts +46 -0
- package/src/composables/useScrollContainer.ts +108 -0
- package/src/composables/useShortcut.ts +3 -0
- package/src/data-fetching/docStore.test.ts +74 -0
- package/src/data-fetching/docStore.ts +18 -7
- package/src/data-fetching/useCall/types.ts +1 -0
- package/src/data-fetching/useCall/useCall.test.ts +43 -0
- package/src/data-fetching/useCall/useCall.ts +17 -4
- package/src/data-fetching/useDoc/useDoc.test.ts +61 -2
- package/src/data-fetching/useDoc/useDoc.ts +35 -15
- package/src/data-fetching/useDoctype/useDoctype.ts +6 -5
- package/src/data-fetching/useFrappeFetch.ts +17 -4
- package/src/data-fetching/useList/types.ts +1 -0
- package/src/data-fetching/useList/useList.test.ts +40 -0
- package/src/data-fetching/useList/useList.ts +20 -13
- package/src/data-fetching/useNewDoc/useNewDoc.ts +9 -5
- package/src/directives/focus.ts +3 -3
- package/src/index.ts +105 -74
- package/src/mocks/handlers.ts +8 -0
- package/src/molecules/editor/Editor.cy.ts +6 -1
- package/src/molecules/editor/EditorBubbleMenu.vue +1 -1
- package/src/molecules/editor/EditorContent.vue +50 -9
- package/src/molecules/editor/EditorFixedMenu.vue +2 -1
- package/src/molecules/editor/EditorFloatingMenu.vue +1 -1
- package/src/molecules/editor/EditorTableMenu.vue +303 -0
- package/src/molecules/editor/MenuItems.vue +81 -69
- package/src/molecules/editor/commands.ts +10 -2
- package/src/molecules/editor/components/AttachmentNodeView.vue +115 -0
- package/src/molecules/editor/components/EditorDropZone.vue +105 -0
- package/src/molecules/editor/components/EditorPopover.vue +89 -0
- package/src/molecules/editor/components/MediaNodeView.vue +156 -49
- package/src/molecules/editor/components/MediaResizeHandles.vue +38 -0
- package/src/molecules/editor/components/MediaToolbar.vue +108 -54
- package/src/molecules/editor/components/TableContextMenu.vue +144 -0
- package/src/molecules/editor/components/UploadProgressIndicator.vue +33 -0
- package/src/molecules/editor/components/VideoControls.vue +208 -0
- package/src/molecules/editor/components/font-color/ColorSwatchGrid.vue +3 -5
- package/src/molecules/editor/components/font-color/fontColorController.ts +28 -23
- package/src/molecules/editor/components/font-color/swatches.ts +1 -1
- package/src/molecules/editor/components/media-node-view-controller.ts +4 -4
- package/src/molecules/editor/components/media-node-view-utils.test.ts +0 -13
- package/src/molecules/editor/components/media-node-view-utils.ts +0 -10
- package/src/molecules/editor/components/table-color/tableCellColorController.ts +80 -0
- package/src/molecules/editor/components/table-size-picker/TableSizePicker.vue +92 -0
- package/src/molecules/editor/components/table-size-picker/tableSizePickerController.ts +40 -0
- package/src/molecules/editor/composables/useEditorFileDrop.ts +117 -0
- package/src/molecules/editor/composables/useFloatingPopup.ts +46 -2
- package/src/molecules/editor/composables/useNodeViewResize.test.ts +60 -11
- package/src/molecules/editor/composables/useNodeViewResize.ts +80 -21
- package/src/molecules/editor/composables/useTableCellColorState.ts +78 -0
- package/src/molecules/editor/composables/useWindowFileDragging.ts +68 -0
- package/src/molecules/editor/extensions/attachment/attachment-engine.ts +250 -0
- package/src/molecules/editor/extensions/attachment/attachment-extension.ts +248 -0
- package/src/molecules/editor/extensions/attachment/attachment.flow.test.ts +80 -0
- package/src/molecules/editor/extensions/attachment/attachment.roundtrip.test.ts +50 -0
- package/src/molecules/editor/extensions/attachment/index.ts +5 -0
- package/src/molecules/editor/extensions/code-block/CodeBlockComponent.css +16 -26
- package/src/molecules/editor/extensions/code-block/CodeBlockComponent.vue +107 -22
- package/src/molecules/editor/extensions/content-paste/content-paste-extension.ts +5 -3
- package/src/molecules/editor/extensions/iframe/IframeInsertDialog.vue +34 -7
- package/src/molecules/editor/extensions/iframe/IframeNodeView.vue +54 -48
- package/src/molecules/editor/extensions/iframe/iframe-allowlist.ts +33 -4
- package/src/molecules/editor/extensions/iframe/iframe-commands.ts +49 -5
- package/src/molecules/editor/extensions/iframe/iframe-embed-utils.ts +30 -0
- package/src/molecules/editor/extensions/iframe/iframe-extension.ts +4 -4
- package/src/molecules/editor/extensions/iframe/iframeInsertDialogController.ts +12 -1
- package/src/molecules/editor/extensions/iframe/index.ts +4 -4
- package/src/molecules/editor/extensions/iframe/useIframeDialog.ts +25 -13
- package/src/molecules/editor/extensions/image/image-extension.ts +29 -11
- package/src/molecules/editor/extensions/image-group/ImageGroupGrid.vue +27 -11
- package/src/molecules/editor/extensions/image-group/ImageGroupGridCell.vue +55 -11
- package/src/molecules/editor/extensions/image-group/ImageGroupNodeView.vue +64 -31
- package/src/molecules/editor/extensions/image-group/ImageGroupUploadDialog.vue +84 -40
- package/src/molecules/editor/extensions/image-group/image-group-extension.ts +4 -3
- package/src/molecules/editor/extensions/image-group/useImageGroupDialog.ts +116 -17
- package/src/molecules/editor/extensions/link/LinkEditorPopup.vue +214 -71
- package/src/molecules/editor/extensions/link/link-click-plugin.ts +10 -15
- package/src/molecules/editor/extensions/link/link-commands.ts +24 -3
- package/src/molecules/editor/extensions/link/link-extension.ts +11 -2
- package/src/molecules/editor/extensions/link/link-popup-controller.test.ts +49 -0
- package/src/molecules/editor/extensions/link/link-popup-controller.ts +67 -2
- package/src/molecules/editor/extensions/link/link-shortcut-plugin.ts +7 -2
- package/src/molecules/editor/extensions/media-drop/media-drop-extension.ts +177 -0
- package/src/molecules/editor/extensions/shared/media-dimensions.ts +36 -3
- package/src/molecules/editor/extensions/shared/media-node-ops.ts +17 -0
- package/src/molecules/editor/extensions/shared/media-plugin.test.ts +60 -0
- package/src/molecules/editor/extensions/shared/media-plugin.ts +7 -37
- package/src/molecules/editor/extensions/shared/media-upload-engine.test.ts +39 -2
- package/src/molecules/editor/extensions/shared/media-upload-engine.ts +104 -10
- package/src/molecules/editor/extensions/shared/media-upload-state.ts +55 -0
- package/src/molecules/editor/extensions/shared/media-upload-types.ts +52 -1
- package/src/molecules/editor/extensions/shared/suggestion-helpers.ts +20 -0
- package/src/molecules/editor/extensions/shared/suggestion-types.ts +7 -0
- package/src/molecules/editor/extensions/shared/upload-types.ts +1 -0
- package/src/molecules/editor/extensions/slash-commands/slash-commands-extension.test.ts +38 -0
- package/src/molecules/editor/extensions/slash-commands/slash-commands-extension.ts +119 -55
- package/src/molecules/editor/extensions/suggestion/SuggestionList.vue +62 -22
- package/src/molecules/editor/extensions/suggestion/SuggestionListItem.vue +1 -1
- package/src/molecules/editor/extensions/suggestion/createSuggestionExtension.ts +4 -0
- package/src/molecules/editor/extensions/table/drag-scroll.ts +154 -0
- package/src/molecules/editor/extensions/table/table-cell-color.ts +188 -0
- package/src/molecules/editor/extensions/table/table-navigation.ts +560 -0
- package/src/molecules/editor/extensions/table/table-selection-overlay.ts +137 -0
- package/src/molecules/editor/extensions/toc-node/TocNodeView.vue +1 -1
- package/src/molecules/editor/extensions/toc-node/toc-render.ts +7 -3
- package/src/molecules/editor/extensions/video/video-extension.ts +72 -5
- package/src/molecules/editor/extensions.ts +176 -8
- package/src/molecules/editor/index.ts +9 -4
- package/src/molecules/editor/kits.test.ts +21 -1
- package/src/molecules/editor/kits.ts +56 -33
- package/src/molecules/editor/menu.test.ts +3 -1
- package/src/molecules/editor/menu.ts +180 -18
- package/src/molecules/editor/stories/Comment.vue +4 -4
- package/src/molecules/editor/stories/Inline.vue +1 -1
- package/src/molecules/editor/stories/Primitives.vue +1 -1
- package/src/molecules/editor/stories/RichText.vue +1 -1
- package/src/molecules/editor/style.css +80 -3
- package/src/molecules/editor/useEditor.test.ts +4 -0
- package/src/molecules/editor/useEditor.ts +7 -3
- package/src/molecules/list/List.cy.ts +626 -0
- package/src/molecules/list/List.vue +93 -0
- package/src/molecules/list/ListCell.vue +15 -0
- package/src/molecules/list/ListGroup.vue +40 -0
- package/src/molecules/list/ListHeader.vue +62 -0
- package/src/molecules/list/ListHeaderCell.vue +26 -0
- package/src/molecules/list/ListHeaderCellSort.vue +106 -0
- package/src/molecules/list/ListRow.vue +55 -0
- package/src/molecules/list/ListRowBase.vue +103 -0
- package/src/molecules/list/ListRows.vue +120 -0
- package/src/molecules/list/index.ts +24 -0
- package/src/molecules/list/list-context.ts +44 -0
- package/src/molecules/list/list.api.md +276 -0
- package/src/molecules/list/list.md +119 -0
- package/src/molecules/list/stories/Columns.vue +103 -0
- package/src/molecules/list/stories/Feed.vue +105 -0
- package/src/molecules/list/stories/Virtual.vue +31 -0
- package/src/molecules/list/style.css +173 -0
- package/src/molecules/list/types.ts +82 -0
- package/src/molecules/list/useVirtualRows.ts +80 -0
- package/src/resources/documentResource.d.ts +28 -0
- package/src/resources/documentResource.js +32 -4
- package/src/resources/documentResource.test.ts +111 -0
- package/src/resources/listResource.d.ts +33 -0
- package/src/resources/plugin.d.ts +7 -0
- package/src/resources/resources.d.ts +35 -0
- package/src/utils/{call.js → call.ts} +50 -9
- package/src/utils/config.ts +16 -1
- package/src/utils/confirmDialog.d.ts +17 -0
- package/src/utils/debounce.ts +6 -7
- package/src/utils/dialog.cy.ts +9 -9
- package/src/utils/fileSize.ts +36 -0
- package/src/utils/fileUploadHandler.ts +46 -6
- package/src/utils/frappeRequest.test.ts +79 -0
- package/src/utils/{frappeRequest.js → frappeRequest.ts} +76 -13
- package/src/utils/pageMeta.ts +1 -1
- package/src/utils/plugin.ts +58 -0
- package/src/utils/{request.js → request.ts} +28 -3
- package/src/utils/{socketio.js → socketio.ts} +11 -1
- package/src/utils/theme.ts +48 -57
- package/src/utils/useFileUpload.ts +80 -23
- package/tailwind/audit-token-drift.cjs +176 -0
- package/tailwind/colorPalette.js +69 -8
- package/tailwind/colors.json +1115 -397
- package/tailwind/figma-tokens-to-theme.js +364 -0
- package/tailwind/generated/colors.json +1217 -0
- package/tailwind/generated/effects.json +41 -0
- package/tailwind/generated/radius.json +20 -0
- package/tailwind/generated/typography.json +479 -0
- package/tailwind/migrate-tokens-v2.js +453 -0
- package/tailwind/migrate-tokens-v2.test.js +55 -0
- package/tailwind/plugin.js +220 -15
- package/tailwind/preset.js +15 -0
- package/tailwind/tokens.js +35 -37
- package/tsconfig.base.json +2 -2
- package/vitepress/components/Docs/CommandPalette.vue +214 -0
- package/vitepress/components/Docs/Demo.vue +60 -0
- package/vitepress/components/Docs/EmitsTable.vue +99 -0
- package/vitepress/components/Docs/MobileNavSheet.vue +148 -0
- package/vitepress/components/Docs/OnThisPage.vue +74 -0
- package/vitepress/components/Docs/PrevNextBtns.vue +58 -0
- package/vitepress/components/Docs/PropsTable.vue +185 -0
- package/vitepress/components/Docs/Search.vue +34 -0
- package/vitepress/components/Docs/Sidebar.vue +91 -0
- package/vitepress/components/Docs/SlotsTable.vue +99 -0
- package/vitepress/components/Docs/index.ts +13 -0
- package/vitepress/components/Docs/sidebarList.ts +33 -0
- package/vitepress/components/Layout.vue +58 -0
- package/vitepress/components/Navbar.vue +126 -0
- package/vitepress/components/index.ts +3 -0
- package/vitepress/composables/index.ts +3 -0
- package/vitepress/composables/useTheme.ts +39 -0
- package/vitepress/css/fonts/Inter-Italic.var.woff2 +0 -0
- package/vitepress/css/fonts/Inter.var.woff2 +0 -0
- package/vitepress/css/style.css +198 -0
- package/vitepress/index.d.ts +23 -0
- package/vitepress/index.node.ts +175 -0
- package/vitepress/index.ts +17 -0
- package/vitepress/plugins/colocatedComponentDocs.ts +164 -0
- package/vitepress/plugins/componentTransformer.ts +286 -0
- package/vitepress/plugins/index.ts +12 -0
- package/vitepress/state.ts +10 -0
- package/vitepress/theme/index.ts +21 -0
- package/frappe/telemetry/utils.ts +0 -15
- package/src/components/Badge/stories/Themes.vue +0 -10
- package/src/components/Badge/stories/Variants.vue +0 -10
- package/src/components/Button/stories/Icons.vue +0 -27
- package/src/components/Button/stories/Sizes.vue +0 -11
- package/src/components/Button/stories/Themes.vue +0 -10
- package/src/components/Button/stories/Variants.vue +0 -10
- package/src/components/Popover/stories/Hover.vue +0 -14
- package/src/components/Resource.vue +0 -21
- package/src/components/Sidebar/stories/Example.vue +0 -76
- package/src/components/Spinner.vue +0 -3
- package/src/components/TabButtons/TabButtons.story.vue +0 -22
- package/src/utils/plugin.js +0 -24
- /package/src/components/Dropdown/stories/{02_Shortcuts.vue → Shortcuts.vue} +0 -0
- /package/src/components/Dropdown/stories/{01_Simple.vue → Simple.vue} +0 -0
- /package/src/components/Dropdown/stories/{03_Submenus.vue → Submenus.vue} +0 -0
- /package/src/components/Dropdown/stories/{04_Switches.vue → Switches.vue} +0 -0
- /package/src/components/{Dropdown/DropdownRenderContentAsChild.vue → Menu/MenuRenderContentAsChild.vue} +0 -0
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import {
|
|
2
|
+
computed,
|
|
3
|
+
onBeforeUnmount,
|
|
4
|
+
ref,
|
|
5
|
+
watch,
|
|
6
|
+
type ComputedRef,
|
|
7
|
+
} from 'vue'
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Module-level registry connecting an app's shell to its scroll element across
|
|
11
|
+
* the component tree — the shell that owns the scroll region and a routed page
|
|
12
|
+
* that wants to read/drive it aren't ancestor and descendant.
|
|
13
|
+
*
|
|
14
|
+
* A stack (not a single ref) so that a desktop <-> mobile layout swap hands the
|
|
15
|
+
* active container over cleanly regardless of mount/unmount order, mirroring the
|
|
16
|
+
* PageHeader target registry (`components/PageHeader/target.ts`).
|
|
17
|
+
*/
|
|
18
|
+
const containers = ref<HTMLElement[]>([])
|
|
19
|
+
|
|
20
|
+
/** The current scroll container — the most recently registered one. */
|
|
21
|
+
export const activeScrollContainer: ComputedRef<HTMLElement | null> = computed(
|
|
22
|
+
() => containers.value[containers.value.length - 1] ?? null,
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
/** Register a shell's scroll element. Called by the shell, not by app code. */
|
|
26
|
+
export function registerScrollContainer(el: HTMLElement) {
|
|
27
|
+
containers.value = [...containers.value, el]
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** Remove a previously registered scroll element. */
|
|
31
|
+
export function unregisterScrollContainer(el: HTMLElement) {
|
|
32
|
+
containers.value = containers.value.filter((c) => c !== el)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Plain (non-hook) accessor for the active scroll container. Usable outside a
|
|
37
|
+
* component `setup()` — e.g. inside a vue-router `scrollBehavior` or navigation
|
|
38
|
+
* guard, where composables can't run. Returns `null` if no shell is mounted.
|
|
39
|
+
*/
|
|
40
|
+
export function getScrollContainer(): HTMLElement | null {
|
|
41
|
+
return activeScrollContainer.value
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** Scroll the active container. No-op when none is registered. */
|
|
45
|
+
export function scrollTo(options: ScrollToOptions = {}) {
|
|
46
|
+
activeScrollContainer.value?.scrollTo(options)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** Smooth-scroll the active container to the top. No-op when none is registered. */
|
|
50
|
+
export function scrollToTop() {
|
|
51
|
+
activeScrollContainer.value?.scrollTo({ top: 0, behavior: 'smooth' })
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export interface UseScrollContainerOptions {
|
|
55
|
+
/** Scroll offset (px) past which `isScrolled` flips to true. Default 200. */
|
|
56
|
+
threshold?: number
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export interface UseScrollContainer {
|
|
60
|
+
/** The live scroll element — read `scrollTop`/`scrollHeight` or call `scrollBy` on it. */
|
|
61
|
+
el: ComputedRef<HTMLElement | null>
|
|
62
|
+
/** Scroll the active container. */
|
|
63
|
+
scrollTo: (options?: ScrollToOptions) => void
|
|
64
|
+
/** Smooth-scroll to the top. */
|
|
65
|
+
scrollToTop: () => void
|
|
66
|
+
/** Reactive: is the container scrolled past `threshold`. */
|
|
67
|
+
isScrolled: ComputedRef<boolean>
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Reactive access to the app shell's scroll container.
|
|
72
|
+
*
|
|
73
|
+
* `isScrolled` tracks the *active* container and re-binds automatically across a
|
|
74
|
+
* layout swap, so it stays correct when desktop and mobile shells hand over.
|
|
75
|
+
*/
|
|
76
|
+
export function useScrollContainer(
|
|
77
|
+
options: UseScrollContainerOptions = {},
|
|
78
|
+
): UseScrollContainer {
|
|
79
|
+
const { threshold = 200 } = options
|
|
80
|
+
const scrollTop = ref(0)
|
|
81
|
+
|
|
82
|
+
const onScroll = () => {
|
|
83
|
+
scrollTop.value = activeScrollContainer.value?.scrollTop ?? 0
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Follow the active container as it changes (layout swap): detach from the
|
|
87
|
+
// previous element, attach to the new one, and re-read immediately.
|
|
88
|
+
watch(
|
|
89
|
+
activeScrollContainer,
|
|
90
|
+
(el, prev) => {
|
|
91
|
+
prev?.removeEventListener('scroll', onScroll)
|
|
92
|
+
el?.addEventListener('scroll', onScroll, { passive: true })
|
|
93
|
+
onScroll()
|
|
94
|
+
},
|
|
95
|
+
{ immediate: true },
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
onBeforeUnmount(() => {
|
|
99
|
+
activeScrollContainer.value?.removeEventListener('scroll', onScroll)
|
|
100
|
+
})
|
|
101
|
+
|
|
102
|
+
return {
|
|
103
|
+
el: activeScrollContainer,
|
|
104
|
+
scrollTo,
|
|
105
|
+
scrollToTop,
|
|
106
|
+
isScrolled: computed(() => scrollTop.value > threshold),
|
|
107
|
+
}
|
|
108
|
+
}
|
|
@@ -190,6 +190,9 @@ function globalKeyupHandler(e: KeyboardEvent) {
|
|
|
190
190
|
}
|
|
191
191
|
|
|
192
192
|
function attachGlobalListener() {
|
|
193
|
+
// No-op during SSR — registration still works, listeners attach on the
|
|
194
|
+
// first client-side useShortcut call.
|
|
195
|
+
if (typeof document === 'undefined') return
|
|
193
196
|
if (listenerAttached) return
|
|
194
197
|
listenerAttached = true
|
|
195
198
|
document.addEventListener('keydown', globalKeydownHandler)
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @vitest-environment node
|
|
3
|
+
*/
|
|
4
|
+
import { computed, watchSyncEffect } from 'vue'
|
|
5
|
+
import { afterEach, beforeEach, describe, expect, it } from 'vitest'
|
|
6
|
+
import { docStore } from './docStore'
|
|
7
|
+
import { idbStore } from './idbStore'
|
|
8
|
+
|
|
9
|
+
const DOCTYPE = 'User'
|
|
10
|
+
const identity = <T>(d: T): T => d
|
|
11
|
+
|
|
12
|
+
function idbKey(name: string) {
|
|
13
|
+
return `doc:${DOCTYPE}/${name}`
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Mirror useDoc's `doc` computed: it calls getDoc and dereferences the returned
|
|
18
|
+
* ref *without* optional chaining, so a getDoc that returns undefined throws.
|
|
19
|
+
* A synchronous watcher reproduces the in-browser render effect that re-runs
|
|
20
|
+
* this computed the instant setDoc assigns the ref — the trigger for the crash.
|
|
21
|
+
*/
|
|
22
|
+
function subscribeLikeUseDoc(name: string) {
|
|
23
|
+
const doc = computed(() => docStore.getDoc(DOCTYPE, name, identity).value)
|
|
24
|
+
const seen: unknown[] = []
|
|
25
|
+
const stop = watchSyncEffect(() => {
|
|
26
|
+
seen.push(doc.value)
|
|
27
|
+
})
|
|
28
|
+
return { doc, seen, stop }
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
describe('docStore', () => {
|
|
32
|
+
beforeEach(async () => {
|
|
33
|
+
await docStore.clearAll()
|
|
34
|
+
})
|
|
35
|
+
afterEach(async () => {
|
|
36
|
+
await docStore.clearAll()
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
it('first uncached load: setDoc does not crash a synchronously-tracked reader and keeps the IDB copy', async () => {
|
|
40
|
+
const name = 'user1'
|
|
41
|
+
const { doc, stop } = subscribeLikeUseDoc(name)
|
|
42
|
+
// Nothing cached yet.
|
|
43
|
+
expect(doc.value).toBe(null)
|
|
44
|
+
|
|
45
|
+
const record = { doctype: DOCTYPE, name, email: 'user1@example.com' }
|
|
46
|
+
// Regression: before the fix this rejects. Assigning docRef.value synchronously
|
|
47
|
+
// re-enters getDoc, which (lastFetched unset) saw the entry as stale, evicted
|
|
48
|
+
// the ref it had just created, and the reader dereferenced undefined.
|
|
49
|
+
await expect(docStore.setDoc({ ...record })).resolves.toBeUndefined()
|
|
50
|
+
|
|
51
|
+
expect(doc.value).toMatchObject(record)
|
|
52
|
+
// The IDB copy setDoc wrote must survive — no spurious stale-reload eviction.
|
|
53
|
+
expect(await idbStore.get(idbKey(name))).toMatchObject(record)
|
|
54
|
+
stop()
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
it('stale access: getDoc never returns undefined (no self-eviction under its caller)', async () => {
|
|
58
|
+
const name = 'user2'
|
|
59
|
+
const record = { doctype: DOCTYPE, name }
|
|
60
|
+
await docStore.setDoc({ ...record })
|
|
61
|
+
|
|
62
|
+
// Force the entry past the cache timeout, as a long-lived useDoc would be.
|
|
63
|
+
const key = `${DOCTYPE}/${name}`
|
|
64
|
+
;(
|
|
65
|
+
docStore as unknown as { lastFetched: Map<string, number> }
|
|
66
|
+
).lastFetched.set(key, Date.now() - 10 * 60 * 1000)
|
|
67
|
+
|
|
68
|
+
const ref = docStore.getDoc(DOCTYPE, name, identity, { staleOnError: true })
|
|
69
|
+
expect(ref).toBeDefined()
|
|
70
|
+
expect(() => ref.value).not.toThrow()
|
|
71
|
+
expect(ref.value).toMatchObject(record)
|
|
72
|
+
expect(await idbStore.get(idbKey(name))).toMatchObject(record)
|
|
73
|
+
})
|
|
74
|
+
})
|
|
@@ -38,11 +38,15 @@ class DocStore {
|
|
|
38
38
|
if (!this.docs.has(key)) {
|
|
39
39
|
this.docs.set(key, ref(null))
|
|
40
40
|
}
|
|
41
|
+
// Mark fresh BEFORE assigning the ref. Assigning docRef.value synchronously
|
|
42
|
+
// re-runs any computed reading this doc (e.g. useDoc's `doc`), which calls
|
|
43
|
+
// getDoc again — if the entry still looked stale at that point it would kick
|
|
44
|
+
// off a needless reload that evicts the IDB copy we just wrote.
|
|
45
|
+
this.lastFetched.set(key, Date.now())
|
|
41
46
|
const docRef = this.docs.get(key)
|
|
42
47
|
if (docRef) {
|
|
43
48
|
docRef.value = doc
|
|
44
49
|
}
|
|
45
|
-
this.lastFetched.set(key, Date.now())
|
|
46
50
|
} catch (error) {
|
|
47
51
|
console.error('Failed to set doc in IDB:', error)
|
|
48
52
|
throw error
|
|
@@ -52,9 +56,10 @@ class DocStore {
|
|
|
52
56
|
getDoc(
|
|
53
57
|
doctype: string,
|
|
54
58
|
name: MaybeRefOrGetter<string>,
|
|
55
|
-
transform
|
|
59
|
+
transform?: (doc: Doc) => Doc,
|
|
60
|
+
options: { staleOnError?: boolean } = {},
|
|
56
61
|
): Ref<Doc | null> {
|
|
57
|
-
const nameStr = toValue(name)
|
|
62
|
+
const nameStr = toValue(name)?.trim()
|
|
58
63
|
if (!doctype || !nameStr) {
|
|
59
64
|
throw new Error('doctype and name are required')
|
|
60
65
|
}
|
|
@@ -62,9 +67,9 @@ class DocStore {
|
|
|
62
67
|
|
|
63
68
|
if (!this.docs.has(key)) {
|
|
64
69
|
this.docs.set(key, ref(null))
|
|
65
|
-
this.loadDoc(key, true, transform)
|
|
70
|
+
this.loadDoc(key, true, transform, options)
|
|
66
71
|
} else if (this.isStale(key)) {
|
|
67
|
-
this.loadDoc(key, false, transform)
|
|
72
|
+
this.loadDoc(key, false, transform, options)
|
|
68
73
|
}
|
|
69
74
|
|
|
70
75
|
return this.docs.get(key)!
|
|
@@ -73,11 +78,17 @@ class DocStore {
|
|
|
73
78
|
private async loadDoc(
|
|
74
79
|
key: DocKey,
|
|
75
80
|
isFirstLoad: boolean,
|
|
76
|
-
transform
|
|
81
|
+
transform?: (doc: Doc) => Doc,
|
|
82
|
+
options: { staleOnError?: boolean } = {},
|
|
77
83
|
) {
|
|
78
84
|
try {
|
|
79
85
|
if (!isFirstLoad && this.isStale(key)) {
|
|
80
|
-
|
|
86
|
+
this.lastFetched.delete(key)
|
|
87
|
+
if (!options.staleOnError) {
|
|
88
|
+
// Keep the IDB copy only when callers explicitly opt into stale
|
|
89
|
+
// read-only fallback, such as offline-capable routes.
|
|
90
|
+
await idbStore.delete(this.storePrefix + key)
|
|
91
|
+
}
|
|
81
92
|
}
|
|
82
93
|
|
|
83
94
|
const idbDoc = (await idbStore.get(this.storePrefix + key)) as Doc | null
|
|
@@ -207,4 +207,47 @@ describe('useCall', () => {
|
|
|
207
207
|
await waitUntilValueChanges(() => secondCall.data)
|
|
208
208
|
expect(secondCall.data).toBe('pong')
|
|
209
209
|
})
|
|
210
|
+
|
|
211
|
+
it('keeps cached data visible when a refetch fails', async () => {
|
|
212
|
+
const cacheKey = 'offline-ping'
|
|
213
|
+
const call = useCall({
|
|
214
|
+
url: url('/api/v2/method/ping'),
|
|
215
|
+
cacheKey,
|
|
216
|
+
})
|
|
217
|
+
|
|
218
|
+
await waitUntilValueChanges(() => call.data)
|
|
219
|
+
expect(call.data).toBe('pong')
|
|
220
|
+
|
|
221
|
+
const offlineCall = useCall({
|
|
222
|
+
url: url('/api/v2/method/network-error'),
|
|
223
|
+
cacheKey,
|
|
224
|
+
staleOnError: true,
|
|
225
|
+
})
|
|
226
|
+
|
|
227
|
+
await waitUntilValueChanges(() => offlineCall.data)
|
|
228
|
+
await waitUntilValueChanges(() => offlineCall.loading)
|
|
229
|
+
|
|
230
|
+
expect(offlineCall.error).toBeTruthy()
|
|
231
|
+
expect(offlineCall.data).toBe('pong')
|
|
232
|
+
})
|
|
233
|
+
|
|
234
|
+
it('does not parse empty network failures as Frappe errors', async () => {
|
|
235
|
+
const logSpy = vi.spyOn(console, 'log').mockImplementation(() => {})
|
|
236
|
+
const networkCall = useCall({
|
|
237
|
+
url: url('/api/v2/method/network-error'),
|
|
238
|
+
immediate: false,
|
|
239
|
+
})
|
|
240
|
+
|
|
241
|
+
networkCall.fetch()
|
|
242
|
+
await networkCall.promise.catch(() => {})
|
|
243
|
+
await waitUntilValueChanges(() => networkCall.loading)
|
|
244
|
+
|
|
245
|
+
expect(networkCall.error).toBeTruthy()
|
|
246
|
+
expect(logSpy).not.toHaveBeenCalledWith(
|
|
247
|
+
'Error parsing error response:',
|
|
248
|
+
expect.anything(),
|
|
249
|
+
)
|
|
250
|
+
|
|
251
|
+
logSpy.mockRestore()
|
|
252
|
+
})
|
|
210
253
|
})
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { computed, reactive, readonly, ref, unref } from 'vue'
|
|
2
2
|
import { AfterFetchContext, UseFetchOptions } from '@vueuse/core'
|
|
3
|
-
import { useFrappeFetch } from '../useFrappeFetch'
|
|
3
|
+
import { FrappeResponseError, useFrappeFetch } from '../useFrappeFetch'
|
|
4
4
|
import { unrefObject, makeGetParams, normalizeCacheKey } from '../utils'
|
|
5
5
|
import { idbStore } from '../idbStore'
|
|
6
6
|
import { BasicParams, UseCallOptions } from './types'
|
|
@@ -17,6 +17,7 @@ export function useCall<TResponse, TParams extends BasicParams = undefined>(
|
|
|
17
17
|
baseUrl = '',
|
|
18
18
|
initialData,
|
|
19
19
|
cacheKey,
|
|
20
|
+
staleOnError = false,
|
|
20
21
|
transform,
|
|
21
22
|
beforeSubmit,
|
|
22
23
|
onSuccess,
|
|
@@ -177,7 +178,15 @@ export function useCall<TResponse, TParams extends BasicParams = undefined>(
|
|
|
177
178
|
let cachedResponse = ref<TResponse | null>(null)
|
|
178
179
|
|
|
179
180
|
const _data = computed(() => {
|
|
180
|
-
if (
|
|
181
|
+
if (
|
|
182
|
+
normalizedCacheKey &&
|
|
183
|
+
cachedResponse.value != null &&
|
|
184
|
+
canUseCachedFallback(error.value, staleOnError) &&
|
|
185
|
+
(out.loading ||
|
|
186
|
+
!out.isFinished ||
|
|
187
|
+
data.value?.data == null ||
|
|
188
|
+
error.value)
|
|
189
|
+
) {
|
|
181
190
|
let cachedData = cachedResponse.value as TResponse
|
|
182
191
|
if (transform) {
|
|
183
192
|
let returnValue = transform(cachedData)
|
|
@@ -219,7 +228,11 @@ export function useCall<TResponse, TParams extends BasicParams = undefined>(
|
|
|
219
228
|
|
|
220
229
|
return out as Omit<typeof out, 'submit'> & {
|
|
221
230
|
submit: TParams extends undefined
|
|
222
|
-
? (params?: never) => Promise<
|
|
223
|
-
: (params
|
|
231
|
+
? (params?: never) => Promise<TResponse | null>
|
|
232
|
+
: (params?: TParams) => Promise<TResponse | null>
|
|
224
233
|
}
|
|
225
234
|
}
|
|
235
|
+
|
|
236
|
+
function canUseCachedFallback(error: unknown, staleOnError: boolean) {
|
|
237
|
+
return !error || (staleOnError && !(error instanceof FrappeResponseError))
|
|
238
|
+
}
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
import { ref } from 'vue'
|
|
5
5
|
import { baseUrl, waitUntilValueChanges } from '../../mocks/utils'
|
|
6
6
|
import { useCall, useDoc } from '../index'
|
|
7
|
+
import { docStore } from '../docStore'
|
|
7
8
|
|
|
8
9
|
describe('useDoc', () => {
|
|
9
10
|
it('it returns expected object', async () => {
|
|
@@ -104,7 +105,7 @@ describe('useDoc', () => {
|
|
|
104
105
|
await waitUntilValueChanges(() => user.doc)
|
|
105
106
|
|
|
106
107
|
// Initial email value
|
|
107
|
-
expect(user.doc
|
|
108
|
+
expect(user.doc!.email).toBe('user1@example.com')
|
|
108
109
|
|
|
109
110
|
// Update email
|
|
110
111
|
const newEmail = 'updated@example.com'
|
|
@@ -114,6 +115,64 @@ describe('useDoc', () => {
|
|
|
114
115
|
await waitUntilValueChanges(() => user.doc)
|
|
115
116
|
|
|
116
117
|
// Verify that the doc was updated
|
|
117
|
-
expect(user.doc
|
|
118
|
+
expect(user.doc!.email).toBe(newEmail)
|
|
119
|
+
})
|
|
120
|
+
|
|
121
|
+
it('does not bind or fetch while the name is empty', async () => {
|
|
122
|
+
interface User {
|
|
123
|
+
name: string
|
|
124
|
+
email: string
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const user = useDoc<User>({
|
|
128
|
+
baseUrl,
|
|
129
|
+
doctype: 'User',
|
|
130
|
+
name: '',
|
|
131
|
+
})
|
|
132
|
+
|
|
133
|
+
// No name → nothing to bind and no request should fire.
|
|
134
|
+
expect(user.doc).toBe(null)
|
|
135
|
+
expect(user.loading).toBe(false)
|
|
136
|
+
})
|
|
137
|
+
|
|
138
|
+
it('binds to the doc once a late-resolving name is set', async () => {
|
|
139
|
+
interface User {
|
|
140
|
+
name: string
|
|
141
|
+
email: string
|
|
142
|
+
first_name: string
|
|
143
|
+
last_name: string
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Name is empty at setup (e.g. bound to a doc that is still loading).
|
|
147
|
+
const name = ref('')
|
|
148
|
+
const user = useDoc<User>({
|
|
149
|
+
baseUrl,
|
|
150
|
+
doctype: 'User',
|
|
151
|
+
name,
|
|
152
|
+
})
|
|
153
|
+
|
|
154
|
+
// Nothing fetched or bound yet.
|
|
155
|
+
expect(user.doc).toBe(null)
|
|
156
|
+
expect(user.loading).toBe(false)
|
|
157
|
+
|
|
158
|
+
// Name resolves after setup — the doc ref should re-point to the real slot.
|
|
159
|
+
name.value = 'user1'
|
|
160
|
+
|
|
161
|
+
await waitUntilValueChanges(() => user.loading, true)
|
|
162
|
+
await waitUntilValueChanges(() => user.loading, false)
|
|
163
|
+
|
|
164
|
+
expect(user.doc).toStrictEqual({
|
|
165
|
+
doctype: 'User',
|
|
166
|
+
name: 'user1',
|
|
167
|
+
email: 'user1@example.com',
|
|
168
|
+
first_name: 'User',
|
|
169
|
+
last_name: '1',
|
|
170
|
+
})
|
|
171
|
+
})
|
|
172
|
+
|
|
173
|
+
it('throws when getDoc is called with a whitespace-only name', () => {
|
|
174
|
+
expect(() => docStore.getDoc('User', ' ', (d) => d)).toThrow(
|
|
175
|
+
'doctype and name are required',
|
|
176
|
+
)
|
|
118
177
|
})
|
|
119
178
|
})
|
|
@@ -24,8 +24,10 @@ type TransformMethods<T> = {
|
|
|
24
24
|
: never
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
-
interface DocMethodOption<T = any>
|
|
28
|
-
|
|
27
|
+
interface DocMethodOption<T = any> extends Omit<
|
|
28
|
+
UseCallOptions<T>,
|
|
29
|
+
'url' | 'baseUrl'
|
|
30
|
+
> {
|
|
29
31
|
name: string
|
|
30
32
|
}
|
|
31
33
|
|
|
@@ -36,6 +38,7 @@ interface UseDocOptions<TDoc> {
|
|
|
36
38
|
url?: string
|
|
37
39
|
methods?: Record<string, string | DocMethodOption>
|
|
38
40
|
immediate?: boolean
|
|
41
|
+
staleOnError?: boolean
|
|
39
42
|
transform?: (doc: TDoc & { doctype: string }) => TDoc & { doctype: string }
|
|
40
43
|
}
|
|
41
44
|
|
|
@@ -49,6 +52,7 @@ export function useDoc<TDoc extends { name: string }, TMethods = {}>(
|
|
|
49
52
|
url: customUrl = '',
|
|
50
53
|
methods = {},
|
|
51
54
|
immediate = true,
|
|
55
|
+
staleOnError = false,
|
|
52
56
|
transform,
|
|
53
57
|
} = options
|
|
54
58
|
|
|
@@ -72,7 +76,10 @@ export function useDoc<TDoc extends { name: string }, TMethods = {}>(
|
|
|
72
76
|
}
|
|
73
77
|
|
|
74
78
|
const fetchOptions: UseFetchOptions = {
|
|
75
|
-
|
|
79
|
+
// Don't fire the initial GET while the name is still unresolved (it would
|
|
80
|
+
// hit the malformed `/api/v2/document/<doctype>/` URL). refetch:true makes
|
|
81
|
+
// the request fire automatically once the name resolves and the URL changes.
|
|
82
|
+
immediate: immediate && Boolean(toValue(name)?.trim()),
|
|
76
83
|
refetch: true,
|
|
77
84
|
afterFetch(ctx: AfterFetchContext<{ data: TDoc }>) {
|
|
78
85
|
if (ctx.data) {
|
|
@@ -130,11 +137,10 @@ export function useDoc<TDoc extends { name: string }, TMethods = {}>(
|
|
|
130
137
|
immediate: false,
|
|
131
138
|
refetch: false,
|
|
132
139
|
onSuccess(data) {
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
}
|
|
137
|
-
docStore.setDoc(docWithType)
|
|
140
|
+
// Store the untransformed doc; the `doc` computed applies `transform` on
|
|
141
|
+
// read. Transforming here too would run it twice (a bug for any
|
|
142
|
+
// non-idempotent transform). Mirrors afterFetch.
|
|
143
|
+
docStore.setDoc({ ...data, doctype })
|
|
138
144
|
listStore.updateRow(doctype, data)
|
|
139
145
|
},
|
|
140
146
|
})
|
|
@@ -152,14 +158,28 @@ export function useDoc<TDoc extends { name: string }, TMethods = {}>(
|
|
|
152
158
|
},
|
|
153
159
|
})
|
|
154
160
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
+
// Bind reactively to the document keyed by the *current* name. Resolving the
|
|
162
|
+
// name once at setup would statically bind to whatever it was then — if the
|
|
163
|
+
// name resolves after setup (e.g. while the GET is still in flight) the ref
|
|
164
|
+
// would never re-point to the real cache slot. A computed re-evaluates when
|
|
165
|
+
// the name resolves and when the store ref is populated.
|
|
166
|
+
const doc = computed<TDoc | null>(() => {
|
|
167
|
+
const nameStr = toValue(name)?.trim()
|
|
168
|
+
if (!nameStr) return null
|
|
169
|
+
const storeRef = docStore.getDoc(doctype, nameStr, transform as any, {
|
|
170
|
+
staleOnError,
|
|
171
|
+
}) as Ref<TDoc | null>
|
|
172
|
+
let value = storeRef.value
|
|
173
|
+
if (value && transform) {
|
|
174
|
+
try {
|
|
175
|
+
value = transform(value as TDoc & { doctype: string })
|
|
176
|
+
} catch (e) {
|
|
177
|
+
docStore.removeDoc(doctype, nameStr)
|
|
178
|
+
return null
|
|
179
|
+
}
|
|
161
180
|
}
|
|
162
|
-
|
|
181
|
+
return value
|
|
182
|
+
})
|
|
163
183
|
let out = reactive({
|
|
164
184
|
doc,
|
|
165
185
|
error,
|
|
@@ -7,7 +7,7 @@ interface UseDoctypeOptions {
|
|
|
7
7
|
baseUrl?: string
|
|
8
8
|
}
|
|
9
9
|
|
|
10
|
-
export function useDoctype<T
|
|
10
|
+
export function useDoctype<T extends Record<string, any>>(
|
|
11
11
|
doctype: string,
|
|
12
12
|
options: UseDoctypeOptions = {},
|
|
13
13
|
) {
|
|
@@ -37,7 +37,8 @@ function useInsert<T>(doctype: string, options: UseDoctypeOptions = {}) {
|
|
|
37
37
|
|
|
38
38
|
return {
|
|
39
39
|
...out,
|
|
40
|
-
submit: (params: Partial<T>) =>
|
|
40
|
+
submit: (params: Partial<T>) =>
|
|
41
|
+
out.submit(params as any).then(() => out.data),
|
|
41
42
|
}
|
|
42
43
|
}
|
|
43
44
|
|
|
@@ -109,7 +110,7 @@ function useRunDocMethod(doctype: string, options: UseDoctypeOptions = {}) {
|
|
|
109
110
|
validateError.value = null
|
|
110
111
|
}
|
|
111
112
|
}
|
|
112
|
-
return runDocMethod.submit(params)
|
|
113
|
+
return runDocMethod.submit(params as any)
|
|
113
114
|
},
|
|
114
115
|
isLoading: (name: string, method: string) => {
|
|
115
116
|
return (
|
|
@@ -158,7 +159,7 @@ function useRunMethod(doctype: string, options: UseDoctypeOptions = {}) {
|
|
|
158
159
|
validateError.value = null
|
|
159
160
|
}
|
|
160
161
|
}
|
|
161
|
-
return runMethod.submit(params)
|
|
162
|
+
return runMethod.submit(params as any)
|
|
162
163
|
},
|
|
163
164
|
isLoading: (method: string) => {
|
|
164
165
|
return (
|
|
@@ -190,7 +191,7 @@ function useSetValue<T>(doctype: string, options: UseDoctypeOptions = {}) {
|
|
|
190
191
|
...setValue,
|
|
191
192
|
submit: ({ name, ...values }: SetValueParams) => {
|
|
192
193
|
url.value = `/api/v2/document/${doctype}/${name}`
|
|
193
|
-
return setValue.submit({ name, ...values })
|
|
194
|
+
return setValue.submit({ name, ...values } as any)
|
|
194
195
|
},
|
|
195
196
|
}
|
|
196
197
|
}
|
|
@@ -25,8 +25,13 @@ export class FrappeResponseError extends Error {
|
|
|
25
25
|
this.indicator = options.indicator
|
|
26
26
|
|
|
27
27
|
// Maintains proper stack trace for where our error was thrown (only available on V8)
|
|
28
|
-
|
|
29
|
-
Error
|
|
28
|
+
const captureStackTrace = (
|
|
29
|
+
Error as ErrorConstructor & {
|
|
30
|
+
captureStackTrace?: (target: object, constructor?: Function) => void
|
|
31
|
+
}
|
|
32
|
+
).captureStackTrace
|
|
33
|
+
if (captureStackTrace) {
|
|
34
|
+
captureStackTrace(this, FrappeResponseError)
|
|
30
35
|
}
|
|
31
36
|
}
|
|
32
37
|
}
|
|
@@ -77,9 +82,17 @@ export const useFrappeFetch = createFetch({
|
|
|
77
82
|
indicator: string
|
|
78
83
|
}
|
|
79
84
|
try {
|
|
85
|
+
if (!ctx.data) {
|
|
86
|
+
return ctx
|
|
87
|
+
}
|
|
88
|
+
|
|
80
89
|
let errorResponse = JSON.parse(ctx.data)
|
|
81
|
-
let errors: Array<FrappeError> = errorResponse.errors
|
|
82
|
-
let error = errors[0] // assuming only one error for now
|
|
90
|
+
let errors: Array<FrappeError> | undefined = errorResponse.errors
|
|
91
|
+
let error = errors?.[0] // assuming only one error for now
|
|
92
|
+
if (!error) {
|
|
93
|
+
return ctx
|
|
94
|
+
}
|
|
95
|
+
|
|
83
96
|
let errorDescription = error.message
|
|
84
97
|
? `: ${error.message}`
|
|
85
98
|
: error.exception
|
|
@@ -212,4 +212,44 @@ describe('useList', () => {
|
|
|
212
212
|
{ name: 'User2', email: 'user2@example.com' },
|
|
213
213
|
])
|
|
214
214
|
})
|
|
215
|
+
|
|
216
|
+
it('keeps cached data visible when a refetch fails', async () => {
|
|
217
|
+
interface User {
|
|
218
|
+
name: string
|
|
219
|
+
email: string
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
const cacheKey = 'offline-users'
|
|
223
|
+
const cachedUsers = useList<User>({
|
|
224
|
+
baseUrl,
|
|
225
|
+
doctype: 'User',
|
|
226
|
+
fields: ['name', 'email'],
|
|
227
|
+
cacheKey,
|
|
228
|
+
limit: 2,
|
|
229
|
+
})
|
|
230
|
+
|
|
231
|
+
await waitUntilValueChanges(() => cachedUsers.data)
|
|
232
|
+
expect(cachedUsers.data).toStrictEqual([
|
|
233
|
+
{ name: 'User1', email: 'user1@example.com' },
|
|
234
|
+
{ name: 'User2', email: 'user2@example.com' },
|
|
235
|
+
])
|
|
236
|
+
|
|
237
|
+
const offlineUsers = useList<User>({
|
|
238
|
+
baseUrl,
|
|
239
|
+
doctype: 'InvalidDoctype',
|
|
240
|
+
fields: ['name', 'email'],
|
|
241
|
+
cacheKey,
|
|
242
|
+
staleOnError: true,
|
|
243
|
+
limit: 2,
|
|
244
|
+
})
|
|
245
|
+
|
|
246
|
+
await waitUntilValueChanges(() => offlineUsers.data)
|
|
247
|
+
await waitUntilValueChanges(() => offlineUsers.loading)
|
|
248
|
+
|
|
249
|
+
expect(offlineUsers.error).toBeTruthy()
|
|
250
|
+
expect(offlineUsers.data).toStrictEqual([
|
|
251
|
+
{ name: 'User1', email: 'user1@example.com' },
|
|
252
|
+
{ name: 'User2', email: 'user2@example.com' },
|
|
253
|
+
])
|
|
254
|
+
})
|
|
215
255
|
})
|