frappe-ui 1.0.0-beta.2 → 1.0.0-beta.20
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 +3 -3
- 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/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.vue +1 -1
- package/src/components/Calendar/CalendarDaily.vue +1 -1
- package/src/components/Calendar/CalendarMonthEvent.vue +1 -1
- package/src/components/Calendar/CalendarMonthly.vue +2 -2
- package/src/components/Calendar/CalendarWeekDayEvent.vue +2 -2
- package/src/components/Calendar/CalendarWeekly.vue +2 -2
- package/src/components/Calendar/EventModalContent.vue +6 -6
- package/src/components/Calendar/ShowMoreCalendarEvent.vue +1 -1
- package/src/components/Calendar/stories/CustomHeader.vue +1 -1
- 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 +6 -6
- package/src/components/Checkbox/Checkbox.cy.ts +10 -2
- package/src/components/Checkbox/Checkbox.md +28 -0
- package/src/components/Checkbox/Checkbox.vue +8 -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/CodeEditor/CodeEditor.api.md +140 -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 +21 -21
- 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 +1 -7
- 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 +32 -0
- package/src/components/DesktopShell/DesktopShell.cy.ts +39 -0
- package/src/components/DesktopShell/DesktopShell.md +31 -0
- package/src/components/DesktopShell/DesktopShell.vue +58 -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.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/Divider/Divider.api.md +1 -1
- package/src/components/Divider/Divider.md +4 -0
- package/src/components/Dropdown/Dropdown.api.md +1 -31
- 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/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 +101 -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.vue +2 -2
- package/src/components/ListFilter/ListFilter.vue +2 -2
- 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/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 +71 -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.vue +1 -1
- package/src/components/MultiSelect/MultiSelect.api.md +9 -9
- 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 +113 -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/index.ts +6 -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/Pill/Pill.api.md +99 -0
- package/src/components/Pill/Pill.md +20 -0
- package/src/components/Pill/Pill.vue +161 -0
- package/src/components/Pill/index.ts +10 -0
- package/src/components/Pill/stories/BrowserEdges.vue +42 -0
- package/src/components/Pill/stories/Sizes.vue +49 -0
- package/src/components/Pill/stories/Variants.vue +53 -0
- package/src/components/Pill/types.ts +43 -0
- package/src/components/Popover/Popover.api.md +126 -41
- 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 +92 -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 +43 -0
- package/src/components/ScrollArea/ScrollBar.vue +95 -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 +5 -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 +72 -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 +112 -69
- 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 +2 -2
- package/src/components/Sidebar/SidebarItem.vue +130 -97
- 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 +97 -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/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 +206 -109
- package/src/components/TabButtons/index.ts +11 -1
- 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.vue +5 -2
- 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/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 +88 -0
- package/src/components/ThemeSwitcher/ThemeSwitcher.md +70 -0
- package/src/components/ThemeSwitcher/ThemeSwitcher.vue +142 -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/useScreenSize.ts +46 -0
- package/src/composables/useScrollContainer.ts +108 -0
- package/src/data-fetching/docStore.test.ts +73 -0
- package/src/data-fetching/docStore.ts +16 -3
- package/src/data-fetching/useDoc/useDoc.test.ts +59 -0
- package/src/data-fetching/useDoc/useDoc.ts +29 -13
- package/src/index.ts +19 -1
- package/src/molecules/editor/Editor.cy.ts +6 -1
- package/src/molecules/editor/EditorBubbleMenu.vue +1 -1
- package/src/molecules/editor/EditorContent.vue +48 -7
- 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 +6 -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/video/video-extension.ts +72 -5
- package/src/molecules/editor/extensions.ts +176 -8
- package/src/molecules/editor/index.ts +7 -0
- 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 +305 -0
- package/src/molecules/list/List.vue +42 -0
- package/src/molecules/list/ListCell.vue +15 -0
- package/src/molecules/list/ListHeader.vue +26 -0
- package/src/molecules/list/ListHeaderCell.vue +26 -0
- package/src/molecules/list/ListHeaderCellSort.vue +72 -0
- package/src/molecules/list/ListRow.vue +48 -0
- package/src/molecules/list/ListRowBase.vue +79 -0
- package/src/molecules/list/ListRows.vue +82 -0
- package/src/molecules/list/index.ts +23 -0
- package/src/molecules/list/list-context.ts +28 -0
- package/src/molecules/list/list.api.md +223 -0
- package/src/molecules/list/list.md +91 -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 +140 -0
- package/src/molecules/list/types.ts +66 -0
- package/src/molecules/list/useVirtualRows.ts +80 -0
- package/src/resources/documentResource.js +32 -4
- package/src/resources/documentResource.test.ts +111 -0
- package/src/utils/config.ts +15 -1
- package/src/utils/dialog.cy.ts +9 -9
- package/src/utils/fileSize.ts +36 -0
- package/src/utils/fileUploadHandler.ts +49 -5
- package/src/utils/frappeRequest.js +21 -0
- package/src/utils/frappeRequest.test.ts +64 -0
- package/src/utils/plugin.js +19 -0
- package/src/utils/request.js +1 -0
- package/src/utils/theme.ts +48 -57
- package/src/utils/useFileUpload.ts +68 -22
- 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/tokens.js +35 -37
- 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/Sidebar/stories/Example.vue +0 -76
- package/src/components/Spinner.vue +0 -3
- package/src/components/TabButtons/TabButtons.story.vue +0 -22
- /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,328 @@
|
|
|
1
|
+
import { defineComponent, h, ref } from 'vue'
|
|
2
|
+
import FloatingWindow from './FloatingWindow.vue'
|
|
3
|
+
import type { WindowMode } from './types'
|
|
4
|
+
|
|
5
|
+
// A controlled host that surfaces the bound mode as text, so the v-model sync is
|
|
6
|
+
// observable, and forwards a storageKey for the persistence tests. The default
|
|
7
|
+
// slot is tagged so we can assert it collapses in the tray.
|
|
8
|
+
function controlledWindow(
|
|
9
|
+
options: {
|
|
10
|
+
initialMode?: WindowMode
|
|
11
|
+
storageKey?: string | null
|
|
12
|
+
title?: string
|
|
13
|
+
} = {},
|
|
14
|
+
) {
|
|
15
|
+
return defineComponent({
|
|
16
|
+
setup() {
|
|
17
|
+
const mode = ref<WindowMode>(options.initialMode ?? 'docked')
|
|
18
|
+
return { mode }
|
|
19
|
+
},
|
|
20
|
+
render() {
|
|
21
|
+
return [
|
|
22
|
+
h('span', { 'data-cy': 'mode' }, this.mode),
|
|
23
|
+
h(
|
|
24
|
+
FloatingWindow,
|
|
25
|
+
{
|
|
26
|
+
title: options.title ?? 'Messages',
|
|
27
|
+
mode: this.mode,
|
|
28
|
+
'onUpdate:mode': (value: WindowMode) => (this.mode = value),
|
|
29
|
+
storageKey: options.storageKey ?? null,
|
|
30
|
+
},
|
|
31
|
+
{ default: () => h('div', { 'data-cy': 'body' }, 'Conversation') },
|
|
32
|
+
),
|
|
33
|
+
]
|
|
34
|
+
},
|
|
35
|
+
})
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Simulate a pointer resize drag on a named edge/corner handle. Only the delta
|
|
39
|
+
// matters: startResize records the pointerdown position, then the window-level
|
|
40
|
+
// pointermove/pointerup (bubbled up from <body>) drive the resize loop.
|
|
41
|
+
function dragResize(
|
|
42
|
+
name: string,
|
|
43
|
+
from: { x: number; y: number },
|
|
44
|
+
delta: { x: number; y: number },
|
|
45
|
+
) {
|
|
46
|
+
cy.get(`[data-resize=${name}]`).trigger('pointerdown', {
|
|
47
|
+
eventConstructor: 'PointerEvent',
|
|
48
|
+
button: 0,
|
|
49
|
+
clientX: from.x,
|
|
50
|
+
clientY: from.y,
|
|
51
|
+
})
|
|
52
|
+
cy.get('body').trigger('pointermove', {
|
|
53
|
+
eventConstructor: 'PointerEvent',
|
|
54
|
+
clientX: from.x + delta.x,
|
|
55
|
+
clientY: from.y + delta.y,
|
|
56
|
+
})
|
|
57
|
+
cy.get('body').trigger('pointerup', { eventConstructor: 'PointerEvent' })
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
describe('FloatingWindow', () => {
|
|
61
|
+
beforeEach(() => {
|
|
62
|
+
// Deterministic geometry: the default panel (460×520) fits with room to
|
|
63
|
+
// spare, so nothing clamps to the viewport.
|
|
64
|
+
cy.viewport(1200, 800)
|
|
65
|
+
localStorage.clear()
|
|
66
|
+
// The single-detached registry lives in module scope; clear its body flag
|
|
67
|
+
// so one test never bleeds into the next.
|
|
68
|
+
document.body.classList.remove('has-floating-window')
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
it('renders docked by default with the window body visible', () => {
|
|
72
|
+
cy.mount(controlledWindow())
|
|
73
|
+
|
|
74
|
+
cy.get('[data-cy=mode]').should('have.text', 'docked')
|
|
75
|
+
cy.get('.floating-window')
|
|
76
|
+
.should('exist')
|
|
77
|
+
.and('have.attr', 'data-state', 'docked')
|
|
78
|
+
cy.get('[data-cy=body]').should('be.visible')
|
|
79
|
+
// Docked offers pop-out and minimize, never a close.
|
|
80
|
+
cy.get('[aria-label="Pop out"]').should('exist')
|
|
81
|
+
cy.get('[aria-label="Close"]').should('not.exist')
|
|
82
|
+
})
|
|
83
|
+
|
|
84
|
+
it('walks docked → floating → minimized → floating → docked from the chrome', () => {
|
|
85
|
+
cy.mount(controlledWindow())
|
|
86
|
+
|
|
87
|
+
// Pop out: detaches, casts a shadow, and surfaces the resize grip.
|
|
88
|
+
cy.get('[aria-label="Pop out"]').click()
|
|
89
|
+
cy.get('[data-cy=mode]').should('have.text', 'floating')
|
|
90
|
+
cy.get('.floating-window').should('have.attr', 'data-state', 'floating')
|
|
91
|
+
cy.get('[aria-label="Resize window"]').should('exist')
|
|
92
|
+
cy.get('[data-cy=body]').should('be.visible')
|
|
93
|
+
|
|
94
|
+
// Minimize: collapses to the tray, hiding the body, narrowed to 320px.
|
|
95
|
+
cy.get('[aria-label="Minimize"]').click()
|
|
96
|
+
cy.get('[data-cy=mode]').should('have.text', 'minimized')
|
|
97
|
+
cy.get('[data-cy=body]').should('not.be.visible')
|
|
98
|
+
cy.get('.floating-window').invoke('outerWidth').should('eq', 320)
|
|
99
|
+
|
|
100
|
+
// Expand back out of the tray.
|
|
101
|
+
cy.get('[aria-label="Expand"]').click()
|
|
102
|
+
cy.get('[data-cy=mode]').should('have.text', 'floating')
|
|
103
|
+
cy.get('[data-cy=body]').should('be.visible')
|
|
104
|
+
|
|
105
|
+
// Close: docks back into the host layout.
|
|
106
|
+
cy.get('[aria-label="Close"]').click()
|
|
107
|
+
cy.get('[data-cy=mode]').should('have.text', 'docked')
|
|
108
|
+
cy.get('.floating-window').should('have.attr', 'data-state', 'docked')
|
|
109
|
+
})
|
|
110
|
+
|
|
111
|
+
it('lets the host drive the window through v-model:mode', () => {
|
|
112
|
+
const Host = defineComponent({
|
|
113
|
+
setup() {
|
|
114
|
+
const mode = ref<WindowMode>('docked')
|
|
115
|
+
return { mode }
|
|
116
|
+
},
|
|
117
|
+
render() {
|
|
118
|
+
return [
|
|
119
|
+
h(
|
|
120
|
+
'button',
|
|
121
|
+
{ 'data-cy': 'go-float', onClick: () => (this.mode = 'floating') },
|
|
122
|
+
'float',
|
|
123
|
+
),
|
|
124
|
+
h(FloatingWindow, {
|
|
125
|
+
title: 'Messages',
|
|
126
|
+
mode: this.mode,
|
|
127
|
+
'onUpdate:mode': (value: WindowMode) => (this.mode = value),
|
|
128
|
+
}),
|
|
129
|
+
]
|
|
130
|
+
},
|
|
131
|
+
})
|
|
132
|
+
|
|
133
|
+
cy.mount(Host)
|
|
134
|
+
cy.get('.floating-window').should('have.attr', 'data-state', 'docked')
|
|
135
|
+
|
|
136
|
+
// Host flips the bound mode; the window detaches to match.
|
|
137
|
+
cy.get('[data-cy=go-float]').click()
|
|
138
|
+
cy.get('.floating-window').should('have.attr', 'data-state', 'floating')
|
|
139
|
+
cy.get('[aria-label="Close"]').should('exist')
|
|
140
|
+
})
|
|
141
|
+
|
|
142
|
+
describe('persistence', () => {
|
|
143
|
+
const KEY = 'fw:test:persist'
|
|
144
|
+
|
|
145
|
+
it('restores a saved mode on mount and adopts it into v-model', () => {
|
|
146
|
+
// Seed a floating session, then mount with the bound mode still at its
|
|
147
|
+
// docked default. The restored mode must win and propagate up, or a
|
|
148
|
+
// controlled host would render docked over a floating window.
|
|
149
|
+
localStorage.setItem(
|
|
150
|
+
KEY,
|
|
151
|
+
JSON.stringify({
|
|
152
|
+
mode: 'floating',
|
|
153
|
+
rect: { x: 120, y: 120, width: 500, height: 400 },
|
|
154
|
+
}),
|
|
155
|
+
)
|
|
156
|
+
|
|
157
|
+
cy.mount(controlledWindow({ storageKey: KEY, initialMode: 'docked' }))
|
|
158
|
+
|
|
159
|
+
cy.get('[data-cy=mode]').should('have.text', 'floating')
|
|
160
|
+
cy.get('.floating-window').should('have.attr', 'data-state', 'floating')
|
|
161
|
+
cy.get('.floating-window').invoke('outerWidth').should('eq', 500)
|
|
162
|
+
})
|
|
163
|
+
|
|
164
|
+
it('writes mode changes back to storage', () => {
|
|
165
|
+
cy.mount(controlledWindow({ storageKey: KEY }))
|
|
166
|
+
|
|
167
|
+
cy.get('[aria-label="Pop out"]').click()
|
|
168
|
+
cy.get('[data-cy=mode]').should('have.text', 'floating')
|
|
169
|
+
cy.wrap(null).then(() => {
|
|
170
|
+
const saved = JSON.parse(localStorage.getItem(KEY) as string)
|
|
171
|
+
expect(saved.mode).to.equal('floating')
|
|
172
|
+
})
|
|
173
|
+
})
|
|
174
|
+
})
|
|
175
|
+
|
|
176
|
+
it('pins the previously detached window when a second one pops out', () => {
|
|
177
|
+
// Only one window may float at a time; popping a second one out docks the
|
|
178
|
+
// first via the shared activeDocker registry.
|
|
179
|
+
const TwoWindows = defineComponent({
|
|
180
|
+
render() {
|
|
181
|
+
return [
|
|
182
|
+
h(FloatingWindow, { title: 'Window A' }),
|
|
183
|
+
h(FloatingWindow, { title: 'Window B' }),
|
|
184
|
+
]
|
|
185
|
+
},
|
|
186
|
+
})
|
|
187
|
+
|
|
188
|
+
cy.mount(TwoWindows)
|
|
189
|
+
|
|
190
|
+
cy.contains('.floating-window', 'Window A')
|
|
191
|
+
.find('[aria-label="Pop out"]')
|
|
192
|
+
.click()
|
|
193
|
+
cy.contains('.floating-window', 'Window A').should(
|
|
194
|
+
'have.attr',
|
|
195
|
+
'data-state',
|
|
196
|
+
'floating',
|
|
197
|
+
)
|
|
198
|
+
|
|
199
|
+
cy.contains('.floating-window', 'Window B')
|
|
200
|
+
.find('[aria-label="Pop out"]')
|
|
201
|
+
.click()
|
|
202
|
+
|
|
203
|
+
// B is now floating; A was pinned back into the docked layout.
|
|
204
|
+
cy.contains('.floating-window', 'Window B').should(
|
|
205
|
+
'have.attr',
|
|
206
|
+
'data-state',
|
|
207
|
+
'floating',
|
|
208
|
+
)
|
|
209
|
+
cy.contains('.floating-window', 'Window A')
|
|
210
|
+
.should('have.attr', 'data-state', 'docked')
|
|
211
|
+
.find('[aria-label="Pop out"]')
|
|
212
|
+
.should('exist')
|
|
213
|
+
})
|
|
214
|
+
|
|
215
|
+
describe('keyboard accessibility', () => {
|
|
216
|
+
it('names every window control for assistive tech', () => {
|
|
217
|
+
cy.mount(controlledWindow())
|
|
218
|
+
|
|
219
|
+
cy.get('[aria-label="Minimize"]').should('exist')
|
|
220
|
+
cy.get('[aria-label="Pop out"]').should('exist')
|
|
221
|
+
|
|
222
|
+
cy.get('[aria-label="Pop out"]').click()
|
|
223
|
+
cy.get('[aria-label="Close"]').should('exist')
|
|
224
|
+
cy.get('[aria-label="Resize window"]').should('exist')
|
|
225
|
+
})
|
|
226
|
+
|
|
227
|
+
it('expands from the tray via a focusable, named control', () => {
|
|
228
|
+
cy.mount(controlledWindow())
|
|
229
|
+
|
|
230
|
+
cy.get('[aria-label="Pop out"]').click()
|
|
231
|
+
cy.get('[aria-label="Minimize"]').click()
|
|
232
|
+
cy.get('[data-cy=mode]').should('have.text', 'minimized')
|
|
233
|
+
cy.get('[data-cy=body]').should('not.be.visible')
|
|
234
|
+
|
|
235
|
+
// Reachable by keyboard and named; being a native <button> it activates on
|
|
236
|
+
// Enter/Space. Clicking exercises the same handler that activation fires.
|
|
237
|
+
cy.get('[aria-label="Expand"]')
|
|
238
|
+
.should('be.visible')
|
|
239
|
+
.focus()
|
|
240
|
+
.should('be.focused')
|
|
241
|
+
.click()
|
|
242
|
+
|
|
243
|
+
cy.get('[data-cy=mode]').should('have.text', 'floating')
|
|
244
|
+
cy.get('[data-cy=body]').should('be.visible')
|
|
245
|
+
})
|
|
246
|
+
|
|
247
|
+
it('resizes with the arrow keys on the grip, clamped to the minimum', () => {
|
|
248
|
+
cy.mount(controlledWindow())
|
|
249
|
+
cy.get('[aria-label="Pop out"]').click()
|
|
250
|
+
cy.get('.floating-window').invoke('outerWidth').should('eq', 460)
|
|
251
|
+
|
|
252
|
+
// One arrow step is 16px.
|
|
253
|
+
cy.get('[aria-label="Resize window"]')
|
|
254
|
+
.focus()
|
|
255
|
+
.trigger('keydown', { key: 'ArrowRight' })
|
|
256
|
+
cy.get('.floating-window').invoke('outerWidth').should('eq', 476)
|
|
257
|
+
|
|
258
|
+
// Shrinking past the 380px floor stops at the floor, never below.
|
|
259
|
+
cy.get('[aria-label="Resize window"]').then(($grip) => {
|
|
260
|
+
for (let i = 0; i < 20; i++) {
|
|
261
|
+
cy.wrap($grip).trigger('keydown', { key: 'ArrowLeft' })
|
|
262
|
+
}
|
|
263
|
+
})
|
|
264
|
+
cy.get('.floating-window').invoke('outerWidth').should('eq', 380)
|
|
265
|
+
})
|
|
266
|
+
})
|
|
267
|
+
|
|
268
|
+
describe('pointercancel cleanup', () => {
|
|
269
|
+
it('stops resize and removes move listener when resize is cancelled by the browser', () => {
|
|
270
|
+
cy.mount(controlledWindow())
|
|
271
|
+
cy.get('[aria-label="Pop out"]').click()
|
|
272
|
+
cy.get('.floating-window').invoke('outerWidth').should('eq', 460)
|
|
273
|
+
|
|
274
|
+
// Start resize, then cancel (OS gesture / context-menu interrupt).
|
|
275
|
+
cy.get('[data-resize=se]').trigger('pointerdown', {
|
|
276
|
+
eventConstructor: 'PointerEvent',
|
|
277
|
+
button: 0,
|
|
278
|
+
clientX: 800,
|
|
279
|
+
clientY: 700,
|
|
280
|
+
})
|
|
281
|
+
cy.get('body').trigger('pointercancel', { eventConstructor: 'PointerEvent' })
|
|
282
|
+
// Move after cancel must be a no-op (listener torn down).
|
|
283
|
+
cy.get('body').trigger('pointermove', {
|
|
284
|
+
eventConstructor: 'PointerEvent',
|
|
285
|
+
clientX: 900,
|
|
286
|
+
clientY: 800,
|
|
287
|
+
})
|
|
288
|
+
cy.get('.floating-window').invoke('outerWidth').should('eq', 460)
|
|
289
|
+
})
|
|
290
|
+
})
|
|
291
|
+
|
|
292
|
+
describe('edge and corner resize', () => {
|
|
293
|
+
// Floating default is 460×520, parked bottom-right in the 1200×800 viewport:
|
|
294
|
+
// x = 1200 - 460 - 24 = 716, y = 800 - 520 - 24 = 256.
|
|
295
|
+
const START_X = 716
|
|
296
|
+
const START_Y = 256
|
|
297
|
+
|
|
298
|
+
it('grows from the bottom-right corner, leaving the origin put', () => {
|
|
299
|
+
cy.mount(controlledWindow())
|
|
300
|
+
cy.get('[aria-label="Pop out"]').click()
|
|
301
|
+
|
|
302
|
+
dragResize('se', { x: START_X + 460, y: START_Y + 520 }, { x: 40, y: 30 })
|
|
303
|
+
|
|
304
|
+
cy.get('.floating-window').should(($el) => {
|
|
305
|
+
const r = $el[0].getBoundingClientRect()
|
|
306
|
+
expect(r.width).to.be.closeTo(500, 1)
|
|
307
|
+
expect(r.height).to.be.closeTo(550, 1)
|
|
308
|
+
expect(r.left).to.be.closeTo(START_X, 1)
|
|
309
|
+
expect(r.top).to.be.closeTo(START_Y, 1)
|
|
310
|
+
})
|
|
311
|
+
})
|
|
312
|
+
|
|
313
|
+
it('grows from the top-left corner, shifting the origin to anchor the opposite edge', () => {
|
|
314
|
+
cy.mount(controlledWindow())
|
|
315
|
+
cy.get('[aria-label="Pop out"]').click()
|
|
316
|
+
|
|
317
|
+
dragResize('nw', { x: START_X, y: START_Y }, { x: -40, y: -30 })
|
|
318
|
+
|
|
319
|
+
cy.get('.floating-window').should(($el) => {
|
|
320
|
+
const r = $el[0].getBoundingClientRect()
|
|
321
|
+
expect(r.width).to.be.closeTo(500, 1)
|
|
322
|
+
expect(r.height).to.be.closeTo(550, 1)
|
|
323
|
+
expect(r.left).to.be.closeTo(START_X - 40, 1)
|
|
324
|
+
expect(r.top).to.be.closeTo(START_Y - 30, 1)
|
|
325
|
+
})
|
|
326
|
+
})
|
|
327
|
+
})
|
|
328
|
+
})
|
|
@@ -0,0 +1,303 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<!--
|
|
3
|
+
Teleport to <body> in every detached mode so the panel escapes any host
|
|
4
|
+
layout's overflow/stacking contexts. `disabled` keeps it in-flow while
|
|
5
|
+
docked. Teleport moves the node without remounting, so content inside (an
|
|
6
|
+
editor, a form) never loses focus or state.
|
|
7
|
+
-->
|
|
8
|
+
<Teleport to="body" :disabled="isDocked">
|
|
9
|
+
<div
|
|
10
|
+
ref="panel"
|
|
11
|
+
v-bind="$attrs"
|
|
12
|
+
:data-state="mode"
|
|
13
|
+
class="floating-window relative flex flex-col rounded-lg border border-outline-gray-2 bg-surface-elevation-1"
|
|
14
|
+
:class="isDocked ? '' : 'shadow-2xl'"
|
|
15
|
+
:style="style"
|
|
16
|
+
>
|
|
17
|
+
<!-- Title bar, doubles as the drag handle while floating. A host-supplied
|
|
18
|
+
`header` slot replaces it entirely (and becomes the drag handle);
|
|
19
|
+
otherwise the built-in chrome renders. -->
|
|
20
|
+
<div
|
|
21
|
+
ref="handle"
|
|
22
|
+
class="flex select-none items-center justify-between gap-2"
|
|
23
|
+
:class="[
|
|
24
|
+
isFloating ? 'cursor-move' : '',
|
|
25
|
+
$slots.header ? '' : 'gap-2 px-2.5 py-1.5',
|
|
26
|
+
]"
|
|
27
|
+
@pointerdown="startDrag"
|
|
28
|
+
>
|
|
29
|
+
<!-- Host-owned header: takes the whole row, supplies its own controls. -->
|
|
30
|
+
<div v-if="$slots.header" class="min-w-0 flex-1">
|
|
31
|
+
<slot
|
|
32
|
+
name="header"
|
|
33
|
+
v-bind="{ mode, dock, float, minimize, expandFromTray }"
|
|
34
|
+
/>
|
|
35
|
+
</div>
|
|
36
|
+
|
|
37
|
+
<!-- Built-in chrome -->
|
|
38
|
+
<template v-else>
|
|
39
|
+
<div class="flex min-w-0 items-center gap-2">
|
|
40
|
+
<span class="truncate text-sm font-medium text-ink-gray-8">
|
|
41
|
+
{{ title }}
|
|
42
|
+
</span>
|
|
43
|
+
</div>
|
|
44
|
+
|
|
45
|
+
<div class="flex shrink-0 items-center gap-1">
|
|
46
|
+
<slot name="actions" v-bind="{ mode, dock, float, minimize }" />
|
|
47
|
+
<!-- Minimize control, hidden when the window is not minimizable. In
|
|
48
|
+
the tray it's already minimized, so offer to expand it back out;
|
|
49
|
+
everywhere else offer to minimize. -->
|
|
50
|
+
<template v-if="minimizable">
|
|
51
|
+
<Button
|
|
52
|
+
v-if="isMinimized"
|
|
53
|
+
variant="ghost"
|
|
54
|
+
label="Expand"
|
|
55
|
+
tooltip="Expand"
|
|
56
|
+
@click="expandFromTray"
|
|
57
|
+
>
|
|
58
|
+
<template #icon><LucideMaximize2 class="h-4 w-4" /></template>
|
|
59
|
+
</Button>
|
|
60
|
+
<Button
|
|
61
|
+
v-else
|
|
62
|
+
variant="ghost"
|
|
63
|
+
label="Minimize"
|
|
64
|
+
tooltip="Minimize"
|
|
65
|
+
@click="minimize"
|
|
66
|
+
>
|
|
67
|
+
<template #icon><LucideMinus class="h-4 w-4" /></template>
|
|
68
|
+
</Button>
|
|
69
|
+
</template>
|
|
70
|
+
<!-- Expand to a floating window when docked; close (dock back) once
|
|
71
|
+
detached. The close (X) action always sits last. -->
|
|
72
|
+
<Button
|
|
73
|
+
variant="ghost"
|
|
74
|
+
:label="isDocked ? 'Pop out' : 'Close'"
|
|
75
|
+
:tooltip="isDocked ? 'Pop out' : 'Close'"
|
|
76
|
+
@click="isDocked ? float() : dock()"
|
|
77
|
+
>
|
|
78
|
+
<template #icon>
|
|
79
|
+
<component
|
|
80
|
+
:is="isDocked ? LucideMaximize2 : LucideX"
|
|
81
|
+
class="h-4 w-4"
|
|
82
|
+
/>
|
|
83
|
+
</template>
|
|
84
|
+
</Button>
|
|
85
|
+
</div>
|
|
86
|
+
</template>
|
|
87
|
+
</div>
|
|
88
|
+
|
|
89
|
+
<!-- Body, collapsed in the tray (minimized) -->
|
|
90
|
+
<div v-show="!isMinimized" class="min-h-0 flex-1 overflow-auto">
|
|
91
|
+
<slot :mode="mode" />
|
|
92
|
+
</div>
|
|
93
|
+
|
|
94
|
+
<!-- Pinned footer (composer / action bar), collapsed in the tray. -->
|
|
95
|
+
<div v-if="$slots.footer" v-show="!isMinimized" class="shrink-0">
|
|
96
|
+
<slot name="footer" :mode="mode" />
|
|
97
|
+
</div>
|
|
98
|
+
|
|
99
|
+
<!-- Resize zones straddling each edge and corner (floating only), so the
|
|
100
|
+
window can be dragged from any side. They're hidden from assistive
|
|
101
|
+
tech; the bottom-right corner is rendered as a focusable button that
|
|
102
|
+
also resizes with the arrow keys, the keyboard counterpart. -->
|
|
103
|
+
<template v-if="isFloating">
|
|
104
|
+
<div
|
|
105
|
+
v-for="handle in resizeHandles"
|
|
106
|
+
:key="handle.name"
|
|
107
|
+
:data-resize="handle.name"
|
|
108
|
+
aria-hidden="true"
|
|
109
|
+
class="absolute"
|
|
110
|
+
:class="handle.class"
|
|
111
|
+
@pointerdown.prevent="startResize($event, handle.dir)"
|
|
112
|
+
/>
|
|
113
|
+
<button
|
|
114
|
+
type="button"
|
|
115
|
+
data-resize="se"
|
|
116
|
+
aria-label="Resize window"
|
|
117
|
+
class="absolute -bottom-1 -right-1 z-10 size-4 cursor-nwse-resize rounded-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-outline-gray-3"
|
|
118
|
+
@pointerdown.prevent="startResize($event, { x: 1, y: 1 })"
|
|
119
|
+
@keydown="onResizeKey"
|
|
120
|
+
/>
|
|
121
|
+
</template>
|
|
122
|
+
</div>
|
|
123
|
+
</Teleport>
|
|
124
|
+
</template>
|
|
125
|
+
|
|
126
|
+
<script setup lang="ts">
|
|
127
|
+
import { computed, ref, watch } from 'vue'
|
|
128
|
+
import LucideX from '~icons/lucide/x'
|
|
129
|
+
import LucideMinus from '~icons/lucide/minus'
|
|
130
|
+
import LucideMaximize2 from '~icons/lucide/maximize-2'
|
|
131
|
+
import { Button } from '#components/Button'
|
|
132
|
+
import { useFloatingWindow } from './useFloatingWindow'
|
|
133
|
+
import type { ResizeDir, WindowMode } from './types'
|
|
134
|
+
|
|
135
|
+
// The template root is <Teleport>, which can't receive fallthrough attributes,
|
|
136
|
+
// so forward them (class, style, etc.) onto the panel element explicitly.
|
|
137
|
+
defineOptions({ inheritAttrs: false })
|
|
138
|
+
|
|
139
|
+
/** Window state. Use `v-model:mode` to drive it from the host. */
|
|
140
|
+
const modeModel = defineModel<WindowMode>('mode', { default: 'docked' })
|
|
141
|
+
|
|
142
|
+
const props = withDefaults(
|
|
143
|
+
defineProps<{
|
|
144
|
+
/** Title shown in the built-in title bar. */
|
|
145
|
+
title?: string
|
|
146
|
+
/**
|
|
147
|
+
* localStorage key to persist `{ mode, rect }` under. When `null`,
|
|
148
|
+
* geometry resets every session.
|
|
149
|
+
*/
|
|
150
|
+
storageKey?: string | null
|
|
151
|
+
/**
|
|
152
|
+
* Whether the window can collapse to the bottom-right tray. When `false`,
|
|
153
|
+
* the minimize control is hidden, leaving only dock and pop-out, for
|
|
154
|
+
* composer-style windows that should never minimize.
|
|
155
|
+
*/
|
|
156
|
+
minimizable?: boolean
|
|
157
|
+
}>(),
|
|
158
|
+
{
|
|
159
|
+
title: '',
|
|
160
|
+
storageKey: null,
|
|
161
|
+
minimizable: true,
|
|
162
|
+
},
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
defineSlots<{
|
|
166
|
+
/** The scrollable window body. Receives the current `mode`. */
|
|
167
|
+
default?: (props: { mode: WindowMode }) => any
|
|
168
|
+
/**
|
|
169
|
+
* Pinned region below the scrollable body, for a composer or action bar.
|
|
170
|
+
* Hidden, along with the body, while minimized.
|
|
171
|
+
*/
|
|
172
|
+
footer?: (props: { mode: WindowMode }) => any
|
|
173
|
+
/**
|
|
174
|
+
* Replaces the built-in title bar entirely. Becomes the drag handle while
|
|
175
|
+
* floating and must supply its own controls from the slot props.
|
|
176
|
+
*/
|
|
177
|
+
header?: (props: {
|
|
178
|
+
mode: WindowMode
|
|
179
|
+
dock: () => void
|
|
180
|
+
float: () => void
|
|
181
|
+
minimize: () => void
|
|
182
|
+
expandFromTray: () => void
|
|
183
|
+
}) => any
|
|
184
|
+
/** Extra controls inserted before the built-in window buttons. */
|
|
185
|
+
actions?: (props: {
|
|
186
|
+
mode: WindowMode
|
|
187
|
+
dock: () => void
|
|
188
|
+
float: () => void
|
|
189
|
+
minimize: () => void
|
|
190
|
+
}) => any
|
|
191
|
+
}>()
|
|
192
|
+
|
|
193
|
+
const panel = ref<HTMLElement | null>(null)
|
|
194
|
+
const handle = ref<HTMLElement | null>(null)
|
|
195
|
+
|
|
196
|
+
const {
|
|
197
|
+
mode,
|
|
198
|
+
style,
|
|
199
|
+
startDrag,
|
|
200
|
+
startResize,
|
|
201
|
+
resizeBy,
|
|
202
|
+
dock,
|
|
203
|
+
float,
|
|
204
|
+
minimize,
|
|
205
|
+
expandFromTray,
|
|
206
|
+
setMode,
|
|
207
|
+
} = useFloatingWindow(panel, handle, {
|
|
208
|
+
initialMode: modeModel.value,
|
|
209
|
+
storageKey: props.storageKey,
|
|
210
|
+
})
|
|
211
|
+
|
|
212
|
+
const isDocked = computed(() => mode.value === 'docked')
|
|
213
|
+
const isFloating = computed(() => mode.value === 'floating')
|
|
214
|
+
const isMinimized = computed(() => mode.value === 'minimized')
|
|
215
|
+
|
|
216
|
+
interface ResizeHandle {
|
|
217
|
+
name: string
|
|
218
|
+
dir: ResizeDir
|
|
219
|
+
class: string
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// Invisible zones straddling each edge and three corners. They extend slightly
|
|
223
|
+
// past the panel so they sit over the shadow, not the body's scrollbar. Corners
|
|
224
|
+
// come after edges so they layer on top and win the cursor where they meet. The
|
|
225
|
+
// bottom-right corner is rendered separately, as the keyboard-capable button.
|
|
226
|
+
const resizeHandles: ResizeHandle[] = [
|
|
227
|
+
{
|
|
228
|
+
name: 'n',
|
|
229
|
+
dir: { x: 0, y: -1 },
|
|
230
|
+
class: '-top-1 inset-x-0 h-2 cursor-ns-resize',
|
|
231
|
+
},
|
|
232
|
+
{
|
|
233
|
+
name: 's',
|
|
234
|
+
dir: { x: 0, y: 1 },
|
|
235
|
+
class: '-bottom-1 inset-x-0 h-2 cursor-ns-resize',
|
|
236
|
+
},
|
|
237
|
+
{
|
|
238
|
+
name: 'w',
|
|
239
|
+
dir: { x: -1, y: 0 },
|
|
240
|
+
class: '-left-1 inset-y-0 w-2 cursor-ew-resize',
|
|
241
|
+
},
|
|
242
|
+
{
|
|
243
|
+
name: 'e',
|
|
244
|
+
dir: { x: 1, y: 0 },
|
|
245
|
+
class: '-right-1 inset-y-0 w-2 cursor-ew-resize',
|
|
246
|
+
},
|
|
247
|
+
{
|
|
248
|
+
name: 'nw',
|
|
249
|
+
dir: { x: -1, y: -1 },
|
|
250
|
+
class: '-top-1 -left-1 size-4 z-10 cursor-nwse-resize',
|
|
251
|
+
},
|
|
252
|
+
{
|
|
253
|
+
name: 'ne',
|
|
254
|
+
dir: { x: 1, y: -1 },
|
|
255
|
+
class: '-top-1 -right-1 size-4 z-10 cursor-nesw-resize',
|
|
256
|
+
},
|
|
257
|
+
{
|
|
258
|
+
name: 'sw',
|
|
259
|
+
dir: { x: -1, y: 1 },
|
|
260
|
+
class: '-bottom-1 -left-1 size-4 z-10 cursor-nesw-resize',
|
|
261
|
+
},
|
|
262
|
+
]
|
|
263
|
+
|
|
264
|
+
// Keyboard resize: each arrow nudges the panel by a fixed step, the keyboard
|
|
265
|
+
// counterpart to dragging the bottom-right corner.
|
|
266
|
+
const RESIZE_STEP = 16
|
|
267
|
+
const RESIZE_DELTAS: Record<string, [number, number]> = {
|
|
268
|
+
ArrowRight: [RESIZE_STEP, 0],
|
|
269
|
+
ArrowLeft: [-RESIZE_STEP, 0],
|
|
270
|
+
ArrowDown: [0, RESIZE_STEP],
|
|
271
|
+
ArrowUp: [0, -RESIZE_STEP],
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
function onResizeKey(event: KeyboardEvent) {
|
|
275
|
+
const delta = RESIZE_DELTAS[event.key]
|
|
276
|
+
if (!delta) return
|
|
277
|
+
event.preventDefault()
|
|
278
|
+
resizeBy(...delta)
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
// The composable owns `mode` (it restores from storage), so mirror it into the
|
|
282
|
+
// v-model in both directions without looping. Immediate so a host's
|
|
283
|
+
// `v-model:mode` adopts a storage-restored mode on mount, not just on the next
|
|
284
|
+
// change.
|
|
285
|
+
watch(mode, (value) => (modeModel.value = value), { immediate: true })
|
|
286
|
+
watch(modeModel, (value) => value !== mode.value && setMode(value))
|
|
287
|
+
</script>
|
|
288
|
+
|
|
289
|
+
<!--
|
|
290
|
+
Global, intentionally unscoped. While a window is detached it's fixed at
|
|
291
|
+
z-index 50; reka-ui teleports popovers (dropdowns, autocompletes, menus) to
|
|
292
|
+
<body> and copies their content z-index (which is `auto` for most
|
|
293
|
+
components) onto the positioning wrapper, so they render *behind* the window
|
|
294
|
+
and become unclickable. Lift the popover layer to the app's popover z (100,
|
|
295
|
+
matching NestedPopover). `!important` is required to beat reka's inline
|
|
296
|
+
`z-index: auto`. Scoped to `.has-floating-window` so it only applies while a
|
|
297
|
+
window is open; docked windows are in normal flow and need no override.
|
|
298
|
+
-->
|
|
299
|
+
<style>
|
|
300
|
+
body.has-floating-window [data-reka-popper-content-wrapper] {
|
|
301
|
+
z-index: 100 !important;
|
|
302
|
+
}
|
|
303
|
+
</style>
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/** The three window states a FloatingWindow can be in. */
|
|
2
|
+
export type WindowMode = 'docked' | 'floating' | 'minimized'
|
|
3
|
+
|
|
4
|
+
/** Position + size of a floating panel, in viewport pixels. */
|
|
5
|
+
export interface Rect {
|
|
6
|
+
x: number
|
|
7
|
+
y: number
|
|
8
|
+
width: number
|
|
9
|
+
height: number
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Which edges a resize drag pulls. `-1` pulls the top/left edge (and moves the
|
|
14
|
+
* panel so the opposite edge stays put), `1` the bottom/right edge, `0` leaves
|
|
15
|
+
* that axis fixed. A corner sets both axes.
|
|
16
|
+
*/
|
|
17
|
+
export interface ResizeDir {
|
|
18
|
+
x: -1 | 0 | 1
|
|
19
|
+
y: -1 | 0 | 1
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** Options for the headless useFloatingWindow composable. */
|
|
23
|
+
export interface FloatingWindowOptions {
|
|
24
|
+
/** Initial window state. */
|
|
25
|
+
initialMode?: WindowMode
|
|
26
|
+
/**
|
|
27
|
+
* localStorage key to persist `{ mode, rect }` under. When null, geometry
|
|
28
|
+
* resets every session.
|
|
29
|
+
*/
|
|
30
|
+
storageKey?: string | null
|
|
31
|
+
}
|