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,560 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Spreadsheet-style cell navigation for tables.
|
|
3
|
+
*
|
|
4
|
+
* Two modes, distinguished purely by the selection type (no extra state):
|
|
5
|
+
* - **Navigate** — the selection is a single-cell `CellSelection`. The cell is
|
|
6
|
+
* highlighted (prosemirror-tables draws `.selectedCell`; styled in style.css),
|
|
7
|
+
* there's no caret, and arrow keys move between cells.
|
|
8
|
+
* - **Edit** — a normal `TextSelection` inside the cell. Arrows move the caret
|
|
9
|
+
* as usual; Escape returns to navigate.
|
|
10
|
+
*
|
|
11
|
+
* Flow: click a cell → navigate (highlight). Enter (or a second click, or
|
|
12
|
+
* typing) → edit. Edit mode is sticky: clicks inside the cell only move the
|
|
13
|
+
* caret; it exits only via Enter, Escape (→ navigate) or Tab (→ next cell).
|
|
14
|
+
* Arrow keys in navigate mode → move the active cell; at the top/bottom edge
|
|
15
|
+
* they leave the table (so keyboard users are never trapped inside it).
|
|
16
|
+
*
|
|
17
|
+
* Runs at a high priority so its `handleKeyDown` precedes the Table extension's
|
|
18
|
+
* built-in arrow handling (which would otherwise collapse a CellSelection to a
|
|
19
|
+
* caret on arrow press).
|
|
20
|
+
*/
|
|
21
|
+
import { Extension } from '@tiptap/core'
|
|
22
|
+
import type { Editor } from '@tiptap/core'
|
|
23
|
+
import { Plugin, Selection, TextSelection } from '@tiptap/pm/state'
|
|
24
|
+
import type { EditorView } from '@tiptap/pm/view'
|
|
25
|
+
import type { ResolvedPos } from '@tiptap/pm/model'
|
|
26
|
+
import {
|
|
27
|
+
CellSelection,
|
|
28
|
+
TableMap,
|
|
29
|
+
cellAround,
|
|
30
|
+
columnResizingPluginKey,
|
|
31
|
+
goToNextCell,
|
|
32
|
+
isInTable,
|
|
33
|
+
nextCell,
|
|
34
|
+
selectionCell,
|
|
35
|
+
} from '@tiptap/pm/tables'
|
|
36
|
+
import { trackPointerDrag } from './drag-scroll'
|
|
37
|
+
|
|
38
|
+
type Axis = 'horiz' | 'vert'
|
|
39
|
+
|
|
40
|
+
const ARROWS = ['ArrowLeft', 'ArrowRight', 'ArrowUp', 'ArrowDown'] as const
|
|
41
|
+
|
|
42
|
+
const LONG_PRESS_MS = 500
|
|
43
|
+
const LONG_PRESS_SLOP = 8
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* In edit mode, true when a plain arrow press would move the caret out of the
|
|
47
|
+
* current cell (so we can swallow it — the cell is a sandbox; leave via Escape or
|
|
48
|
+
* Tab). Caret movement that stays inside the cell is left to the default.
|
|
49
|
+
*/
|
|
50
|
+
function arrowLeavesCell(view: EditorView, event: KeyboardEvent): boolean {
|
|
51
|
+
if (event.metaKey || event.ctrlKey || event.altKey || event.shiftKey) {
|
|
52
|
+
return false
|
|
53
|
+
}
|
|
54
|
+
const { state } = view
|
|
55
|
+
if (!state.selection.empty) return false
|
|
56
|
+
const $head = state.selection.$head
|
|
57
|
+
const $cell = cellAround($head)
|
|
58
|
+
if (!$cell) return false
|
|
59
|
+
const cell = $cell.nodeAfter
|
|
60
|
+
if (!cell) return false
|
|
61
|
+
// First / last caret positions inside the cell, and their textblocks.
|
|
62
|
+
const firstPos = Selection.near(state.doc.resolve($cell.pos + 1), 1).from
|
|
63
|
+
const lastPos = Selection.near(
|
|
64
|
+
state.doc.resolve($cell.pos + cell.nodeSize - 1),
|
|
65
|
+
-1,
|
|
66
|
+
).from
|
|
67
|
+
switch (event.key) {
|
|
68
|
+
case 'ArrowLeft':
|
|
69
|
+
return $head.pos <= firstPos
|
|
70
|
+
case 'ArrowRight':
|
|
71
|
+
return $head.pos >= lastPos
|
|
72
|
+
case 'ArrowUp':
|
|
73
|
+
// At the top line of the cell's first block.
|
|
74
|
+
return (
|
|
75
|
+
view.endOfTextblock('up') &&
|
|
76
|
+
$head.start() === state.doc.resolve(firstPos).start()
|
|
77
|
+
)
|
|
78
|
+
case 'ArrowDown':
|
|
79
|
+
// At the bottom line of the cell's last block.
|
|
80
|
+
return (
|
|
81
|
+
view.endOfTextblock('down') &&
|
|
82
|
+
$head.start() === state.doc.resolve(lastPos).start()
|
|
83
|
+
)
|
|
84
|
+
default:
|
|
85
|
+
return false
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/** Select a whole cell (navigate mode), highlighting it. */
|
|
90
|
+
function selectCell(editor: Editor, pos: number): boolean {
|
|
91
|
+
const { state, view } = editor
|
|
92
|
+
view.dispatch(
|
|
93
|
+
state.tr
|
|
94
|
+
.setSelection(CellSelection.create(state.doc, pos))
|
|
95
|
+
.scrollIntoView(),
|
|
96
|
+
)
|
|
97
|
+
return true
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/** Drop the caret into a cell (edit mode), at the end of its content. */
|
|
101
|
+
function editCell(editor: Editor, $cell: ResolvedPos, at?: number): boolean {
|
|
102
|
+
const { state, view } = editor
|
|
103
|
+
const cell = $cell.nodeAfter
|
|
104
|
+
if (!cell) return false
|
|
105
|
+
const pos = at ?? $cell.pos + cell.nodeSize - 1
|
|
106
|
+
const selection = TextSelection.near(state.doc.resolve(pos), -1)
|
|
107
|
+
view.dispatch(state.tr.setSelection(selection).scrollIntoView())
|
|
108
|
+
return true
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/** The table node enclosing a cell position, with its map and start offset. */
|
|
112
|
+
function tableAround($cell: ResolvedPos) {
|
|
113
|
+
const table = $cell.node(-1)
|
|
114
|
+
return { table, start: $cell.start(-1), map: TableMap.get(table) }
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Leave the table vertically: put the caret in the nearest textblock before/
|
|
119
|
+
* after it, creating an adjacent paragraph when the table is the document's
|
|
120
|
+
* first/last node — so navigate mode is never a keyboard trap.
|
|
121
|
+
*/
|
|
122
|
+
function exitTable(editor: Editor, dir: 1 | -1): boolean {
|
|
123
|
+
const { state, view } = editor
|
|
124
|
+
const $cell = selectionCell(state)
|
|
125
|
+
let depth = $cell.depth
|
|
126
|
+
while (depth > 0 && $cell.node(depth).type.name !== 'table') depth--
|
|
127
|
+
if (!depth) return true
|
|
128
|
+
const pos = dir === 1 ? $cell.after(depth) : $cell.before(depth)
|
|
129
|
+
const target = Selection.findFrom(state.doc.resolve(pos), dir, true)
|
|
130
|
+
if (target) {
|
|
131
|
+
view.dispatch(state.tr.setSelection(target).scrollIntoView())
|
|
132
|
+
return true
|
|
133
|
+
}
|
|
134
|
+
const paragraph = state.schema.nodes.paragraph
|
|
135
|
+
if (!paragraph) return true
|
|
136
|
+
const tr = state.tr.insert(pos, paragraph.create())
|
|
137
|
+
tr.setSelection(TextSelection.create(tr.doc, pos + 1)).scrollIntoView()
|
|
138
|
+
view.dispatch(tr)
|
|
139
|
+
return true
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Move the active cell one step along an axis. A horizontal edge is a consumed
|
|
144
|
+
* no-op; a vertical edge exits the table.
|
|
145
|
+
*/
|
|
146
|
+
function moveCell(editor: Editor, axis: Axis, dir: number): boolean {
|
|
147
|
+
const { state } = editor
|
|
148
|
+
const $next = nextCell(selectionCell(state), axis, dir)
|
|
149
|
+
if ($next) return selectCell(editor, $next.pos)
|
|
150
|
+
if (axis === 'vert') return exitTable(editor, dir as 1 | -1)
|
|
151
|
+
return true
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Grow/shrink the cell selection one step along an axis (spreadsheet-style
|
|
156
|
+
* Shift-Arrow): keep the anchor cell fixed, move only the head cell. The
|
|
157
|
+
* selection is always rectangular, so moving the head fills in the block.
|
|
158
|
+
* No-op (but consumed) at an edge.
|
|
159
|
+
*/
|
|
160
|
+
function extendCell(editor: Editor, axis: Axis, dir: number): boolean {
|
|
161
|
+
const { state, view } = editor
|
|
162
|
+
const sel = state.selection
|
|
163
|
+
if (!(sel instanceof CellSelection)) return false
|
|
164
|
+
const $next = nextCell(sel.$headCell, axis, dir)
|
|
165
|
+
if ($next) {
|
|
166
|
+
view.dispatch(
|
|
167
|
+
state.tr
|
|
168
|
+
.setSelection(new CellSelection(sel.$anchorCell, $next))
|
|
169
|
+
.scrollIntoView(),
|
|
170
|
+
)
|
|
171
|
+
}
|
|
172
|
+
return true
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Tab / Shift-Tab: move to the next/previous cell and land in navigate mode
|
|
177
|
+
* (highlight, no caret), exiting edit mode. Wraps across rows via the table's
|
|
178
|
+
* own `goToNextCell`; Tab on the very last cell appends a row first
|
|
179
|
+
* (spreadsheet convention, and what the stock TipTap Tab shortcut did before
|
|
180
|
+
* this plugin intercepted it).
|
|
181
|
+
*/
|
|
182
|
+
function tabToCell(editor: Editor, dir: 1 | -1): boolean {
|
|
183
|
+
const { state, view } = editor
|
|
184
|
+
if (!isInTable(state)) return false
|
|
185
|
+
if (!goToNextCell(dir)(state, view.dispatch)) {
|
|
186
|
+
if (dir !== 1) return true
|
|
187
|
+
if (!editor.can().addRowAfter()) return true
|
|
188
|
+
editor.chain().addRowAfter().run()
|
|
189
|
+
if (!goToNextCell(dir)(editor.state, view.dispatch)) return true
|
|
190
|
+
}
|
|
191
|
+
const $cell = cellAround(editor.state.selection.$head)
|
|
192
|
+
if ($cell) selectCell(editor, $cell.pos)
|
|
193
|
+
return true
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/** Select every cell of the table containing `$cell`. */
|
|
197
|
+
function selectWholeTable(editor: Editor, $cell: ResolvedPos): boolean {
|
|
198
|
+
const { state, view } = editor
|
|
199
|
+
const { start, map } = tableAround($cell)
|
|
200
|
+
view.dispatch(
|
|
201
|
+
state.tr.setSelection(
|
|
202
|
+
new CellSelection(
|
|
203
|
+
state.doc.resolve(start + map.map[0]),
|
|
204
|
+
state.doc.resolve(start + map.map[map.map.length - 1]),
|
|
205
|
+
),
|
|
206
|
+
),
|
|
207
|
+
)
|
|
208
|
+
return true
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* Mod+A inside a table escalates instead of jumping straight to the document:
|
|
213
|
+
* cell content → whole table → (default select-all) whole doc. Keeps the
|
|
214
|
+
* cell-as-sandbox model consistent for selection too.
|
|
215
|
+
*/
|
|
216
|
+
function escalateSelectAll(editor: Editor): boolean {
|
|
217
|
+
const { state, view } = editor
|
|
218
|
+
const sel = state.selection
|
|
219
|
+
if (sel instanceof CellSelection) {
|
|
220
|
+
const { start, map } = tableAround(sel.$anchorCell)
|
|
221
|
+
const rect = map.rectBetween(
|
|
222
|
+
sel.$anchorCell.pos - start,
|
|
223
|
+
sel.$headCell.pos - start,
|
|
224
|
+
)
|
|
225
|
+
const wholeTable =
|
|
226
|
+
rect.left === 0 &&
|
|
227
|
+
rect.top === 0 &&
|
|
228
|
+
rect.right === map.width &&
|
|
229
|
+
rect.bottom === map.height
|
|
230
|
+
if (wholeTable) return false
|
|
231
|
+
return selectWholeTable(editor, sel.$anchorCell)
|
|
232
|
+
}
|
|
233
|
+
const $cell = cellAround(sel.$head)
|
|
234
|
+
if (!$cell) return false
|
|
235
|
+
const cell = $cell.nodeAfter
|
|
236
|
+
if (!cell) return false
|
|
237
|
+
const from = Selection.near(state.doc.resolve($cell.pos + 1), 1).from
|
|
238
|
+
const to = Selection.near(
|
|
239
|
+
state.doc.resolve($cell.pos + cell.nodeSize - 1),
|
|
240
|
+
-1,
|
|
241
|
+
).from
|
|
242
|
+
if (sel.from === from && sel.to === to) {
|
|
243
|
+
return selectWholeTable(editor, $cell)
|
|
244
|
+
}
|
|
245
|
+
view.dispatch(state.tr.setSelection(TextSelection.create(state.doc, from, to)))
|
|
246
|
+
return true
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/** A single-cell CellSelection sitting on exactly the given cell. */
|
|
250
|
+
function isOnCell(selection: unknown, $cell: ResolvedPos): boolean {
|
|
251
|
+
return (
|
|
252
|
+
selection instanceof CellSelection &&
|
|
253
|
+
selection.$anchorCell.pos === $cell.pos &&
|
|
254
|
+
selection.$headCell.pos === $cell.pos
|
|
255
|
+
)
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/** The cell (if any) under a pointer event. */
|
|
259
|
+
function cellAtCoords(
|
|
260
|
+
view: EditorView,
|
|
261
|
+
event: { clientX: number; clientY: number },
|
|
262
|
+
): ResolvedPos | null {
|
|
263
|
+
const coords = view.posAtCoords({ left: event.clientX, top: event.clientY })
|
|
264
|
+
if (!coords) return null
|
|
265
|
+
return cellAround(view.state.doc.resolve(coords.pos))
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
/**
|
|
269
|
+
* Touch: leave taps to the default (caret + keyboard — navigate mode has
|
|
270
|
+
* little value without physical arrow keys), but surface the table context
|
|
271
|
+
* menu on long-press, since iOS never fires `contextmenu` on its own. Android
|
|
272
|
+
* does — the capture listener below cancels the timer so it can't double-open.
|
|
273
|
+
*/
|
|
274
|
+
function watchLongPress(event: PointerEvent): boolean {
|
|
275
|
+
const { clientX, clientY, pointerId } = event
|
|
276
|
+
const target = event.target
|
|
277
|
+
if (!(target instanceof Element)) return false
|
|
278
|
+
|
|
279
|
+
const cleanup = () => {
|
|
280
|
+
clearTimeout(timer)
|
|
281
|
+
document.removeEventListener('pointermove', onMove)
|
|
282
|
+
document.removeEventListener('pointerup', onEnd)
|
|
283
|
+
document.removeEventListener('pointercancel', onEnd)
|
|
284
|
+
document.removeEventListener('contextmenu', onNativeMenu, true)
|
|
285
|
+
}
|
|
286
|
+
const timer = window.setTimeout(() => {
|
|
287
|
+
cleanup()
|
|
288
|
+
target.dispatchEvent(
|
|
289
|
+
new MouseEvent('contextmenu', {
|
|
290
|
+
bubbles: true,
|
|
291
|
+
cancelable: true,
|
|
292
|
+
clientX,
|
|
293
|
+
clientY,
|
|
294
|
+
}),
|
|
295
|
+
)
|
|
296
|
+
}, LONG_PRESS_MS)
|
|
297
|
+
const onMove = (e: PointerEvent) => {
|
|
298
|
+
if (e.pointerId !== pointerId) return
|
|
299
|
+
if (
|
|
300
|
+
Math.hypot(e.clientX - clientX, e.clientY - clientY) > LONG_PRESS_SLOP
|
|
301
|
+
) {
|
|
302
|
+
cleanup()
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
const onEnd = (e: PointerEvent) => {
|
|
306
|
+
if (e.pointerId === pointerId) cleanup()
|
|
307
|
+
}
|
|
308
|
+
const onNativeMenu = () => cleanup()
|
|
309
|
+
document.addEventListener('pointermove', onMove)
|
|
310
|
+
document.addEventListener('pointerup', onEnd)
|
|
311
|
+
document.addEventListener('pointercancel', onEnd)
|
|
312
|
+
document.addEventListener('contextmenu', onNativeMenu, true)
|
|
313
|
+
return false
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
export const TableNavigation = Extension.create({
|
|
317
|
+
name: 'tableNavigation',
|
|
318
|
+
// Beat the Table extension's own arrow/keymap handling.
|
|
319
|
+
priority: 200,
|
|
320
|
+
|
|
321
|
+
addProseMirrorPlugins() {
|
|
322
|
+
const editor = this.editor
|
|
323
|
+
|
|
324
|
+
return [
|
|
325
|
+
new Plugin({
|
|
326
|
+
props: {
|
|
327
|
+
handleKeyDown(view, event) {
|
|
328
|
+
// Read-only: leave caret/selection to the default. No cell nav.
|
|
329
|
+
if (!editor.isEditable) return false
|
|
330
|
+
const { state } = view
|
|
331
|
+
if (!isInTable(state)) return false
|
|
332
|
+
const navigating = state.selection instanceof CellSelection
|
|
333
|
+
|
|
334
|
+
// Mod+A escalates: cell content → whole table → whole doc.
|
|
335
|
+
if (
|
|
336
|
+
(event.metaKey || event.ctrlKey) &&
|
|
337
|
+
!event.altKey &&
|
|
338
|
+
!event.shiftKey &&
|
|
339
|
+
event.key.toLowerCase() === 'a'
|
|
340
|
+
) {
|
|
341
|
+
return escalateSelectAll(editor)
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
// Tab moves to the next/previous cell in navigate mode, exiting edit
|
|
345
|
+
// mode (works from either mode). Consume it in a table either way so
|
|
346
|
+
// focus never leaves the cell grid.
|
|
347
|
+
if (event.key === 'Tab') {
|
|
348
|
+
if (event.metaKey || event.ctrlKey || event.altKey) return false
|
|
349
|
+
tabToCell(editor, event.shiftKey ? -1 : 1)
|
|
350
|
+
return true
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
// Shift+Arrow grows the cell selection (spreadsheet-style), anchor
|
|
354
|
+
// fixed. Only once a CellSelection exists (navigate mode); in edit
|
|
355
|
+
// mode let the default extend the text selection within the cell.
|
|
356
|
+
if (
|
|
357
|
+
event.shiftKey &&
|
|
358
|
+
!event.metaKey &&
|
|
359
|
+
!event.ctrlKey &&
|
|
360
|
+
!event.altKey &&
|
|
361
|
+
(ARROWS as readonly string[]).includes(event.key)
|
|
362
|
+
) {
|
|
363
|
+
if (!navigating) return false
|
|
364
|
+
switch (event.key) {
|
|
365
|
+
case 'ArrowRight':
|
|
366
|
+
return extendCell(editor, 'horiz', 1)
|
|
367
|
+
case 'ArrowLeft':
|
|
368
|
+
return extendCell(editor, 'horiz', -1)
|
|
369
|
+
case 'ArrowDown':
|
|
370
|
+
return extendCell(editor, 'vert', 1)
|
|
371
|
+
case 'ArrowUp':
|
|
372
|
+
return extendCell(editor, 'vert', -1)
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
// In edit mode, keep arrows inside the cell (no escaping to the next
|
|
377
|
+
// cell or out of the table); leave the cell via Escape or Tab.
|
|
378
|
+
if (
|
|
379
|
+
!navigating &&
|
|
380
|
+
(ARROWS as readonly string[]).includes(event.key)
|
|
381
|
+
) {
|
|
382
|
+
return arrowLeavesCell(view, event)
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
switch (event.key) {
|
|
386
|
+
case 'ArrowRight':
|
|
387
|
+
return navigating ? moveCell(editor, 'horiz', 1) : false
|
|
388
|
+
case 'ArrowLeft':
|
|
389
|
+
return navigating ? moveCell(editor, 'horiz', -1) : false
|
|
390
|
+
case 'ArrowDown':
|
|
391
|
+
return navigating ? moveCell(editor, 'vert', 1) : false
|
|
392
|
+
case 'ArrowUp':
|
|
393
|
+
return navigating ? moveCell(editor, 'vert', -1) : false
|
|
394
|
+
case 'Enter':
|
|
395
|
+
if (navigating) {
|
|
396
|
+
return editCell(
|
|
397
|
+
editor,
|
|
398
|
+
(state.selection as CellSelection).$anchorCell,
|
|
399
|
+
)
|
|
400
|
+
}
|
|
401
|
+
// Plain Enter in edit mode returns to navigate mode
|
|
402
|
+
// (spreadsheet-style); Shift+Enter still inserts a line break.
|
|
403
|
+
if (
|
|
404
|
+
!event.shiftKey &&
|
|
405
|
+
!event.metaKey &&
|
|
406
|
+
!event.ctrlKey &&
|
|
407
|
+
!event.altKey
|
|
408
|
+
) {
|
|
409
|
+
return selectCell(editor, selectionCell(state).pos)
|
|
410
|
+
}
|
|
411
|
+
return false
|
|
412
|
+
case 'Escape':
|
|
413
|
+
if (!navigating)
|
|
414
|
+
return selectCell(editor, selectionCell(state).pos)
|
|
415
|
+
return false
|
|
416
|
+
default:
|
|
417
|
+
return false
|
|
418
|
+
}
|
|
419
|
+
},
|
|
420
|
+
|
|
421
|
+
handleDOMEvents: {
|
|
422
|
+
// Select the cell on pointerdown — before the browser places a text
|
|
423
|
+
// caret — so a click lands straight in navigate mode with no caret
|
|
424
|
+
// flash. A second click on the already-active cell falls through to
|
|
425
|
+
// the default (caret) to start editing.
|
|
426
|
+
pointerdown(view, event: PointerEvent) {
|
|
427
|
+
// Read-only: don't hijack clicks into cell selection.
|
|
428
|
+
if (!editor.isEditable) return false
|
|
429
|
+
|
|
430
|
+
// Touch: taps keep the default caret behavior (and scrolling);
|
|
431
|
+
// a long-press opens the table context menu.
|
|
432
|
+
if (event.pointerType === 'touch') {
|
|
433
|
+
if (!cellAtCoords(view, event)) return false
|
|
434
|
+
return watchLongPress(event)
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
// Pointer is over a column-resize border (the resize plugin set its
|
|
438
|
+
// active handle on the preceding mousemove). Bail so its own,
|
|
439
|
+
// lower-priority mousedown can start the drag instead of us
|
|
440
|
+
// swallowing it as a cell selection.
|
|
441
|
+
const resizeState = columnResizingPluginKey.getState(view.state)
|
|
442
|
+
if (resizeState && resizeState.activeHandle > -1) return false
|
|
443
|
+
if (
|
|
444
|
+
event.button !== 0 ||
|
|
445
|
+
event.shiftKey ||
|
|
446
|
+
event.ctrlKey ||
|
|
447
|
+
event.metaKey ||
|
|
448
|
+
event.altKey
|
|
449
|
+
) {
|
|
450
|
+
return false
|
|
451
|
+
}
|
|
452
|
+
const $cell = cellAtCoords(view, event)
|
|
453
|
+
if (!$cell) return false
|
|
454
|
+
// Clicking the active cell again → let the default place the caret.
|
|
455
|
+
if (isOnCell(view.state.selection, $cell)) return false
|
|
456
|
+
// Already editing this cell → let the default move the caret;
|
|
457
|
+
// clicks never exit edit mode (only Enter / Escape / Tab do).
|
|
458
|
+
const $editing = cellAround(view.state.selection.$head)
|
|
459
|
+
if (
|
|
460
|
+
!(view.state.selection instanceof CellSelection) &&
|
|
461
|
+
$editing &&
|
|
462
|
+
$editing.pos === $cell.pos
|
|
463
|
+
) {
|
|
464
|
+
return false
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
event.preventDefault()
|
|
468
|
+
const anchorPos = $cell.pos
|
|
469
|
+
view.dispatch(
|
|
470
|
+
view.state.tr.setSelection(
|
|
471
|
+
CellSelection.create(view.state.doc, anchorPos),
|
|
472
|
+
),
|
|
473
|
+
)
|
|
474
|
+
if (!view.hasFocus()) view.focus()
|
|
475
|
+
|
|
476
|
+
// Drag to extend into a multi-cell selection (e.g. for merge),
|
|
477
|
+
// with edge auto-scroll so wide tables can be selected past the
|
|
478
|
+
// wrapper's visible window.
|
|
479
|
+
const anchorDom = view.nodeDOM(anchorPos)
|
|
480
|
+
const wrapper =
|
|
481
|
+
anchorDom instanceof HTMLElement
|
|
482
|
+
? anchorDom.closest<HTMLElement>('.tableWrapper')
|
|
483
|
+
: null
|
|
484
|
+
trackPointerDrag({
|
|
485
|
+
event,
|
|
486
|
+
area: wrapper,
|
|
487
|
+
onPoint(x, y) {
|
|
488
|
+
const $head = cellAtCoords(view, { clientX: x, clientY: y })
|
|
489
|
+
if (!$head) return
|
|
490
|
+
// Never extend across table boundaries.
|
|
491
|
+
const $anchor = view.state.doc.resolve(anchorPos)
|
|
492
|
+
if ($head.start(-1) !== $anchor.start(-1)) return
|
|
493
|
+
const next = CellSelection.create(
|
|
494
|
+
view.state.doc,
|
|
495
|
+
anchorPos,
|
|
496
|
+
$head.pos,
|
|
497
|
+
)
|
|
498
|
+
if (!view.state.selection.eq(next)) {
|
|
499
|
+
view.dispatch(view.state.tr.setSelection(next))
|
|
500
|
+
}
|
|
501
|
+
},
|
|
502
|
+
})
|
|
503
|
+
return true
|
|
504
|
+
},
|
|
505
|
+
|
|
506
|
+
// IME input never reaches handleTextInput, so drop into edit mode
|
|
507
|
+
// before the composition starts — composing against a CellSelection
|
|
508
|
+
// (which has no DOM caret) drops or mangles the input.
|
|
509
|
+
compositionstart(view) {
|
|
510
|
+
if (!editor.isEditable) return false
|
|
511
|
+
const { selection } = view.state
|
|
512
|
+
if (!(selection instanceof CellSelection)) return false
|
|
513
|
+
editCell(editor, selection.$anchorCell)
|
|
514
|
+
return false
|
|
515
|
+
},
|
|
516
|
+
},
|
|
517
|
+
|
|
518
|
+
// Typing while a cell is selected starts editing (append) instead of
|
|
519
|
+
// letting a CellSelection swallow the input. Input lands in the
|
|
520
|
+
// anchor cell — the active cell of a multi-cell selection.
|
|
521
|
+
handleTextInput(view, _from, _to, text) {
|
|
522
|
+
if (!editor.isEditable) return false
|
|
523
|
+
const { selection } = view.state
|
|
524
|
+
if (!(selection instanceof CellSelection)) return false
|
|
525
|
+
const $cell = selection.$anchorCell
|
|
526
|
+
const cell = $cell.nodeAfter
|
|
527
|
+
if (!cell) return false
|
|
528
|
+
const end = $cell.pos + cell.nodeSize - 1
|
|
529
|
+
view.dispatch(
|
|
530
|
+
view.state.tr
|
|
531
|
+
.setSelection(
|
|
532
|
+
TextSelection.near(view.state.doc.resolve(end), -1),
|
|
533
|
+
)
|
|
534
|
+
.insertText(text)
|
|
535
|
+
.scrollIntoView(),
|
|
536
|
+
)
|
|
537
|
+
return true
|
|
538
|
+
},
|
|
539
|
+
|
|
540
|
+
// Match type-to-append: pasting non-table content on a selected cell
|
|
541
|
+
// drops the caret at the end of the anchor cell and pastes there,
|
|
542
|
+
// instead of replacing the cell's content. Table-shaped slices keep
|
|
543
|
+
// prosemirror-tables' structured cell paste.
|
|
544
|
+
handlePaste(view, _event, slice) {
|
|
545
|
+
if (!editor.isEditable) return false
|
|
546
|
+
const { selection } = view.state
|
|
547
|
+
if (!(selection instanceof CellSelection)) return false
|
|
548
|
+
let tableContent = false
|
|
549
|
+
slice.content.forEach((node) => {
|
|
550
|
+
if (node.type.spec.tableRole) tableContent = true
|
|
551
|
+
})
|
|
552
|
+
if (tableContent) return false
|
|
553
|
+
editCell(editor, selection.$anchorCell)
|
|
554
|
+
return false
|
|
555
|
+
},
|
|
556
|
+
},
|
|
557
|
+
}),
|
|
558
|
+
]
|
|
559
|
+
},
|
|
560
|
+
})
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Draws ONE rounded box around a multi-cell selection instead of ringing each
|
|
3
|
+
* cell on its own.
|
|
4
|
+
*
|
|
5
|
+
* prosemirror-tables flags every selected cell with `.selectedCell`, which by
|
|
6
|
+
* default we paint with a translucent fill + a per-cell ring (see style.css).
|
|
7
|
+
* For a 2+ cell selection that reads as a grid of boxes. Here we instead:
|
|
8
|
+
* - add a `table-cell-range` class to the editor so the per-cell ring is
|
|
9
|
+
* suppressed (the fill stays, so the region reads as one block), and
|
|
10
|
+
* - overlay a single element spanning the union of the selected cells' rects,
|
|
11
|
+
* appended to the `.tableWrapper` (a positioned, scrollable ancestor) so it
|
|
12
|
+
* tracks horizontal scroll for free.
|
|
13
|
+
*
|
|
14
|
+
* A single selected cell (navigate mode) is left untouched — it keeps its ring.
|
|
15
|
+
*/
|
|
16
|
+
import { Extension } from '@tiptap/core'
|
|
17
|
+
import { Plugin } from '@tiptap/pm/state'
|
|
18
|
+
import { CellSelection } from '@tiptap/pm/tables'
|
|
19
|
+
import type { EditorView } from '@tiptap/pm/view'
|
|
20
|
+
|
|
21
|
+
interface Rect {
|
|
22
|
+
left: number
|
|
23
|
+
top: number
|
|
24
|
+
right: number
|
|
25
|
+
bottom: number
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
class SelectionOverlay {
|
|
29
|
+
private el: HTMLElement | null = null
|
|
30
|
+
// The box is measured from live cell rects, but only transactions reach
|
|
31
|
+
// `update` — re-measure when the window reflows or the table itself resizes
|
|
32
|
+
// (column drag, image load) so the box doesn't drift between edits.
|
|
33
|
+
private resizeObserver: ResizeObserver | null = null
|
|
34
|
+
private observed: HTMLElement | null = null
|
|
35
|
+
private onResize = () => this.update(this.view)
|
|
36
|
+
|
|
37
|
+
constructor(private view: EditorView) {
|
|
38
|
+
window.addEventListener('resize', this.onResize)
|
|
39
|
+
if (typeof ResizeObserver !== 'undefined') {
|
|
40
|
+
this.resizeObserver = new ResizeObserver(() => this.update(this.view))
|
|
41
|
+
}
|
|
42
|
+
this.update(view)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
update(view: EditorView): void {
|
|
46
|
+
this.view = view
|
|
47
|
+
const { selection } = view.state
|
|
48
|
+
const isRange =
|
|
49
|
+
selection instanceof CellSelection &&
|
|
50
|
+
selection.$anchorCell.pos !== selection.$headCell.pos
|
|
51
|
+
view.dom.classList.toggle('table-cell-range', isRange)
|
|
52
|
+
if (!isRange) {
|
|
53
|
+
this.remove()
|
|
54
|
+
return
|
|
55
|
+
}
|
|
56
|
+
this.draw(view, selection as CellSelection)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
private draw(view: EditorView, selection: CellSelection): void {
|
|
60
|
+
let union: Rect | null = null
|
|
61
|
+
selection.forEachCell((_cell, pos) => {
|
|
62
|
+
const dom = view.nodeDOM(pos)
|
|
63
|
+
if (!(dom instanceof HTMLElement)) return
|
|
64
|
+
const r = dom.getBoundingClientRect()
|
|
65
|
+
union = union
|
|
66
|
+
? {
|
|
67
|
+
left: Math.min(union.left, r.left),
|
|
68
|
+
top: Math.min(union.top, r.top),
|
|
69
|
+
right: Math.max(union.right, r.right),
|
|
70
|
+
bottom: Math.max(union.bottom, r.bottom),
|
|
71
|
+
}
|
|
72
|
+
: { left: r.left, top: r.top, right: r.right, bottom: r.bottom }
|
|
73
|
+
})
|
|
74
|
+
if (!union) {
|
|
75
|
+
this.remove()
|
|
76
|
+
return
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const anchorDom = view.nodeDOM(selection.$anchorCell.pos)
|
|
80
|
+
const host =
|
|
81
|
+
(anchorDom instanceof HTMLElement
|
|
82
|
+
? (anchorDom.closest('.tableWrapper') as HTMLElement | null)
|
|
83
|
+
: null) ?? (view.dom as HTMLElement)
|
|
84
|
+
const hostRect = host.getBoundingClientRect()
|
|
85
|
+
this.observe(host)
|
|
86
|
+
|
|
87
|
+
// Convert viewport rect → host content coordinates (so the box scrolls with
|
|
88
|
+
// the wrapper's horizontal overflow).
|
|
89
|
+
const box: Rect = union
|
|
90
|
+
const el = this.ensure(host)
|
|
91
|
+
el.style.left = `${box.left - hostRect.left + host.scrollLeft}px`
|
|
92
|
+
el.style.top = `${box.top - hostRect.top + host.scrollTop}px`
|
|
93
|
+
el.style.width = `${box.right - box.left}px`
|
|
94
|
+
el.style.height = `${box.bottom - box.top}px`
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
private ensure(host: HTMLElement): HTMLElement {
|
|
98
|
+
if (this.el && this.el.parentElement === host) return this.el
|
|
99
|
+
this.remove()
|
|
100
|
+
const el = document.createElement('div')
|
|
101
|
+
el.className = 'table-selection-box'
|
|
102
|
+
el.setAttribute('aria-hidden', 'true')
|
|
103
|
+
host.appendChild(el)
|
|
104
|
+
this.el = el
|
|
105
|
+
return el
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
private observe(host: HTMLElement): void {
|
|
109
|
+
if (!this.resizeObserver || this.observed === host) return
|
|
110
|
+
this.resizeObserver.disconnect()
|
|
111
|
+
this.resizeObserver.observe(host)
|
|
112
|
+
this.observed = host
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
private remove(): void {
|
|
116
|
+
this.el?.remove()
|
|
117
|
+
this.el = null
|
|
118
|
+
this.resizeObserver?.disconnect()
|
|
119
|
+
this.observed = null
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
destroy(): void {
|
|
123
|
+
this.view.dom.classList.remove('table-cell-range')
|
|
124
|
+
this.remove()
|
|
125
|
+
window.removeEventListener('resize', this.onResize)
|
|
126
|
+
this.resizeObserver = null
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
export const TableSelectionOverlay = Extension.create({
|
|
131
|
+
name: 'tableSelectionOverlay',
|
|
132
|
+
addProseMirrorPlugins() {
|
|
133
|
+
return [new Plugin({ view: (view) => new SelectionOverlay(view) })]
|
|
134
|
+
},
|
|
135
|
+
})
|
|
136
|
+
|
|
137
|
+
export { TableSelectionOverlay as default }
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
<button
|
|
21
21
|
v-if="isEditable"
|
|
22
22
|
@click="removeNode"
|
|
23
|
-
class="absolute top-2 right-2 bg-black/65 hover:bg-black/80 text-ink-gray-4 hover:text-ink-
|
|
23
|
+
class="absolute top-2 right-2 bg-black/65 hover:bg-black/80 text-ink-gray-4 hover:text-ink-base p-1 rounded opacity-0 group-hover:opacity-100 transition-opacity"
|
|
24
24
|
title="Remove table of contents"
|
|
25
25
|
>
|
|
26
26
|
<span class="lucide-x size-4" />
|