@eocrm/design-system 0.0.0-stage → 0.6.18
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/AI-PRIMER.md +341 -0
- package/LICENSE +202 -0
- package/README.md +266 -2
- package/docs/components/Accordion.md +114 -0
- package/docs/components/AgentAvatar.md +58 -0
- package/docs/components/Alert.md +81 -0
- package/docs/components/AppLayout.md +70 -0
- package/docs/components/Avatar.md +49 -0
- package/docs/components/AvatarGroup.md +47 -0
- package/docs/components/Badge.md +60 -0
- package/docs/components/Banner.md +89 -0
- package/docs/components/BrandIcon.md +32 -0
- package/docs/components/Breadcrumb.md +72 -0
- package/docs/components/Button.md +107 -0
- package/docs/components/ButtonGroup.md +78 -0
- package/docs/components/Calendar.md +158 -0
- package/docs/components/Card.md +165 -0
- package/docs/components/CatalogPicker.md +83 -0
- package/docs/components/Chart.md +114 -0
- package/docs/components/ChatBubble.md +55 -0
- package/docs/components/ChatLauncher.md +97 -0
- package/docs/components/Checkbox.md +55 -0
- package/docs/components/CircularProgress.md +53 -0
- package/docs/components/Cluster.md +96 -0
- package/docs/components/Code.md +35 -0
- package/docs/components/CodeBlock.md +67 -0
- package/docs/components/ColorPicker.md +115 -0
- package/docs/components/ConfirmationPopover.md +70 -0
- package/docs/components/Constrain.md +39 -0
- package/docs/components/CurrencySelect.md +57 -0
- package/docs/components/CursorPagination.md +43 -0
- package/docs/components/DashboardCanvas.md +79 -0
- package/docs/components/DashboardWidget.md +131 -0
- package/docs/components/DataTable.md +88 -0
- package/docs/components/DatePicker.md +68 -0
- package/docs/components/DateRangePicker.md +71 -0
- package/docs/components/DateStrip.md +56 -0
- package/docs/components/DefinitionList.md +82 -0
- package/docs/components/Divider.md +57 -0
- package/docs/components/Dot.md +26 -0
- package/docs/components/Drawer.md +94 -0
- package/docs/components/DropdownMenu.md +299 -0
- package/docs/components/EmojiPicker.md +84 -0
- package/docs/components/EmptyState.md +55 -0
- package/docs/components/EntityChip.md +102 -0
- package/docs/components/ErrorState.md +72 -0
- package/docs/components/Field.md +91 -0
- package/docs/components/FileUpload.md +109 -0
- package/docs/components/FilterChip.md +88 -0
- package/docs/components/FlowCanvas.md +108 -0
- package/docs/components/FormRow.md +31 -0
- package/docs/components/FormSection.md +37 -0
- package/docs/components/Grid.md +103 -0
- package/docs/components/Highlight.md +75 -0
- package/docs/components/HighlightedText.md +50 -0
- package/docs/components/IconPicker.md +134 -0
- package/docs/components/IconTile.md +30 -0
- package/docs/components/Image.md +84 -0
- package/docs/components/ImageCrop.md +163 -0
- package/docs/components/Indent.md +31 -0
- package/docs/components/InlineDatePicker.md +58 -0
- package/docs/components/InlineDateRangePicker.md +57 -0
- package/docs/components/Input.md +54 -0
- package/docs/components/Kanban.md +122 -0
- package/docs/components/Kbd.md +41 -0
- package/docs/components/Lightbox.md +105 -0
- package/docs/components/Link.md +65 -0
- package/docs/components/LinkCard.md +41 -0
- package/docs/components/LiquidEditor.md +84 -0
- package/docs/components/List.md +68 -0
- package/docs/components/LiveRegion.md +29 -0
- package/docs/components/LocaleProvider.md +25 -0
- package/docs/components/Logo.md +41 -0
- package/docs/components/Masonry.md +48 -0
- package/docs/components/MediaTile.md +95 -0
- package/docs/components/Modal.md +107 -0
- package/docs/components/MoneyInput.md +62 -0
- package/docs/components/OptionsPicker.md +123 -0
- package/docs/components/OtpInput.md +65 -0
- package/docs/components/Page.md +35 -0
- package/docs/components/PageHeader.md +133 -0
- package/docs/components/Pagination.md +44 -0
- package/docs/components/Palette.md +32 -0
- package/docs/components/PasswordInput.md +45 -0
- package/docs/components/PasswordStrengthMeter.md +27 -0
- package/docs/components/PersonDisplay.md +153 -0
- package/docs/components/PhoneInput.md +54 -0
- package/docs/components/PillMenu.md +91 -0
- package/docs/components/Popover.md +127 -0
- package/docs/components/Progress.md +56 -0
- package/docs/components/QrCode.md +34 -0
- package/docs/components/Radio.md +37 -0
- package/docs/components/RadioGroup.md +56 -0
- package/docs/components/Rail.md +191 -0
- package/docs/components/RelativeDateInput.md +114 -0
- package/docs/components/RichText.md +44 -0
- package/docs/components/RichTextEditor.md +112 -0
- package/docs/components/Screen.md +37 -0
- package/docs/components/ScrollArea.md +39 -0
- package/docs/components/Select.md +131 -0
- package/docs/components/SettingRow.md +98 -0
- package/docs/components/Skeleton.md +68 -0
- package/docs/components/Slider.md +92 -0
- package/docs/components/SlotGrid.md +55 -0
- package/docs/components/SocialButton.md +31 -0
- package/docs/components/Sortable.md +127 -0
- package/docs/components/SortableGroup.md +52 -0
- package/docs/components/Sparkline.md +51 -0
- package/docs/components/Split.md +76 -0
- package/docs/components/Stack.md +59 -0
- package/docs/components/StagePath.md +66 -0
- package/docs/components/StatGroup.md +61 -0
- package/docs/components/StatTile.md +58 -0
- package/docs/components/Sticky.md +69 -0
- package/docs/components/Switch.md +89 -0
- package/docs/components/Table.md +184 -0
- package/docs/components/TableOfContents.md +76 -0
- package/docs/components/Tabs.md +94 -0
- package/docs/components/Text.md +63 -0
- package/docs/components/Textarea.md +59 -0
- package/docs/components/Thread.md +52 -0
- package/docs/components/TimeField.md +70 -0
- package/docs/components/Timeline.md +57 -0
- package/docs/components/Title.md +54 -0
- package/docs/components/ToastViewport.md +61 -0
- package/docs/components/Tooltip.md +63 -0
- package/docs/components/TopBar.md +214 -0
- package/docs/components/Tour.md +85 -0
- package/docs/components/TreeView.md +113 -0
- package/docs/components/VisuallyHidden.md +43 -0
- package/docs/components/WidgetPreview.md +54 -0
- package/docs/components/useBelowBreakpoint.md +11 -0
- package/docs/components/useMonth.md +18 -0
- package/docs/setup.md +51 -0
- package/docs/theming.md +95 -0
- package/docs/tokens.md +123 -0
- package/docs/transient-state.md +56 -0
- package/package.json +86 -4
- package/src/_meta/manifest.ts +293 -0
- package/src/app/AppProvider.tsx +78 -0
- package/src/app/index.ts +3 -0
- package/src/app/themeTokens.ts +95 -0
- package/src/calendar/dateMath.ts +114 -0
- package/src/calendar/formatters.ts +75 -0
- package/src/calendar/index.ts +39 -0
- package/src/calendar/types.ts +36 -0
- package/src/calendar/useAgenda.ts +70 -0
- package/src/calendar/useDay.ts +51 -0
- package/src/calendar/useMonth.ts +82 -0
- package/src/calendar/useWeek.ts +62 -0
- package/src/calendar/weekInfo.ts +83 -0
- package/src/components/Accordion/Accordion.module.scss +304 -0
- package/src/components/Accordion/Accordion.tokens.scss +64 -0
- package/src/components/Accordion/Accordion.tsx +272 -0
- package/src/components/Accordion/AccordionContent.tsx +36 -0
- package/src/components/Accordion/AccordionItem.tsx +66 -0
- package/src/components/Accordion/AccordionTrigger.tsx +121 -0
- package/src/components/Accordion/context.ts +47 -0
- package/src/components/Accordion/index.ts +14 -0
- package/src/components/Alert/Alert.module.scss +92 -0
- package/src/components/Alert/Alert.tokens.scss +60 -0
- package/src/components/Alert/Alert.tsx +136 -0
- package/src/components/Alert/index.ts +2 -0
- package/src/components/AppLayout/AppLayout.module.scss +130 -0
- package/src/components/AppLayout/AppLayout.tokens.scss +27 -0
- package/src/components/AppLayout/AppLayout.tsx +211 -0
- package/src/components/AppLayout/index.ts +2 -0
- package/src/components/Avatar/AgentAvatar.module.scss +117 -0
- package/src/components/Avatar/AgentAvatar.tsx +122 -0
- package/src/components/Avatar/Avatar.module.scss +181 -0
- package/src/components/Avatar/Avatar.tokens.scss +107 -0
- package/src/components/Avatar/Avatar.tsx +210 -0
- package/src/components/Avatar/AvatarGroup.module.scss +128 -0
- package/src/components/Avatar/AvatarGroup.tsx +119 -0
- package/src/components/Avatar/AvatarGroupContext.tsx +20 -0
- package/src/components/Avatar/index.ts +6 -0
- package/src/components/Badge/Badge.module.scss +124 -0
- package/src/components/Badge/Badge.tokens.scss +74 -0
- package/src/components/Badge/Badge.tsx +164 -0
- package/src/components/Badge/index.ts +2 -0
- package/src/components/Banner/Banner.module.scss +73 -0
- package/src/components/Banner/Banner.tokens.scss +51 -0
- package/src/components/Banner/Banner.tsx +138 -0
- package/src/components/Banner/index.ts +2 -0
- package/src/components/BrandIcon/BrandIcon.module.scss +5 -0
- package/src/components/BrandIcon/BrandIcon.tsx +99 -0
- package/src/components/BrandIcon/index.ts +2 -0
- package/src/components/Breadcrumb/Breadcrumb.module.scss +32 -0
- package/src/components/Breadcrumb/Breadcrumb.tokens.scss +9 -0
- package/src/components/Breadcrumb/Breadcrumb.tsx +146 -0
- package/src/components/Breadcrumb/index.ts +2 -0
- package/src/components/Button/Button.module.scss +167 -0
- package/src/components/Button/Button.tokens.scss +90 -0
- package/src/components/Button/Button.tsx +205 -0
- package/src/components/Button/index.ts +2 -0
- package/src/components/ButtonGroup/ButtonGroup.module.scss +134 -0
- package/src/components/ButtonGroup/ButtonGroup.tokens.scss +42 -0
- package/src/components/ButtonGroup/ButtonGroup.tsx +278 -0
- package/src/components/ButtonGroup/ButtonGroupItem.tsx +74 -0
- package/src/components/ButtonGroup/context.ts +45 -0
- package/src/components/ButtonGroup/index.ts +2 -0
- package/src/components/Calendar/AgendaView.module.scss +231 -0
- package/src/components/Calendar/AgendaView.tsx +214 -0
- package/src/components/Calendar/AllDayBand.module.scss +18 -0
- package/src/components/Calendar/AllDayBand.tsx +75 -0
- package/src/components/Calendar/Calendar.module.scss +23 -0
- package/src/components/Calendar/Calendar.tokens.scss +262 -0
- package/src/components/Calendar/Calendar.tsx +370 -0
- package/src/components/Calendar/DayCell.module.scss +30 -0
- package/src/components/Calendar/DayCell.tsx +50 -0
- package/src/components/Calendar/DayView.module.scss +10 -0
- package/src/components/Calendar/DayView.tsx +163 -0
- package/src/components/Calendar/EventChip.module.scss +171 -0
- package/src/components/Calendar/EventChip.tsx +115 -0
- package/src/components/Calendar/HourGrid.module.scss +123 -0
- package/src/components/Calendar/HourGrid.tsx +381 -0
- package/src/components/Calendar/MonthView.module.scss +99 -0
- package/src/components/Calendar/MonthView.tsx +296 -0
- package/src/components/Calendar/TimedEvent.module.scss +205 -0
- package/src/components/Calendar/TimedEvent.tsx +247 -0
- package/src/components/Calendar/ViewSwitcher.module.scss +3 -0
- package/src/components/Calendar/ViewSwitcher.tsx +47 -0
- package/src/components/Calendar/WeekView.module.scss +10 -0
- package/src/components/Calendar/WeekView.tsx +137 -0
- package/src/components/Calendar/eventColor.ts +60 -0
- package/src/components/Calendar/index.ts +18 -0
- package/src/components/Calendar/types.ts +451 -0
- package/src/components/Calendar/useEventDrag.ts +783 -0
- package/src/components/Calendar/utils.ts +622 -0
- package/src/components/Card/Card.module.scss +264 -0
- package/src/components/Card/Card.tokens.scss +55 -0
- package/src/components/Card/Card.tsx +170 -0
- package/src/components/Card/CardBody.tsx +45 -0
- package/src/components/Card/CardHeader.tsx +113 -0
- package/src/components/Card/CardList.tsx +24 -0
- package/src/components/Card/CardListRow.tsx +24 -0
- package/src/components/Card/index.ts +7 -0
- package/src/components/CatalogPicker/CatalogPicker.module.scss +193 -0
- package/src/components/CatalogPicker/CatalogPicker.tokens.scss +26 -0
- package/src/components/CatalogPicker/CatalogPicker.tsx +339 -0
- package/src/components/CatalogPicker/index.ts +2 -0
- package/src/components/Chart/Chart.module.scss +287 -0
- package/src/components/Chart/Chart.tokens.scss +63 -0
- package/src/components/Chart/Chart.tsx +607 -0
- package/src/components/Chart/ChartLegend.tsx +99 -0
- package/src/components/Chart/ChartTable.tsx +62 -0
- package/src/components/Chart/ChartTooltip.tsx +91 -0
- package/src/components/Chart/chartModel.ts +689 -0
- package/src/components/Chart/index.ts +2 -0
- package/src/components/ChatBubble/ChatBubble.module.scss +93 -0
- package/src/components/ChatBubble/ChatBubble.tokens.scss +17 -0
- package/src/components/ChatBubble/ChatBubble.tsx +61 -0
- package/src/components/ChatBubble/ChatBubbleList.tsx +27 -0
- package/src/components/ChatBubble/index.ts +3 -0
- package/src/components/ChatLauncher/ChatLauncher.module.scss +68 -0
- package/src/components/ChatLauncher/ChatLauncher.tokens.scss +27 -0
- package/src/components/ChatLauncher/ChatLauncher.tsx +202 -0
- package/src/components/ChatLauncher/index.ts +2 -0
- package/src/components/ChatLauncher/isAtBottom.ts +12 -0
- package/src/components/Checkbox/Checkbox.module.scss +147 -0
- package/src/components/Checkbox/Checkbox.tokens.scss +44 -0
- package/src/components/Checkbox/Checkbox.tsx +188 -0
- package/src/components/Checkbox/index.ts +2 -0
- package/src/components/CircularProgress/CircularProgress.module.scss +133 -0
- package/src/components/CircularProgress/CircularProgress.tokens.scss +40 -0
- package/src/components/CircularProgress/CircularProgress.tsx +139 -0
- package/src/components/CircularProgress/index.ts +7 -0
- package/src/components/Cluster/Cluster.module.scss +145 -0
- package/src/components/Cluster/Cluster.tokens.scss +11 -0
- package/src/components/Cluster/Cluster.tsx +215 -0
- package/src/components/Cluster/index.ts +2 -0
- package/src/components/Code/Code.module.scss +28 -0
- package/src/components/Code/Code.tokens.scss +15 -0
- package/src/components/Code/Code.tsx +44 -0
- package/src/components/Code/index.ts +2 -0
- package/src/components/CodeBlock/CodeBlock.module.scss +146 -0
- package/src/components/CodeBlock/CodeBlock.tokens.scss +27 -0
- package/src/components/CodeBlock/CodeBlock.tsx +274 -0
- package/src/components/CodeBlock/highlight.ts +179 -0
- package/src/components/CodeBlock/index.ts +8 -0
- package/src/components/CodeBlock/sampleSync.ts +42 -0
- package/src/components/ColorPicker/ColorPicker.module.scss +286 -0
- package/src/components/ColorPicker/ColorPicker.tokens.scss +60 -0
- package/src/components/ColorPicker/ColorPicker.tsx +367 -0
- package/src/components/ColorPicker/ColorPickerPanel.tsx +265 -0
- package/src/components/ColorPicker/SVSquare.tsx +189 -0
- package/src/components/ColorPicker/colorMath.ts +141 -0
- package/src/components/ColorPicker/index.ts +6 -0
- package/src/components/ConfirmationPopover/ConfirmationPopover.module.scss +39 -0
- package/src/components/ConfirmationPopover/ConfirmationPopover.tokens.scss +13 -0
- package/src/components/ConfirmationPopover/ConfirmationPopover.tsx +395 -0
- package/src/components/ConfirmationPopover/index.ts +2 -0
- package/src/components/Constrain/Constrain.module.scss +99 -0
- package/src/components/Constrain/Constrain.tsx +79 -0
- package/src/components/Constrain/index.ts +2 -0
- package/src/components/CurrencySelect/CurrencySelect.module.scss +7 -0
- package/src/components/CurrencySelect/CurrencySelect.tsx +167 -0
- package/src/components/CurrencySelect/index.ts +3 -0
- package/src/components/CursorPagination/CursorPagination.module.scss +69 -0
- package/src/components/CursorPagination/CursorPagination.tokens.scss +32 -0
- package/src/components/CursorPagination/CursorPagination.tsx +121 -0
- package/src/components/CursorPagination/index.ts +2 -0
- package/src/components/DashboardCanvas/DashboardCanvas.module.scss +360 -0
- package/src/components/DashboardCanvas/DashboardCanvas.tokens.scss +48 -0
- package/src/components/DashboardCanvas/DashboardCanvas.tsx +1270 -0
- package/src/components/DashboardCanvas/DashboardCanvasItem.tsx +140 -0
- package/src/components/DashboardCanvas/DashboardCanvasSection.tsx +174 -0
- package/src/components/DashboardCanvas/engine.ts +363 -0
- package/src/components/DashboardCanvas/index.ts +9 -0
- package/src/components/DashboardWidget/DashboardWidget.module.scss +100 -0
- package/src/components/DashboardWidget/DashboardWidget.tokens.scss +16 -0
- package/src/components/DashboardWidget/DashboardWidget.tsx +188 -0
- package/src/components/DashboardWidget/index.ts +7 -0
- package/src/components/DataTable/BodyRow.tsx +197 -0
- package/src/components/DataTable/ColumnVisibilityTrigger.module.scss +7 -0
- package/src/components/DataTable/ColumnVisibilityTrigger.tsx +105 -0
- package/src/components/DataTable/DataTable.module.scss +511 -0
- package/src/components/DataTable/DataTable.tokens.scss +80 -0
- package/src/components/DataTable/DataTable.tsx +734 -0
- package/src/components/DataTable/HeaderCell.module.scss +215 -0
- package/src/components/DataTable/HeaderCell.tsx +440 -0
- package/src/components/DataTable/columnShift.ts +117 -0
- package/src/components/DataTable/index.ts +23 -0
- package/src/components/DataTable/pinStyle.ts +47 -0
- package/src/components/DataTable/reorderColumns.ts +66 -0
- package/src/components/DataTable/types.ts +289 -0
- package/src/components/DataTable/useColumnDragShift.ts +216 -0
- package/src/components/DataTable/useDataTable.ts +372 -0
- package/src/components/DataTable/useResizeHandle.ts +97 -0
- package/src/components/DatePicker/DatePicker.module.scss +180 -0
- package/src/components/DatePicker/DatePicker.tokens.scss +152 -0
- package/src/components/DatePicker/DatePicker.tsx +664 -0
- package/src/components/DatePicker/DatePickerGrid.module.scss +172 -0
- package/src/components/DatePicker/DatePickerGrid.tsx +341 -0
- package/src/components/DatePicker/InlineDatePicker.module.scss +32 -0
- package/src/components/DatePicker/InlineDatePicker.tsx +201 -0
- package/src/components/DatePicker/index.ts +4 -0
- package/src/components/DatePicker/utils.ts +364 -0
- package/src/components/DateRangePicker/DateRangePicker.module.scss +237 -0
- package/src/components/DateRangePicker/DateRangePicker.tokens.scss +78 -0
- package/src/components/DateRangePicker/DateRangePicker.tsx +720 -0
- package/src/components/DateRangePicker/InlineDateRangePicker.module.scss +94 -0
- package/src/components/DateRangePicker/InlineDateRangePicker.tsx +376 -0
- package/src/components/DateRangePicker/index.ts +5 -0
- package/src/components/DateRangePicker/utils.ts +151 -0
- package/src/components/DateStrip/DateStrip.module.scss +78 -0
- package/src/components/DateStrip/DateStrip.tokens.scss +23 -0
- package/src/components/DateStrip/DateStrip.tsx +205 -0
- package/src/components/DateStrip/index.ts +2 -0
- package/src/components/DefinitionList/DefinitionList.module.scss +102 -0
- package/src/components/DefinitionList/DefinitionList.tokens.scss +29 -0
- package/src/components/DefinitionList/DefinitionList.tsx +198 -0
- package/src/components/DefinitionList/index.ts +9 -0
- package/src/components/Divider/Divider.module.scss +144 -0
- package/src/components/Divider/Divider.tokens.scss +21 -0
- package/src/components/Divider/Divider.tsx +103 -0
- package/src/components/Divider/index.ts +2 -0
- package/src/components/Dot/Dot.module.scss +40 -0
- package/src/components/Dot/Dot.tokens.scss +4 -0
- package/src/components/Dot/Dot.tsx +45 -0
- package/src/components/Dot/index.ts +2 -0
- package/src/components/Drawer/Body.tsx +23 -0
- package/src/components/Drawer/Close.tsx +35 -0
- package/src/components/Drawer/Content.tsx +93 -0
- package/src/components/Drawer/Drawer.module.scss +232 -0
- package/src/components/Drawer/Drawer.tokens.scss +38 -0
- package/src/components/Drawer/Drawer.tsx +23 -0
- package/src/components/Drawer/DrawerRoot.tsx +190 -0
- package/src/components/Drawer/Footer.tsx +24 -0
- package/src/components/Drawer/Header.tsx +64 -0
- package/src/components/Drawer/Overlay.tsx +82 -0
- package/src/components/Drawer/context.ts +44 -0
- package/src/components/Drawer/index.ts +12 -0
- package/src/components/Drawer/useDragToClose.ts +149 -0
- package/src/components/DropdownMenu/CheckboxItem.tsx +144 -0
- package/src/components/DropdownMenu/Content.tsx +419 -0
- package/src/components/DropdownMenu/DropdownMenu.module.scss +280 -0
- package/src/components/DropdownMenu/DropdownMenu.tokens.scss +88 -0
- package/src/components/DropdownMenu/DropdownMenu.tsx +43 -0
- package/src/components/DropdownMenu/Group.tsx +76 -0
- package/src/components/DropdownMenu/Item.tsx +148 -0
- package/src/components/DropdownMenu/ItemIndicator.tsx +30 -0
- package/src/components/DropdownMenu/Radio.tsx +178 -0
- package/src/components/DropdownMenu/Root.tsx +100 -0
- package/src/components/DropdownMenu/Sub.tsx +332 -0
- package/src/components/DropdownMenu/Trigger.tsx +76 -0
- package/src/components/DropdownMenu/context.ts +65 -0
- package/src/components/DropdownMenu/index.ts +20 -0
- package/src/components/EmojiPicker/EmojiPicker.module.scss +84 -0
- package/src/components/EmojiPicker/EmojiPicker.tokens.scss +24 -0
- package/src/components/EmojiPicker/EmojiPicker.tsx +341 -0
- package/src/components/EmojiPicker/emojiData.ts +245 -0
- package/src/components/EmojiPicker/index.ts +3 -0
- package/src/components/EmptyState/EmptyState.module.scss +102 -0
- package/src/components/EmptyState/EmptyState.tokens.scss +28 -0
- package/src/components/EmptyState/EmptyState.tsx +112 -0
- package/src/components/EmptyState/index.ts +7 -0
- package/src/components/EntityChip/EntityChip.module.scss +275 -0
- package/src/components/EntityChip/EntityChip.tokens.scss +66 -0
- package/src/components/EntityChip/EntityChip.tsx +649 -0
- package/src/components/EntityChip/index.ts +10 -0
- package/src/components/ErrorState/ErrorState.module.scss +105 -0
- package/src/components/ErrorState/ErrorState.tokens.scss +32 -0
- package/src/components/ErrorState/ErrorState.tsx +125 -0
- package/src/components/ErrorState/index.ts +8 -0
- package/src/components/Field/Field.module.scss +46 -0
- package/src/components/Field/Field.tsx +185 -0
- package/src/components/Field/index.ts +2 -0
- package/src/components/FileUpload/FileUpload.module.scss +187 -0
- package/src/components/FileUpload/FileUpload.tokens.scss +81 -0
- package/src/components/FileUpload/FileUpload.tsx +569 -0
- package/src/components/FileUpload/formatBytes.ts +34 -0
- package/src/components/FileUpload/iconForFile.ts +40 -0
- package/src/components/FileUpload/index.ts +2 -0
- package/src/components/FilterChip/FilterChip.module.scss +61 -0
- package/src/components/FilterChip/FilterChip.tokens.scss +25 -0
- package/src/components/FilterChip/FilterChip.tsx +208 -0
- package/src/components/FilterChip/index.ts +5 -0
- package/src/components/FlowCanvas/FlowCanvas.module.scss +329 -0
- package/src/components/FlowCanvas/FlowCanvas.tokens.scss +45 -0
- package/src/components/FlowCanvas/FlowCanvas.tsx +1867 -0
- package/src/components/FlowCanvas/FlowControls.tsx +46 -0
- package/src/components/FlowCanvas/FlowEdge.tsx +58 -0
- package/src/components/FlowCanvas/FlowNode.tsx +76 -0
- package/src/components/FlowCanvas/edgePath.ts +85 -0
- package/src/components/FlowCanvas/index.ts +4 -0
- package/src/components/FlowCanvas/layout.ts +148 -0
- package/src/components/FlowCanvas/spatialNav.ts +76 -0
- package/src/components/FlowCanvas/types.ts +59 -0
- package/src/components/FlowCanvas/useViewport.ts +122 -0
- package/src/components/FormRow/FormRow.module.scss +2 -0
- package/src/components/FormRow/FormRow.tsx +37 -0
- package/src/components/FormRow/index.ts +2 -0
- package/src/components/FormSection/FormSection.module.scss +24 -0
- package/src/components/FormSection/FormSection.tsx +46 -0
- package/src/components/FormSection/index.ts +2 -0
- package/src/components/Grid/Grid.module.scss +162 -0
- package/src/components/Grid/Grid.tokens.scss +13 -0
- package/src/components/Grid/Grid.tsx +226 -0
- package/src/components/Grid/GridItem.tsx +77 -0
- package/src/components/Grid/index.ts +11 -0
- package/src/components/Highlight/Highlight.module.scss +43 -0
- package/src/components/Highlight/Highlight.tokens.scss +35 -0
- package/src/components/Highlight/Highlight.tsx +164 -0
- package/src/components/Highlight/HighlightOverlay.tsx +100 -0
- package/src/components/Highlight/index.ts +2 -0
- package/src/components/HighlightedText/HighlightedText.module.scss +11 -0
- package/src/components/HighlightedText/HighlightedText.tokens.scss +9 -0
- package/src/components/HighlightedText/HighlightedText.tsx +50 -0
- package/src/components/HighlightedText/highlightRanges.ts +44 -0
- package/src/components/HighlightedText/index.ts +2 -0
- package/src/components/IconPicker/IconPicker.module.scss +175 -0
- package/src/components/IconPicker/IconPicker.tokens.scss +40 -0
- package/src/components/IconPicker/IconPicker.tsx +629 -0
- package/src/components/IconPicker/index.ts +9 -0
- package/src/components/IconTile/IconTile.module.scss +57 -0
- package/src/components/IconTile/IconTile.tokens.scss +16 -0
- package/src/components/IconTile/IconTile.tsx +91 -0
- package/src/components/IconTile/index.ts +2 -0
- package/src/components/Image/Image.module.scss +266 -0
- package/src/components/Image/Image.tokens.scss +55 -0
- package/src/components/Image/Image.tsx +342 -0
- package/src/components/Image/index.ts +2 -0
- package/src/components/ImageCrop/ImageCrop.module.scss +274 -0
- package/src/components/ImageCrop/ImageCrop.tokens.scss +33 -0
- package/src/components/ImageCrop/ImageCrop.tsx +542 -0
- package/src/components/ImageCrop/cropGeometry.ts +120 -0
- package/src/components/ImageCrop/extractCropBlob.ts +140 -0
- package/src/components/ImageCrop/index.ts +11 -0
- package/src/components/ImageCrop/useCropPreview.ts +161 -0
- package/src/components/Indent/Indent.module.scss +33 -0
- package/src/components/Indent/Indent.tokens.scss +10 -0
- package/src/components/Indent/Indent.tsx +46 -0
- package/src/components/Indent/index.ts +2 -0
- package/src/components/Input/Input.module.scss +99 -0
- package/src/components/Input/Input.tokens.scss +54 -0
- package/src/components/Input/Input.tsx +105 -0
- package/src/components/Input/Suggestions.tsx +260 -0
- package/src/components/Input/index.ts +2 -0
- package/src/components/Kanban/Kanban.module.scss +39 -0
- package/src/components/Kanban/Kanban.tokens.scss +15 -0
- package/src/components/Kanban/Kanban.tsx +951 -0
- package/src/components/Kanban/index.ts +7 -0
- package/src/components/Kbd/Kbd.module.scss +47 -0
- package/src/components/Kbd/Kbd.tokens.scss +26 -0
- package/src/components/Kbd/Kbd.tsx +73 -0
- package/src/components/Kbd/index.ts +2 -0
- package/src/components/Lightbox/Lightbox.module.scss +283 -0
- package/src/components/Lightbox/Lightbox.tokens.scss +36 -0
- package/src/components/Lightbox/Lightbox.tsx +506 -0
- package/src/components/Lightbox/index.ts +2 -0
- package/src/components/Link/Link.module.scss +78 -0
- package/src/components/Link/Link.tokens.scss +28 -0
- package/src/components/Link/Link.tsx +114 -0
- package/src/components/Link/index.ts +2 -0
- package/src/components/LinkCard/LinkCard.module.scss +92 -0
- package/src/components/LinkCard/LinkCard.tokens.scss +9 -0
- package/src/components/LinkCard/LinkCard.tsx +72 -0
- package/src/components/LinkCard/index.ts +2 -0
- package/src/components/LiquidEditor/AutocompleteMenu.tsx +133 -0
- package/src/components/LiquidEditor/HighlightLayer.tsx +53 -0
- package/src/components/LiquidEditor/InsertVariableMenu.tsx +81 -0
- package/src/components/LiquidEditor/LiquidEditor.module.scss +339 -0
- package/src/components/LiquidEditor/LiquidEditor.tokens.scss +81 -0
- package/src/components/LiquidEditor/LiquidEditor.tsx +412 -0
- package/src/components/LiquidEditor/PreviewPane.tsx +29 -0
- package/src/components/LiquidEditor/caretCoordinates.ts +97 -0
- package/src/components/LiquidEditor/index.ts +7 -0
- package/src/components/LiquidEditor/liquidFilters.ts +23 -0
- package/src/components/LiquidEditor/liquidTokenizer.ts +235 -0
- package/src/components/LiquidEditor/types.ts +101 -0
- package/src/components/LiquidEditor/useLiquidAutocomplete.ts +178 -0
- package/src/components/List/List.module.scss +105 -0
- package/src/components/List/List.tokens.scss +42 -0
- package/src/components/List/List.tsx +124 -0
- package/src/components/List/index.ts +9 -0
- package/src/components/LiveRegion/LiveRegion.module.scss +3 -0
- package/src/components/LiveRegion/LiveRegion.tsx +116 -0
- package/src/components/LiveRegion/index.ts +2 -0
- package/src/components/Logo/Logo.module.scss +139 -0
- package/src/components/Logo/Logo.tokens.scss +14 -0
- package/src/components/Logo/Logo.tsx +126 -0
- package/src/components/Logo/index.ts +2 -0
- package/src/components/Masonry/Masonry.module.scss +42 -0
- package/src/components/Masonry/Masonry.tokens.scss +8 -0
- package/src/components/Masonry/Masonry.tsx +136 -0
- package/src/components/Masonry/index.ts +2 -0
- package/src/components/Masonry/masonryUtils.ts +42 -0
- package/src/components/MediaTile/MediaTile.module.scss +183 -0
- package/src/components/MediaTile/MediaTile.tokens.scss +60 -0
- package/src/components/MediaTile/MediaTile.tsx +208 -0
- package/src/components/MediaTile/index.ts +8 -0
- package/src/components/Modal/Body.tsx +27 -0
- package/src/components/Modal/Close.tsx +45 -0
- package/src/components/Modal/Content.tsx +98 -0
- package/src/components/Modal/Footer.tsx +28 -0
- package/src/components/Modal/Header.tsx +51 -0
- package/src/components/Modal/Modal.module.scss +262 -0
- package/src/components/Modal/Modal.tokens.scss +50 -0
- package/src/components/Modal/Modal.tsx +23 -0
- package/src/components/Modal/ModalRoot.tsx +299 -0
- package/src/components/Modal/Overlay.tsx +101 -0
- package/src/components/Modal/context.ts +60 -0
- package/src/components/Modal/index.ts +11 -0
- package/src/components/MoneyInput/MoneyInput.module.scss +57 -0
- package/src/components/MoneyInput/MoneyInput.tsx +433 -0
- package/src/components/MoneyInput/index.ts +2 -0
- package/src/components/OptionsPicker/OptionsPicker.module.scss +216 -0
- package/src/components/OptionsPicker/OptionsPicker.tokens.scss +68 -0
- package/src/components/OptionsPicker/OptionsPicker.tsx +936 -0
- package/src/components/OptionsPicker/index.ts +9 -0
- package/src/components/OtpInput/OtpInput.module.scss +69 -0
- package/src/components/OtpInput/OtpInput.tokens.scss +45 -0
- package/src/components/OtpInput/OtpInput.tsx +368 -0
- package/src/components/OtpInput/index.ts +2 -0
- package/src/components/Page/Page.module.scss +30 -0
- package/src/components/Page/Page.tokens.scss +11 -0
- package/src/components/Page/Page.tsx +53 -0
- package/src/components/Page/index.ts +2 -0
- package/src/components/PageHeader/PageHeader.module.scss +197 -0
- package/src/components/PageHeader/PageHeader.tokens.scss +41 -0
- package/src/components/PageHeader/PageHeader.tsx +310 -0
- package/src/components/PageHeader/index.ts +20 -0
- package/src/components/Pagination/Pagination.module.scss +117 -0
- package/src/components/Pagination/Pagination.tokens.scss +54 -0
- package/src/components/Pagination/Pagination.tsx +165 -0
- package/src/components/Pagination/index.ts +4 -0
- package/src/components/Pagination/paginationRange.ts +72 -0
- package/src/components/PasswordInput/PasswordInput.module.scss +120 -0
- package/src/components/PasswordInput/PasswordInput.tokens.scss +44 -0
- package/src/components/PasswordInput/PasswordInput.tsx +210 -0
- package/src/components/PasswordInput/index.ts +2 -0
- package/src/components/PasswordStrengthMeter/PasswordStrengthMeter.module.scss +80 -0
- package/src/components/PasswordStrengthMeter/PasswordStrengthMeter.tokens.scss +40 -0
- package/src/components/PasswordStrengthMeter/PasswordStrengthMeter.tsx +96 -0
- package/src/components/PasswordStrengthMeter/index.ts +2 -0
- package/src/components/PersonDisplay/PersonDisplay.module.scss +180 -0
- package/src/components/PersonDisplay/PersonDisplay.tokens.scss +35 -0
- package/src/components/PersonDisplay/PersonDisplay.tsx +420 -0
- package/src/components/PersonDisplay/index.ts +10 -0
- package/src/components/PhoneInput/PhoneInput.module.scss +23 -0
- package/src/components/PhoneInput/PhoneInput.tsx +271 -0
- package/src/components/PhoneInput/index.ts +4 -0
- package/src/components/PhoneInput/phone.ts +128 -0
- package/src/components/PillMenu/PillMenu.module.scss +117 -0
- package/src/components/PillMenu/PillMenu.tokens.scss +22 -0
- package/src/components/PillMenu/PillMenu.tsx +324 -0
- package/src/components/PillMenu/index.ts +2 -0
- package/src/components/Popover/Anchor.tsx +37 -0
- package/src/components/Popover/Close.tsx +45 -0
- package/src/components/Popover/Content.tsx +213 -0
- package/src/components/Popover/Heading.tsx +33 -0
- package/src/components/Popover/Popover.module.scss +177 -0
- package/src/components/Popover/Popover.tokens.scss +22 -0
- package/src/components/Popover/Popover.tsx +23 -0
- package/src/components/Popover/PopoverRoot.tsx +75 -0
- package/src/components/Popover/Trigger.tsx +86 -0
- package/src/components/Popover/context.ts +22 -0
- package/src/components/Popover/index.ts +7 -0
- package/src/components/Progress/Progress.module.scss +110 -0
- package/src/components/Progress/Progress.tokens.scss +44 -0
- package/src/components/Progress/Progress.tsx +129 -0
- package/src/components/Progress/index.ts +2 -0
- package/src/components/QrCode/QrCode.module.scss +97 -0
- package/src/components/QrCode/QrCode.tokens.scss +17 -0
- package/src/components/QrCode/QrCode.tsx +233 -0
- package/src/components/QrCode/index.ts +2 -0
- package/src/components/QrCode/qr.ts +161 -0
- package/src/components/Radio/Radio.module.scss +175 -0
- package/src/components/Radio/Radio.tokens.scss +59 -0
- package/src/components/Radio/Radio.tsx +127 -0
- package/src/components/Radio/RadioGroup.module.scss +42 -0
- package/src/components/Radio/RadioGroup.tsx +120 -0
- package/src/components/Radio/RadioGroupContext.tsx +30 -0
- package/src/components/Radio/index.ts +4 -0
- package/src/components/Rail/Rail.module.scss +510 -0
- package/src/components/Rail/Rail.tokens.scss +92 -0
- package/src/components/Rail/Rail.tsx +287 -0
- package/src/components/Rail/RailCollapseToggle.tsx +57 -0
- package/src/components/Rail/RailFooter.tsx +17 -0
- package/src/components/Rail/RailGroup.tsx +728 -0
- package/src/components/Rail/RailGroupContext.ts +9 -0
- package/src/components/Rail/RailHeader.tsx +23 -0
- package/src/components/Rail/RailItem.tsx +119 -0
- package/src/components/Rail/RailSection.tsx +39 -0
- package/src/components/Rail/RailSpacer.tsx +19 -0
- package/src/components/Rail/index.ts +9 -0
- package/src/components/RelativeDateInput/RelativeDateInput.module.scss +15 -0
- package/src/components/RelativeDateInput/RelativeDateInput.tokens.scss +6 -0
- package/src/components/RelativeDateInput/RelativeDateInput.tsx +474 -0
- package/src/components/RelativeDateInput/index.ts +10 -0
- package/src/components/RelativeDateInput/relativeDate.ts +127 -0
- package/src/components/RichText/RichText.module.scss +5 -0
- package/src/components/RichText/RichText.tsx +70 -0
- package/src/components/RichText/RichTextAttachment.module.scss +82 -0
- package/src/components/RichText/RichTextAttachment.tsx +122 -0
- package/src/components/RichText/_prose.scss +79 -0
- package/src/components/RichText/attachmentIcon.tsx +24 -0
- package/src/components/RichText/engine/attachment.ts +154 -0
- package/src/components/RichText/engine/autolink.ts +64 -0
- package/src/components/RichText/engine/blockUnit.ts +174 -0
- package/src/components/RichText/engine/colorMarks.ts +85 -0
- package/src/components/RichText/engine/escape.ts +11 -0
- package/src/components/RichText/engine/fromHtml.ts +353 -0
- package/src/components/RichText/engine/fromMarkdown.ts +20 -0
- package/src/components/RichText/engine/inlines.ts +82 -0
- package/src/components/RichText/engine/listDepths.ts +32 -0
- package/src/components/RichText/engine/marks.ts +80 -0
- package/src/components/RichText/engine/mdToHtml.ts +178 -0
- package/src/components/RichText/engine/model.ts +157 -0
- package/src/components/RichText/engine/position.ts +118 -0
- package/src/components/RichText/engine/renderDoc.tsx +395 -0
- package/src/components/RichText/engine/renderLink.ts +15 -0
- package/src/components/RichText/engine/renderMention.ts +16 -0
- package/src/components/RichText/engine/safeHref.ts +22 -0
- package/src/components/RichText/engine/toHtml.ts +154 -0
- package/src/components/RichText/engine/toMarkdown.ts +97 -0
- package/src/components/RichText/engine/transforms.ts +434 -0
- package/src/components/RichText/imageMaxHeightStyle.ts +10 -0
- package/src/components/RichText/index.ts +31 -0
- package/src/components/RichTextEditor/RichTextAttachmentConfig.tsx +264 -0
- package/src/components/RichTextEditor/RichTextBlockControls.tsx +365 -0
- package/src/components/RichTextEditor/RichTextBlockMenu.tsx +162 -0
- package/src/components/RichTextEditor/RichTextColorMenu.tsx +115 -0
- package/src/components/RichTextEditor/RichTextEditor.module.scss +298 -0
- package/src/components/RichTextEditor/RichTextEditor.tsx +1910 -0
- package/src/components/RichTextEditor/RichTextImageResizer.tsx +131 -0
- package/src/components/RichTextEditor/RichTextLinkEditor.tsx +151 -0
- package/src/components/RichTextEditor/RichTextMentionMenu.tsx +99 -0
- package/src/components/RichTextEditor/RichTextToolbar.tsx +379 -0
- package/src/components/RichTextEditor/autolinkInput.ts +65 -0
- package/src/components/RichTextEditor/blockDrop.ts +9 -0
- package/src/components/RichTextEditor/blockReflow.ts +62 -0
- package/src/components/RichTextEditor/commands.ts +300 -0
- package/src/components/RichTextEditor/history.ts +93 -0
- package/src/components/RichTextEditor/icons.tsx +152 -0
- package/src/components/RichTextEditor/imageSize.ts +70 -0
- package/src/components/RichTextEditor/index.ts +5 -0
- package/src/components/RichTextEditor/input.ts +209 -0
- package/src/components/RichTextEditor/inputRules.ts +71 -0
- package/src/components/RichTextEditor/links.ts +103 -0
- package/src/components/RichTextEditor/mentionContext.ts +37 -0
- package/src/components/RichTextEditor/mentions.ts +28 -0
- package/src/components/RichTextEditor/preventSelectionLoss.ts +15 -0
- package/src/components/RichTextEditor/selection.ts +415 -0
- package/src/components/RichTextEditor/shortcuts.ts +44 -0
- package/src/components/RichTextEditor/useAnchoredFloating.ts +50 -0
- package/src/components/RichTextEditor/useAttachmentConfig.ts +134 -0
- package/src/components/RichTextEditor/useDismissOnOutsidePointerDown.ts +26 -0
- package/src/components/RichTextEditor/useLinkEditor.ts +107 -0
- package/src/components/RichTextEditor/useMention.ts +226 -0
- package/src/components/RichTextEditor/useOverlayRect.ts +73 -0
- package/src/components/RichTextEditor/useUpload.ts +246 -0
- package/src/components/Screen/Screen.module.scss +46 -0
- package/src/components/Screen/Screen.tokens.scss +20 -0
- package/src/components/Screen/Screen.tsx +97 -0
- package/src/components/Screen/index.ts +2 -0
- package/src/components/ScrollArea/ScrollArea.module.scss +39 -0
- package/src/components/ScrollArea/ScrollArea.tokens.scss +15 -0
- package/src/components/ScrollArea/ScrollArea.tsx +154 -0
- package/src/components/ScrollArea/index.ts +2 -0
- package/src/components/Select/Chip.tsx +45 -0
- package/src/components/Select/Empty.tsx +39 -0
- package/src/components/Select/Error.tsx +51 -0
- package/src/components/Select/HiddenInputs.tsx +44 -0
- package/src/components/Select/Listbox.tsx +557 -0
- package/src/components/Select/Loading.tsx +30 -0
- package/src/components/Select/Select.module.scss +512 -0
- package/src/components/Select/Select.tokens.scss +140 -0
- package/src/components/Select/Select.tsx +685 -0
- package/src/components/Select/Trigger.tsx +952 -0
- package/src/components/Select/context.ts +101 -0
- package/src/components/Select/emptyStateText.ts +19 -0
- package/src/components/Select/index.ts +9 -0
- package/src/components/Select/useSelectState.ts +157 -0
- package/src/components/Select/utils-types.ts +7 -0
- package/src/components/Select/utils.ts +122 -0
- package/src/components/SettingRow/SettingRow.module.scss +166 -0
- package/src/components/SettingRow/SettingRow.tokens.scss +37 -0
- package/src/components/SettingRow/SettingRow.tsx +242 -0
- package/src/components/SettingRow/index.ts +7 -0
- package/src/components/Skeleton/Skeleton.module.scss +55 -0
- package/src/components/Skeleton/Skeleton.tokens.scss +17 -0
- package/src/components/Skeleton/Skeleton.tsx +131 -0
- package/src/components/Skeleton/index.ts +4 -0
- package/src/components/Skeleton/useSkeletonVisibility.ts +95 -0
- package/src/components/Slider/Slider.module.scss +293 -0
- package/src/components/Slider/Slider.tokens.scss +98 -0
- package/src/components/Slider/Slider.tsx +624 -0
- package/src/components/Slider/index.ts +9 -0
- package/src/components/SlotGrid/SlotGrid.module.scss +61 -0
- package/src/components/SlotGrid/SlotGrid.tokens.scss +21 -0
- package/src/components/SlotGrid/SlotGrid.tsx +111 -0
- package/src/components/SlotGrid/index.ts +2 -0
- package/src/components/SocialButton/SocialButton.module.scss +4 -0
- package/src/components/SocialButton/SocialButton.tsx +40 -0
- package/src/components/SocialButton/index.ts +2 -0
- package/src/components/Sortable/Sortable.module.scss +177 -0
- package/src/components/Sortable/Sortable.tokens.scss +27 -0
- package/src/components/Sortable/Sortable.tsx +572 -0
- package/src/components/Sortable/containerAwareCollision.ts +125 -0
- package/src/components/Sortable/index.ts +13 -0
- package/src/components/SortableGroup/SortableGroup.module.scss +23 -0
- package/src/components/SortableGroup/SortableGroup.tsx +315 -0
- package/src/components/SortableGroup/index.ts +4 -0
- package/src/components/SortableGroup/moveSortableItem.ts +39 -0
- package/src/components/Sparkline/Sparkline.module.scss +28 -0
- package/src/components/Sparkline/Sparkline.tokens.scss +7 -0
- package/src/components/Sparkline/Sparkline.tsx +133 -0
- package/src/components/Sparkline/index.ts +2 -0
- package/src/components/Sparkline/sparklineModel.ts +119 -0
- package/src/components/Split/Split.module.scss +158 -0
- package/src/components/Split/Split.tokens.scss +10 -0
- package/src/components/Split/Split.tsx +231 -0
- package/src/components/Split/index.ts +2 -0
- package/src/components/Stack/Stack.module.scss +67 -0
- package/src/components/Stack/Stack.tokens.scss +11 -0
- package/src/components/Stack/Stack.tsx +100 -0
- package/src/components/Stack/index.ts +2 -0
- package/src/components/StagePath/StagePath.module.scss +255 -0
- package/src/components/StagePath/StagePath.tokens.scss +43 -0
- package/src/components/StagePath/StagePath.tsx +176 -0
- package/src/components/StagePath/index.ts +2 -0
- package/src/components/StatGroup/StatGroup.module.scss +6 -0
- package/src/components/StatGroup/StatGroup.tsx +70 -0
- package/src/components/StatGroup/StatGroupContext.ts +10 -0
- package/src/components/StatGroup/StatTile.module.scss +99 -0
- package/src/components/StatGroup/StatTile.tokens.scss +35 -0
- package/src/components/StatGroup/StatTile.tsx +143 -0
- package/src/components/StatGroup/index.ts +4 -0
- package/src/components/Sticky/Sticky.module.scss +98 -0
- package/src/components/Sticky/Sticky.tokens.scss +17 -0
- package/src/components/Sticky/Sticky.tsx +103 -0
- package/src/components/Sticky/index.ts +2 -0
- package/src/components/Switch/Switch.module.scss +182 -0
- package/src/components/Switch/Switch.tokens.scss +59 -0
- package/src/components/Switch/Switch.tsx +214 -0
- package/src/components/Switch/index.ts +2 -0
- package/src/components/Table/Table.module.scss +308 -0
- package/src/components/Table/Table.tokens.scss +79 -0
- package/src/components/Table/Table.tsx +381 -0
- package/src/components/Table/index.ts +15 -0
- package/src/components/TableOfContents/TableOfContents.module.scss +83 -0
- package/src/components/TableOfContents/TableOfContents.tokens.scss +26 -0
- package/src/components/TableOfContents/TableOfContents.tsx +192 -0
- package/src/components/TableOfContents/activeSection.ts +18 -0
- package/src/components/TableOfContents/index.ts +2 -0
- package/src/components/Tabs/Tabs.module.scss +359 -0
- package/src/components/Tabs/Tabs.tokens.scss +50 -0
- package/src/components/Tabs/Tabs.tsx +538 -0
- package/src/components/Tabs/index.ts +2 -0
- package/src/components/Text/Text.module.scss +115 -0
- package/src/components/Text/Text.tokens.scss +55 -0
- package/src/components/Text/Text.tsx +197 -0
- package/src/components/Text/index.ts +2 -0
- package/src/components/Textarea/Textarea.module.scss +94 -0
- package/src/components/Textarea/Textarea.tokens.scss +48 -0
- package/src/components/Textarea/Textarea.tsx +246 -0
- package/src/components/Textarea/index.ts +2 -0
- package/src/components/Thread/Thread.module.scss +161 -0
- package/src/components/Thread/Thread.tokens.scss +32 -0
- package/src/components/Thread/Thread.tsx +176 -0
- package/src/components/Thread/index.ts +2 -0
- package/src/components/TimeField/TimeField.module.scss +232 -0
- package/src/components/TimeField/TimeField.tsx +793 -0
- package/src/components/TimeField/index.ts +2 -0
- package/src/components/Timeline/Timeline.module.scss +58 -0
- package/src/components/Timeline/Timeline.tokens.scss +13 -0
- package/src/components/Timeline/Timeline.tsx +53 -0
- package/src/components/Timeline/index.ts +2 -0
- package/src/components/Title/Title.module.scss +84 -0
- package/src/components/Title/Title.tokens.scss +37 -0
- package/src/components/Title/Title.tsx +150 -0
- package/src/components/Title/index.ts +2 -0
- package/src/components/Toast/Toast.module.scss +299 -0
- package/src/components/Toast/Toast.tokens.scss +65 -0
- package/src/components/Toast/Toast.tsx +122 -0
- package/src/components/Toast/ToastViewport.tsx +166 -0
- package/src/components/Toast/api.ts +175 -0
- package/src/components/Toast/index.ts +5 -0
- package/src/components/Toast/store.ts +133 -0
- package/src/components/Toast/useToastTimer.ts +94 -0
- package/src/components/Tooltip/Tooltip.module.scss +99 -0
- package/src/components/Tooltip/Tooltip.tokens.scss +23 -0
- package/src/components/Tooltip/Tooltip.tsx +341 -0
- package/src/components/Tooltip/index.ts +2 -0
- package/src/components/TopBar/TopBar.module.scss +235 -0
- package/src/components/TopBar/TopBar.tokens.scss +79 -0
- package/src/components/TopBar/TopBar.tsx +87 -0
- package/src/components/TopBar/TopBarEnd.tsx +20 -0
- package/src/components/TopBar/TopBarIconButton.tsx +86 -0
- package/src/components/TopBar/TopBarSearch.tsx +319 -0
- package/src/components/TopBar/TopBarSearchPanel.tsx +268 -0
- package/src/components/TopBar/TopBarStart.tsx +20 -0
- package/src/components/TopBar/index.ts +11 -0
- package/src/components/Tour/Spotlight.tsx +121 -0
- package/src/components/Tour/Tour.module.scss +210 -0
- package/src/components/Tour/Tour.tokens.scss +58 -0
- package/src/components/Tour/Tour.tsx +617 -0
- package/src/components/Tour/index.ts +2 -0
- package/src/components/Tour/useTourTarget.ts +115 -0
- package/src/components/TreeView/TreeView.module.scss +268 -0
- package/src/components/TreeView/TreeView.tokens.scss +36 -0
- package/src/components/TreeView/TreeView.tsx +536 -0
- package/src/components/TreeView/index.ts +3 -0
- package/src/components/TreeView/treeModel.ts +224 -0
- package/src/components/VisuallyHidden/VisuallyHidden.module.scss +7 -0
- package/src/components/VisuallyHidden/VisuallyHidden.tsx +29 -0
- package/src/components/VisuallyHidden/index.ts +2 -0
- package/src/components/WidgetPreview/WidgetPreview.module.scss +11 -0
- package/src/components/WidgetPreview/WidgetPreview.tokens.scss +26 -0
- package/src/components/WidgetPreview/WidgetPreview.tsx +36 -0
- package/src/components/WidgetPreview/WidgetShape.module.scss +140 -0
- package/src/components/WidgetPreview/WidgetShape.tsx +159 -0
- package/src/components/WidgetPreview/index.ts +2 -0
- package/src/components/_internal/Trend.tsx +27 -0
- package/src/components/_internal/choiceTile.scss +74 -0
- package/src/components/_internal/collapse.scss +10 -0
- package/src/components/_internal/collapse.ts +58 -0
- package/src/components/_internal/currency.ts +257 -0
- package/src/components/_internal/dragAnnouncements.ts +200 -0
- package/src/components/_internal/fieldWiring.ts +225 -0
- package/src/components/_internal/gridSpan.ts +54 -0
- package/src/components/_internal/isoDay.ts +35 -0
- package/src/components/_internal/letterKey.ts +26 -0
- package/src/components/_internal/overlay/focusRestore.ts +21 -0
- package/src/components/_internal/overlay/index.ts +12 -0
- package/src/components/_internal/overlay/tourHold.ts +23 -0
- package/src/components/_internal/overlay/useFocusTrap.ts +120 -0
- package/src/components/_internal/overlay/useInOverlay.ts +81 -0
- package/src/components/_internal/overlay/useOverlayStack.ts +232 -0
- package/src/components/_internal/overlay/useScrollLock.ts +112 -0
- package/src/components/_internal/refs.ts +48 -0
- package/src/components/_internal/statusColor.ts +26 -0
- package/src/components/_internal/timeUtils.ts +23 -0
- package/src/components/_internal/useAsyncOptions.ts +145 -0
- package/src/components/_internal/useClippedTooltip.ts +50 -0
- package/src/components/_internal/useControllableState.ts +50 -0
- package/src/components.manifest.json +1141 -0
- package/src/hooks/useBelowBreakpoint.ts +58 -0
- package/src/i18n/I18nProvider.tsx +52 -0
- package/src/i18n/LocaleProvider.tsx +17 -0
- package/src/i18n/en.ts +648 -0
- package/src/i18n/format.ts +71 -0
- package/src/i18n/index.ts +13 -0
- package/src/i18n/messages.ts +1275 -0
- package/src/i18n/ru.ts +677 -0
- package/src/i18n/useLocale.ts +19 -0
- package/src/i18n/useTranslation.ts +58 -0
- package/src/index.ts +913 -0
- package/src/palette/index.ts +2 -0
- package/src/palette/palette.ts +108 -0
- package/src/styles/dark.scss +4 -0
- package/src/styles/global.scss +7 -0
- package/src/styles/mixins.scss +143 -0
- package/src/styles/reset.scss +78 -0
- package/src/styles/tokens.scss +18 -0
- package/src/styles/typography.scss +44 -0
|
@@ -0,0 +1,783 @@
|
|
|
1
|
+
import {
|
|
2
|
+
useCallback,
|
|
3
|
+
useEffect,
|
|
4
|
+
useRef,
|
|
5
|
+
useState,
|
|
6
|
+
type MutableRefObject,
|
|
7
|
+
type PointerEvent,
|
|
8
|
+
} from 'react';
|
|
9
|
+
import { startOfDay } from '../../calendar/dateMath';
|
|
10
|
+
import type {
|
|
11
|
+
CalendarDropCandidate,
|
|
12
|
+
CalendarDropResult,
|
|
13
|
+
CalendarEvent,
|
|
14
|
+
CalendarEventMove,
|
|
15
|
+
CalendarEventResize,
|
|
16
|
+
TimedEventBlock,
|
|
17
|
+
} from './types';
|
|
18
|
+
import type { HourGridColumnRef } from './utils';
|
|
19
|
+
|
|
20
|
+
/** Which gesture a drag represents. */
|
|
21
|
+
export type DragMode = 'move' | 'resize';
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* The live, uncommitted placement of the block being dragged.
|
|
25
|
+
*
|
|
26
|
+
* `startMinutes` / `endMinutes` are grid coordinates for rendering; `startsAt`
|
|
27
|
+
* / `endsAt` are the projected instants the consumer will be offered. They are
|
|
28
|
+
* computed together so the block's rendered position, its visible time label,
|
|
29
|
+
* and the payload can never disagree.
|
|
30
|
+
*/
|
|
31
|
+
export interface DragPreview {
|
|
32
|
+
/** `CalendarEvent.id` of the block being dragged. */
|
|
33
|
+
eventId: string;
|
|
34
|
+
mode: DragMode;
|
|
35
|
+
/** Column the block currently hovers over — may differ from where it started. */
|
|
36
|
+
columnIndex: number;
|
|
37
|
+
/** Minutes from `hourRange[0] * 60`. May sit outside the visible window (see `project`). */
|
|
38
|
+
startMinutes: number;
|
|
39
|
+
/** Minutes from `hourRange[0] * 60`. May sit outside the visible window (see `project`). */
|
|
40
|
+
endMinutes: number;
|
|
41
|
+
/** Projected start instant — `startsAt` of the proposal. */
|
|
42
|
+
startsAt: Date;
|
|
43
|
+
/** Projected end instant — `endsAt` of the proposal. */
|
|
44
|
+
endsAt: Date;
|
|
45
|
+
/** `canDropEvent` refused this placement — styled as a refused drop while the pointer rests on it. */
|
|
46
|
+
invalid: boolean;
|
|
47
|
+
/** The bounds moved the requested value — the gesture asked to go further than it may. */
|
|
48
|
+
clamped: boolean;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** Pixels the pointer must travel before a press becomes a drag rather than a click. */
|
|
52
|
+
const DRAG_THRESHOLD_PX = 3;
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* What the grid should announce in its live region. The hook reports the
|
|
56
|
+
* placement and the outcome; the caller owns the wording, because formatting a
|
|
57
|
+
* time is locale work and every user-facing string has to come from i18n.
|
|
58
|
+
*/
|
|
59
|
+
export interface DragAnnouncement {
|
|
60
|
+
/** The event being dragged — every message names it, as the rest of the library's drag a11y does. */
|
|
61
|
+
event: CalendarEvent;
|
|
62
|
+
mode: DragMode;
|
|
63
|
+
startsAt: Date;
|
|
64
|
+
endsAt: Date;
|
|
65
|
+
/** The placement was refused — by `canDropEvent` mid-drag, or by the handler on drop. */
|
|
66
|
+
refused: boolean;
|
|
67
|
+
/** The gesture asked to go further than the grid allows, so nothing changed. */
|
|
68
|
+
atEdge: boolean;
|
|
69
|
+
/** The gesture ended exactly where it started — nothing refused it, it simply moved nothing. */
|
|
70
|
+
unchanged: boolean;
|
|
71
|
+
/** The gesture was abandoned (pointer cancelled, window blurred) and proposed nothing. */
|
|
72
|
+
cancelled: boolean;
|
|
73
|
+
/** Monotonic, so an identical message still re-announces. */
|
|
74
|
+
seq: number;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export interface UseEventDragArgs {
|
|
78
|
+
/** The grid's columns, in render order. */
|
|
79
|
+
columns: readonly HourGridColumnRef[];
|
|
80
|
+
/** Pixel height of one hour row — the px↔minutes conversion factor. */
|
|
81
|
+
hourRowHeight: number;
|
|
82
|
+
/** Inclusive start, exclusive end of the visible hour range. */
|
|
83
|
+
hourRange: readonly [number, number];
|
|
84
|
+
/** Snap granularity in minutes. Values below 1 are treated as 1. */
|
|
85
|
+
snapMinutes: number;
|
|
86
|
+
/** Live DOM nodes of the day columns, for pointer hit-testing. */
|
|
87
|
+
columnElements: MutableRefObject<(HTMLElement | null)[]>;
|
|
88
|
+
onEventMove?: (event: CalendarEvent, next: CalendarEventMove) => CalendarDropResult;
|
|
89
|
+
onEventResize?: (event: CalendarEvent, next: CalendarEventResize) => CalendarDropResult;
|
|
90
|
+
canDropEvent?: (event: CalendarEvent, next: CalendarDropCandidate) => boolean;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export interface UseEventDragResult {
|
|
94
|
+
/** The in-flight placement, or `null` when nothing is being dragged. */
|
|
95
|
+
preview: DragPreview | null;
|
|
96
|
+
/** Latest thing worth announcing, or `null` before any gesture. */
|
|
97
|
+
announcement: DragAnnouncement | null;
|
|
98
|
+
/** True while a drag gesture owns the pointer. */
|
|
99
|
+
dragging: boolean;
|
|
100
|
+
/** Whether this grid accepts move gestures at all. */
|
|
101
|
+
canMove: boolean;
|
|
102
|
+
/** Whether this grid accepts resize gestures at all. */
|
|
103
|
+
canResize: boolean;
|
|
104
|
+
/** Begin a pointer drag from a `pointerdown` on a block (or its resize handle). */
|
|
105
|
+
startPointerDrag: (block: TimedEventBlock, mode: DragMode, e: PointerEvent) => void;
|
|
106
|
+
/** Keyboard equivalent: nudge a block by whole snap steps and/or whole columns. */
|
|
107
|
+
nudge: (
|
|
108
|
+
block: TimedEventBlock,
|
|
109
|
+
delta: { mode: DragMode; steps: number; columns?: number },
|
|
110
|
+
) => void;
|
|
111
|
+
/** True when the click that follows the drag we just finished must be swallowed. */
|
|
112
|
+
consumeClickSuppression: () => boolean;
|
|
113
|
+
/** Clear a stale suppression flag at the start of any fresh press. */
|
|
114
|
+
resetClickSuppression: () => void;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* The gesture's semantic starting point, captured once when it begins.
|
|
119
|
+
*
|
|
120
|
+
* This exists to keep `TimedEventBlock` out of the projection entirely. A
|
|
121
|
+
* block carries *render* coordinates: `endMinutes` is clipped to the bottom of
|
|
122
|
+
* the column for anything ending on a later day (`layoutEventsForHourGrid`),
|
|
123
|
+
* and reading it for a semantic decision is what produced every drag bug this
|
|
124
|
+
* component has had — a truncated overnight booking, a reversed gesture, a
|
|
125
|
+
* no-op guard that could never match. The frame is derived from the
|
|
126
|
+
* `CalendarEvent`, so clipping cannot leak into a proposal.
|
|
127
|
+
*
|
|
128
|
+
* Invariant for everything below: the controller reads `frame`, never `block`.
|
|
129
|
+
*/
|
|
130
|
+
interface DragFrame {
|
|
131
|
+
event: CalendarEvent;
|
|
132
|
+
/** Column the gesture started in. */
|
|
133
|
+
columnIndex: number;
|
|
134
|
+
/** Minutes from `hourRange[0] * 60` to the event's real start. May be negative. */
|
|
135
|
+
startMinutes: number;
|
|
136
|
+
/**
|
|
137
|
+
* Minutes from `hourRange[0] * 60` to the event's real end, in WALL-CLOCK
|
|
138
|
+
* minutes. Never clipped. This is grid geometry — it is what the block's
|
|
139
|
+
* height is drawn from and what the bounds and the no-op check compare.
|
|
140
|
+
*/
|
|
141
|
+
endMinutes: number;
|
|
142
|
+
/**
|
|
143
|
+
* ELAPSED milliseconds between the event's start and end.
|
|
144
|
+
*
|
|
145
|
+
* Kept separately from the wall-clock span because on a daylight-saving day
|
|
146
|
+
* the two genuinely differ, and each is right for a different job: the grid
|
|
147
|
+
* draws wall-clock (01:00–05:00 occupies four rows even though only three
|
|
148
|
+
* hours pass), while a booking IS its elapsed time — a 30-minute
|
|
149
|
+
* appointment takes 30 real minutes on any day of the year. The proposal
|
|
150
|
+
* therefore preserves this, not the row count.
|
|
151
|
+
*/
|
|
152
|
+
durationMs: number;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/** A Date on `day`'s calendar date, at `minutes` past local midnight. */
|
|
156
|
+
function dateAtMinutes(day: Date, minutes: number): Date {
|
|
157
|
+
const d = startOfDay(day);
|
|
158
|
+
// Wall-clock arithmetic, and it rolls past 24:00 into the next day — which
|
|
159
|
+
// is what an overnight booking needs.
|
|
160
|
+
d.setMinutes(d.getMinutes() + minutes);
|
|
161
|
+
return d;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/** Round to the nearest multiple of `snap` (always >= 1 so this can't divide by zero). */
|
|
165
|
+
function snapTo(minutes: number, snap: number): number {
|
|
166
|
+
return Math.round(minutes / snap) * snap;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function clamp(value: number, min: number, max: number): number {
|
|
170
|
+
return Math.min(Math.max(value, min), max);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/** Capture the semantic frame for a gesture on `block`. */
|
|
174
|
+
function frameOf(block: TimedEventBlock, baseMinutes: number): DragFrame {
|
|
175
|
+
const { event } = block;
|
|
176
|
+
const start = event.startsAt;
|
|
177
|
+
const startMinutes = start.getHours() * 60 + start.getMinutes() - baseMinutes;
|
|
178
|
+
const end = event.endsAt ?? event.startsAt;
|
|
179
|
+
// Wall-clock, NOT elapsed: `dateAtMinutes` projects in wall-clock minutes,
|
|
180
|
+
// so an elapsed-ms duration disagrees with it by an hour either side of a
|
|
181
|
+
// daylight-saving transition — proposing a resize that changes nothing, or
|
|
182
|
+
// a move that silently loses an hour.
|
|
183
|
+
const dayDiff = Math.round(
|
|
184
|
+
(startOfDay(end).getTime() - startOfDay(start).getTime()) / 86_400_000,
|
|
185
|
+
);
|
|
186
|
+
const startWall = start.getHours() * 60 + start.getMinutes();
|
|
187
|
+
const endWall = end.getHours() * 60 + end.getMinutes();
|
|
188
|
+
const wallMinutes = Math.max(0, dayDiff * 24 * 60 + (endWall - startWall));
|
|
189
|
+
return {
|
|
190
|
+
event,
|
|
191
|
+
columnIndex: block.dayIndex,
|
|
192
|
+
startMinutes,
|
|
193
|
+
endMinutes: startMinutes + wallMinutes,
|
|
194
|
+
durationMs: Math.max(0, end.getTime() - start.getTime()),
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
interface ProjectionConfig {
|
|
199
|
+
columns: readonly HourGridColumnRef[];
|
|
200
|
+
baseMinutes: number;
|
|
201
|
+
visibleMinutes: number;
|
|
202
|
+
snap: number;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Turn a raw target (in grid minutes) into a fully-resolved preview: snapped,
|
|
207
|
+
* bounded, and projected to real instants.
|
|
208
|
+
*
|
|
209
|
+
* **One invariant governs both bounds: the allowed range always contains the
|
|
210
|
+
* value the gesture started from.** That is what keeps a clamp honest — it can
|
|
211
|
+
* refuse to move further, but it can never move the event in the direction
|
|
212
|
+
* opposite to the one the user asked for. Without it, an event already sitting
|
|
213
|
+
* outside the window (clipped at the top of the grid, or ending after
|
|
214
|
+
* midnight) gets yanked *into* the window by a gesture that asked to push it
|
|
215
|
+
* further out.
|
|
216
|
+
*
|
|
217
|
+
* Within that invariant:
|
|
218
|
+
* - a move keeps the start inside the visible window, because an unclamped
|
|
219
|
+
* upward drag goes negative and projects back into the previous calendar
|
|
220
|
+
* day — a proposal for a date the pointer never visited. The end is free to
|
|
221
|
+
* fall outside; an overnight booking must keep its duration.
|
|
222
|
+
* - a resize may grow the event by at most a day per gesture, which stops a
|
|
223
|
+
* runaway pointer delta rolling through whole dates while leaving every
|
|
224
|
+
* duration — including a multi-day one — reachable.
|
|
225
|
+
*/
|
|
226
|
+
function project(
|
|
227
|
+
frame: DragFrame,
|
|
228
|
+
mode: DragMode,
|
|
229
|
+
columnIndex: number,
|
|
230
|
+
target: number,
|
|
231
|
+
cfg: ProjectionConfig,
|
|
232
|
+
/** Skip snapping — the caller already decided the exact target (a pure column change). */
|
|
233
|
+
presnapped = false,
|
|
234
|
+
): DragPreview {
|
|
235
|
+
const column = cfg.columns[columnIndex] ?? cfg.columns[frame.columnIndex];
|
|
236
|
+
const duration = frame.endMinutes - frame.startMinutes;
|
|
237
|
+
|
|
238
|
+
if (mode === 'move') {
|
|
239
|
+
const requested = presnapped ? target : snapTo(target, cfg.snap);
|
|
240
|
+
const startMinutes = clamp(
|
|
241
|
+
requested,
|
|
242
|
+
Math.min(0, frame.startMinutes),
|
|
243
|
+
Math.max(cfg.visibleMinutes - cfg.snap, frame.startMinutes),
|
|
244
|
+
);
|
|
245
|
+
const movedStart = dateAtMinutes(column.date, cfg.baseMinutes + startMinutes);
|
|
246
|
+
return {
|
|
247
|
+
eventId: frame.event.id,
|
|
248
|
+
mode,
|
|
249
|
+
columnIndex,
|
|
250
|
+
startMinutes,
|
|
251
|
+
endMinutes: startMinutes + duration,
|
|
252
|
+
clamped: startMinutes !== requested,
|
|
253
|
+
startsAt: movedStart,
|
|
254
|
+
// Offset from the projected START by the event's ELAPSED duration, not
|
|
255
|
+
// recomputed from midnight in wall-clock minutes. Recomputing collapses
|
|
256
|
+
// on a spring-forward day — 02:00 and 03:00 are the same instant, so a
|
|
257
|
+
// 60-minute booking dragged onto the skipped hour came back with
|
|
258
|
+
// `endsAt === startsAt`, a zero-length proposal.
|
|
259
|
+
endsAt: new Date(movedStart.getTime() + frame.durationMs),
|
|
260
|
+
invalid: false,
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// Resize leaves the start alone — including when it sits above the visible
|
|
265
|
+
// window (an event that began before `hourRange`).
|
|
266
|
+
//
|
|
267
|
+
// The ceiling bounds the CHANGE (a day's growth per gesture), not the
|
|
268
|
+
// absolute duration. An absolute cap collapses onto the current end for
|
|
269
|
+
// anything already that long, which — combined with the no-op check in
|
|
270
|
+
// `commit` — silently swallows every attempt to lengthen a multi-day event.
|
|
271
|
+
const startMinutes = frame.startMinutes;
|
|
272
|
+
const requested = presnapped ? target : snapTo(target, cfg.snap);
|
|
273
|
+
const endMinutes = clamp(
|
|
274
|
+
requested,
|
|
275
|
+
// Symmetrical with the ceiling: a gesture may shrink by at most a day too,
|
|
276
|
+
// so a runaway delta can't collapse a three-day booking to 15 minutes.
|
|
277
|
+
Math.max(Math.min(startMinutes + cfg.snap, frame.endMinutes), frame.endMinutes - 24 * 60),
|
|
278
|
+
frame.endMinutes + 24 * 60,
|
|
279
|
+
);
|
|
280
|
+
return {
|
|
281
|
+
eventId: frame.event.id,
|
|
282
|
+
mode,
|
|
283
|
+
columnIndex,
|
|
284
|
+
startMinutes,
|
|
285
|
+
endMinutes,
|
|
286
|
+
clamped: endMinutes !== requested,
|
|
287
|
+
startsAt: frame.event.startsAt,
|
|
288
|
+
endsAt: dateAtMinutes(column.date, cfg.baseMinutes + endMinutes),
|
|
289
|
+
invalid: false,
|
|
290
|
+
};
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
/** Duck-typed thenable check — a consumer may return a non-native promise. */
|
|
294
|
+
function isThenable(value: unknown): value is Promise<void | boolean> {
|
|
295
|
+
return (
|
|
296
|
+
typeof value === 'object' &&
|
|
297
|
+
value !== null &&
|
|
298
|
+
typeof (value as { then?: unknown }).then === 'function'
|
|
299
|
+
);
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
/**
|
|
303
|
+
* Pointer + keyboard drag controller for the hour grid's timed blocks.
|
|
304
|
+
*
|
|
305
|
+
* Hand-rolled on pointer events rather than built on `@dnd-kit`: the gesture
|
|
306
|
+
* is a continuous px→minutes projection onto one shared time axis, not a
|
|
307
|
+
* sortable list reorder, so the sensor/collision machinery would be pure
|
|
308
|
+
* overhead here.
|
|
309
|
+
*
|
|
310
|
+
* The consumer stays the source of truth. A drop is *proposed*, never
|
|
311
|
+
* applied: `canDropEvent` vetoes it live during the gesture, and the preview
|
|
312
|
+
* renders refused for as long as the pointer sits on a refused slot.
|
|
313
|
+
* `onEventMove` / `onEventResize` can also veto on release, by returning
|
|
314
|
+
* `false` or a promise that resolves `false` or rejects; that verdict is
|
|
315
|
+
* announced in the live region rather than styled, because by then the
|
|
316
|
+
* gesture is over and the preview is being discarded either way. The block's
|
|
317
|
+
* resting position always comes from `events`, so an accepted drop only moves
|
|
318
|
+
* anything because the consumer commits it. Nothing here mutates the event.
|
|
319
|
+
*/
|
|
320
|
+
export function useEventDrag({
|
|
321
|
+
columns,
|
|
322
|
+
hourRowHeight,
|
|
323
|
+
hourRange,
|
|
324
|
+
snapMinutes,
|
|
325
|
+
columnElements,
|
|
326
|
+
onEventMove,
|
|
327
|
+
onEventResize,
|
|
328
|
+
canDropEvent,
|
|
329
|
+
}: UseEventDragArgs): UseEventDragResult {
|
|
330
|
+
const [preview, setPreview] = useState<DragPreview | null>(null);
|
|
331
|
+
const [dragging, setDragging] = useState(false);
|
|
332
|
+
const [announcement, setAnnouncement] = useState<DragAnnouncement | null>(null);
|
|
333
|
+
const seqRef = useRef(0);
|
|
334
|
+
|
|
335
|
+
const announce = useCallback(
|
|
336
|
+
(
|
|
337
|
+
event: CalendarEvent,
|
|
338
|
+
p: DragPreview,
|
|
339
|
+
opts: { refused?: boolean; atEdge?: boolean; unchanged?: boolean; cancelled?: boolean } = {},
|
|
340
|
+
) => {
|
|
341
|
+
seqRef.current += 1;
|
|
342
|
+
setAnnouncement({
|
|
343
|
+
event,
|
|
344
|
+
mode: p.mode,
|
|
345
|
+
startsAt: p.startsAt,
|
|
346
|
+
endsAt: p.endsAt,
|
|
347
|
+
refused: opts.refused ?? false,
|
|
348
|
+
atEdge: opts.atEdge ?? false,
|
|
349
|
+
unchanged: opts.unchanged ?? false,
|
|
350
|
+
cancelled: opts.cancelled ?? false,
|
|
351
|
+
seq: seqRef.current,
|
|
352
|
+
});
|
|
353
|
+
},
|
|
354
|
+
[],
|
|
355
|
+
);
|
|
356
|
+
|
|
357
|
+
const canMove = onEventMove !== undefined;
|
|
358
|
+
const canResize = onEventResize !== undefined;
|
|
359
|
+
const snap = Math.max(1, Math.round(snapMinutes));
|
|
360
|
+
const baseMinutes = hourRange[0] * 60;
|
|
361
|
+
const visibleMinutes = Math.max(0, (hourRange[1] - hourRange[0]) * 60);
|
|
362
|
+
|
|
363
|
+
// Mutable gesture bookkeeping. Kept in a ref (not state) so the window
|
|
364
|
+
// listeners below can be registered exactly once per gesture instead of
|
|
365
|
+
// re-subscribing on every pointermove.
|
|
366
|
+
const gestureRef = useRef<{
|
|
367
|
+
token: number;
|
|
368
|
+
frame: DragFrame;
|
|
369
|
+
mode: DragMode;
|
|
370
|
+
pointerId: number;
|
|
371
|
+
originX: number;
|
|
372
|
+
originY: number;
|
|
373
|
+
moved: boolean;
|
|
374
|
+
} | null>(null);
|
|
375
|
+
const previewRef = useRef<DragPreview | null>(null);
|
|
376
|
+
const suppressClickRef = useRef(false);
|
|
377
|
+
// Monotonic gesture id. A settling async commit from gesture N must not tear
|
|
378
|
+
// down gesture N+1 that the user has already started.
|
|
379
|
+
const tokenRef = useRef(0);
|
|
380
|
+
|
|
381
|
+
// Latest props, read by the window listeners without re-subscribing. Synced
|
|
382
|
+
// in an effect so the values the listeners see are always ones that actually
|
|
383
|
+
// committed.
|
|
384
|
+
const latest = useRef({
|
|
385
|
+
columns,
|
|
386
|
+
hourRowHeight,
|
|
387
|
+
baseMinutes,
|
|
388
|
+
visibleMinutes,
|
|
389
|
+
snap,
|
|
390
|
+
canDropEvent,
|
|
391
|
+
columnElements,
|
|
392
|
+
});
|
|
393
|
+
useEffect(() => {
|
|
394
|
+
latest.current = {
|
|
395
|
+
columns,
|
|
396
|
+
hourRowHeight,
|
|
397
|
+
baseMinutes,
|
|
398
|
+
visibleMinutes,
|
|
399
|
+
snap,
|
|
400
|
+
canDropEvent,
|
|
401
|
+
columnElements,
|
|
402
|
+
};
|
|
403
|
+
});
|
|
404
|
+
|
|
405
|
+
const setPreviewBoth = useCallback((next: DragPreview | null) => {
|
|
406
|
+
previewRef.current = next;
|
|
407
|
+
setPreview(next);
|
|
408
|
+
}, []);
|
|
409
|
+
|
|
410
|
+
/**
|
|
411
|
+
* Mark the click that follows a gesture as one to swallow.
|
|
412
|
+
*
|
|
413
|
+
* Only pointer-derived clicks ever consume this (see `HourGrid`): a keyboard
|
|
414
|
+
* activation produces a click with no preceding `pointerdown`, so nothing
|
|
415
|
+
* would reset the flag and a gesture abandoned without a pointerup would
|
|
416
|
+
* silently eat the user's next Enter press.
|
|
417
|
+
*/
|
|
418
|
+
const armClickSuppression = useCallback(() => {
|
|
419
|
+
suppressClickRef.current = true;
|
|
420
|
+
}, []);
|
|
421
|
+
|
|
422
|
+
const cfg = useCallback(
|
|
423
|
+
(): ProjectionConfig => ({
|
|
424
|
+
columns: latest.current.columns,
|
|
425
|
+
baseMinutes: latest.current.baseMinutes,
|
|
426
|
+
visibleMinutes: latest.current.visibleMinutes,
|
|
427
|
+
snap: latest.current.snap,
|
|
428
|
+
}),
|
|
429
|
+
[],
|
|
430
|
+
);
|
|
431
|
+
|
|
432
|
+
const toPayload = useCallback((p: DragPreview): CalendarEventMove => {
|
|
433
|
+
const column = latest.current.columns[p.columnIndex];
|
|
434
|
+
return {
|
|
435
|
+
startsAt: p.startsAt,
|
|
436
|
+
endsAt: p.endsAt,
|
|
437
|
+
resourceId: column?.resourceId ?? undefined,
|
|
438
|
+
};
|
|
439
|
+
}, []);
|
|
440
|
+
|
|
441
|
+
const validate = useCallback(
|
|
442
|
+
(p: DragPreview, frame: DragFrame): boolean => {
|
|
443
|
+
const check = latest.current.canDropEvent;
|
|
444
|
+
if (!check) return true;
|
|
445
|
+
const move = toPayload(p);
|
|
446
|
+
// A resize can't change the column, so the candidate it offers doesn't
|
|
447
|
+
// carry a `resourceId` the consumer might read and act on.
|
|
448
|
+
const candidate: CalendarDropCandidate =
|
|
449
|
+
p.mode === 'move'
|
|
450
|
+
? { mode: 'move', ...move }
|
|
451
|
+
: {
|
|
452
|
+
mode: 'resize',
|
|
453
|
+
startsAt: move.startsAt,
|
|
454
|
+
endsAt: move.endsAt,
|
|
455
|
+
// The lane it is in, not a lane it is moving to.
|
|
456
|
+
resourceId: move.resourceId,
|
|
457
|
+
};
|
|
458
|
+
return check(frame.event, candidate);
|
|
459
|
+
},
|
|
460
|
+
[toPayload],
|
|
461
|
+
);
|
|
462
|
+
|
|
463
|
+
/**
|
|
464
|
+
* End the live gesture. Always runs to completion for the gesture that owns
|
|
465
|
+
* the pointer — never token-guarded, because leaving `gestureRef` populated
|
|
466
|
+
* is what strands the hook: `dragging` stays true, the window listeners stay
|
|
467
|
+
* subscribed, and `nudge` refuses to start because it thinks a drag is live.
|
|
468
|
+
*/
|
|
469
|
+
const endGesture = useCallback(() => {
|
|
470
|
+
gestureRef.current = null;
|
|
471
|
+
setDragging(false);
|
|
472
|
+
}, []);
|
|
473
|
+
|
|
474
|
+
/**
|
|
475
|
+
* Drop the preview — but only if a newer gesture has not already taken over.
|
|
476
|
+
* This IS token-guarded: a settling async commit from gesture N must not
|
|
477
|
+
* wipe the preview gesture N+1 is currently drawing.
|
|
478
|
+
*/
|
|
479
|
+
const clearPreview = useCallback(
|
|
480
|
+
(token: number) => {
|
|
481
|
+
if (token !== tokenRef.current) return;
|
|
482
|
+
setPreviewBoth(null);
|
|
483
|
+
},
|
|
484
|
+
[setPreviewBoth],
|
|
485
|
+
);
|
|
486
|
+
|
|
487
|
+
const finish = useCallback(
|
|
488
|
+
(token: number) => {
|
|
489
|
+
endGesture();
|
|
490
|
+
clearPreview(token);
|
|
491
|
+
},
|
|
492
|
+
[endGesture, clearPreview],
|
|
493
|
+
);
|
|
494
|
+
|
|
495
|
+
/**
|
|
496
|
+
* A gesture thrown away rather than committed. The live region was narrating
|
|
497
|
+
* a specific new time as the user dragged; leaving that as the last thing
|
|
498
|
+
* announced tells a screen-reader user the event moved when it did not.
|
|
499
|
+
*/
|
|
500
|
+
const abandon = useCallback(
|
|
501
|
+
(g: { frame: DragFrame; moved: boolean }) => {
|
|
502
|
+
const p = previewRef.current;
|
|
503
|
+
if (g.moved && p) announce(g.frame.event, p, { cancelled: true });
|
|
504
|
+
},
|
|
505
|
+
[announce],
|
|
506
|
+
);
|
|
507
|
+
|
|
508
|
+
const commit = useCallback(
|
|
509
|
+
(p: DragPreview, frame: DragFrame, token: number) => {
|
|
510
|
+
// The gesture asked for something the bounds refused, so the proposal is
|
|
511
|
+
// identical to where the event already is. Say so, and do NOT call the
|
|
512
|
+
// handler — the documented consumer shape is an API write, and a drag
|
|
513
|
+
// that moved nothing must not fire one.
|
|
514
|
+
//
|
|
515
|
+
// Lives here rather than on the keyboard path so pointer drags get it
|
|
516
|
+
// too: a fully-clamped drag is just as much a no-op as a clamped nudge.
|
|
517
|
+
const currentEnd = frame.event.endsAt ?? frame.event.startsAt;
|
|
518
|
+
const sameInstants =
|
|
519
|
+
p.startsAt.getTime() === frame.event.startsAt.getTime() &&
|
|
520
|
+
p.endsAt.getTime() === currentEnd.getTime();
|
|
521
|
+
const sameCoordinates =
|
|
522
|
+
p.startMinutes === frame.startMinutes && p.endMinutes === frame.endMinutes;
|
|
523
|
+
// Both checks, because they catch different things. Coordinates catch a
|
|
524
|
+
// clamped gesture; instants catch one whose grid coordinates moved but
|
|
525
|
+
// whose projection landed back on the very same moment — which is what a
|
|
526
|
+
// daylight-saving collapse does, and it would otherwise fire the
|
|
527
|
+
// consumer's API write proposing the value the event already has.
|
|
528
|
+
if ((sameInstants || sameCoordinates) && p.columnIndex === frame.columnIndex) {
|
|
529
|
+
// Two different things end up here. `clamped` means the bounds refused
|
|
530
|
+
// to go further; otherwise the user simply released where they
|
|
531
|
+
// started, and telling them they "cannot move any further" would be
|
|
532
|
+
// nonsense.
|
|
533
|
+
announce(frame.event, p, p.clamped ? { atEdge: true } : { unchanged: true });
|
|
534
|
+
finish(token);
|
|
535
|
+
return;
|
|
536
|
+
}
|
|
537
|
+
// A refused placement never reaches the consumer.
|
|
538
|
+
if (!validate(p, frame)) {
|
|
539
|
+
announce(frame.event, p, { refused: true });
|
|
540
|
+
finish(token);
|
|
541
|
+
return;
|
|
542
|
+
}
|
|
543
|
+
const payload = toPayload(p);
|
|
544
|
+
const result: CalendarDropResult =
|
|
545
|
+
p.mode === 'move'
|
|
546
|
+
? onEventMove?.(frame.event, payload)
|
|
547
|
+
: onEventResize?.(frame.event, { startsAt: payload.startsAt, endsAt: payload.endsAt });
|
|
548
|
+
|
|
549
|
+
if (isThenable(result)) {
|
|
550
|
+
// Hold the preview until the consumer's async verdict lands, so an
|
|
551
|
+
// accepted drop doesn't flash back to its old slot while the request
|
|
552
|
+
// is in flight. The gesture itself is over.
|
|
553
|
+
endGesture();
|
|
554
|
+
result.then(
|
|
555
|
+
(verdict) => {
|
|
556
|
+
// Guarded on the token: a verdict that lands after the user has
|
|
557
|
+
// begun another gesture must not narrate the old placement over
|
|
558
|
+
// the new one.
|
|
559
|
+
if (token === tokenRef.current)
|
|
560
|
+
announce(frame.event, p, { refused: verdict === false });
|
|
561
|
+
clearPreview(token);
|
|
562
|
+
},
|
|
563
|
+
() => {
|
|
564
|
+
if (token === tokenRef.current) announce(frame.event, p, { refused: true });
|
|
565
|
+
clearPreview(token);
|
|
566
|
+
},
|
|
567
|
+
);
|
|
568
|
+
return;
|
|
569
|
+
}
|
|
570
|
+
announce(frame.event, p, { refused: result === false });
|
|
571
|
+
finish(token);
|
|
572
|
+
},
|
|
573
|
+
[validate, toPayload, onEventMove, onEventResize, finish, endGesture, clearPreview, announce],
|
|
574
|
+
);
|
|
575
|
+
|
|
576
|
+
const startPointerDrag = useCallback(
|
|
577
|
+
(block: TimedEventBlock, mode: DragMode, e: PointerEvent) => {
|
|
578
|
+
if (mode === 'move' && !canMove) return;
|
|
579
|
+
if (mode === 'resize' && !canResize) return;
|
|
580
|
+
// Primary button only — a right-click must not start a reschedule.
|
|
581
|
+
if (e.button !== 0) return;
|
|
582
|
+
e.stopPropagation();
|
|
583
|
+
suppressClickRef.current = false;
|
|
584
|
+
// A fresh press starts from the event's real position — never from a
|
|
585
|
+
// preview left behind by a previous gesture whose async commit has not
|
|
586
|
+
// settled yet.
|
|
587
|
+
setPreviewBoth(null);
|
|
588
|
+
tokenRef.current += 1;
|
|
589
|
+
gestureRef.current = {
|
|
590
|
+
token: tokenRef.current,
|
|
591
|
+
frame: frameOf(block, latest.current.baseMinutes),
|
|
592
|
+
mode,
|
|
593
|
+
pointerId: e.pointerId,
|
|
594
|
+
originX: e.clientX,
|
|
595
|
+
originY: e.clientY,
|
|
596
|
+
moved: false,
|
|
597
|
+
};
|
|
598
|
+
setDragging(true);
|
|
599
|
+
},
|
|
600
|
+
[canMove, canResize, setPreviewBoth],
|
|
601
|
+
);
|
|
602
|
+
|
|
603
|
+
// One subscription per gesture. Listening on `window` (rather than relying
|
|
604
|
+
// on pointer capture alone) keeps the drag alive when the pointer leaves
|
|
605
|
+
// the grid — including drops outside it, which simply resolve to the last
|
|
606
|
+
// valid column.
|
|
607
|
+
useEffect(() => {
|
|
608
|
+
if (!dragging) return;
|
|
609
|
+
|
|
610
|
+
const handleMove = (e: globalThis.PointerEvent) => {
|
|
611
|
+
const g = gestureRef.current;
|
|
612
|
+
if (!g || e.pointerId !== g.pointerId) return;
|
|
613
|
+
const dy = e.clientY - g.originY;
|
|
614
|
+
const dx = e.clientX - g.originX;
|
|
615
|
+
if (!g.moved && Math.abs(dy) < DRAG_THRESHOLD_PX && Math.abs(dx) < DRAG_THRESHOLD_PX) return;
|
|
616
|
+
g.moved = true;
|
|
617
|
+
|
|
618
|
+
const { hourRowHeight: rowH, columns: cols, columnElements: els } = latest.current;
|
|
619
|
+
const rawDelta = (dy / rowH) * 60;
|
|
620
|
+
|
|
621
|
+
let columnIndex = previewRef.current?.columnIndex ?? g.frame.columnIndex;
|
|
622
|
+
if (g.mode === 'move') {
|
|
623
|
+
// Hit-test the columns horizontally so a drag can cross into another
|
|
624
|
+
// day (week view) or another resource lane (resource day view).
|
|
625
|
+
const hit = els.current.findIndex((el) => {
|
|
626
|
+
if (!el) return false;
|
|
627
|
+
const r = el.getBoundingClientRect();
|
|
628
|
+
// A zero-width rect means the element has no layout to hit-test
|
|
629
|
+
// against; treating that as "no hit" keeps the column stable.
|
|
630
|
+
if (r.width === 0) return false;
|
|
631
|
+
return e.clientX >= r.left && e.clientX < r.right;
|
|
632
|
+
});
|
|
633
|
+
if (hit !== -1) columnIndex = hit;
|
|
634
|
+
}
|
|
635
|
+
if (columnIndex < 0 || columnIndex >= cols.length) columnIndex = g.frame.columnIndex;
|
|
636
|
+
|
|
637
|
+
const target =
|
|
638
|
+
g.mode === 'move' ? g.frame.startMinutes + rawDelta : g.frame.endMinutes + rawDelta;
|
|
639
|
+
const next = project(g.frame, g.mode, columnIndex, target, cfg());
|
|
640
|
+
next.invalid = !validate(next, g.frame);
|
|
641
|
+
const prev = previewRef.current;
|
|
642
|
+
setPreviewBoth(next);
|
|
643
|
+
// Announce only when the proposed slot actually changed — snapping makes
|
|
644
|
+
// that discrete, so this is one message per slot rather than per pixel.
|
|
645
|
+
if (
|
|
646
|
+
!prev ||
|
|
647
|
+
prev.startMinutes !== next.startMinutes ||
|
|
648
|
+
prev.endMinutes !== next.endMinutes ||
|
|
649
|
+
prev.columnIndex !== next.columnIndex ||
|
|
650
|
+
prev.invalid !== next.invalid
|
|
651
|
+
) {
|
|
652
|
+
announce(g.frame.event, next, { refused: next.invalid });
|
|
653
|
+
}
|
|
654
|
+
};
|
|
655
|
+
|
|
656
|
+
const handleUp = (e: globalThis.PointerEvent) => {
|
|
657
|
+
const g = gestureRef.current;
|
|
658
|
+
if (!g || e.pointerId !== g.pointerId) return;
|
|
659
|
+
const p = previewRef.current;
|
|
660
|
+
if (!g.moved || !p) {
|
|
661
|
+
// A press that never crossed the threshold is a click, not a drag.
|
|
662
|
+
finish(g.token);
|
|
663
|
+
return;
|
|
664
|
+
}
|
|
665
|
+
// The click event that follows this pointerup would otherwise open the
|
|
666
|
+
// consumer's detail UI — or, if the pointer ended over the column
|
|
667
|
+
// background rather than the block, create a booking at that slot.
|
|
668
|
+
armClickSuppression();
|
|
669
|
+
commit(p, g.frame, g.token);
|
|
670
|
+
};
|
|
671
|
+
|
|
672
|
+
const handleCancel = (e: globalThis.PointerEvent) => {
|
|
673
|
+
const g = gestureRef.current;
|
|
674
|
+
if (!g || e.pointerId !== g.pointerId) return;
|
|
675
|
+
// Pointer cancellation (a system gesture taking over, the element being
|
|
676
|
+
// removed) discards the drag without proposing anything. Say so: the
|
|
677
|
+
// last thing announced was a specific new time, and leaving that
|
|
678
|
+
// standing tells a screen-reader user the event moved when it didn't.
|
|
679
|
+
abandon(g);
|
|
680
|
+
finish(g.token);
|
|
681
|
+
};
|
|
682
|
+
|
|
683
|
+
// Last-resort recovery. There is no pointer capture here, so a `pointerup`
|
|
684
|
+
// delivered to another document — an alt-tab mid-drag, a devtools
|
|
685
|
+
// detach — never reaches us, and the gesture would otherwise stay live
|
|
686
|
+
// forever, taking the keyboard path down with it.
|
|
687
|
+
const handleBlur = () => {
|
|
688
|
+
const g = gestureRef.current;
|
|
689
|
+
if (!g) return;
|
|
690
|
+
// Arm the suppression the same way `handleUp` does. Without it the
|
|
691
|
+
// `pointerup` that follows finds no gesture, its `click` reaches the
|
|
692
|
+
// column, and the consumer's "create a booking here" UI opens at the
|
|
693
|
+
// spot the user was dragging to. Unlike `handleUp` there is no
|
|
694
|
+
// guarantee a click ever arrives to consume the flag, so it is armed
|
|
695
|
+
// with an expiry — see `armClickSuppression`.
|
|
696
|
+
if (g.moved) armClickSuppression();
|
|
697
|
+
abandon(g);
|
|
698
|
+
finish(g.token);
|
|
699
|
+
};
|
|
700
|
+
|
|
701
|
+
window.addEventListener('pointermove', handleMove);
|
|
702
|
+
window.addEventListener('pointerup', handleUp);
|
|
703
|
+
window.addEventListener('pointercancel', handleCancel);
|
|
704
|
+
window.addEventListener('blur', handleBlur);
|
|
705
|
+
return () => {
|
|
706
|
+
window.removeEventListener('pointermove', handleMove);
|
|
707
|
+
window.removeEventListener('pointerup', handleUp);
|
|
708
|
+
window.removeEventListener('pointercancel', handleCancel);
|
|
709
|
+
window.removeEventListener('blur', handleBlur);
|
|
710
|
+
};
|
|
711
|
+
}, [
|
|
712
|
+
dragging,
|
|
713
|
+
commit,
|
|
714
|
+
finish,
|
|
715
|
+
setPreviewBoth,
|
|
716
|
+
validate,
|
|
717
|
+
cfg,
|
|
718
|
+
announce,
|
|
719
|
+
abandon,
|
|
720
|
+
armClickSuppression,
|
|
721
|
+
]);
|
|
722
|
+
|
|
723
|
+
const nudge = useCallback(
|
|
724
|
+
(block: TimedEventBlock, delta: { mode: DragMode; steps: number; columns?: number }) => {
|
|
725
|
+
if (delta.mode === 'move' && !canMove) return;
|
|
726
|
+
if (delta.mode === 'resize' && !canResize) return;
|
|
727
|
+
// A pointer gesture already owns the hook (pointerdown focused the
|
|
728
|
+
// block, so a keypress can land mid-drag). Bumping the token here would
|
|
729
|
+
// orphan that gesture: its pointerup would no-op and leave the grid
|
|
730
|
+
// stuck in the dragging state.
|
|
731
|
+
if (gestureRef.current) return;
|
|
732
|
+
|
|
733
|
+
const frame = frameOf(block, latest.current.baseMinutes);
|
|
734
|
+
const step = latest.current.snap;
|
|
735
|
+
const columnIndex = clamp(
|
|
736
|
+
frame.columnIndex + (delta.columns ?? 0),
|
|
737
|
+
0,
|
|
738
|
+
Math.max(0, latest.current.columns.length - 1),
|
|
739
|
+
);
|
|
740
|
+
|
|
741
|
+
// A pure column change must not also re-snap the time — moving a 09:07
|
|
742
|
+
// booking to the next lane should leave it at 09:07.
|
|
743
|
+
const base = delta.mode === 'move' ? frame.startMinutes : frame.endMinutes;
|
|
744
|
+
const pureColumnChange = delta.steps === 0;
|
|
745
|
+
const target = pureColumnChange ? base : snapTo(base, step) + delta.steps * step;
|
|
746
|
+
// `presnapped` for a pure column change, or `project` would re-snap the
|
|
747
|
+
// untouched time and quietly drag a 09:07 booking to 09:00.
|
|
748
|
+
const next = project(frame, delta.mode, columnIndex, target, cfg(), pureColumnChange);
|
|
749
|
+
|
|
750
|
+
tokenRef.current += 1;
|
|
751
|
+
// `commit` owns the no-op check, so a clamped nudge and a clamped drag
|
|
752
|
+
// report identically.
|
|
753
|
+
commit(next, frame, tokenRef.current);
|
|
754
|
+
},
|
|
755
|
+
[canMove, canResize, commit, cfg],
|
|
756
|
+
);
|
|
757
|
+
|
|
758
|
+
const consumeClickSuppression = useCallback(() => {
|
|
759
|
+
const was = suppressClickRef.current;
|
|
760
|
+
suppressClickRef.current = false;
|
|
761
|
+
return was;
|
|
762
|
+
}, []);
|
|
763
|
+
|
|
764
|
+
// A drop released outside the grid produces no click at all, so the flag
|
|
765
|
+
// would otherwise survive to swallow the next genuine one. Every fresh
|
|
766
|
+
// press clears it — including presses that start no gesture (a plain click,
|
|
767
|
+
// or a press on a block in a resize-only grid).
|
|
768
|
+
const resetClickSuppression = useCallback(() => {
|
|
769
|
+
suppressClickRef.current = false;
|
|
770
|
+
}, []);
|
|
771
|
+
|
|
772
|
+
return {
|
|
773
|
+
preview,
|
|
774
|
+
announcement,
|
|
775
|
+
dragging,
|
|
776
|
+
canMove,
|
|
777
|
+
canResize,
|
|
778
|
+
startPointerDrag,
|
|
779
|
+
nudge,
|
|
780
|
+
consumeClickSuppression,
|
|
781
|
+
resetClickSuppression,
|
|
782
|
+
};
|
|
783
|
+
}
|