@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,415 @@
|
|
|
1
|
+
// selection.ts — map between the contentEditable DOM and model positions. All
|
|
2
|
+
// functions take the editable root element; block elements carry `data-block-id`.
|
|
3
|
+
import type { Point, Range } from '../RichText/engine/model';
|
|
4
|
+
|
|
5
|
+
/** Selector + predicate for atomic inline widgets (renderLink / renderMention
|
|
6
|
+
* substitutions): contentEditable=false nodes whose display text may differ from
|
|
7
|
+
* the `data-len` MODEL length they represent. */
|
|
8
|
+
const ATOMIC_WIDGET_SELECTOR = '[data-rich-link],[data-rich-mention]';
|
|
9
|
+
function isAtomicWidget(el: HTMLElement): boolean {
|
|
10
|
+
return el.hasAttribute('data-rich-link') || el.hasAttribute('data-rich-mention');
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/** The MODEL length an atomic widget (`[data-rich-link]` / `[data-rich-mention]`)
|
|
14
|
+
* represents. Falls back to its display-text length if `data-len` is
|
|
15
|
+
* missing/non-numeric (so a malformed widget degrades gracefully instead of
|
|
16
|
+
* poisoning the offset math with NaN). */
|
|
17
|
+
function widgetLen(w: HTMLElement): number {
|
|
18
|
+
const n = Number(w.dataset.len);
|
|
19
|
+
return Number.isFinite(n) ? n : (w.textContent?.length ?? 0);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function blockElementFor(root: HTMLElement, node: Node): HTMLElement | null {
|
|
23
|
+
let el: Node | null = node.nodeType === Node.TEXT_NODE ? node.parentNode : node;
|
|
24
|
+
while (el && el !== root) {
|
|
25
|
+
if (el instanceof HTMLElement && el.hasAttribute('data-block-id')) return el;
|
|
26
|
+
el = el.parentNode;
|
|
27
|
+
}
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* A void attachment block element (`<figure data-block-id>`), or null. The
|
|
33
|
+
* `data-block-id` guard is load-bearing: callers rely on it to safely read the id.
|
|
34
|
+
* In editable mode renderDoc emits `<figure>` ONLY for attachment blocks and always
|
|
35
|
+
* stamps `data-block-id`, so every `data-block-id` figure in the editor is an
|
|
36
|
+
* attachment — the tag + attribute check is sufficient.
|
|
37
|
+
*/
|
|
38
|
+
function figureBlock(node: Node | null | undefined): HTMLElement | null {
|
|
39
|
+
return node instanceof HTMLElement &&
|
|
40
|
+
node.tagName === 'FIGURE' &&
|
|
41
|
+
node.hasAttribute('data-block-id')
|
|
42
|
+
? node
|
|
43
|
+
: null;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** The first/last block element at or inside a root-level child node — the node
|
|
47
|
+
* itself when it IS a block, else its first/last `[data-block-id]` descendant
|
|
48
|
+
* (a `<ul>`/`<ol>` wrapper contains its `<li data-block-id>` items). Null for
|
|
49
|
+
* text/absent nodes. */
|
|
50
|
+
function blockWithin(node: Node | undefined, which: 'first' | 'last'): HTMLElement | null {
|
|
51
|
+
if (!(node instanceof HTMLElement)) return null;
|
|
52
|
+
if (node.hasAttribute('data-block-id')) return node;
|
|
53
|
+
const all = node.querySelectorAll<HTMLElement>('[data-block-id]');
|
|
54
|
+
return all.length > 0 ? all[which === 'first' ? 0 : all.length - 1] : null;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Character offset within `blockEl` of the DOM position `(node, offset)`. Works
|
|
59
|
+
* for both text-node boundaries (offset = char index) and element-node
|
|
60
|
+
* boundaries (offset = child index, e.g. a caret at an inline-element edge,
|
|
61
|
+
* after a `<br>`, or a word/line selection) — the browser Selection API
|
|
62
|
+
* produces both. Measured as the length of the text between the block start and
|
|
63
|
+
* the point via a DOM Range, which counts only character data (so `<br>`,
|
|
64
|
+
* empty blocks, and nested mark spans are all handled correctly).
|
|
65
|
+
*
|
|
66
|
+
* Atomic widgets (`[data-rich-link]` / `[data-rich-mention]`) are corrected for:
|
|
67
|
+
* such a widget renders DISPLAY text (e.g. "#1 Task") but represents `data-len`
|
|
68
|
+
* MODEL chars (e.g. the URL it links). `range.toString()` counts the display
|
|
69
|
+
* text, so for every widget that lies fully before the range end we add
|
|
70
|
+
* `data-len - displayLength`. When a block contains no atomic widget the
|
|
71
|
+
* correction loop runs zero times, so this is an exact no-op for ordinary content.
|
|
72
|
+
*/
|
|
73
|
+
function offsetWithinBlock(blockEl: HTMLElement, node: Node, offset: number): number {
|
|
74
|
+
const doc = blockEl.ownerDocument;
|
|
75
|
+
const range = doc.createRange();
|
|
76
|
+
range.setStart(blockEl, 0);
|
|
77
|
+
try {
|
|
78
|
+
range.setEnd(node, offset);
|
|
79
|
+
} catch {
|
|
80
|
+
return 0; // node not inside blockEl (shouldn't happen) → block start
|
|
81
|
+
}
|
|
82
|
+
let len = range.toString().length;
|
|
83
|
+
for (const w of blockEl.querySelectorAll<HTMLElement>(ATOMIC_WIDGET_SELECTOR)) {
|
|
84
|
+
// The widget's display text is included in `len` iff our range end is at or
|
|
85
|
+
// past the point immediately AFTER the widget. Compare our range's END
|
|
86
|
+
// boundary against a collapsed range positioned just after the widget:
|
|
87
|
+
// compareBoundaryPoints(END_TO_END, afterW) returns where our END sits
|
|
88
|
+
// relative to afterW's (collapsed) boundary — >= 0 means our end is at/after
|
|
89
|
+
// the point just past the widget, so the whole widget falls within
|
|
90
|
+
// [blockStart, (node,offset)] and we replace its display length with data-len.
|
|
91
|
+
// (END_TO_END, not END_TO_START: the latter compares against afterW's START
|
|
92
|
+
// and mis-classifies the exact-boundary case in jsdom.)
|
|
93
|
+
const afterW = doc.createRange();
|
|
94
|
+
afterW.setStartAfter(w);
|
|
95
|
+
afterW.collapse(true);
|
|
96
|
+
if (range.compareBoundaryPoints(Range.END_TO_END, afterW) >= 0) {
|
|
97
|
+
const declared = widgetLen(w);
|
|
98
|
+
const shown = w.textContent?.length ?? 0;
|
|
99
|
+
len += declared - shown;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
return len;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Map a DOM `(node, offset)` to a model `Point`, or `null` if outside any block.
|
|
107
|
+
*
|
|
108
|
+
* @param root - The contentEditable root element.
|
|
109
|
+
* @param node - The DOM node from a `Selection` or `Range` boundary.
|
|
110
|
+
* @param offset - Character offset within `node` (for text nodes) or child index
|
|
111
|
+
* (for element nodes, as the browser Selection API may return).
|
|
112
|
+
* @returns The model `Point`, or `null` when `node` is not inside a block
|
|
113
|
+
* element with a `data-block-id` attribute.
|
|
114
|
+
*/
|
|
115
|
+
export function pointFromDom(root: HTMLElement, node: Node, offset: number): Point | null {
|
|
116
|
+
// Root-level caret (between blocks): if it sits at the boundary of a void
|
|
117
|
+
// figure, resolve to that void's {id, 0}. (A void can't host an interior caret,
|
|
118
|
+
// so the browser anchors the selection on the root around it.)
|
|
119
|
+
if (node === root) {
|
|
120
|
+
const kids = root.childNodes; // root Selection offsets count ALL child nodes
|
|
121
|
+
// Prefer the figure just AFTER the caret (caret-before-next-node), else the
|
|
122
|
+
// one just before it. `kids[-1]` (offset 0) is undefined → figureBlock null.
|
|
123
|
+
const fig = figureBlock(kids[offset]) ?? figureBlock(kids[offset - 1]);
|
|
124
|
+
if (fig) return { blockId: fig.getAttribute('data-block-id')!, offset: 0 };
|
|
125
|
+
}
|
|
126
|
+
const blockEl = blockElementFor(root, node);
|
|
127
|
+
if (blockEl) {
|
|
128
|
+
// A void figure block has no text — its only position is offset 0. (A caret on a
|
|
129
|
+
// figure descendant ascends to the figure via blockElementFor.)
|
|
130
|
+
if (blockEl.tagName === 'FIGURE') {
|
|
131
|
+
return { blockId: blockEl.getAttribute('data-block-id')!, offset: 0 };
|
|
132
|
+
}
|
|
133
|
+
return {
|
|
134
|
+
blockId: blockEl.getAttribute('data-block-id')!,
|
|
135
|
+
offset: offsetWithinBlock(blockEl, node, offset),
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
// Container-level caret: an element inside the editor with no block ancestor —
|
|
139
|
+
// the root itself or a <ul>/<ol> list wrapper (the only elements renderDoc puts
|
|
140
|
+
// between root and blocks). This happens when the block the selection lived in
|
|
141
|
+
// is removed from the DOM — e.g. a controlled external value replacement
|
|
142
|
+
// (parent setDoc(emptyDoc())) swaps the block elements, and the DOM collapses
|
|
143
|
+
// the live selection boundary to (container, childIndex). Resolve to the start
|
|
144
|
+
// of the next block, else the end of the previous one. Returning null here
|
|
145
|
+
// would make readSelection null, and the beforeinput handler would cede the
|
|
146
|
+
// keystroke to the browser — which then mutates the contentEditable DOM
|
|
147
|
+
// outside the model (raw text over the re-rendered content, onChange
|
|
148
|
+
// stalled). See #321.
|
|
149
|
+
if (node instanceof HTMLElement && (node === root || root.contains(node))) {
|
|
150
|
+
const kids = node.childNodes;
|
|
151
|
+
const next = blockWithin(kids[offset], 'first');
|
|
152
|
+
if (next) return { blockId: next.getAttribute('data-block-id')!, offset: 0 };
|
|
153
|
+
const prev = blockWithin(kids[offset - 1], 'last');
|
|
154
|
+
if (prev) {
|
|
155
|
+
// A figure here is unreachable (root-adjacent figures are caught by the
|
|
156
|
+
// figureBlock check above; figures never nest in list wrappers), so the
|
|
157
|
+
// end offset is always the block's widget-corrected full model length.
|
|
158
|
+
return {
|
|
159
|
+
blockId: prev.getAttribute('data-block-id')!,
|
|
160
|
+
offset: offsetWithinBlock(prev, prev, prev.childNodes.length),
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
return null;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Map a model `Point` to a DOM `{ node, offset }` inside the editable root.
|
|
169
|
+
*
|
|
170
|
+
* @param root - The contentEditable root element.
|
|
171
|
+
* @param point - The model position to locate in the DOM.
|
|
172
|
+
* @returns `{ node, offset }` suitable for `Range.setStart`/`setEnd`, or `null`
|
|
173
|
+
* when the block is not found in the DOM (e.g. after an async re-render).
|
|
174
|
+
*/
|
|
175
|
+
export function pointToDom(root: HTMLElement, point: Point): { node: Node; offset: number } | null {
|
|
176
|
+
const blockEl = root.querySelector<HTMLElement>(`[data-block-id="${CSS.escape(point.blockId)}"]`);
|
|
177
|
+
if (!blockEl) return null;
|
|
178
|
+
// Void figure: the caret can't go inside — return a position just before it in
|
|
179
|
+
// its parent (the editable root), so the browser shows an edge caret.
|
|
180
|
+
if (blockEl.tagName === 'FIGURE') {
|
|
181
|
+
const parent = blockEl.parentNode!;
|
|
182
|
+
const index = Array.prototype.indexOf.call(parent.childNodes, blockEl);
|
|
183
|
+
return { node: parent, offset: index };
|
|
184
|
+
}
|
|
185
|
+
// Walk TEXT nodes and atomic widgets (`[data-rich-link]` / `[data-rich-mention]`),
|
|
186
|
+
// accumulating model length: text → its char length, widget → `data-len` (NOT its
|
|
187
|
+
// display text; we never descend into a widget). A widget is contenteditable=false,
|
|
188
|
+
// so the caret can only sit adjacent to it — when `remaining` lands at a widget we
|
|
189
|
+
// return a point in the widget's PARENT at the widget's child index (before)
|
|
190
|
+
// or +1 (after), never inside it. With no widgets present this behaves exactly
|
|
191
|
+
// like the previous text-only TreeWalker (FILTER_SKIP descends into mark spans).
|
|
192
|
+
const walker = blockEl.ownerDocument.createTreeWalker(
|
|
193
|
+
blockEl,
|
|
194
|
+
NodeFilter.SHOW_TEXT | NodeFilter.SHOW_ELEMENT,
|
|
195
|
+
{
|
|
196
|
+
acceptNode(n) {
|
|
197
|
+
if (n instanceof HTMLElement && isAtomicWidget(n)) return NodeFilter.FILTER_ACCEPT; // the widget itself, atomic
|
|
198
|
+
// Reject the widget's contents so the walk treats it as one opaque unit
|
|
199
|
+
// (a TreeWalker descends into an ACCEPTED element, so we must REJECT its
|
|
200
|
+
// descendants — REJECT skips the node AND its subtree, unlike SKIP).
|
|
201
|
+
let p: Node | null = n.parentNode;
|
|
202
|
+
while (p && p !== blockEl) {
|
|
203
|
+
if (p instanceof HTMLElement && isAtomicWidget(p)) return NodeFilter.FILTER_REJECT;
|
|
204
|
+
p = p.parentNode;
|
|
205
|
+
}
|
|
206
|
+
if (n.nodeType === Node.TEXT_NODE) return NodeFilter.FILTER_ACCEPT;
|
|
207
|
+
return NodeFilter.FILTER_SKIP; // descend into ordinary inline elements
|
|
208
|
+
},
|
|
209
|
+
},
|
|
210
|
+
);
|
|
211
|
+
let remaining = point.offset;
|
|
212
|
+
let last: Text | null = null;
|
|
213
|
+
let n = walker.nextNode();
|
|
214
|
+
while (n) {
|
|
215
|
+
if (n.nodeType === Node.TEXT_NODE) {
|
|
216
|
+
const t = n as Text;
|
|
217
|
+
const len = t.textContent?.length ?? 0;
|
|
218
|
+
if (remaining <= len) return { node: t, offset: remaining };
|
|
219
|
+
remaining -= len;
|
|
220
|
+
last = t;
|
|
221
|
+
} else {
|
|
222
|
+
const w = n as HTMLElement;
|
|
223
|
+
const parent = w.parentNode!;
|
|
224
|
+
const index = Array.prototype.indexOf.call(parent.childNodes, w);
|
|
225
|
+
const declared = widgetLen(w);
|
|
226
|
+
// At the widget's leading edge → caret just before it.
|
|
227
|
+
if (remaining <= 0) return { node: parent, offset: index };
|
|
228
|
+
// Inside the widget's model span (incl. its trailing edge) → caret after it.
|
|
229
|
+
if (remaining <= declared) return { node: parent, offset: index + 1 };
|
|
230
|
+
remaining -= declared;
|
|
231
|
+
last = null; // can't anchor a leftover offset to a non-text widget
|
|
232
|
+
}
|
|
233
|
+
n = walker.nextNode();
|
|
234
|
+
}
|
|
235
|
+
if (last) return { node: last, offset: last.textContent?.length ?? 0 };
|
|
236
|
+
return { node: blockEl, offset: 0 }; // empty block (only a <br>)
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* Read the current DOM selection as a model `Range`, or `null` if not inside `root`.
|
|
241
|
+
*
|
|
242
|
+
* @param root - The contentEditable root element.
|
|
243
|
+
* @returns The model `Range`, or `null` when the selection is outside the root
|
|
244
|
+
* or the document has no selection.
|
|
245
|
+
*/
|
|
246
|
+
export function readSelection(root: HTMLElement): Range | null {
|
|
247
|
+
const sel = root.ownerDocument.getSelection();
|
|
248
|
+
if (!sel || sel.rangeCount === 0 || !sel.anchorNode || !sel.focusNode) return null;
|
|
249
|
+
if (!root.contains(sel.anchorNode) || !root.contains(sel.focusNode)) return null;
|
|
250
|
+
const anchor = pointFromDom(root, sel.anchorNode, sel.anchorOffset);
|
|
251
|
+
const focus = pointFromDom(root, sel.focusNode, sel.focusOffset);
|
|
252
|
+
return anchor && focus ? { anchor, focus } : null;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/**
|
|
256
|
+
* Set the DOM selection from a model `Range`.
|
|
257
|
+
*
|
|
258
|
+
* @param root - The contentEditable root element.
|
|
259
|
+
* @param range - The model range to restore. When anchor === focus, sets a
|
|
260
|
+
* collapsed caret; otherwise extends the selection directionally.
|
|
261
|
+
*/
|
|
262
|
+
export function writeSelection(root: HTMLElement, range: Range): void {
|
|
263
|
+
const sel = root.ownerDocument.getSelection();
|
|
264
|
+
if (!sel) return;
|
|
265
|
+
const a = pointToDom(root, range.anchor);
|
|
266
|
+
const f = pointToDom(root, range.focus);
|
|
267
|
+
if (!a || !f) return;
|
|
268
|
+
const domRange = root.ownerDocument.createRange();
|
|
269
|
+
domRange.setStart(a.node, a.offset);
|
|
270
|
+
domRange.setEnd(a.node, a.offset);
|
|
271
|
+
sel.removeAllRanges();
|
|
272
|
+
sel.addRange(domRange);
|
|
273
|
+
try {
|
|
274
|
+
sel.extend(f.node, f.offset);
|
|
275
|
+
} catch {
|
|
276
|
+
// some environments (jsdom) have a partial Selection — collapsed caret is fine
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
/** A viewport rect (subset of DOMRect) used to anchor floating UI to a selection. */
|
|
281
|
+
export type Rect = { top: number; left: number; height: number; width: number };
|
|
282
|
+
|
|
283
|
+
/**
|
|
284
|
+
* The viewport rect to anchor floating UI (the link editor) to the current DOM
|
|
285
|
+
* selection. Prefers the selection's own rect; when that's degenerate — a collapsed
|
|
286
|
+
* caret in an EMPTY block returns an all-zero rect — it falls back to the caret's
|
|
287
|
+
* BLOCK element (the active line), so an anchored popover opens beside that line.
|
|
288
|
+
* Only if there's no block (caret directly in root) does it fall back to `root`.
|
|
289
|
+
* (The old root fallback used the editor's full height, which dropped the link
|
|
290
|
+
* popover below the whole document — visible via `toolbar="auto"` + Link on an
|
|
291
|
+
* empty composer.)
|
|
292
|
+
*/
|
|
293
|
+
export function selectionRect(root: HTMLElement): Rect {
|
|
294
|
+
const sel = typeof window !== 'undefined' ? window.getSelection() : null;
|
|
295
|
+
if (sel && sel.rangeCount > 0) {
|
|
296
|
+
const range = sel.getRangeAt(0);
|
|
297
|
+
let r: DOMRect | null = null;
|
|
298
|
+
try {
|
|
299
|
+
r = range.getBoundingClientRect();
|
|
300
|
+
} catch {
|
|
301
|
+
// jsdom does not implement Range.getBoundingClientRect — fall through.
|
|
302
|
+
}
|
|
303
|
+
if (r && (r.width || r.height || r.top || r.left)) {
|
|
304
|
+
return { top: r.top, left: r.left, width: r.width, height: r.height };
|
|
305
|
+
}
|
|
306
|
+
// Degenerate selection rect (e.g. collapsed caret in an empty block). Anchor to
|
|
307
|
+
// the caret's block element (the active line), not the whole editor.
|
|
308
|
+
const blockEl = blockElementFor(root, range.startContainer);
|
|
309
|
+
if (blockEl) {
|
|
310
|
+
const b = blockEl.getBoundingClientRect();
|
|
311
|
+
return { top: b.top, left: b.left, width: b.width, height: b.height };
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
const rr = root.getBoundingClientRect();
|
|
315
|
+
return { top: rr.top, left: rr.left, width: 0, height: rr.height };
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
/**
|
|
319
|
+
* The viewport rect of a MODEL range, re-derived from the DOM live. Unlike
|
|
320
|
+
* `selectionRect` (which reads the window selection), this is independent of the
|
|
321
|
+
* current selection — so a popover that has stolen focus (the link editor) can
|
|
322
|
+
* still track its original anchor line on scroll by re-measuring on each
|
|
323
|
+
* reposition. Returns null when the range can't be mapped to the DOM. Falls back
|
|
324
|
+
* to the anchor's block element (the active line) when the range rect is
|
|
325
|
+
* degenerate (a collapsed caret in an empty block).
|
|
326
|
+
*/
|
|
327
|
+
export function rangeRect(root: HTMLElement, range: Range): Rect | null {
|
|
328
|
+
const a = pointToDom(root, range.anchor);
|
|
329
|
+
const f = pointToDom(root, range.focus);
|
|
330
|
+
if (!a || !f) return null;
|
|
331
|
+
// Order the two boundary points by document position before building the DOM
|
|
332
|
+
// range. A backward (right-to-left) model selection has its anchor AFTER its
|
|
333
|
+
// focus; feeding setStart(anchor)/setEnd(focus) straight through would not throw —
|
|
334
|
+
// the DOM clamps end-before-start to a COLLAPSED range, which measures as a zero
|
|
335
|
+
// rect and would wrongly degrade to the block fallback below. Pick start = the
|
|
336
|
+
// earlier point so the span rect is correct regardless of selection direction.
|
|
337
|
+
const startFirst =
|
|
338
|
+
a.node === f.node
|
|
339
|
+
? a.offset <= f.offset
|
|
340
|
+
: !!(a.node.compareDocumentPosition(f.node) & Node.DOCUMENT_POSITION_FOLLOWING);
|
|
341
|
+
const [s, e] = startFirst ? [a, f] : [f, a];
|
|
342
|
+
const domRange = root.ownerDocument.createRange();
|
|
343
|
+
try {
|
|
344
|
+
domRange.setStart(s.node, s.offset);
|
|
345
|
+
domRange.setEnd(e.node, e.offset);
|
|
346
|
+
} catch {
|
|
347
|
+
return null;
|
|
348
|
+
}
|
|
349
|
+
let r: DOMRect | null = null;
|
|
350
|
+
try {
|
|
351
|
+
r = domRange.getBoundingClientRect();
|
|
352
|
+
} catch {
|
|
353
|
+
// jsdom does not implement Range.getBoundingClientRect — fall through.
|
|
354
|
+
}
|
|
355
|
+
if (r && (r.width || r.height || r.top || r.left)) {
|
|
356
|
+
return { top: r.top, left: r.left, width: r.width, height: r.height };
|
|
357
|
+
}
|
|
358
|
+
const blockEl = blockElementFor(root, a.node);
|
|
359
|
+
if (blockEl) {
|
|
360
|
+
const b = blockEl.getBoundingClientRect();
|
|
361
|
+
return { top: b.top, left: b.left, width: b.width, height: b.height };
|
|
362
|
+
}
|
|
363
|
+
return null;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
/**
|
|
367
|
+
* Which side of a void block's figure the native caret sits on, or null when it
|
|
368
|
+
* isn't beside/inside that figure. `inside` = the caret is WITHIN the
|
|
369
|
+
* `contentEditable=false` figure, where browsers fire no `beforeinput` (the
|
|
370
|
+
* editor must handle keys itself); otherwise it's a container-level caret next
|
|
371
|
+
* to the figure (e.g. where writeSelection parks it), which edits normally.
|
|
372
|
+
*/
|
|
373
|
+
export function voidCaretSide(
|
|
374
|
+
root: HTMLElement,
|
|
375
|
+
blockId: string,
|
|
376
|
+
): { side: 'before' | 'after'; inside: boolean } | null {
|
|
377
|
+
const sel = root.ownerDocument.getSelection();
|
|
378
|
+
const fig = figureBlock(
|
|
379
|
+
root.querySelector<HTMLElement>(`[data-block-id="${CSS.escape(blockId)}"]`),
|
|
380
|
+
);
|
|
381
|
+
const node = sel?.anchorNode;
|
|
382
|
+
if (!sel || !node || !fig) return null;
|
|
383
|
+
if (fig.contains(node)) {
|
|
384
|
+
// "Before" only at the very start of the figure: offset 0 on a node that is
|
|
385
|
+
// the first descendant chain of the figure (no content precedes it).
|
|
386
|
+
let before = sel.anchorOffset === 0;
|
|
387
|
+
for (let n: Node | null = node; before && n && n !== fig; n = n.parentNode) {
|
|
388
|
+
if (n.previousSibling) before = false;
|
|
389
|
+
}
|
|
390
|
+
return { side: before ? 'before' : 'after', inside: true };
|
|
391
|
+
}
|
|
392
|
+
const kids = node.childNodes;
|
|
393
|
+
if (kids[sel.anchorOffset] === fig) return { side: 'before', inside: false };
|
|
394
|
+
if (kids[sel.anchorOffset - 1] === fig) return { side: 'after', inside: false };
|
|
395
|
+
return null;
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
/**
|
|
399
|
+
* A block's OWN text as rendered in the DOM, in MODEL offsets: nested child
|
|
400
|
+
* blocks (a list item's sublist) are skipped and each atomic widget counts as
|
|
401
|
+
* `data-len` placeholder chars. Null when the block isn't rendered. Diffing two
|
|
402
|
+
* snapshots of it locates what an IME composition changed.
|
|
403
|
+
*/
|
|
404
|
+
export function blockOwnText(root: HTMLElement, blockId: string): string | null {
|
|
405
|
+
const el = root.querySelector<HTMLElement>(`[data-block-id="${CSS.escape(blockId)}"]`);
|
|
406
|
+
if (!el) return null;
|
|
407
|
+
const walk = (n: Node): string => {
|
|
408
|
+
if (n.nodeType === Node.TEXT_NODE) return n.textContent ?? '';
|
|
409
|
+
if (!(n instanceof HTMLElement)) return '';
|
|
410
|
+
if (n !== el && n.hasAttribute('data-block-id')) return '';
|
|
411
|
+
if (isAtomicWidget(n)) return '\uFFFC'.repeat(widgetLen(n));
|
|
412
|
+
return Array.from(n.childNodes, walk).join('');
|
|
413
|
+
};
|
|
414
|
+
return walk(el);
|
|
415
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
// shortcuts.ts — map a keydown to the inline mark it toggles. `shortcutMark` is
|
|
2
|
+
// pure (key + modifiers → Mark | null); the editor reads it for both the
|
|
3
|
+
// selection toggle and the collapsed-caret pending-mark path.
|
|
4
|
+
import type { Mark } from '../RichText/engine/model';
|
|
5
|
+
import { isLetterKey } from '../_internal/letterKey';
|
|
6
|
+
|
|
7
|
+
/** The minimal keyboard-event shape the shortcut helpers read (matches `KeyboardEvent`). */
|
|
8
|
+
export interface ShortcutKey {
|
|
9
|
+
key: string;
|
|
10
|
+
/** Physical key — the fallback on non-Latin layouts (#654). */
|
|
11
|
+
code: string;
|
|
12
|
+
/** Alt/AltGr held — disables the `code` fallback (AltGr letters are typing). */
|
|
13
|
+
altKey?: boolean;
|
|
14
|
+
metaKey: boolean;
|
|
15
|
+
ctrlKey: boolean;
|
|
16
|
+
shiftKey: boolean;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Map a keyboard event (key + modifiers) to the inline mark it toggles, or
|
|
21
|
+
* `null` when the combination isn't a formatting shortcut. The single source of
|
|
22
|
+
* truth for the key→mark mapping — the editor reads it for both the selection
|
|
23
|
+
* toggle and the collapsed-caret pending-mark path. Pure: no DOM, no side
|
|
24
|
+
* effects.
|
|
25
|
+
*
|
|
26
|
+
* Handled shortcuts:
|
|
27
|
+
* - ⌘/Ctrl+B → bold
|
|
28
|
+
* - ⌘/Ctrl+I → italic
|
|
29
|
+
* - ⌘/Ctrl+U → underline
|
|
30
|
+
* - ⌘/Ctrl+⇧X → strike
|
|
31
|
+
*
|
|
32
|
+
* @example
|
|
33
|
+
* const mark = shortcutMark(e);
|
|
34
|
+
* if (mark) { e.preventDefault(); commit(toggleMark(doc, range, mark)); }
|
|
35
|
+
*/
|
|
36
|
+
export function shortcutMark(e: ShortcutKey): Mark | null {
|
|
37
|
+
const mod = e.metaKey || e.ctrlKey;
|
|
38
|
+
if (!mod) return null;
|
|
39
|
+
if (isLetterKey(e, 'b') && !e.shiftKey) return { type: 'bold' };
|
|
40
|
+
if (isLetterKey(e, 'i') && !e.shiftKey) return { type: 'italic' };
|
|
41
|
+
if (isLetterKey(e, 'u') && !e.shiftKey) return { type: 'underline' };
|
|
42
|
+
if (isLetterKey(e, 'x') && e.shiftKey) return { type: 'strike' };
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
// useAnchoredFloating.ts — shared portal-overlay positioning for <RichTextEditor>.
|
|
2
|
+
// The link bubble, @-mention listbox, and attachment config popover all anchor a
|
|
3
|
+
// Floating UI virtual element to a (live) selection/figure rect — fixed strategy,
|
|
4
|
+
// flip + shift, auto-updating on scroll/resize. This collapses that duplicated
|
|
5
|
+
// scaffolding into one hook. Internal; NOT exported from the package.
|
|
6
|
+
import { useMemo } from 'react';
|
|
7
|
+
import { useFloating, autoUpdate, flip, shift, offset } from '@floating-ui/react-dom';
|
|
8
|
+
import type { Rect } from './selection';
|
|
9
|
+
|
|
10
|
+
/** Expand a `Rect` (top/left/width/height) into the full DOMRect shape Floating UI reads. */
|
|
11
|
+
function rectToDomRect(r: Rect): DOMRect {
|
|
12
|
+
return {
|
|
13
|
+
x: r.left,
|
|
14
|
+
y: r.top,
|
|
15
|
+
top: r.top,
|
|
16
|
+
left: r.left,
|
|
17
|
+
right: r.left + r.width,
|
|
18
|
+
bottom: r.top + r.height,
|
|
19
|
+
width: r.width,
|
|
20
|
+
height: r.height,
|
|
21
|
+
} as DOMRect;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Shared portal-overlay positioning: a Floating UI virtual element anchored to a
|
|
26
|
+
* (live) rect, fixed strategy + flip + shift, auto-updating on scroll/resize.
|
|
27
|
+
* Mirrors the link/mention/attachment overlays' anchoring in one place.
|
|
28
|
+
*
|
|
29
|
+
* `getAnchorRect`, when provided, is re-read on every reposition so the overlay
|
|
30
|
+
* tracks the selection line / figure on scroll (the static `anchorRect` is the
|
|
31
|
+
* fallback for the initial frame / when the live rect is unavailable). `opts.offset`
|
|
32
|
+
* sets the gap between anchor and overlay (defaults to 6).
|
|
33
|
+
*/
|
|
34
|
+
export function useAnchoredFloating(
|
|
35
|
+
anchorRect: Rect,
|
|
36
|
+
getAnchorRect: (() => Rect | null) | undefined,
|
|
37
|
+
opts: { offset?: number } = {},
|
|
38
|
+
) {
|
|
39
|
+
const virtualRef = useMemo(
|
|
40
|
+
() => ({ getBoundingClientRect: () => rectToDomRect(getAnchorRect?.() ?? anchorRect) }),
|
|
41
|
+
[anchorRect, getAnchorRect],
|
|
42
|
+
);
|
|
43
|
+
return useFloating({
|
|
44
|
+
placement: 'bottom-start',
|
|
45
|
+
strategy: 'fixed',
|
|
46
|
+
whileElementsMounted: autoUpdate,
|
|
47
|
+
middleware: [offset(opts.offset ?? 6), flip(), shift({ padding: 4 })],
|
|
48
|
+
elements: { reference: virtualRef },
|
|
49
|
+
});
|
|
50
|
+
}
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import { useCallback, useState } from 'react';
|
|
2
|
+
import type { RichDoc, Range } from '../RichText/engine/model';
|
|
3
|
+
import type { EditKind } from './history';
|
|
4
|
+
import { collapsedRange } from '../RichText/engine/position';
|
|
5
|
+
import { updateAttachmentBlock, clearAttachmentFields } from '../RichText/engine/attachment';
|
|
6
|
+
|
|
7
|
+
interface UseAttachmentConfigArgs {
|
|
8
|
+
/** The editable root — read to refocus the editor on close. */
|
|
9
|
+
rootRef: React.RefObject<HTMLDivElement | null>;
|
|
10
|
+
/** The editor's commit path (history + onChange round-trip) for the
|
|
11
|
+
* non-live config edits (alt / align / width-reset). */
|
|
12
|
+
commit: (result: { doc: RichDoc; selection: Range }, kind?: EditKind) => void;
|
|
13
|
+
/** The editor-owned live-resize commit: records ONE coalescing `'resize'`
|
|
14
|
+
* history entry (whole drag = one undo step), writes NO pending selection (so
|
|
15
|
+
* the slider/handle keeps focus mid-drag), and chains off `liveDocRef`. The
|
|
16
|
+
* hook builds the resized doc and hands it here so the history coupling stays
|
|
17
|
+
* isolated in the editor. */
|
|
18
|
+
commitResizeLive: (doc: RichDoc) => void;
|
|
19
|
+
/** The synchronously-latest controlled doc (`() => latest.current.value`). */
|
|
20
|
+
getValue: () => RichDoc;
|
|
21
|
+
/** The synchronously-latest live doc (`() => liveDocRef.current`) — read by
|
|
22
|
+
* the live-resize handler so rapid moves chain off each other before React
|
|
23
|
+
* re-renders. */
|
|
24
|
+
getLiveDoc: () => RichDoc;
|
|
25
|
+
/** Mirror of the upload controller — `replace` swaps an attachment's file. */
|
|
26
|
+
uploaderRef: React.RefObject<{ replace: (id: string, file: File) => void }>;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* The attachment-config cluster, extracted from `RichTextEditor`: owns the
|
|
31
|
+
* `configBlockId` open-state and the alt/align/width/width-reset/replace
|
|
32
|
+
* handlers for the `<RichTextAttachmentConfig>` popover. The editor keeps the
|
|
33
|
+
* popover JSX (it derives the configured block + anchor rect from render scope)
|
|
34
|
+
* and the live-resize history coupling (via the injected `commitResizeLive`);
|
|
35
|
+
* only the state + handlers live here.
|
|
36
|
+
*
|
|
37
|
+
* Config edits don't move the caret meaningfully (the popover holds focus), so
|
|
38
|
+
* each non-live commit pins a collapsed selection on the block. `onConfigWidth`
|
|
39
|
+
* is the LIVE-resize path (fires on every slider/handle move) — it builds the
|
|
40
|
+
* resized doc here and routes through `commitResizeLive`, NOT `commit`.
|
|
41
|
+
*/
|
|
42
|
+
export function useAttachmentConfig({
|
|
43
|
+
rootRef,
|
|
44
|
+
commit,
|
|
45
|
+
commitResizeLive,
|
|
46
|
+
getValue,
|
|
47
|
+
getLiveDoc,
|
|
48
|
+
uploaderRef,
|
|
49
|
+
}: UseAttachmentConfigArgs) {
|
|
50
|
+
const [configBlockId, setConfigBlockId] = useState<string | null>(null);
|
|
51
|
+
|
|
52
|
+
// Open/close toggle `configBlockId`; close returns focus to the editor.
|
|
53
|
+
const onConfigOpen = useCallback((id: string) => setConfigBlockId(id), []);
|
|
54
|
+
const onConfigClose = useCallback(() => {
|
|
55
|
+
setConfigBlockId(null);
|
|
56
|
+
rootRef.current?.focus();
|
|
57
|
+
}, [rootRef]);
|
|
58
|
+
|
|
59
|
+
const onConfigAlt = useCallback(
|
|
60
|
+
(id: string, alt: string) => {
|
|
61
|
+
commit(
|
|
62
|
+
{
|
|
63
|
+
doc: updateAttachmentBlock(getValue(), id, { alt }),
|
|
64
|
+
selection: collapsedRange(id),
|
|
65
|
+
},
|
|
66
|
+
'other',
|
|
67
|
+
);
|
|
68
|
+
},
|
|
69
|
+
[commit, getValue],
|
|
70
|
+
);
|
|
71
|
+
const onConfigAlign = useCallback(
|
|
72
|
+
(id: string, align: 'left' | 'center' | 'right') => {
|
|
73
|
+
commit(
|
|
74
|
+
{
|
|
75
|
+
doc: updateAttachmentBlock(getValue(), id, { align }),
|
|
76
|
+
selection: collapsedRange(id),
|
|
77
|
+
},
|
|
78
|
+
'other',
|
|
79
|
+
);
|
|
80
|
+
},
|
|
81
|
+
[commit, getValue],
|
|
82
|
+
);
|
|
83
|
+
const onConfigWidth = useCallback(
|
|
84
|
+
(id: string, width: number) => {
|
|
85
|
+
// Live resize: fires on every slider/handle MOVE (not just the end) so the
|
|
86
|
+
// image tracks the control. Three deliberate choices vs a plain `commit`,
|
|
87
|
+
// all owned by the injected `commitResizeLive`:
|
|
88
|
+
// • Coalescing 'resize' kind → the whole drag is ONE undo step (reverting
|
|
89
|
+
// to the pre-drag width), instead of one per pixel.
|
|
90
|
+
// • `pendingSelectionRef` cleared (no caret placed) → the slider/handle
|
|
91
|
+
// keeps focus; a `writeSelection` into the editable would yank it mid-drag.
|
|
92
|
+
// • Reads `liveDocRef` (the synchronously-latest doc) so rapid moves that
|
|
93
|
+
// fire before React re-renders still chain off each other.
|
|
94
|
+
// Width only; clearing height lets the browser keep the aspect ratio.
|
|
95
|
+
let d = updateAttachmentBlock(getLiveDoc(), id, { width });
|
|
96
|
+
d = clearAttachmentFields(d, id, ['height']);
|
|
97
|
+
commitResizeLive(d);
|
|
98
|
+
},
|
|
99
|
+
[commitResizeLive, getLiveDoc],
|
|
100
|
+
);
|
|
101
|
+
const onConfigWidthReset = useCallback(
|
|
102
|
+
(id: string) => {
|
|
103
|
+
commit(
|
|
104
|
+
{
|
|
105
|
+
doc: clearAttachmentFields(getValue(), id, ['width', 'height']),
|
|
106
|
+
selection: collapsedRange(id),
|
|
107
|
+
},
|
|
108
|
+
'other',
|
|
109
|
+
);
|
|
110
|
+
},
|
|
111
|
+
[commit, getValue],
|
|
112
|
+
);
|
|
113
|
+
const onConfigReplace = useCallback(
|
|
114
|
+
(id: string, file: File) => {
|
|
115
|
+
uploaderRef.current?.replace(id, file);
|
|
116
|
+
// The block flips to `status: 'uploading'`, which unmounts the popover (it
|
|
117
|
+
// only shows for `ready`). Clear config state too so it doesn't spontaneously
|
|
118
|
+
// reappear when the upload settles back to `ready`.
|
|
119
|
+
setConfigBlockId(null);
|
|
120
|
+
},
|
|
121
|
+
[uploaderRef],
|
|
122
|
+
);
|
|
123
|
+
|
|
124
|
+
return {
|
|
125
|
+
configBlockId,
|
|
126
|
+
onConfigOpen,
|
|
127
|
+
onConfigClose,
|
|
128
|
+
onConfigAlt,
|
|
129
|
+
onConfigAlign,
|
|
130
|
+
onConfigWidth,
|
|
131
|
+
onConfigWidthReset,
|
|
132
|
+
onConfigReplace,
|
|
133
|
+
};
|
|
134
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
// useDismissOnOutsidePointerDown.ts — shared "click outside to dismiss" for the
|
|
2
|
+
// <RichTextEditor> floating overlays (link bubble, attachment config popover).
|
|
3
|
+
// A capture-phase document pointerdown listener fires `onClose` when the press
|
|
4
|
+
// lands outside `ref`. Capture phase + pointerdown (not click) so the dismissal
|
|
5
|
+
// runs before the press can move/clear the editor selection. Internal; NOT
|
|
6
|
+
// exported from the package.
|
|
7
|
+
import { useEffect, type RefObject } from 'react';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Call `onClose` when a pointerdown lands outside the element referenced by `ref`.
|
|
11
|
+
* The listener is registered on `document` in the capture phase. `ref` is a stable
|
|
12
|
+
* `useRef` object, so the effect effectively re-subscribes only when `onClose`'s
|
|
13
|
+
* identity changes — matching the overlays' original `[onClose]`-keyed effects.
|
|
14
|
+
*/
|
|
15
|
+
export function useDismissOnOutsidePointerDown(
|
|
16
|
+
ref: RefObject<HTMLElement | null>,
|
|
17
|
+
onClose: () => void,
|
|
18
|
+
) {
|
|
19
|
+
useEffect(() => {
|
|
20
|
+
const onPointerDown = (e: PointerEvent) => {
|
|
21
|
+
if (ref.current && !ref.current.contains(e.target as Node)) onClose();
|
|
22
|
+
};
|
|
23
|
+
document.addEventListener('pointerdown', onPointerDown, true);
|
|
24
|
+
return () => document.removeEventListener('pointerdown', onPointerDown, true);
|
|
25
|
+
}, [ref, onClose]);
|
|
26
|
+
}
|