@flikk/ui 1.0.0-beta.30 → 1.0.0-beta.32
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/CHANGELOG.md +375 -5
- package/dist/components/ai/AgentRequest/AgentRequest.animations.d.ts +25 -0
- package/dist/components/ai/AgentRequest/AgentRequest.animations.js +81 -0
- package/dist/components/ai/AgentRequest/AgentRequest.d.ts +3 -0
- package/dist/components/ai/AgentRequest/AgentRequest.js +435 -0
- package/dist/components/ai/AgentRequest/AgentRequest.theme.d.ts +2 -0
- package/dist/components/ai/AgentRequest/AgentRequest.theme.js +106 -0
- package/dist/components/ai/AgentRequest/AgentRequest.types.d.ts +241 -0
- package/dist/components/ai/AgentRequest/index.d.ts +3 -0
- package/dist/components/ai/AgentStatus/AgentStatus.d.ts +3 -0
- package/dist/components/ai/AgentStatus/AgentStatus.js +144 -0
- package/dist/components/ai/AgentStatus/AgentStatus.theme.d.ts +2 -0
- package/dist/components/ai/AgentStatus/AgentStatus.theme.js +37 -0
- package/dist/components/ai/AgentStatus/AgentStatus.types.d.ts +116 -0
- package/dist/components/ai/AgentStatus/index.d.ts +3 -0
- package/dist/components/ai/CodeBlock/CodeBlock.js +3 -3
- package/dist/components/ai/CodeBlock/CodeBlock.theme.js +2 -2
- package/dist/components/ai/PromptInput/PromptInput.js +13 -14
- package/dist/components/ai/PromptInput/PromptInput.theme.js +3 -3
- package/dist/components/ai/PromptInput/VoiceRecorder.js +3 -3
- package/dist/components/ai/PromptSuggestion/PromptSuggestion.theme.js +2 -2
- package/dist/components/ai/Reasoning/Reasoning.d.ts +5 -0
- package/dist/components/ai/Reasoning/Reasoning.js +40 -0
- package/dist/components/ai/Reasoning/Reasoning.theme.d.ts +6 -0
- package/dist/components/ai/Reasoning/Reasoning.theme.js +36 -0
- package/dist/components/ai/Reasoning/Reasoning.types.d.ts +73 -0
- package/dist/components/ai/Reasoning/ReasoningTrace.d.ts +11 -0
- package/dist/components/ai/Reasoning/ReasoningTrace.js +84 -0
- package/dist/components/ai/Reasoning/index.d.ts +3 -0
- package/dist/components/ai/SourceCitation/SourceCitation.d.ts +4 -0
- package/dist/components/ai/SourceCitation/SourceCitation.js +29 -0
- package/dist/components/ai/SourceCitation/SourceCitation.theme.d.ts +2 -0
- package/dist/components/ai/SourceCitation/SourceCitation.theme.js +23 -0
- package/dist/components/ai/SourceCitation/SourceCitation.types.d.ts +55 -0
- package/dist/components/ai/SourceCitation/index.d.ts +3 -0
- package/dist/components/ai/StreamingResponse/AnimatedText.d.ts +7 -0
- package/dist/components/ai/StreamingResponse/AnimatedText.js +8 -7
- package/dist/components/ai/StreamingResponse/ErrorDisplay.js +2 -2
- package/dist/components/ai/StreamingResponse/MarkdownRenderer.d.ts +5 -0
- package/dist/components/ai/StreamingResponse/MarkdownRenderer.js +4 -4
- package/dist/components/ai/StreamingResponse/StreamingCursor.d.ts +2 -2
- package/dist/components/ai/StreamingResponse/StreamingCursor.js +10 -7
- package/dist/components/ai/StreamingResponse/StreamingResponse.animations.d.ts +10 -0
- package/dist/components/ai/StreamingResponse/StreamingResponse.animations.js +59 -33
- package/dist/components/ai/StreamingResponse/StreamingResponse.js +14 -10
- package/dist/components/ai/StreamingResponse/StreamingResponse.theme.js +3 -1
- package/dist/components/ai/StreamingResponse/StreamingResponse.types.d.ts +33 -2
- package/dist/components/ai/StreamingResponse/StreamingSurface.d.ts +13 -0
- package/dist/components/ai/StreamingResponse/StreamingSurface.js +38 -0
- package/dist/components/ai/StreamingResponse/WordAnimationContext.d.ts +5 -0
- package/dist/components/ai/StreamingResponse/WordAnimationContext.js +5 -4
- package/dist/components/ai/StreamingResponse/index.d.ts +4 -2
- package/dist/components/ai/TokenCounter/TokenCounter.js +2 -2
- package/dist/components/ai/TokenCounter/TokenCounter.theme.js +1 -1
- package/dist/components/ai/index.d.ts +6 -2
- package/dist/components/ai/index.js +10 -3
- package/dist/components/ai/shared/StatusIndicator.d.ts +8 -0
- package/dist/components/ai/shared/StatusIndicator.js +34 -0
- package/dist/components/canvas/BaseNode.js +1 -1
- package/dist/components/canvas/CanvasToolbar.js +2 -3
- package/dist/components/canvas/Edge.js +2 -2
- package/dist/components/canvas/NodeControls.js +2 -3
- package/dist/components/canvas/NodeRenderer.js +4 -4
- package/dist/components/charts/ActivityRings/ActivityRings.js +4 -4
- package/dist/components/charts/ActivityRings/ActivityRings.theme.js +1 -1
- package/dist/components/charts/AreaChart/AreaChart.js +24 -35
- package/dist/components/charts/AreaChart/AreaChart.theme.d.ts +1 -6
- package/dist/components/charts/AreaChart/AreaChart.theme.js +10 -22
- package/dist/components/charts/AreaChart/AreaChart.types.d.ts +18 -11
- package/dist/components/charts/AreaChart/AreaChart.types.js +10 -3
- package/dist/components/charts/AreaChart/index.d.ts +1 -1
- package/dist/components/charts/BarChart/BarChart.js +13 -32
- package/dist/components/charts/BarChart/BarChart.types.d.ts +20 -16
- package/dist/components/charts/BarChart/BarChart.types.js +11 -4
- package/dist/components/charts/ComboChart/BarRenderer.js +10 -7
- package/dist/components/charts/ComboChart/BarRenderer.types.d.ts +1 -1
- package/dist/components/charts/ComboChart/ComboChart.js +11 -24
- package/dist/components/charts/ComboChart/ComboChart.types.d.ts +15 -13
- package/dist/components/charts/ComboChart/ComboChart.types.js +14 -2
- package/dist/components/charts/ComboChart/ComboChartCanvas.js +1 -1
- package/dist/components/charts/ComboChart/LineRenderer.js +16 -11
- package/dist/components/charts/ComboChart/LineRenderer.types.d.ts +1 -1
- package/dist/components/charts/DonutChart/DonutChart.theme.js +4 -4
- package/dist/components/charts/DonutChart/MultiSegmentDonutChart.theme.js +1 -1
- package/dist/components/charts/Heatmap/Heatmap.js +49 -42
- package/dist/components/charts/Heatmap/Heatmap.theme.d.ts +6 -2
- package/dist/components/charts/Heatmap/Heatmap.theme.js +20 -8
- package/dist/components/charts/Heatmap/Heatmap.types.d.ts +49 -82
- package/dist/components/charts/Heatmap/HeatmapCell.js +30 -81
- package/dist/components/charts/Heatmap/HeatmapLegend.d.ts +7 -6
- package/dist/components/charts/Heatmap/HeatmapLegend.js +6 -6
- package/dist/components/charts/Heatmap/index.d.ts +1 -1
- package/dist/components/charts/Heatmap/utils/heatmapUtils.d.ts +7 -12
- package/dist/components/charts/Heatmap/utils/heatmapUtils.js +16 -61
- package/dist/components/charts/Heatmap/utils/heatmapUtils.test.d.ts +1 -0
- package/dist/components/charts/HorizontalBarChart/HorizontalBarChart.d.ts +16 -0
- package/dist/components/charts/HorizontalBarChart/HorizontalBarChart.js +237 -0
- package/dist/components/charts/HorizontalBarChart/HorizontalBarChart.types.d.ts +62 -0
- package/dist/components/charts/HorizontalBarChart/HorizontalBarChart.types.js +21 -0
- package/dist/components/charts/HorizontalBarChart/index.d.ts +3 -0
- package/dist/components/charts/LineChart/LineChart.js +11 -28
- package/dist/components/charts/LineChart/LineChart.types.d.ts +11 -14
- package/dist/components/charts/LineChart/LineChart.types.js +11 -3
- package/dist/components/charts/LollipopChart/LollipopChart.d.ts +3 -0
- package/dist/components/charts/LollipopChart/LollipopChart.js +361 -0
- package/dist/components/charts/LollipopChart/LollipopChart.types.d.ts +42 -0
- package/dist/components/charts/LollipopChart/LollipopChart.types.js +12 -0
- package/dist/components/charts/LollipopChart/index.d.ts +3 -0
- package/dist/components/charts/RadarChart/RadarChart.js +1 -1
- package/dist/components/charts/StackedBarChart/StackedBarChart.js +151 -133
- package/dist/components/charts/StackedBarChart/StackedBarChart.types.d.ts +20 -18
- package/dist/components/charts/StackedBarChart/StackedBarChart.types.js +10 -4
- package/dist/components/charts/hooks/useChartAccessibility.d.ts +1 -1
- package/dist/components/charts/hooks/useChartAccessibility.js +12 -6
- package/dist/components/charts/hooks/useTooltipPosition.js +18 -6
- package/dist/components/charts/index.d.ts +10 -8
- package/dist/components/charts/index.js +8 -4
- package/dist/components/charts/shared/ChartAxis/CategoryYAxis.d.ts +14 -0
- package/dist/components/charts/shared/ChartAxis/CategoryYAxis.js +35 -0
- package/dist/components/charts/shared/ChartAxis/ChartAxis.types.d.ts +69 -8
- package/dist/components/charts/shared/ChartAxis/ValueXAxis.d.ts +14 -0
- package/dist/components/charts/shared/ChartAxis/ValueXAxis.js +64 -0
- package/dist/components/charts/shared/ChartAxis/XAxis.js +1 -1
- package/dist/components/charts/shared/ChartAxis/YAxis.js +2 -2
- package/dist/components/charts/shared/ChartAxis/index.d.ts +3 -1
- package/dist/components/charts/shared/ChartGrid/HorizontalGrid.d.ts +0 -2
- package/dist/components/charts/shared/ChartGrid/HorizontalGrid.js +1 -1
- package/dist/components/charts/shared/ChartGrid/VerticalGrid.d.ts +29 -0
- package/dist/components/charts/shared/ChartGrid/VerticalGrid.js +26 -0
- package/dist/components/charts/shared/ChartGrid/index.d.ts +2 -0
- package/dist/components/charts/shared/ChartMarker/ChartMarker.js +7 -1
- package/dist/components/charts/shared/ChartText/ChartText.theme.js +1 -1
- package/dist/components/charts/shared/index.d.ts +1 -1
- package/dist/components/charts/theme/chart.theme.js +21 -26
- package/dist/components/charts/types/chart.types.d.ts +9 -5
- package/dist/components/charts/types/chart.types.js +1 -7
- package/dist/components/charts/utils/animation-utils.d.ts +12 -7
- package/dist/components/charts/utils/animation-utils.js +19 -21
- package/dist/components/charts/utils/bar-emphasis.d.ts +32 -3
- package/dist/components/charts/utils/bar-emphasis.js +24 -8
- package/dist/components/charts/utils/index.d.ts +1 -0
- package/dist/components/charts/utils/series-color.d.ts +7 -0
- package/dist/components/charts/utils/series-color.js +20 -0
- package/dist/components/core/Accordion/Accordion.theme.js +3 -3
- package/dist/components/core/Accordion/AccordionTrigger.js +2 -2
- package/dist/components/core/Alert/Alert.animations.d.ts +11 -3
- package/dist/components/core/Alert/Alert.animations.js +30 -8
- package/dist/components/core/Alert/Alert.js +23 -9
- package/dist/components/core/Alert/Alert.theme.js +1 -1
- package/dist/components/core/Alert/Alert.types.d.ts +4 -2
- package/dist/components/core/AlertDialog/AlertDialog.js +2 -2
- package/dist/components/core/Avatar/Avatar.js +50 -42
- package/dist/components/core/Avatar/Avatar.theme.d.ts +8 -4
- package/dist/components/core/Avatar/Avatar.theme.js +64 -8
- package/dist/components/core/Avatar/Avatar.types.d.ts +13 -2
- package/dist/components/core/Avatar/index.d.ts +1 -1
- package/dist/components/core/AvatarGroup/AvatarGroup.js +6 -8
- package/dist/components/core/AvatarGroup/AvatarGroup.theme.d.ts +1 -2
- package/dist/components/core/AvatarGroup/AvatarGroup.theme.js +2 -6
- package/dist/components/core/AvatarGroup/AvatarGroup.types.d.ts +0 -6
- package/dist/components/core/AvatarGroup/index.d.ts +1 -1
- package/dist/components/core/Badge/Badge.animations.d.ts +0 -5
- package/dist/components/core/Badge/Badge.animations.js +5 -82
- package/dist/components/core/Badge/Badge.d.ts +7 -4
- package/dist/components/core/Badge/Badge.js +33 -65
- package/dist/components/core/Badge/Badge.theme.js +26 -24
- package/dist/components/core/Badge/Badge.types.d.ts +18 -34
- package/dist/components/core/Breadcrumbs/Breadcrumbs.theme.d.ts +1 -4
- package/dist/components/core/Breadcrumbs/Breadcrumbs.theme.js +6 -6
- package/dist/components/core/Button/Button.js +14 -58
- package/dist/components/core/Button/Button.theme.d.ts +2 -0
- package/dist/components/core/Button/Button.theme.js +35 -22
- package/dist/components/core/Button/Button.types.d.ts +26 -13
- package/dist/components/core/Button/index.d.ts +0 -2
- package/dist/components/core/Calendar/Calendar.js +20 -5
- package/dist/components/core/Calendar/Calendar.theme.js +15 -23
- package/dist/components/core/Calendar/Calendar.types.d.ts +0 -1
- package/dist/components/core/Calendar/CalendarMini/CalendarMini.js +3 -3
- package/dist/components/core/Calendar/CalendarMini/CalendarMini.theme.js +2 -2
- package/dist/components/core/Card/Card.js +7 -2
- package/dist/components/core/Card/Card.theme.js +40 -4
- package/dist/components/core/Card/Card.types.d.ts +20 -0
- package/dist/components/core/Card/CardBody.js +9 -1
- package/dist/components/core/Card/CardContext.d.ts +13 -0
- package/dist/components/core/Card/CardContext.js +14 -0
- package/dist/components/core/Card/CardFooter.js +1 -1
- package/dist/components/core/Card/CardHeader.js +4 -1
- package/dist/components/core/CommandPalette/CommandPalette.js +5 -4
- package/dist/components/core/CommandPalette/CommandPalette.theme.js +3 -7
- package/dist/components/core/CommandPalette/CommandPalette.types.d.ts +2 -0
- package/dist/components/core/ContextMenu/ContextMenu.theme.js +4 -3
- package/dist/components/core/ContextMenu/ContextMenu.types.d.ts +8 -1
- package/dist/components/core/ContextMenu/ContextMenuItem.js +2 -2
- package/dist/components/core/ContextMenu/ContextMenuSub.d.ts +11 -1
- package/dist/components/core/ContextMenu/ContextMenuSub.js +35 -22
- package/dist/components/core/ContextMenu/ContextMenuSubBody.js +14 -3
- package/dist/components/core/ContextMenu/ContextMenuSubTrigger.js +22 -9
- package/dist/components/core/DragDrop/DragDrop.theme.js +2 -2
- package/dist/components/core/Drawer/Drawer.theme.js +7 -4
- package/dist/components/core/Drawer/DrawerHeader.js +2 -3
- package/dist/components/core/Dropdown/Dropdown.theme.js +3 -1
- package/dist/components/core/Dropdown/DropdownItem.js +2 -2
- package/dist/components/core/Dropdown/DropdownMenu.js +22 -7
- package/dist/components/core/Dropdown/DropdownTrigger.js +16 -5
- package/dist/components/core/HeroCard/HeroCard.theme.js +5 -2
- package/dist/components/core/Kbd/Kbd.theme.js +4 -2
- package/dist/components/core/Link/Link.js +7 -5
- package/dist/components/core/Masonry/Masonry.theme.js +1 -1
- package/dist/components/core/MenuItem/MenuItem.js +2 -1
- package/dist/components/core/MenuItem/MenuItem.theme.js +13 -1
- package/dist/components/core/Message/Message.js +4 -2
- package/dist/components/core/Message/Message.theme.js +8 -10
- package/dist/components/core/Message/MessageAudio.js +2 -2
- package/dist/components/core/Message/MessageFile.js +2 -2
- package/dist/components/core/Message/MessageLink.js +2 -2
- package/dist/components/core/Message/MessageStatusIndicator.js +5 -5
- package/dist/components/core/Modal/Modal.animations.d.ts +1 -1
- package/dist/components/core/Modal/Modal.animations.js +50 -11
- package/dist/components/core/Modal/Modal.d.ts +1 -1
- package/dist/components/core/Modal/Modal.js +6 -2
- package/dist/components/core/Modal/Modal.theme.js +9 -4
- package/dist/components/core/Modal/ModalHeader.js +2 -3
- package/dist/components/core/ModalStack/ModalStackModal.js +4 -2
- package/dist/components/core/NavItem/NavItem.js +8 -7
- package/dist/components/core/NavItem/NavItem.theme.js +1 -1
- package/dist/components/core/OfflineIndicator/OfflineIndicator.animations.d.ts +22 -0
- package/dist/components/core/OfflineIndicator/OfflineIndicator.animations.js +54 -0
- package/dist/components/core/OfflineIndicator/OfflineIndicator.js +23 -24
- package/dist/components/core/OfflineIndicator/OfflineIndicator.theme.js +27 -6
- package/dist/components/core/OfflineIndicator/OfflineIndicator.types.d.ts +17 -0
- package/dist/components/core/PageHeading/PageHeading.theme.js +1 -1
- package/dist/components/core/PageHeading/PageHeadingBackButton.js +2 -2
- package/dist/components/core/Pagination/Pagination.js +3 -3
- package/dist/components/core/Pagination/Pagination.theme.js +5 -5
- package/dist/components/core/Pill/Pill.js +5 -2
- package/dist/components/core/Pill/Pill.theme.js +9 -5
- package/dist/components/core/Popover/PopoverBody.js +16 -5
- package/dist/components/core/Progress/Progress.d.ts +0 -25
- package/dist/components/core/Progress/Progress.js +79 -11
- package/dist/components/core/Progress/Progress.theme.js +17 -0
- package/dist/components/core/Progress/Progress.types.d.ts +47 -1
- package/dist/components/core/Rating/Rating.js +4 -5
- package/dist/components/core/Rating/Rating.theme.js +2 -2
- package/dist/components/core/Rating/Rating.types.d.ts +3 -2
- package/dist/components/core/ScrollArea/ScrollArea.js +1 -1
- package/dist/components/core/ScrollArea/ScrollArea.theme.js +1 -1
- package/dist/components/core/Segmented/Segmented.theme.js +10 -7
- package/dist/components/core/Segmented/SegmentedContext.js +1 -0
- package/dist/components/core/Sidebar/Sidebar.js +5 -2
- package/dist/components/core/Sidebar/Sidebar.theme.js +2 -2
- package/dist/components/core/Sidebar/Sidebar.types.d.ts +4 -2
- package/dist/components/core/Sidebar/SidebarContent.js +1 -1
- package/dist/components/core/Sidebar/SidebarHeader.js +3 -3
- package/dist/components/core/Sidebar/SidebarMobileTrigger.js +2 -2
- package/dist/components/core/Sidebar/SidebarSearch.js +4 -4
- package/dist/components/core/Sidebar/SidebarToggle.js +3 -3
- package/dist/components/core/Sidebar/SidebarUserProfile.js +3 -3
- package/dist/components/core/Sortable/Sortable.js +2 -2
- package/dist/components/core/Sortable/Sortable.theme.js +1 -1
- package/dist/components/core/Tabs/Tabs.theme.js +1 -1
- package/dist/components/core/Tag/Tag.js +2 -2
- package/dist/components/core/Tag/Tag.theme.js +5 -3
- package/dist/components/core/Toast/Toast.js +3 -3
- package/dist/components/core/Toast/Toast.types.d.ts +1 -1
- package/dist/components/core/Toast/ToastProvider.js +1 -1
- package/dist/components/core/Toast/useToast.js +5 -5
- package/dist/components/core/Tooltip/Tooltip.d.ts +1 -1
- package/dist/components/core/Tooltip/Tooltip.js +42 -10
- package/dist/components/core/Tooltip/Tooltip.types.d.ts +25 -0
- package/dist/components/core/Tree/Tree.js +2 -2
- package/dist/components/core/Tree/Tree.theme.js +1 -1
- package/dist/components/core/index.d.ts +0 -2
- package/dist/components/core/index.js +0 -4
- package/dist/components/core/shared/ChartTooltip/ChartTooltip.animations.d.ts +4 -0
- package/dist/components/core/shared/ChartTooltip/ChartTooltip.animations.js +20 -0
- package/dist/components/{charts → core}/shared/ChartTooltip/ChartTooltip.js +1 -1
- package/dist/components/core/shared/Ripple.d.ts +16 -0
- package/dist/components/core/shared/Ripple.js +141 -0
- package/dist/components/core/shared/interaction.animations.d.ts +51 -0
- package/dist/components/core/shared/interaction.animations.js +59 -0
- package/dist/components/data-display/Feed/Feed.js +2 -2
- package/dist/components/data-display/GanttChart/GanttChart.theme.js +7 -7
- package/dist/components/data-display/GanttChart/GanttChartPanel.js +2 -2
- package/dist/components/data-display/GanttChart/GanttTableGroupRow.js +3 -3
- package/dist/components/data-display/GanttChart/GanttTaskBar.js +1 -1
- package/dist/components/data-display/GanttChart/GanttTaskContextMenu.js +2 -2
- package/dist/components/data-display/GanttChart/GanttTimelineHeader.js +1 -1
- package/dist/components/data-display/GanttChart/GanttToolbar.js +2 -3
- package/dist/components/data-display/KPI/KPI.js +12 -21
- package/dist/components/data-display/KPI/KPI.theme.js +8 -4
- package/dist/components/data-display/Metric/Metric.js +2 -2
- package/dist/components/data-display/Metric/Metric.theme.js +25 -8
- package/dist/components/data-display/Metric/Metric.types.d.ts +1 -1
- package/dist/components/data-display/Table/Table.theme.js +11 -11
- package/dist/components/data-display/Table/TableActions.js +2 -2
- package/dist/components/data-display/Table/TableActionsMenu.js +1 -1
- package/dist/components/data-display/Table/TableColumnManager.js +2 -3
- package/dist/components/data-display/Table/TableContext.js +1 -0
- package/dist/components/data-display/Table/TableDeclarative.js +3 -3
- package/dist/components/data-display/Table/TableFilter.js +13 -6
- package/dist/components/data-display/Table/TableHeader.js +3 -3
- package/dist/components/data-display/Table/TableRow.js +2 -2
- package/dist/components/data-display/Table/TableSelectionHeader.js +4 -4
- package/dist/components/data-display/Timeline/Timeline.theme.js +1 -1
- package/dist/components/data-display/Timeline/TimelineMarker.js +2 -2
- package/dist/components/effects/CustomCursor/CustomCursor.d.ts +1 -1
- package/dist/components/effects/CustomCursor/CustomCursor.js +3 -3
- package/dist/components/effects/CustomCursor/CustomCursor.theme.js +4 -4
- package/dist/components/effects/DotPattern/DotPattern.js +6 -0
- package/dist/components/effects/DotPattern/DotPattern.types.d.ts +4 -2
- package/dist/components/effects/MeshGradient/MeshGradient.utils.d.ts +1 -1
- package/dist/components/effects/MeshGradient/MeshGradient.utils.js +1 -1
- package/dist/components/effects/Neumorphic/InsetCircleButton.js +1 -1
- package/dist/components/effects/Overlay/Overlay.js +7 -3
- package/dist/components/effects/index.d.ts +0 -32
- package/dist/components/effects/index.js +0 -16
- package/dist/components/effects/shared/ShaderCanvas/ShaderCanvas.d.ts +0 -2
- package/dist/components/effects/shared/ShaderCanvas/ShaderCanvas.js +1 -93
- package/dist/components/effects/shared/ShaderCanvas/ShaderCanvas.types.d.ts +0 -10
- package/dist/components/effects/shared/ShaderCanvas/index.d.ts +1 -1
- package/dist/components/effects/shared/index.d.ts +1 -3
- package/dist/components/effects/shared/shaderNoise.glsl.d.ts +1 -1
- package/dist/components/effects/shared/shaderNoise.glsl.js +2 -11
- package/dist/components/forms/Checkbox/Checkbox.js +2 -1
- package/dist/components/forms/Checkbox/Checkbox.theme.js +3 -3
- package/dist/components/forms/ColorPicker/ColorPickerEyeDropper.js +2 -2
- package/dist/components/forms/ColorPicker/ColorPickerGradient.js +2 -2
- package/dist/components/forms/ColorPicker/ColorPickerHeader.js +2 -2
- package/dist/components/forms/ColorPicker/ColorPickerImage.js +3 -3
- package/dist/components/forms/ColorPicker/ColorPickerInput.js +2 -2
- package/dist/components/forms/ColorPicker/ColorPickerPresets.js +2 -2
- package/dist/components/forms/ColorPicker/ColorPickerSavedColors.js +2 -2
- package/dist/components/forms/Combobox/Combobox.js +5 -3
- package/dist/components/forms/Combobox/Combobox.theme.js +2 -2
- package/dist/components/forms/DatePicker/DatePicker.animations.js +16 -25
- package/dist/components/forms/DatePicker/DatePicker.theme.js +2 -2
- package/dist/components/forms/DatePicker/DatePickerBody.js +1 -1
- package/dist/components/forms/DatePicker/DatePickerTrigger.js +2 -2
- package/dist/components/forms/DateRangePicker/DateRangePicker.theme.js +4 -4
- package/dist/components/forms/DateRangePicker/DateRangePickerBody.js +1 -1
- package/dist/components/forms/DateRangePicker/DateRangePickerContext.js +1 -0
- package/dist/components/forms/DateRangePicker/DateRangePickerPresets.js +2 -2
- package/dist/components/forms/DateRangePicker/DateRangePickerTrigger.js +2 -2
- package/dist/components/forms/FileUpload/FileUpload.js +4 -4
- package/dist/components/forms/FileUpload/FileUpload.theme.js +7 -3
- package/dist/components/forms/FileUpload/FileUploadProgress.js +7 -7
- package/dist/components/forms/FileUpload/FileUploadProgress.theme.js +1 -1
- package/dist/components/forms/Input/Input.js +6 -7
- package/dist/components/forms/Input/Input.theme.js +19 -15
- package/dist/components/forms/InputAddress/InputAddress.js +10 -3
- package/dist/components/forms/InputAddress/InputAddress.types.d.ts +2 -2
- package/dist/components/forms/InputCounter/InputCounter.js +3 -3
- package/dist/components/forms/InputOTP/InputOTP.d.ts +3 -12
- package/dist/components/forms/InputOTP/InputOTP.js +140 -151
- package/dist/components/forms/InputOTP/InputOTP.theme.d.ts +5 -1
- package/dist/components/forms/InputOTP/InputOTP.theme.js +40 -5
- package/dist/components/forms/InputOTP/InputOTP.types.d.ts +45 -2
- package/dist/components/forms/InputOTP/index.d.ts +1 -1
- package/dist/components/forms/InputTag/InputTag.js +5 -5
- package/dist/components/forms/InputTag/InputTag.theme.js +1 -2
- package/dist/components/forms/Mention/Mention.theme.js +3 -5
- package/dist/components/forms/Radio/Radio.js +3 -2
- package/dist/components/forms/Radio/Radio.theme.js +3 -3
- package/dist/components/forms/RichTextEditor/RichTextEditor.d.ts +12 -16
- package/dist/components/forms/RichTextEditor/RichTextEditor.js +165 -1681
- package/dist/components/forms/RichTextEditor/RichTextEditor.theme.d.ts +12 -56
- package/dist/components/forms/RichTextEditor/RichTextEditor.theme.js +60 -118
- package/dist/components/forms/RichTextEditor/RichTextEditor.types.d.ts +62 -126
- package/dist/components/forms/RichTextEditor/RichTextLinkField.d.ts +18 -0
- package/dist/components/forms/RichTextEditor/RichTextLinkField.js +46 -0
- package/dist/components/forms/RichTextEditor/RichTextToolbar.d.ts +15 -0
- package/dist/components/forms/RichTextEditor/RichTextToolbar.js +93 -0
- package/dist/components/forms/RichTextEditor/editorCommands.d.ts +50 -0
- package/dist/components/forms/RichTextEditor/editorCommands.js +58 -0
- package/dist/components/forms/RichTextEditor/editorState.d.ts +19 -0
- package/dist/components/forms/RichTextEditor/editorState.js +103 -0
- package/dist/components/forms/RichTextEditor/index.d.ts +5 -3
- package/dist/components/forms/RichTextEditor/sanitizeHtml.d.ts +38 -0
- package/dist/components/forms/RichTextEditor/sanitizeHtml.js +179 -0
- package/dist/components/forms/RichTextEditor/sanitizeHtml.test.d.ts +1 -0
- package/dist/components/forms/Select/Select.d.ts +5 -1
- package/dist/components/forms/Select/Select.js +14 -13
- package/dist/components/forms/Select/Select.theme.js +4 -4
- package/dist/components/forms/Select/Select.types.d.ts +74 -8
- package/dist/components/forms/Select/useSelectState.d.ts +9 -3
- package/dist/components/forms/Select/useSelectState.js +4 -1
- package/dist/components/forms/SelectableCard/SelectableCard.animations.d.ts +1 -2
- package/dist/components/forms/SelectableCard/SelectableCard.animations.js +6 -7
- package/dist/components/forms/SelectableCard/SelectableCard.js +3 -2
- package/dist/components/forms/SelectableCard/SelectableCard.theme.js +1 -1
- package/dist/components/forms/Signature/Signature.js +1 -1
- package/dist/components/forms/Slider/Slider.js +24 -11
- package/dist/components/forms/Slider/Slider.theme.js +9 -3
- package/dist/components/forms/Switch/Switch.theme.js +3 -3
- package/dist/components/forms/TimePicker/TimePicker.theme.js +7 -8
- package/dist/components/forms/TimePicker/TimePickerContent.js +1 -1
- package/dist/components/forms/TimePicker/TimePickerTrigger.js +3 -3
- package/dist/components/forms/forms.theme.js +38 -13
- package/dist/components/forms/index.js +2 -1
- package/dist/components/generative/FormShell.d.ts +22 -0
- package/dist/components/generative/FormShell.js +102 -0
- package/dist/components/generative/GenerativeView.d.ts +52 -1
- package/dist/components/generative/GenerativeView.js +203 -11
- package/dist/components/generative/actions.d.ts +5 -0
- package/dist/components/generative/ai-sdk/index.d.ts +1 -0
- package/dist/components/generative/ai-sdk/index.js +1 -0
- package/dist/components/generative/ai-sdk/useGenerativeChat.d.ts +52 -0
- package/dist/components/generative/ai-sdk/useGenerativeChat.js +73 -0
- package/dist/components/generative/ai-sdk/useGenerativeChat.test.d.ts +1 -0
- package/dist/components/generative/fieldBinding.d.ts +31 -0
- package/dist/components/generative/fieldBinding.js +33 -0
- package/dist/components/generative/fieldBinding.test.d.ts +1 -0
- package/dist/components/generative/formScope.d.ts +71 -0
- package/dist/components/generative/formScope.js +135 -0
- package/dist/components/generative/index.d.ts +4 -0
- package/dist/components/generative/index.js +4 -0
- package/dist/components/generative/registry.d.ts +2 -0
- package/dist/components/generative/registry.js +197 -4
- package/dist/components/generative/resolvers.js +16 -16
- package/dist/components/generative/schema.generated.js +2522 -328
- package/dist/components/generative/toToolResult.d.ts +21 -0
- package/dist/components/generative/toToolResult.js +15 -0
- package/dist/components/generative/toToolResult.test.d.ts +1 -0
- package/dist/components/generative/validate.d.ts +10 -1
- package/dist/components/generative/validate.js +126 -11
- package/dist/components/layout/Grid/Grid.js +1 -0
- package/dist/components/layout/Section/Section.theme.js +1 -1
- package/dist/components/layout/Stack/Stack.js +1 -0
- package/dist/generative.schema.json +2522 -328
- package/dist/hooks/index.d.ts +2 -0
- package/dist/hooks/useElapsedTime.d.ts +28 -0
- package/dist/hooks/useElapsedTime.js +37 -0
- package/dist/hooks/useSelectPortal.d.ts +10 -2
- package/dist/hooks/useSelectPortal.js +6 -3
- package/dist/index.js +2 -5
- package/dist/registry.json +3262 -2464
- package/dist/shadcn-compat.css +6 -6
- package/dist/styles/theme.test.d.ts +1 -0
- package/dist/styles.css +1 -1
- package/dist/test/render.d.ts +17 -0
- package/dist/theme.css +5 -1
- package/dist/themes/ember.css +131 -0
- package/dist/themes/iris.css +94 -0
- package/dist/themes/jade.css +139 -0
- package/dist/themes/precision.css +232 -0
- package/dist/tools.json +2557 -328
- package/dist/utils/colorUtils.d.ts +6 -6
- package/dist/utils/colorUtils.js +8 -8
- package/dist/utils/index.d.ts +0 -1
- package/package.json +39 -6
- package/src/global.scss +32 -28
- package/src/styles/theme.css +547 -176
- package/src/styles/theme.test.ts +119 -0
- package/tailwind.preset.cjs +66 -38
- package/dist/components/charts/FunnelChart/FunnelChart.animations.d.ts +0 -35
- package/dist/components/charts/FunnelChart/FunnelChart.animations.js +0 -59
- package/dist/components/charts/FunnelChart/FunnelChart.d.ts +0 -8
- package/dist/components/charts/FunnelChart/FunnelChart.js +0 -410
- package/dist/components/charts/FunnelChart/FunnelChart.theme.d.ts +0 -2
- package/dist/components/charts/FunnelChart/FunnelChart.theme.js +0 -12
- package/dist/components/charts/FunnelChart/FunnelChart.types.d.ts +0 -105
- package/dist/components/charts/FunnelChart/index.d.ts +0 -4
- package/dist/components/charts/ScatterPlot/ScatterPlot.d.ts +0 -9
- package/dist/components/charts/ScatterPlot/ScatterPlot.js +0 -207
- package/dist/components/charts/ScatterPlot/ScatterPlot.theme.d.ts +0 -18
- package/dist/components/charts/ScatterPlot/ScatterPlot.theme.js +0 -42
- package/dist/components/charts/ScatterPlot/ScatterPlot.types.d.ts +0 -65
- package/dist/components/charts/ScatterPlot/ScatterPlot.types.js +0 -12
- package/dist/components/charts/ScatterPlot/index.d.ts +0 -3
- package/dist/components/core/Button/Button.ripple.d.ts +0 -71
- package/dist/components/core/Button/Button.ripple.js +0 -236
- package/dist/components/core/Calendar/CalendarEvent/CalendarEvent.d.ts +0 -4
- package/dist/components/core/Calendar/CalendarEvent/CalendarEvent.theme.d.ts +0 -12
- package/dist/components/core/Calendar/CalendarEvent/CalendarEvent.types.d.ts +0 -23
- package/dist/components/core/Calendar/CalendarEvent/index.d.ts +0 -2
- package/dist/components/core/CardStack/CardStack.animations.d.ts +0 -47
- package/dist/components/core/CardStack/CardStack.d.ts +0 -3
- package/dist/components/core/CardStack/CardStack.js +0 -187
- package/dist/components/core/CardStack/CardStack.theme.d.ts +0 -6
- package/dist/components/core/CardStack/CardStack.theme.js +0 -33
- package/dist/components/core/CardStack/CardStack.types.d.ts +0 -139
- package/dist/components/core/CardStack/index.d.ts +0 -7
- package/dist/components/core/Carousel/Carousel.d.ts +0 -3
- package/dist/components/core/Carousel/Carousel.js +0 -83
- package/dist/components/core/Carousel/Carousel.theme.d.ts +0 -10
- package/dist/components/core/Carousel/Carousel.theme.js +0 -20
- package/dist/components/core/Carousel/Carousel.types.d.ts +0 -113
- package/dist/components/core/Carousel/CarouselBody.d.ts +0 -4
- package/dist/components/core/Carousel/CarouselBody.js +0 -59
- package/dist/components/core/Carousel/CarouselContext.d.ts +0 -3
- package/dist/components/core/Carousel/CarouselContext.js +0 -13
- package/dist/components/core/Carousel/CarouselItem.d.ts +0 -4
- package/dist/components/core/Carousel/CarouselItem.js +0 -11
- package/dist/components/core/Carousel/CarouselNext.d.ts +0 -4
- package/dist/components/core/Carousel/CarouselNext.js +0 -16
- package/dist/components/core/Carousel/CarouselPrevious.d.ts +0 -4
- package/dist/components/core/Carousel/CarouselPrevious.js +0 -16
- package/dist/components/core/Carousel/index.d.ts +0 -3
- package/dist/components/core/Pill/Pill.animations.d.ts +0 -32
- package/dist/components/core/Pill/Pill.animations.js +0 -16
- package/dist/components/effects/ColorPanels/ColorPanels.d.ts +0 -8
- package/dist/components/effects/ColorPanels/ColorPanels.js +0 -66
- package/dist/components/effects/ColorPanels/ColorPanels.shaders.d.ts +0 -12
- package/dist/components/effects/ColorPanels/ColorPanels.shaders.js +0 -218
- package/dist/components/effects/ColorPanels/ColorPanels.types.d.ts +0 -29
- package/dist/components/effects/ColorPanels/ColorPanels.types.js +0 -3
- package/dist/components/effects/ColorPanels/index.d.ts +0 -2
- package/dist/components/effects/FlutedGlass/FlutedGlass.d.ts +0 -9
- package/dist/components/effects/FlutedGlass/FlutedGlass.js +0 -80
- package/dist/components/effects/FlutedGlass/FlutedGlass.shaders.d.ts +0 -12
- package/dist/components/effects/FlutedGlass/FlutedGlass.shaders.js +0 -335
- package/dist/components/effects/FlutedGlass/FlutedGlass.types.d.ts +0 -61
- package/dist/components/effects/FlutedGlass/FlutedGlass.types.js +0 -16
- package/dist/components/effects/FlutedGlass/index.d.ts +0 -3
- package/dist/components/effects/GlassEffect/GlassEffect.d.ts +0 -15
- package/dist/components/effects/GlassEffect/GlassEffect.js +0 -38
- package/dist/components/effects/GlassEffect/GlassEffect.types.d.ts +0 -19
- package/dist/components/effects/GlassEffect/index.d.ts +0 -2
- package/dist/components/effects/GlassSurface/GlassSurface.d.ts +0 -8
- package/dist/components/effects/GlassSurface/GlassSurface.js +0 -250
- package/dist/components/effects/GlassSurface/GlassSurface.types.d.ts +0 -66
- package/dist/components/effects/GlassSurface/index.d.ts +0 -2
- package/dist/components/effects/GodRays/GodRays.d.ts +0 -8
- package/dist/components/effects/GodRays/GodRays.js +0 -68
- package/dist/components/effects/GodRays/GodRays.shaders.d.ts +0 -10
- package/dist/components/effects/GodRays/GodRays.shaders.js +0 -128
- package/dist/components/effects/GodRays/GodRays.types.d.ts +0 -25
- package/dist/components/effects/GodRays/GodRays.types.js +0 -3
- package/dist/components/effects/GodRays/index.d.ts +0 -2
- package/dist/components/effects/GridPattern/GridPattern.d.ts +0 -18
- package/dist/components/effects/GridPattern/GridPattern.js +0 -65
- package/dist/components/effects/GridPattern/GridPattern.types.d.ts +0 -47
- package/dist/components/effects/GridPattern/index.d.ts +0 -2
- package/dist/components/effects/HalftoneCmyk/HalftoneCmyk.d.ts +0 -10
- package/dist/components/effects/HalftoneCmyk/HalftoneCmyk.js +0 -78
- package/dist/components/effects/HalftoneCmyk/HalftoneCmyk.shaders.d.ts +0 -22
- package/dist/components/effects/HalftoneCmyk/HalftoneCmyk.shaders.js +0 -239
- package/dist/components/effects/HalftoneCmyk/HalftoneCmyk.types.d.ts +0 -52
- package/dist/components/effects/HalftoneCmyk/index.d.ts +0 -2
- package/dist/components/effects/HalftoneDots/HalftoneDots.d.ts +0 -9
- package/dist/components/effects/HalftoneDots/HalftoneDots.js +0 -68
- package/dist/components/effects/HalftoneDots/HalftoneDots.shaders.d.ts +0 -13
- package/dist/components/effects/HalftoneDots/HalftoneDots.shaders.js +0 -296
- package/dist/components/effects/HalftoneDots/HalftoneDots.types.d.ts +0 -43
- package/dist/components/effects/HalftoneDots/HalftoneDots.types.js +0 -12
- package/dist/components/effects/HalftoneDots/index.d.ts +0 -3
- package/dist/components/effects/ImageDithering/ImageDithering.d.ts +0 -9
- package/dist/components/effects/ImageDithering/ImageDithering.js +0 -68
- package/dist/components/effects/ImageDithering/ImageDithering.shaders.d.ts +0 -12
- package/dist/components/effects/ImageDithering/ImageDithering.shaders.js +0 -117
- package/dist/components/effects/ImageDithering/ImageDithering.types.d.ts +0 -30
- package/dist/components/effects/ImageDithering/ImageDithering.types.js +0 -8
- package/dist/components/effects/ImageDithering/index.d.ts +0 -3
- package/dist/components/effects/MagneticElement/MagneticElement.d.ts +0 -30
- package/dist/components/effects/MagneticElement/MagneticElement.js +0 -111
- package/dist/components/effects/MagneticElement/MagneticElement.types.d.ts +0 -44
- package/dist/components/effects/MagneticElement/index.d.ts +0 -2
- package/dist/components/effects/Metaballs/Metaballs.d.ts +0 -8
- package/dist/components/effects/Metaballs/Metaballs.js +0 -59
- package/dist/components/effects/Metaballs/Metaballs.shaders.d.ts +0 -10
- package/dist/components/effects/Metaballs/Metaballs.shaders.js +0 -102
- package/dist/components/effects/Metaballs/Metaballs.types.d.ts +0 -16
- package/dist/components/effects/Metaballs/Metaballs.types.js +0 -3
- package/dist/components/effects/Metaballs/index.d.ts +0 -2
- package/dist/components/effects/PaperTexture/PaperTexture.d.ts +0 -9
- package/dist/components/effects/PaperTexture/PaperTexture.js +0 -61
- package/dist/components/effects/PaperTexture/PaperTexture.shaders.d.ts +0 -22
- package/dist/components/effects/PaperTexture/PaperTexture.shaders.js +0 -145
- package/dist/components/effects/PaperTexture/PaperTexture.types.d.ts +0 -19
- package/dist/components/effects/PaperTexture/index.d.ts +0 -2
- package/dist/components/effects/PulsingBorder/PulsingBorder.d.ts +0 -27
- package/dist/components/effects/PulsingBorder/PulsingBorder.js +0 -174
- package/dist/components/effects/PulsingBorder/PulsingBorder.shaders.d.ts +0 -14
- package/dist/components/effects/PulsingBorder/PulsingBorder.shaders.js +0 -258
- package/dist/components/effects/PulsingBorder/PulsingBorder.types.d.ts +0 -75
- package/dist/components/effects/PulsingBorder/PulsingBorder.types.js +0 -3
- package/dist/components/effects/PulsingBorder/index.d.ts +0 -2
- package/dist/components/effects/SmokeRing/SmokeRing.d.ts +0 -8
- package/dist/components/effects/SmokeRing/SmokeRing.js +0 -62
- package/dist/components/effects/SmokeRing/SmokeRing.shaders.d.ts +0 -14
- package/dist/components/effects/SmokeRing/SmokeRing.shaders.js +0 -130
- package/dist/components/effects/SmokeRing/SmokeRing.types.d.ts +0 -22
- package/dist/components/effects/SmokeRing/SmokeRing.types.js +0 -3
- package/dist/components/effects/SmokeRing/index.d.ts +0 -2
- package/dist/components/effects/StaticGradientMesh/StaticGradientMesh.d.ts +0 -9
- package/dist/components/effects/StaticGradientMesh/StaticGradientMesh.js +0 -59
- package/dist/components/effects/StaticGradientMesh/StaticGradientMesh.shaders.d.ts +0 -14
- package/dist/components/effects/StaticGradientMesh/StaticGradientMesh.shaders.js +0 -129
- package/dist/components/effects/StaticGradientMesh/StaticGradientMesh.types.d.ts +0 -23
- package/dist/components/effects/StaticGradientMesh/StaticGradientMesh.types.js +0 -3
- package/dist/components/effects/StaticGradientMesh/index.d.ts +0 -2
- package/dist/components/effects/Water/Water.d.ts +0 -9
- package/dist/components/effects/Water/Water.js +0 -65
- package/dist/components/effects/Water/Water.shaders.d.ts +0 -11
- package/dist/components/effects/Water/Water.shaders.js +0 -123
- package/dist/components/effects/Water/Water.types.d.ts +0 -25
- package/dist/components/effects/Water/index.d.ts +0 -2
- package/dist/components/effects/shared/bayerMatrices.glsl.d.ts +0 -10
- package/dist/components/effects/shared/bayerMatrices.glsl.js +0 -44
- package/dist/components/effects/shared/imageUV.glsl.d.ts +0 -12
- package/dist/components/effects/shared/imageUV.glsl.js +0 -26
- package/dist/components/forms/RichTextEditor/RichTextEditor.animations.d.ts +0 -7
- package/dist/components/forms/RichTextEditor/RichTextEditor.animations.js +0 -32
- package/dist/utils/calendarUtils.d.ts +0 -60
- /package/dist/components/{charts → core}/shared/ChartTooltip/ChartTooltip.d.ts +0 -0
- /package/dist/components/{charts → core}/shared/ChartTooltip/ChartTooltip.theme.d.ts +0 -0
- /package/dist/components/{charts → core}/shared/ChartTooltip/ChartTooltip.theme.js +0 -0
- /package/dist/components/{charts → core}/shared/ChartTooltip/ChartTooltip.types.d.ts +0 -0
- /package/dist/components/{charts → core}/shared/ChartTooltip/ChartTooltipContent.d.ts +0 -0
- /package/dist/components/{charts → core}/shared/ChartTooltip/index.d.ts +0 -0
|
@@ -1,1716 +1,200 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsxs, jsx } from 'react/jsx-runtime';
|
|
3
|
-
import React__default, {
|
|
4
|
-
import { createPortal } from 'react-dom';
|
|
5
|
-
import { useReducedMotion, AnimatePresence, motion } from 'motion/react';
|
|
3
|
+
import React__default, { useRef, useId, useState, useCallback, useEffect } from 'react';
|
|
6
4
|
import { FormLabel } from '../FormLabel/FormLabel.js';
|
|
7
|
-
import { MONO_COLORS, TAILWIND_COLOR_FAMILIES, getTailwindShadeTokens, richTextEditorTheme, resolveTailwindColor } from './RichTextEditor.theme.js';
|
|
8
|
-
import { floatingToolbarVariants } from './RichTextEditor.animations.js';
|
|
9
5
|
import { cn } from '../../../utils/cn.js';
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
import '
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
15
|
-
import {
|
|
16
|
-
import { createDropdownAnimations } from '../Select/Select.animations.js';
|
|
17
|
-
import { useIsClient } from '../../../hooks/useIsClient.js';
|
|
18
|
-
import { useSurfaceElevation, SurfaceProvider, surfaceClasses } from '../../../theme/SurfaceContext.js';
|
|
6
|
+
import { richTextEditorTheme } from './RichTextEditor.theme.js';
|
|
7
|
+
import { DEFAULT_TOOLBAR_TOOLS, RichTextToolbar } from './RichTextToolbar.js';
|
|
8
|
+
import { RichTextLinkField } from './RichTextLinkField.js';
|
|
9
|
+
import { sanitizeHtml } from './sanitizeHtml.js';
|
|
10
|
+
import { EMPTY_FORMATTING_STATE, readFormattingState } from './editorState.js';
|
|
11
|
+
import { redo, undo, clearFormatting, toggleCodeBlock, toggleBlockquote, toggleOrderedList, toggleBulletList, toggleStrikethrough, toggleUnderline, toggleItalic, toggleBold, setBlockStyle, insertSanitizedHtml, insertText, createLink, removeLink } from './editorCommands.js';
|
|
19
12
|
|
|
20
|
-
const FONT_SIZE_OPTIONS = [
|
|
21
|
-
{ key: "xs", label: "XS", value: "0.75rem", px: 12, className: "text-xs" },
|
|
22
|
-
{ key: "sm", label: "SM", value: "0.875rem", px: 14, className: "text-sm" },
|
|
23
|
-
{ key: "md", label: "MD", value: "1rem", px: 16, className: "text-base" },
|
|
24
|
-
{ key: "lg", label: "LG", value: "1.125rem", px: 18, className: "text-lg" },
|
|
25
|
-
{ key: "xl", label: "XL", value: "1.25rem", px: 20, className: "text-xl" },
|
|
26
|
-
{ key: "2xl", label: "2XL", value: "1.5rem", px: 24, className: "text-2xl" },
|
|
27
|
-
{ key: "3xl", label: "3XL", value: "1.875rem", px: 30, className: "text-3xl" },
|
|
28
|
-
{ key: "4xl", label: "4XL", value: "2.25rem", px: 36, className: "text-4xl" },
|
|
29
|
-
{ key: "5xl", label: "5XL", value: "3rem", px: 48, className: "text-5xl" },
|
|
30
|
-
{ key: "6xl", label: "6XL", value: "3.75rem", px: 60, className: "text-6xl" },
|
|
31
|
-
{ key: "7xl", label: "7XL", value: "4.5rem", px: 72, className: "text-7xl" },
|
|
32
|
-
{ key: "8xl", label: "8XL", value: "6rem", px: 96, className: "text-8xl" },
|
|
33
|
-
{ key: "9xl", label: "9XL", value: "8rem", px: 128, className: "text-9xl" },
|
|
34
|
-
];
|
|
35
|
-
/** All font-size class names — used to scrub stale classes when swapping. */
|
|
36
|
-
const FONT_SIZE_CLASSES = FONT_SIZE_OPTIONS.map((o) => o.className);
|
|
37
|
-
const getFontSizeOption = (key) => { var _a; return (_a = FONT_SIZE_OPTIONS.find((option) => option.key === key)) !== null && _a !== void 0 ? _a : FONT_SIZE_OPTIONS[2]; };
|
|
38
|
-
const getClosestFontSizeKey = (px) => {
|
|
39
|
-
let closest = "md";
|
|
40
|
-
let minDiff = Number.POSITIVE_INFINITY;
|
|
41
|
-
FONT_SIZE_OPTIONS.forEach((option) => {
|
|
42
|
-
const diff = Math.abs(option.px - px);
|
|
43
|
-
if (diff < minDiff) {
|
|
44
|
-
minDiff = diff;
|
|
45
|
-
closest = option.key;
|
|
46
|
-
}
|
|
47
|
-
});
|
|
48
|
-
return closest;
|
|
49
|
-
};
|
|
50
|
-
/**
|
|
51
|
-
* Curated default list of font families shipped with the editor.
|
|
52
|
-
* The `Default` entry clears the inline font-family. The rest are popular
|
|
53
|
-
* Google Fonts chosen for broad weight coverage. Each stack ends in a sensible
|
|
54
|
-
* fallback so text stays readable while the font is loading.
|
|
55
|
-
*/
|
|
56
|
-
const DEFAULT_FONT_FAMILY_OPTIONS = [
|
|
57
|
-
{ key: "default", label: "Default", stack: "" },
|
|
58
|
-
{ key: "inter", label: "Inter", family: "Inter", stack: "'Inter', system-ui, sans-serif" },
|
|
59
|
-
{ key: "roboto", label: "Roboto", family: "Roboto", stack: "'Roboto', system-ui, sans-serif" },
|
|
60
|
-
{ key: "manrope", label: "Manrope", family: "Manrope", stack: "'Manrope', system-ui, sans-serif" },
|
|
61
|
-
{ key: "poppins", label: "Poppins", family: "Poppins", stack: "'Poppins', system-ui, sans-serif" },
|
|
62
|
-
{ key: "lora", label: "Lora", family: "Lora", stack: "'Lora', Georgia, serif" },
|
|
63
|
-
{ key: "playfair", label: "Playfair Display", family: "Playfair Display", stack: "'Playfair Display', Georgia, serif" },
|
|
64
|
-
{ key: "merriweather", label: "Merriweather", family: "Merriweather", stack: "'Merriweather', Georgia, serif" },
|
|
65
|
-
{ key: "jetbrains", label: "JetBrains Mono", family: "JetBrains Mono", stack: "'JetBrains Mono', Menlo, monospace" },
|
|
66
|
-
];
|
|
67
|
-
// Bumped when migrating from raw hex values to Tailwind shade tokens
|
|
68
|
-
// (e.g. "rose-500"). Old hex entries from prior versions are ignored — the
|
|
69
|
-
// picker now applies color via `text-{token}` classes, not via execCommand.
|
|
70
|
-
const RECENT_COLORS_KEY = "flikkui-rte-recent-colors-v2";
|
|
71
|
-
const RECENT_COLORS_LIMIT = 10;
|
|
72
|
-
const DEFAULT_FONT_WEIGHT_REQUEST = [100, 200, 300, 400, 500, 600, 700, 800, 900];
|
|
73
|
-
const loadedGoogleFonts = new Set();
|
|
74
13
|
/**
|
|
75
|
-
*
|
|
76
|
-
* Idempotent: each family is requested at most once per page lifetime.
|
|
77
|
-
* SSR-safe (no-ops when `document` is undefined).
|
|
14
|
+
* RichTextEditor — a semantic authoring surface over `contentEditable`.
|
|
78
15
|
*
|
|
79
|
-
*
|
|
80
|
-
*
|
|
81
|
-
*
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
if (loadedGoogleFonts.has(cacheKey))
|
|
90
|
-
return;
|
|
91
|
-
loadedGoogleFonts.add(cacheKey);
|
|
92
|
-
const familyParam = family.replace(/\s+/g, "+");
|
|
93
|
-
const weightsParam = weights.length > 0 ? `:wght@${weights.join(";")}` : "";
|
|
94
|
-
const href = `https://fonts.googleapis.com/css2?family=${familyParam}${weightsParam}&display=swap`;
|
|
95
|
-
const link = document.createElement("link");
|
|
96
|
-
link.rel = "stylesheet";
|
|
97
|
-
link.href = href;
|
|
98
|
-
link.setAttribute("data-flikkui-font", family);
|
|
99
|
-
document.head.appendChild(link);
|
|
100
|
-
};
|
|
101
|
-
const FONT_WEIGHT_OPTIONS = [
|
|
102
|
-
{ key: "thin", label: "Thin", value: "100", weight: 100, className: "font-thin" },
|
|
103
|
-
{ key: "extralight", label: "Extra Light", value: "200", weight: 200, className: "font-extralight" },
|
|
104
|
-
{ key: "light", label: "Light", value: "300", weight: 300, className: "font-light" },
|
|
105
|
-
{ key: "normal", label: "Regular", value: "400", weight: 400, className: "font-normal" },
|
|
106
|
-
{ key: "medium", label: "Medium", value: "500", weight: 500, className: "font-medium" },
|
|
107
|
-
{ key: "semibold", label: "Semibold", value: "600", weight: 600, className: "font-semibold" },
|
|
108
|
-
{ key: "bold", label: "Bold", value: "700", weight: 700, className: "font-bold" },
|
|
109
|
-
{ key: "extrabold", label: "Extra Bold", value: "800", weight: 800, className: "font-extrabold" },
|
|
110
|
-
{ key: "black", label: "Black", value: "900", weight: 900, className: "font-black" },
|
|
111
|
-
];
|
|
112
|
-
const FONT_WEIGHT_CLASSES = FONT_WEIGHT_OPTIONS.map((o) => o.className);
|
|
113
|
-
const getFontWeightOption = (key) => { var _a; return (_a = FONT_WEIGHT_OPTIONS.find((option) => option.key === key)) !== null && _a !== void 0 ? _a : FONT_WEIGHT_OPTIONS[3]; };
|
|
114
|
-
const getClosestFontWeightKey = (weight) => {
|
|
115
|
-
let closest = "normal";
|
|
116
|
-
let minDiff = Number.POSITIVE_INFINITY;
|
|
117
|
-
FONT_WEIGHT_OPTIONS.forEach((option) => {
|
|
118
|
-
const diff = Math.abs(option.weight - weight);
|
|
119
|
-
if (diff < minDiff) {
|
|
120
|
-
minDiff = diff;
|
|
121
|
-
closest = option.key;
|
|
122
|
-
}
|
|
123
|
-
});
|
|
124
|
-
return closest;
|
|
125
|
-
};
|
|
126
|
-
const LINE_HEIGHT_OPTIONS = [
|
|
127
|
-
{ key: "tight", label: "Tight", value: "1.25", className: "leading-tight" },
|
|
128
|
-
{ key: "snug", label: "Snug", value: "1.375", className: "leading-snug" },
|
|
129
|
-
{ key: "normal", label: "Normal", value: "1.5", className: "leading-normal" },
|
|
130
|
-
{ key: "relaxed", label: "Relaxed", value: "1.625", className: "leading-relaxed" },
|
|
131
|
-
{ key: "loose", label: "Loose", value: "2", className: "leading-loose" },
|
|
132
|
-
];
|
|
133
|
-
const LINE_HEIGHT_CLASSES = LINE_HEIGHT_OPTIONS.map((o) => o.className);
|
|
134
|
-
const getLineHeightOption = (key) => { var _a; return (_a = LINE_HEIGHT_OPTIONS.find((option) => option.key === key)) !== null && _a !== void 0 ? _a : LINE_HEIGHT_OPTIONS[2]; };
|
|
135
|
-
const LETTER_SPACING_OPTIONS = [
|
|
136
|
-
{ key: "tighter", label: "Tighter", value: "-0.05em", em: -0.05, className: "tracking-tighter" },
|
|
137
|
-
{ key: "tight", label: "Tight", value: "-0.025em", em: -0.025, className: "tracking-tight" },
|
|
138
|
-
{ key: "normal", label: "Normal", value: "0em", em: 0, className: "tracking-normal" },
|
|
139
|
-
{ key: "wide", label: "Wide", value: "0.025em", em: 0.025, className: "tracking-wide" },
|
|
140
|
-
{ key: "wider", label: "Wider", value: "0.05em", em: 0.05, className: "tracking-wider" },
|
|
141
|
-
{ key: "widest", label: "Widest", value: "0.1em", em: 0.1, className: "tracking-widest" },
|
|
142
|
-
];
|
|
143
|
-
const LETTER_SPACING_CLASSES = LETTER_SPACING_OPTIONS.map((o) => o.className);
|
|
144
|
-
/**
|
|
145
|
-
* Every text-color class the picker can apply: `text-white`, `text-black`,
|
|
146
|
-
* and `text-{family}-{shade}` for each Tailwind family/shade. Used both to
|
|
147
|
-
* apply the new color and to scrub stale color classes off ancestor wrappers.
|
|
148
|
-
* Derived once at module load — list size is static.
|
|
16
|
+
* Produces portable HTML (`<h2>`, `<strong>`, `<ul>`, `<a href>`) with no
|
|
17
|
+
* framework classes, so stored content renders anywhere. Formatting is applied
|
|
18
|
+
* through `editorCommands.ts` (the only `execCommand` caller) and read back
|
|
19
|
+
* through `editorState.ts`.
|
|
20
|
+
*
|
|
21
|
+
* 🔴 Every write path is sanitized: the `value` prop, paste, drop, and the string
|
|
22
|
+
* handed to `onChange` — the last regardless of whether `onChange` was passed, so
|
|
23
|
+
* an uncontrolled editor is not a hole.
|
|
24
|
+
*
|
|
25
|
+
* 🔴 Per §9, `className`, `ref` and `...rest` target the contentEditable surface.
|
|
149
26
|
*/
|
|
150
|
-
const
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
];
|
|
154
|
-
/** Returns the active text-color token (e.g. `"rose-500"`) walking up from the
|
|
155
|
-
* given element, or `""` if no `text-{token}` class is found before the editor
|
|
156
|
-
* boundary. */
|
|
157
|
-
const matchTextColorClass = (el, editor) => {
|
|
158
|
-
let n = el;
|
|
159
|
-
while (n && n !== editor) {
|
|
160
|
-
for (let i = 0; i < n.classList.length; i++) {
|
|
161
|
-
const cls = n.classList.item(i);
|
|
162
|
-
if (cls && TEXT_COLOR_CLASSES.includes(cls))
|
|
163
|
-
return cls.slice("text-".length);
|
|
164
|
-
}
|
|
165
|
-
n = n.parentElement;
|
|
166
|
-
}
|
|
167
|
-
return "";
|
|
168
|
-
};
|
|
169
|
-
const getLetterSpacingOption = (key) => { var _a; return (_a = LETTER_SPACING_OPTIONS.find((option) => option.key === key)) !== null && _a !== void 0 ? _a : LETTER_SPACING_OPTIONS[2]; };
|
|
170
|
-
const DEFAULT_FORMATTING_STATE = {
|
|
171
|
-
bold: false,
|
|
172
|
-
italic: false,
|
|
173
|
-
underline: false,
|
|
174
|
-
textAlign: "left",
|
|
175
|
-
fontSize: "md",
|
|
176
|
-
fontWeight: "normal",
|
|
177
|
-
fontFamily: "default",
|
|
178
|
-
lineHeight: "normal",
|
|
179
|
-
letterSpacing: "normal",
|
|
180
|
-
// Token like "rose-500" / "white" / "black"; "" means inherit (no class
|
|
181
|
-
// applied). The picker now applies color via `text-{token}` classes
|
|
182
|
-
// rather than execCommand("foreColor"), so the state holds tokens.
|
|
183
|
-
textColor: "",
|
|
184
|
-
};
|
|
185
|
-
const DEFAULT_TOOLBAR = {
|
|
186
|
-
show: true,
|
|
187
|
-
position: "top",
|
|
188
|
-
tools: [
|
|
189
|
-
"fontWeight",
|
|
190
|
-
"italic",
|
|
191
|
-
"underline",
|
|
192
|
-
"separator",
|
|
193
|
-
"fontFamily",
|
|
194
|
-
"fontSize",
|
|
195
|
-
"lineHeight",
|
|
196
|
-
"letterSpacing",
|
|
197
|
-
"textColor",
|
|
198
|
-
"separator",
|
|
199
|
-
"alignLeft",
|
|
200
|
-
"alignCenter",
|
|
201
|
-
"alignRight",
|
|
202
|
-
"separator",
|
|
203
|
-
"undo",
|
|
204
|
-
"redo",
|
|
205
|
-
],
|
|
206
|
-
};
|
|
207
|
-
const RichTextEditor = React__default.forwardRef(({ size = "md", state = "default", className = "", label, labelClassName = "", helperText, errorMessage, helperTextClassName = "", value = "", onChange, placeholder = "Start typing...", id, required, minHeight = 120, maxHeight, toolbar: toolbarProp = DEFAULT_TOOLBAR, theme = {}, fontFamilies = DEFAULT_FONT_FAMILY_OPTIONS, loadFontsOnDemand = true, sanitize, elevation, lift, ...props }, ref) => {
|
|
208
|
-
// Relative elevation: the floating toolbar (bubble menu) and the toolbar
|
|
209
|
-
// dropdowns lift above their host surface (overlay base = 5) so an editor
|
|
210
|
-
// inside a Popover/Modal still reads its floating chrome as a higher plane.
|
|
211
|
-
const surfaceLevel = useSurfaceElevation({ baseRung: 5, offset: 2, elevation, lift });
|
|
212
|
-
const [isFocused, setIsFocused] = useState(false);
|
|
213
|
-
const [showToolbar, setShowToolbar] = useState(false);
|
|
214
|
-
const [toolbarPosition, setToolbarPosition] = useState({ top: 0, left: 0 });
|
|
215
|
-
const [formattingState, setFormattingState] = useState(DEFAULT_FORMATTING_STATE);
|
|
216
|
-
const [showFontSizePicker, setShowFontSizePicker] = useState(false);
|
|
217
|
-
const [showFontWeightPicker, setShowFontWeightPicker] = useState(false);
|
|
218
|
-
const [showFontFamilyPicker, setShowFontFamilyPicker] = useState(false);
|
|
219
|
-
const [showLineHeightPicker, setShowLineHeightPicker] = useState(false);
|
|
220
|
-
const [showLetterSpacingPicker, setShowLetterSpacingPicker] = useState(false);
|
|
221
|
-
const [showColorPicker, setShowColorPicker] = useState(false);
|
|
222
|
-
// Recently used text colors — persisted to localStorage so they survive reloads.
|
|
223
|
-
const [recentColors, setRecentColors] = useState(() => {
|
|
224
|
-
if (typeof window === "undefined")
|
|
225
|
-
return [];
|
|
226
|
-
try {
|
|
227
|
-
const stored = window.localStorage.getItem(RECENT_COLORS_KEY);
|
|
228
|
-
if (!stored)
|
|
229
|
-
return [];
|
|
230
|
-
const parsed = JSON.parse(stored);
|
|
231
|
-
return Array.isArray(parsed)
|
|
232
|
-
? parsed.filter((v) => typeof v === "string").slice(0, RECENT_COLORS_LIMIT)
|
|
233
|
-
: [];
|
|
234
|
-
}
|
|
235
|
-
catch (_a) {
|
|
236
|
-
return [];
|
|
237
|
-
}
|
|
238
|
-
});
|
|
239
|
-
const addRecentColor = useCallback((token) => {
|
|
240
|
-
if (!token)
|
|
241
|
-
return;
|
|
242
|
-
setRecentColors((prev) => {
|
|
243
|
-
const next = [token, ...prev.filter((c) => c !== token)].slice(0, RECENT_COLORS_LIMIT);
|
|
244
|
-
if (typeof window !== "undefined") {
|
|
245
|
-
try {
|
|
246
|
-
window.localStorage.setItem(RECENT_COLORS_KEY, JSON.stringify(next));
|
|
247
|
-
}
|
|
248
|
-
catch (_a) {
|
|
249
|
-
// localStorage unavailable (private mode, quota exceeded, etc.) — ignore.
|
|
250
|
-
}
|
|
251
|
-
}
|
|
252
|
-
return next;
|
|
253
|
-
});
|
|
254
|
-
}, []);
|
|
255
|
-
const shouldReduceMotion = useReducedMotion();
|
|
256
|
-
const isClient = useIsClient();
|
|
257
|
-
// Built-in basic sanitizer: strips <script> tags and on* event handlers
|
|
258
|
-
const sanitizeHtml = useCallback((html) => {
|
|
259
|
-
if (sanitize)
|
|
260
|
-
return sanitize(html);
|
|
261
|
-
// Basic sanitization — strips script tags and inline event handlers
|
|
262
|
-
return html
|
|
263
|
-
.replace(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, '')
|
|
264
|
-
.replace(/\bon\w+\s*=\s*(?:"[^"]*"|'[^']*'|[^\s>]+)/gi, '');
|
|
265
|
-
}, [sanitize]);
|
|
27
|
+
const RichTextEditor = React__default.forwardRef(({ size = "md", state = "default", label, helperText, errorMessage, value, onChange, placeholder, className, wrapperClassName, containerClassName, toolbarClassName, labelClassName, helperTextClassName, required = false, minHeight = 160, maxHeight, toolbar, theme: themeOverrides, sanitize, ...rest }, ref) => {
|
|
28
|
+
var _a;
|
|
29
|
+
const theme = { ...richTextEditorTheme, ...themeOverrides };
|
|
266
30
|
const editorRef = useRef(null);
|
|
267
|
-
const
|
|
268
|
-
const
|
|
269
|
-
const fontWeightTriggerRef = useRef(null);
|
|
270
|
-
const fontFamilyTriggerRef = useRef(null);
|
|
271
|
-
const lineHeightTriggerRef = useRef(null);
|
|
272
|
-
const letterSpacingTriggerRef = useRef(null);
|
|
273
|
-
const colorTriggerRef = useRef(null);
|
|
274
|
-
// Single linear history covering ALL mutations: typing, execCommand
|
|
275
|
-
// (bold/italic/foreColor/justify/…), and direct-DOM style ops. Typing is
|
|
276
|
-
// coalesced into one undo entry per ~500 ms run, so users get coarse-grained
|
|
277
|
-
// word-level undo instead of per-keystroke. Discrete style ops always start
|
|
278
|
-
// a fresh entry by passing kind='op'.
|
|
279
|
-
const historyRef = useRef({ undo: [], redo: [], lastPushKind: null, lastPushAt: 0 });
|
|
280
|
-
const HISTORY_LIMIT = 100;
|
|
281
|
-
const TYPING_COALESCE_MS = 500;
|
|
282
|
-
const pushHistory = useCallback((kind = "op") => {
|
|
283
|
-
if (!editorRef.current)
|
|
284
|
-
return;
|
|
285
|
-
const now = Date.now();
|
|
286
|
-
const stack = historyRef.current;
|
|
287
|
-
// Coalesce successive typing events into a single undo entry while the
|
|
288
|
-
// user is mid-word. Skip when the previous push was also typing AND it
|
|
289
|
-
// happened recently — the existing entry already represents this group.
|
|
290
|
-
if (kind === "typing" &&
|
|
291
|
-
stack.lastPushKind === "typing" &&
|
|
292
|
-
now - stack.lastPushAt < TYPING_COALESCE_MS) {
|
|
293
|
-
stack.lastPushAt = now;
|
|
294
|
-
// Any new mutation still invalidates the redo stack.
|
|
295
|
-
stack.redo = [];
|
|
296
|
-
return;
|
|
297
|
-
}
|
|
298
|
-
stack.undo.push(editorRef.current.innerHTML);
|
|
299
|
-
stack.redo = [];
|
|
300
|
-
stack.lastPushKind = kind;
|
|
301
|
-
stack.lastPushAt = now;
|
|
302
|
-
if (stack.undo.length > HISTORY_LIMIT)
|
|
303
|
-
stack.undo.shift();
|
|
304
|
-
}, []);
|
|
305
|
-
const generatedId = React__default.useId();
|
|
306
|
-
const editorId = id || generatedId;
|
|
307
|
-
const themeStyles = useMemo(() => {
|
|
308
|
-
var _a, _b;
|
|
309
|
-
return ({
|
|
310
|
-
...richTextEditorTheme,
|
|
311
|
-
...theme,
|
|
312
|
-
sizes: {
|
|
313
|
-
...richTextEditorTheme.sizes,
|
|
314
|
-
...((_a = theme === null || theme === void 0 ? void 0 : theme.sizes) !== null && _a !== void 0 ? _a : {}),
|
|
315
|
-
},
|
|
316
|
-
states: {
|
|
317
|
-
...richTextEditorTheme.states,
|
|
318
|
-
...((_b = theme === null || theme === void 0 ? void 0 : theme.states) !== null && _b !== void 0 ? _b : {}),
|
|
319
|
-
},
|
|
320
|
-
});
|
|
321
|
-
}, [theme]);
|
|
322
|
-
const toolbarConfig = useMemo(() => {
|
|
323
|
-
var _a, _b, _c;
|
|
324
|
-
const base = {
|
|
325
|
-
show: (_a = DEFAULT_TOOLBAR.show) !== null && _a !== void 0 ? _a : true,
|
|
326
|
-
position: (_b = DEFAULT_TOOLBAR.position) !== null && _b !== void 0 ? _b : "top",
|
|
327
|
-
tools: [...((_c = DEFAULT_TOOLBAR.tools) !== null && _c !== void 0 ? _c : [])],
|
|
328
|
-
className: DEFAULT_TOOLBAR.className,
|
|
329
|
-
};
|
|
330
|
-
if (toolbarProp) {
|
|
331
|
-
if (typeof toolbarProp.show === "boolean") {
|
|
332
|
-
base.show = toolbarProp.show;
|
|
333
|
-
}
|
|
334
|
-
if (toolbarProp.position) {
|
|
335
|
-
base.position = toolbarProp.position;
|
|
336
|
-
}
|
|
337
|
-
if (toolbarProp.tools) {
|
|
338
|
-
base.tools = [...toolbarProp.tools];
|
|
339
|
-
}
|
|
340
|
-
if (typeof toolbarProp.className === "string") {
|
|
341
|
-
base.className = toolbarProp.className;
|
|
342
|
-
}
|
|
343
|
-
}
|
|
344
|
-
return base;
|
|
345
|
-
}, [toolbarProp]);
|
|
346
|
-
const { position: fontSizePortalPosition, cssVariables: fontSizePortalCssVars, contentRef: fontSizePortalContentRef, isReady: fontSizePortalReady, isFlipped: fontSizeIsFlipped, } = useSelectPortal({
|
|
347
|
-
triggerRef: fontSizeTriggerRef,
|
|
348
|
-
isOpen: showFontSizePicker,
|
|
349
|
-
placement: "bottom-start",
|
|
350
|
-
offset: 6,
|
|
351
|
-
autoWidth: true,
|
|
352
|
-
});
|
|
353
|
-
const { position: fontWeightPortalPosition, cssVariables: fontWeightPortalCssVars, contentRef: fontWeightPortalContentRef, isReady: fontWeightPortalReady, isFlipped: fontWeightIsFlipped, } = useSelectPortal({
|
|
354
|
-
triggerRef: fontWeightTriggerRef,
|
|
355
|
-
isOpen: showFontWeightPicker,
|
|
356
|
-
placement: "bottom-start",
|
|
357
|
-
offset: 6,
|
|
358
|
-
autoWidth: true,
|
|
359
|
-
});
|
|
360
|
-
const { position: fontFamilyPortalPosition, cssVariables: fontFamilyPortalCssVars, contentRef: fontFamilyPortalContentRef, isReady: fontFamilyPortalReady, isFlipped: fontFamilyIsFlipped, } = useSelectPortal({
|
|
361
|
-
triggerRef: fontFamilyTriggerRef,
|
|
362
|
-
isOpen: showFontFamilyPicker,
|
|
363
|
-
placement: "bottom-start",
|
|
364
|
-
offset: 6,
|
|
365
|
-
autoWidth: true,
|
|
366
|
-
});
|
|
367
|
-
const { position: lineHeightPortalPosition, cssVariables: lineHeightPortalCssVars, contentRef: lineHeightPortalContentRef, isReady: lineHeightPortalReady, isFlipped: lineHeightIsFlipped, } = useSelectPortal({
|
|
368
|
-
triggerRef: lineHeightTriggerRef,
|
|
369
|
-
isOpen: showLineHeightPicker,
|
|
370
|
-
placement: "bottom-start",
|
|
371
|
-
offset: 6,
|
|
372
|
-
autoWidth: true,
|
|
373
|
-
});
|
|
374
|
-
const { position: letterSpacingPortalPosition, cssVariables: letterSpacingPortalCssVars, contentRef: letterSpacingPortalContentRef, isReady: letterSpacingPortalReady, isFlipped: letterSpacingIsFlipped, } = useSelectPortal({
|
|
375
|
-
triggerRef: letterSpacingTriggerRef,
|
|
376
|
-
isOpen: showLetterSpacingPicker,
|
|
377
|
-
placement: "bottom-start",
|
|
378
|
-
offset: 6,
|
|
379
|
-
autoWidth: true,
|
|
380
|
-
});
|
|
381
|
-
const { position: colorPortalPosition, cssVariables: colorPortalCssVars, contentRef: colorPortalContentRef, isReady: colorPortalReady, isFlipped: colorIsFlipped, } = useSelectPortal({
|
|
382
|
-
triggerRef: colorTriggerRef,
|
|
383
|
-
isOpen: showColorPicker,
|
|
384
|
-
placement: "bottom-start",
|
|
385
|
-
offset: 6,
|
|
386
|
-
autoWidth: true,
|
|
387
|
-
});
|
|
31
|
+
const generatedId = useId();
|
|
32
|
+
const describedById = `${generatedId}-description`;
|
|
388
33
|
const isDisabled = state === "disabled";
|
|
389
34
|
const isInvalid = state === "invalid";
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
const
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
return "focus";
|
|
401
|
-
return "default";
|
|
402
|
-
};
|
|
403
|
-
const focusEditor = useCallback(() => {
|
|
404
|
-
if (editorRef.current && document.activeElement !== editorRef.current) {
|
|
405
|
-
editorRef.current.focus();
|
|
406
|
-
}
|
|
407
|
-
}, []);
|
|
408
|
-
const closeAllPickers = useCallback(() => {
|
|
409
|
-
setShowFontSizePicker(false);
|
|
410
|
-
setShowFontWeightPicker(false);
|
|
411
|
-
setShowFontFamilyPicker(false);
|
|
412
|
-
setShowLineHeightPicker(false);
|
|
413
|
-
setShowLetterSpacingPicker(false);
|
|
414
|
-
setShowColorPicker(false);
|
|
415
|
-
}, []);
|
|
416
|
-
const togglePicker = useCallback((which) => {
|
|
417
|
-
setShowFontSizePicker((prev) => (which === "fontSize" ? !prev : false));
|
|
418
|
-
setShowFontWeightPicker((prev) => (which === "fontWeight" ? !prev : false));
|
|
419
|
-
setShowFontFamilyPicker((prev) => (which === "fontFamily" ? !prev : false));
|
|
420
|
-
setShowLineHeightPicker((prev) => (which === "lineHeight" ? !prev : false));
|
|
421
|
-
setShowLetterSpacingPicker((prev) => (which === "letterSpacing" ? !prev : false));
|
|
422
|
-
setShowColorPicker((prev) => (which === "color" ? !prev : false));
|
|
423
|
-
}, []);
|
|
424
|
-
const getCurrentFontSize = useCallback(() => {
|
|
425
|
-
if (!editorRef.current)
|
|
426
|
-
return DEFAULT_FORMATTING_STATE.fontSize;
|
|
427
|
-
const selection = window.getSelection();
|
|
428
|
-
let node = null;
|
|
429
|
-
if (selection && selection.rangeCount > 0) {
|
|
430
|
-
node = selection.getRangeAt(0).startContainer;
|
|
431
|
-
if (node && node.nodeType === Node.TEXT_NODE)
|
|
432
|
-
node = node.parentElement;
|
|
433
|
-
}
|
|
434
|
-
// Walk up looking for a `text-*` class (the size classes also bundle a
|
|
435
|
-
// matching line-height — that's why class-based size is reliable).
|
|
436
|
-
while (node && node !== editorRef.current) {
|
|
437
|
-
if (node instanceof HTMLElement) {
|
|
438
|
-
const match = FONT_SIZE_OPTIONS.find((o) => node && node.classList.contains(o.className));
|
|
439
|
-
if (match)
|
|
440
|
-
return match.key;
|
|
441
|
-
}
|
|
442
|
-
node = node ? node.parentNode : null;
|
|
443
|
-
}
|
|
444
|
-
// No explicit class found — fall back to the editor's computed font-size
|
|
445
|
-
// mapped to the closest preset. Handles initial/no-selection state.
|
|
446
|
-
const editorComputed = window.getComputedStyle(editorRef.current);
|
|
447
|
-
const editorSize = parseFloat(editorComputed.fontSize || "");
|
|
448
|
-
return Number.isFinite(editorSize)
|
|
449
|
-
? getClosestFontSizeKey(editorSize)
|
|
450
|
-
: DEFAULT_FORMATTING_STATE.fontSize;
|
|
451
|
-
}, []);
|
|
452
|
-
const getCurrentFontFamily = useCallback(() => {
|
|
453
|
-
if (!editorRef.current)
|
|
454
|
-
return "default";
|
|
455
|
-
const selection = window.getSelection();
|
|
456
|
-
let element = null;
|
|
457
|
-
if (selection && selection.rangeCount > 0) {
|
|
458
|
-
let node = selection.getRangeAt(0).startContainer;
|
|
459
|
-
if (node && node.nodeType === Node.TEXT_NODE)
|
|
460
|
-
node = node.parentElement;
|
|
461
|
-
if (node instanceof HTMLElement)
|
|
462
|
-
element = node;
|
|
463
|
-
}
|
|
464
|
-
while (element && editorRef.current.contains(element)) {
|
|
465
|
-
const inline = element.style.fontFamily;
|
|
466
|
-
if (inline) {
|
|
467
|
-
// Match against the first family in the inline stack
|
|
468
|
-
const firstFamily = inline.split(",")[0].trim().replace(/^['"]|['"]$/g, "");
|
|
469
|
-
const match = fontFamilies.find((opt) => {
|
|
470
|
-
if (!opt.family)
|
|
471
|
-
return false;
|
|
472
|
-
return opt.family.toLowerCase() === firstFamily.toLowerCase();
|
|
473
|
-
});
|
|
474
|
-
if (match)
|
|
475
|
-
return match.key;
|
|
476
|
-
}
|
|
477
|
-
element = element.parentElement;
|
|
478
|
-
}
|
|
479
|
-
return "default";
|
|
480
|
-
}, [fontFamilies]);
|
|
481
|
-
const getCurrentFontWeight = useCallback(() => {
|
|
35
|
+
const [formatting, setFormatting] = useState(EMPTY_FORMATTING_STATE);
|
|
36
|
+
const [isLinkFieldOpen, setLinkFieldOpen] = useState(false);
|
|
37
|
+
const [isEmpty, setIsEmpty] = useState(true);
|
|
38
|
+
// The Range as of the last selection inside the editor. The block <Select>
|
|
39
|
+
// takes focus when opened, which collapses the selection — restoring this
|
|
40
|
+
// before a command is what lets focusable toolbar widgets (which cannot use
|
|
41
|
+
// the preventDefault trick) still act on the user's text.
|
|
42
|
+
const savedRangeRef = useRef(null);
|
|
43
|
+
const sanitizeFn = useCallback((html) => (sanitize ? sanitize(html) : sanitizeHtml(html)), [sanitize]);
|
|
44
|
+
const saveSelection = useCallback(() => {
|
|
482
45
|
var _a;
|
|
483
|
-
if (!editorRef.current)
|
|
484
|
-
return DEFAULT_FORMATTING_STATE.fontWeight;
|
|
485
|
-
const selection = window.getSelection();
|
|
486
|
-
let node = null;
|
|
487
|
-
if (selection && selection.rangeCount > 0) {
|
|
488
|
-
node = selection.getRangeAt(0).startContainer;
|
|
489
|
-
if (node && node.nodeType === Node.TEXT_NODE)
|
|
490
|
-
node = node.parentElement;
|
|
491
|
-
}
|
|
492
|
-
while (node && node !== editorRef.current) {
|
|
493
|
-
if (node instanceof HTMLElement) {
|
|
494
|
-
const match = FONT_WEIGHT_OPTIONS.find((o) => node && node.classList.contains(o.className));
|
|
495
|
-
if (match)
|
|
496
|
-
return match.key;
|
|
497
|
-
}
|
|
498
|
-
node = node ? node.parentNode : null;
|
|
499
|
-
}
|
|
500
|
-
// Fallback: read computed font-weight (covers <strong>/<b> tags from
|
|
501
|
-
// the Bold toggle which produce semantic markup, not classes).
|
|
502
|
-
const target = (_a = (selection && selection.rangeCount > 0
|
|
503
|
-
? (selection.getRangeAt(0).startContainer instanceof HTMLElement
|
|
504
|
-
? selection.getRangeAt(0).startContainer
|
|
505
|
-
: selection.getRangeAt(0).startContainer.parentElement)
|
|
506
|
-
: null)) !== null && _a !== void 0 ? _a : editorRef.current;
|
|
507
|
-
const computed = window.getComputedStyle(target);
|
|
508
|
-
const weight = parseInt(computed.fontWeight, 10);
|
|
509
|
-
return Number.isFinite(weight)
|
|
510
|
-
? getClosestFontWeightKey(weight)
|
|
511
|
-
: DEFAULT_FORMATTING_STATE.fontWeight;
|
|
512
|
-
}, []);
|
|
513
|
-
const getCurrentLineHeight = useCallback(() => {
|
|
514
|
-
if (!editorRef.current)
|
|
515
|
-
return DEFAULT_FORMATTING_STATE.lineHeight;
|
|
516
|
-
const selection = window.getSelection();
|
|
517
|
-
let element = null;
|
|
518
|
-
if (selection && selection.rangeCount > 0) {
|
|
519
|
-
let node = selection.getRangeAt(0).startContainer;
|
|
520
|
-
if (node && node.nodeType === Node.TEXT_NODE)
|
|
521
|
-
node = node.parentElement;
|
|
522
|
-
if (node instanceof HTMLElement)
|
|
523
|
-
element = node;
|
|
524
|
-
}
|
|
525
|
-
while (element && editorRef.current.contains(element)) {
|
|
526
|
-
const match = LINE_HEIGHT_OPTIONS.find((o) => element && element.classList.contains(o.className));
|
|
527
|
-
if (match)
|
|
528
|
-
return match.key;
|
|
529
|
-
element = element.parentElement;
|
|
530
|
-
}
|
|
531
|
-
return DEFAULT_FORMATTING_STATE.lineHeight;
|
|
532
|
-
}, []);
|
|
533
|
-
const getCurrentLetterSpacing = useCallback(() => {
|
|
534
|
-
if (!editorRef.current)
|
|
535
|
-
return DEFAULT_FORMATTING_STATE.letterSpacing;
|
|
536
|
-
const selection = window.getSelection();
|
|
537
|
-
let element = null;
|
|
538
|
-
if (selection && selection.rangeCount > 0) {
|
|
539
|
-
let node = selection.getRangeAt(0).startContainer;
|
|
540
|
-
if (node && node.nodeType === Node.TEXT_NODE)
|
|
541
|
-
node = node.parentElement;
|
|
542
|
-
if (node instanceof HTMLElement)
|
|
543
|
-
element = node;
|
|
544
|
-
}
|
|
545
|
-
while (element && editorRef.current.contains(element)) {
|
|
546
|
-
const match = LETTER_SPACING_OPTIONS.find((o) => element && element.classList.contains(o.className));
|
|
547
|
-
if (match)
|
|
548
|
-
return match.key;
|
|
549
|
-
element = element.parentElement;
|
|
550
|
-
}
|
|
551
|
-
return DEFAULT_FORMATTING_STATE.letterSpacing;
|
|
552
|
-
}, []);
|
|
553
|
-
const updateFormattingState = useCallback(() => {
|
|
554
|
-
if (!editorRef.current || isDisabled) {
|
|
555
|
-
setFormattingState(DEFAULT_FORMATTING_STATE);
|
|
556
|
-
return;
|
|
557
|
-
}
|
|
558
|
-
let bold = false;
|
|
559
|
-
let italic = false;
|
|
560
|
-
let underline = false;
|
|
561
|
-
let textAlign = "left";
|
|
562
|
-
let fontSize = DEFAULT_FORMATTING_STATE.fontSize;
|
|
563
|
-
let fontWeight = DEFAULT_FORMATTING_STATE.fontWeight;
|
|
564
|
-
let fontFamily = DEFAULT_FORMATTING_STATE.fontFamily;
|
|
565
|
-
let lineHeight = DEFAULT_FORMATTING_STATE.lineHeight;
|
|
566
|
-
let letterSpacing = DEFAULT_FORMATTING_STATE.letterSpacing;
|
|
567
|
-
let textColor = DEFAULT_FORMATTING_STATE.textColor;
|
|
568
|
-
// Bold is class-driven — the toolbar Bold button and Weight=Bold
|
|
569
|
-
// dropdown share `font-bold`, so we walk class ancestors and only fall
|
|
570
|
-
// back to computed font-weight when no class is found (covers legacy
|
|
571
|
-
// <strong>/<b> tags from pasted content).
|
|
572
|
-
try {
|
|
573
|
-
const sel = window.getSelection();
|
|
574
|
-
let startEl = null;
|
|
575
|
-
if (sel && sel.rangeCount > 0) {
|
|
576
|
-
let node = sel.getRangeAt(0).startContainer;
|
|
577
|
-
if (node && node.nodeType === Node.TEXT_NODE)
|
|
578
|
-
node = node.parentElement;
|
|
579
|
-
if (node instanceof HTMLElement)
|
|
580
|
-
startEl = node;
|
|
581
|
-
}
|
|
582
|
-
const boldClasses = ["font-bold", "font-extrabold", "font-black"];
|
|
583
|
-
const notBoldClasses = ["font-thin", "font-extralight", "font-light", "font-normal", "font-medium", "font-semibold"];
|
|
584
|
-
let n = startEl;
|
|
585
|
-
let boldClassFound = null;
|
|
586
|
-
while (n && n !== editorRef.current) {
|
|
587
|
-
if (boldClasses.some((c) => n.classList.contains(c))) {
|
|
588
|
-
boldClassFound = true;
|
|
589
|
-
break;
|
|
590
|
-
}
|
|
591
|
-
if (notBoldClasses.some((c) => n.classList.contains(c))) {
|
|
592
|
-
boldClassFound = false;
|
|
593
|
-
break;
|
|
594
|
-
}
|
|
595
|
-
n = n.parentElement;
|
|
596
|
-
}
|
|
597
|
-
bold = boldClassFound !== null
|
|
598
|
-
? boldClassFound
|
|
599
|
-
: startEl
|
|
600
|
-
? parseInt(window.getComputedStyle(startEl).fontWeight, 10) >= 600
|
|
601
|
-
: false;
|
|
602
|
-
}
|
|
603
|
-
catch (error) {
|
|
604
|
-
if (process.env.NODE_ENV !== 'production') {
|
|
605
|
-
console.warn("Failed to determine bold state", error);
|
|
606
|
-
}
|
|
607
|
-
}
|
|
608
|
-
// Italic and underline route through execCommand and produce semantic
|
|
609
|
-
// <em>/<u> tags, so queryCommandState is the canonical read.
|
|
610
|
-
try {
|
|
611
|
-
italic = document.queryCommandState("italic");
|
|
612
|
-
}
|
|
613
|
-
catch (_a) { }
|
|
614
|
-
try {
|
|
615
|
-
underline = document.queryCommandState("underline");
|
|
616
|
-
}
|
|
617
|
-
catch (_b) { }
|
|
618
|
-
try {
|
|
619
|
-
if (document.queryCommandState("justifyCenter")) {
|
|
620
|
-
textAlign = "center";
|
|
621
|
-
}
|
|
622
|
-
else if (document.queryCommandState("justifyRight")) {
|
|
623
|
-
textAlign = "right";
|
|
624
|
-
}
|
|
625
|
-
else if (document.queryCommandState("justifyFull")) {
|
|
626
|
-
textAlign = "justify";
|
|
627
|
-
}
|
|
628
|
-
else {
|
|
629
|
-
textAlign = "left";
|
|
630
|
-
}
|
|
631
|
-
}
|
|
632
|
-
catch (error) {
|
|
633
|
-
if (process.env.NODE_ENV !== 'production') {
|
|
634
|
-
console.warn("queryCommandState alignment failed", error);
|
|
635
|
-
}
|
|
636
|
-
}
|
|
637
|
-
try {
|
|
638
|
-
fontSize = getCurrentFontSize();
|
|
639
|
-
}
|
|
640
|
-
catch (error) {
|
|
641
|
-
if (process.env.NODE_ENV !== 'production') {
|
|
642
|
-
console.warn("Failed to determine font size", error);
|
|
643
|
-
}
|
|
644
|
-
}
|
|
645
|
-
try {
|
|
646
|
-
fontWeight = getCurrentFontWeight();
|
|
647
|
-
}
|
|
648
|
-
catch (error) {
|
|
649
|
-
if (process.env.NODE_ENV !== 'production') {
|
|
650
|
-
console.warn("Failed to determine font weight", error);
|
|
651
|
-
}
|
|
652
|
-
}
|
|
653
|
-
try {
|
|
654
|
-
fontFamily = getCurrentFontFamily();
|
|
655
|
-
}
|
|
656
|
-
catch (error) {
|
|
657
|
-
if (process.env.NODE_ENV !== 'production') {
|
|
658
|
-
console.warn("Failed to determine font family", error);
|
|
659
|
-
}
|
|
660
|
-
}
|
|
661
|
-
try {
|
|
662
|
-
lineHeight = getCurrentLineHeight();
|
|
663
|
-
}
|
|
664
|
-
catch (error) {
|
|
665
|
-
if (process.env.NODE_ENV !== 'production') {
|
|
666
|
-
console.warn("Failed to determine line height", error);
|
|
667
|
-
}
|
|
668
|
-
}
|
|
669
|
-
try {
|
|
670
|
-
letterSpacing = getCurrentLetterSpacing();
|
|
671
|
-
}
|
|
672
|
-
catch (error) {
|
|
673
|
-
if (process.env.NODE_ENV !== 'production') {
|
|
674
|
-
console.warn("Failed to determine letter spacing", error);
|
|
675
|
-
}
|
|
676
|
-
}
|
|
677
|
-
try {
|
|
678
|
-
// Walk up from the selection's start container to find the nearest
|
|
679
|
-
// ancestor carrying a `text-{token}` class; that token (e.g.
|
|
680
|
-
// "rose-500", "white") is the active color. queryCommandValue is no
|
|
681
|
-
// longer used because color is applied via class swaps, not foreColor.
|
|
682
|
-
const sel = window.getSelection();
|
|
683
|
-
let startEl = null;
|
|
684
|
-
if (sel && sel.rangeCount > 0) {
|
|
685
|
-
let node = sel.getRangeAt(0).startContainer;
|
|
686
|
-
if (node && node.nodeType === Node.TEXT_NODE)
|
|
687
|
-
node = node.parentElement;
|
|
688
|
-
if (node instanceof HTMLElement)
|
|
689
|
-
startEl = node;
|
|
690
|
-
}
|
|
691
|
-
textColor = matchTextColorClass(startEl, editorRef.current);
|
|
692
|
-
}
|
|
693
|
-
catch (error) {
|
|
694
|
-
if (process.env.NODE_ENV !== 'production') {
|
|
695
|
-
console.warn("Failed to determine text color", error);
|
|
696
|
-
}
|
|
697
|
-
}
|
|
698
|
-
setFormattingState({
|
|
699
|
-
bold,
|
|
700
|
-
italic,
|
|
701
|
-
underline,
|
|
702
|
-
textAlign,
|
|
703
|
-
fontSize,
|
|
704
|
-
fontWeight,
|
|
705
|
-
fontFamily,
|
|
706
|
-
lineHeight,
|
|
707
|
-
letterSpacing,
|
|
708
|
-
textColor,
|
|
709
|
-
});
|
|
710
|
-
}, [
|
|
711
|
-
getCurrentFontSize,
|
|
712
|
-
getCurrentFontWeight,
|
|
713
|
-
getCurrentFontFamily,
|
|
714
|
-
getCurrentLineHeight,
|
|
715
|
-
getCurrentLetterSpacing,
|
|
716
|
-
isDisabled,
|
|
717
|
-
]);
|
|
718
|
-
const handleContentChange = useCallback(() => {
|
|
719
|
-
if (!editorRef.current || !onChange)
|
|
720
|
-
return;
|
|
721
|
-
const raw = editorRef.current.innerHTML;
|
|
722
|
-
const clean = sanitizeHtml(raw);
|
|
723
|
-
// If sanitizer stripped something (e.g. pasted <script> or on*= handlers),
|
|
724
|
-
// reconcile the DOM so what's rendered matches what we emit.
|
|
725
|
-
if (clean !== raw) {
|
|
726
|
-
editorRef.current.innerHTML = clean;
|
|
727
|
-
}
|
|
728
|
-
onChange(clean);
|
|
729
|
-
}, [onChange, sanitizeHtml]);
|
|
730
|
-
const execEditorCommand = useCallback((command, value) => {
|
|
731
|
-
if (isDisabled || !editorRef.current)
|
|
732
|
-
return;
|
|
733
|
-
focusEditor();
|
|
734
|
-
// Snapshot before mutating so bold/italic/foreColor/justify/… land on
|
|
735
|
-
// the same undo stack as direct-DOM style ops and typing.
|
|
736
|
-
pushHistory("op");
|
|
737
|
-
try {
|
|
738
|
-
document.execCommand(command, false, value);
|
|
739
|
-
}
|
|
740
|
-
catch (error) {
|
|
741
|
-
if (process.env.NODE_ENV !== 'production') {
|
|
742
|
-
console.warn(`execCommand ${command} failed`, error);
|
|
743
|
-
}
|
|
744
|
-
}
|
|
745
|
-
requestAnimationFrame(() => {
|
|
746
|
-
updateFormattingState();
|
|
747
|
-
handleContentChange();
|
|
748
|
-
});
|
|
749
|
-
}, [focusEditor, handleContentChange, isDisabled, pushHistory, updateFormattingState]);
|
|
750
|
-
const handleUndo = useCallback((event) => {
|
|
751
|
-
event === null || event === void 0 ? void 0 : event.preventDefault();
|
|
752
|
-
event === null || event === void 0 ? void 0 : event.stopPropagation();
|
|
753
|
-
const stack = historyRef.current;
|
|
754
|
-
if (stack.undo.length === 0 || !editorRef.current)
|
|
755
|
-
return;
|
|
756
|
-
const previous = stack.undo.pop();
|
|
757
|
-
stack.redo.push(editorRef.current.innerHTML);
|
|
758
|
-
// Reset the typing-coalesce window so the next keystroke after an
|
|
759
|
-
// undo always starts a fresh entry.
|
|
760
|
-
stack.lastPushKind = null;
|
|
761
|
-
editorRef.current.innerHTML = sanitizeHtml(previous);
|
|
762
|
-
requestAnimationFrame(() => {
|
|
763
|
-
updateFormattingState();
|
|
764
|
-
handleContentChange();
|
|
765
|
-
});
|
|
766
|
-
}, [handleContentChange, sanitizeHtml, updateFormattingState]);
|
|
767
|
-
const handleRedo = useCallback((event) => {
|
|
768
|
-
event === null || event === void 0 ? void 0 : event.preventDefault();
|
|
769
|
-
event === null || event === void 0 ? void 0 : event.stopPropagation();
|
|
770
|
-
const stack = historyRef.current;
|
|
771
|
-
if (stack.redo.length === 0 || !editorRef.current)
|
|
772
|
-
return;
|
|
773
|
-
const next = stack.redo.pop();
|
|
774
|
-
stack.undo.push(editorRef.current.innerHTML);
|
|
775
|
-
stack.lastPushKind = null;
|
|
776
|
-
editorRef.current.innerHTML = sanitizeHtml(next);
|
|
777
|
-
requestAnimationFrame(() => {
|
|
778
|
-
updateFormattingState();
|
|
779
|
-
handleContentChange();
|
|
780
|
-
});
|
|
781
|
-
}, [handleContentChange, sanitizeHtml, updateFormattingState]);
|
|
782
|
-
const applyStyleToSelection = useCallback((apply) => {
|
|
783
|
-
var _a, _b, _c;
|
|
784
|
-
if (!editorRef.current)
|
|
785
|
-
return;
|
|
786
46
|
const selection = window.getSelection();
|
|
787
47
|
if (!selection || selection.rangeCount === 0)
|
|
788
48
|
return;
|
|
789
49
|
const range = selection.getRangeAt(0);
|
|
790
|
-
if (
|
|
791
|
-
|
|
792
|
-
if (node && node.nodeType === Node.TEXT_NODE)
|
|
793
|
-
node = node.parentElement;
|
|
794
|
-
while (node && node !== editorRef.current) {
|
|
795
|
-
if (node instanceof HTMLElement) {
|
|
796
|
-
apply(node);
|
|
797
|
-
break;
|
|
798
|
-
}
|
|
799
|
-
node = node.parentElement;
|
|
800
|
-
}
|
|
801
|
-
return;
|
|
802
|
-
}
|
|
803
|
-
// Non-collapsed: try to wrap just the selected range in a styled span so
|
|
804
|
-
// only the selected text changes. surroundContents works when the range
|
|
805
|
-
// doesn't cross element boundaries (the common case for inline selections).
|
|
806
|
-
const span = document.createElement("span");
|
|
807
|
-
apply(span);
|
|
808
|
-
try {
|
|
809
|
-
range.surroundContents(span);
|
|
810
|
-
selection.removeAllRanges();
|
|
811
|
-
const restored = document.createRange();
|
|
812
|
-
restored.selectNodeContents(span);
|
|
813
|
-
selection.addRange(restored);
|
|
814
|
-
return;
|
|
815
|
-
}
|
|
816
|
-
catch (_d) {
|
|
817
|
-
// Range crosses element boundaries — fall through to per-parent walker.
|
|
818
|
-
}
|
|
819
|
-
// Cross-element fallback: split each intersecting text node at the
|
|
820
|
-
// range boundaries so only the selected characters end up wrapped.
|
|
821
|
-
// Collect first — splitText() mutates the tree and breaks live walking.
|
|
822
|
-
const startContainer = range.startContainer;
|
|
823
|
-
const startOffset = range.startOffset;
|
|
824
|
-
const endContainer = range.endContainer;
|
|
825
|
-
const endOffset = range.endOffset;
|
|
826
|
-
const textNodes = [];
|
|
827
|
-
const walker = document.createTreeWalker(range.commonAncestorContainer, NodeFilter.SHOW_TEXT, {
|
|
828
|
-
acceptNode: (n) => range.intersectsNode(n) ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_REJECT,
|
|
829
|
-
});
|
|
830
|
-
let current;
|
|
831
|
-
while ((current = walker.nextNode()))
|
|
832
|
-
textNodes.push(current);
|
|
833
|
-
const editor = editorRef.current;
|
|
834
|
-
let firstWrap = null;
|
|
835
|
-
let lastWrap = null;
|
|
836
|
-
for (const textNode of textNodes) {
|
|
837
|
-
if (!editor.contains(textNode))
|
|
838
|
-
continue;
|
|
839
|
-
// Trim leading portion when range starts inside this text node
|
|
840
|
-
let target = textNode;
|
|
841
|
-
if (textNode === startContainer && startOffset > 0) {
|
|
842
|
-
target = textNode.splitText(startOffset);
|
|
843
|
-
}
|
|
844
|
-
// Trim trailing portion when range ends inside this text node
|
|
845
|
-
if (textNode === endContainer) {
|
|
846
|
-
const endInTarget = textNode === startContainer ? endOffset - startOffset : endOffset;
|
|
847
|
-
if (endInTarget < target.length) {
|
|
848
|
-
target.splitText(endInTarget);
|
|
849
|
-
}
|
|
850
|
-
}
|
|
851
|
-
// Wrap the precise text fragment in a styled span
|
|
852
|
-
const wrap = document.createElement("span");
|
|
853
|
-
apply(wrap);
|
|
854
|
-
(_a = target.parentNode) === null || _a === void 0 ? void 0 : _a.insertBefore(wrap, target);
|
|
855
|
-
wrap.appendChild(target);
|
|
856
|
-
if (!firstWrap)
|
|
857
|
-
firstWrap = wrap;
|
|
858
|
-
lastWrap = wrap;
|
|
859
|
-
}
|
|
860
|
-
// Restore the visible selection across the wrapped fragments so the
|
|
861
|
-
// user doesn't have to re-highlight before chaining another action.
|
|
862
|
-
if (firstWrap && lastWrap) {
|
|
863
|
-
selection.removeAllRanges();
|
|
864
|
-
const restored = document.createRange();
|
|
865
|
-
restored.setStartBefore((_b = firstWrap.firstChild) !== null && _b !== void 0 ? _b : firstWrap);
|
|
866
|
-
restored.setEndAfter((_c = lastWrap.lastChild) !== null && _c !== void 0 ? _c : lastWrap);
|
|
867
|
-
selection.addRange(restored);
|
|
50
|
+
if ((_a = editorRef.current) === null || _a === void 0 ? void 0 : _a.contains(range.commonAncestorContainer)) {
|
|
51
|
+
savedRangeRef.current = range.cloneRange();
|
|
868
52
|
}
|
|
869
53
|
}, []);
|
|
870
|
-
|
|
871
|
-
* Strip a redundant class from any element whose every non-whitespace text
|
|
872
|
-
* descendant already sits under a closer descendant carrying a class from
|
|
873
|
-
* the same group. The outer declaration is dead weight at that point —
|
|
874
|
-
* leaving it in place causes nested wrappers to win on properties whose
|
|
875
|
-
* line box height (e.g. `text-7xl`'s `line-height: 1`) is computed from the
|
|
876
|
-
* outer's own font-size, regardless of what the inner element declares.
|
|
877
|
-
* After stripping, span/font wrappers that are now attribute-less are
|
|
878
|
-
* unwrapped so we don't accumulate empty markup over time.
|
|
879
|
-
*/
|
|
880
|
-
const flattenClassGroup = useCallback((classes) => {
|
|
881
|
-
if (!editorRef.current || classes.length === 0)
|
|
882
|
-
return;
|
|
54
|
+
const restoreSelection = useCallback(() => {
|
|
883
55
|
const editor = editorRef.current;
|
|
884
|
-
|
|
885
|
-
const candidates = Array.from(editor.querySelectorAll(selector));
|
|
886
|
-
for (const ancestor of candidates) {
|
|
887
|
-
const ancestorClass = classes.find((c) => ancestor.classList.contains(c));
|
|
888
|
-
if (!ancestorClass)
|
|
889
|
-
continue;
|
|
890
|
-
const walker = document.createTreeWalker(ancestor, NodeFilter.SHOW_TEXT);
|
|
891
|
-
let everyTextHasCloser = true;
|
|
892
|
-
let sawAnyText = false;
|
|
893
|
-
let textNode;
|
|
894
|
-
while ((textNode = walker.nextNode())) {
|
|
895
|
-
const data = textNode.data;
|
|
896
|
-
if (!data || !data.trim())
|
|
897
|
-
continue;
|
|
898
|
-
sawAnyText = true;
|
|
899
|
-
let p = textNode.parentElement;
|
|
900
|
-
let foundCloser = false;
|
|
901
|
-
while (p && p !== ancestor) {
|
|
902
|
-
if (classes.some((c) => p.classList.contains(c))) {
|
|
903
|
-
foundCloser = true;
|
|
904
|
-
break;
|
|
905
|
-
}
|
|
906
|
-
p = p.parentElement;
|
|
907
|
-
}
|
|
908
|
-
if (!foundCloser) {
|
|
909
|
-
everyTextHasCloser = false;
|
|
910
|
-
break;
|
|
911
|
-
}
|
|
912
|
-
}
|
|
913
|
-
if (!sawAnyText || !everyTextHasCloser)
|
|
914
|
-
continue;
|
|
915
|
-
ancestor.classList.remove(ancestorClass);
|
|
916
|
-
if (ancestor.classList.length === 0)
|
|
917
|
-
ancestor.removeAttribute("class");
|
|
918
|
-
const tag = ancestor.tagName.toLowerCase();
|
|
919
|
-
if ((tag === "span" || tag === "font") &&
|
|
920
|
-
ancestor.attributes.length === 0 &&
|
|
921
|
-
ancestor.parentNode) {
|
|
922
|
-
const parent = ancestor.parentNode;
|
|
923
|
-
while (ancestor.firstChild)
|
|
924
|
-
parent.insertBefore(ancestor.firstChild, ancestor);
|
|
925
|
-
parent.removeChild(ancestor);
|
|
926
|
-
}
|
|
927
|
-
}
|
|
928
|
-
}, []);
|
|
929
|
-
/**
|
|
930
|
-
* Generic class-swap helper. Wraps the selected text in a span carrying
|
|
931
|
-
* `nextClass` (also stripping any sibling class from the same group on
|
|
932
|
-
* that wrap, so we never end up with `text-sm text-7xl`), then runs a
|
|
933
|
-
* flatten pass over the editor to collapse newly-redundant outer wrappers.
|
|
934
|
-
*/
|
|
935
|
-
const applyClassSwap = useCallback((group, nextClass) => {
|
|
936
|
-
applyStyleToSelection((el) => {
|
|
937
|
-
for (const cls of group) {
|
|
938
|
-
if (cls !== nextClass)
|
|
939
|
-
el.classList.remove(cls);
|
|
940
|
-
}
|
|
941
|
-
el.classList.add(nextClass);
|
|
942
|
-
});
|
|
943
|
-
flattenClassGroup(group);
|
|
944
|
-
}, [applyStyleToSelection, flattenClassGroup]);
|
|
945
|
-
const handleBold = useCallback((event) => {
|
|
946
|
-
event === null || event === void 0 ? void 0 : event.preventDefault();
|
|
947
|
-
event === null || event === void 0 ? void 0 : event.stopPropagation();
|
|
948
|
-
if (isDisabled || !editorRef.current)
|
|
949
|
-
return;
|
|
950
|
-
focusEditor();
|
|
951
|
-
const selection = window.getSelection();
|
|
952
|
-
if (!selection || selection.rangeCount === 0)
|
|
953
|
-
return;
|
|
954
|
-
pushHistory();
|
|
955
|
-
// Bold lives in the font-weight class group: ON → font-bold,
|
|
956
|
-
// OFF → font-normal. The toolbar Bold button and the Weight=Bold
|
|
957
|
-
// dropdown entry share the same underlying class, so they stay in
|
|
958
|
-
// sync automatically — toggling one lights up the other.
|
|
959
|
-
const next = formattingState.bold ? "font-normal" : "font-bold";
|
|
960
|
-
applyClassSwap(FONT_WEIGHT_CLASSES, next);
|
|
961
|
-
requestAnimationFrame(() => {
|
|
962
|
-
updateFormattingState();
|
|
963
|
-
handleContentChange();
|
|
964
|
-
});
|
|
965
|
-
}, [
|
|
966
|
-
applyClassSwap,
|
|
967
|
-
focusEditor,
|
|
968
|
-
formattingState.bold,
|
|
969
|
-
handleContentChange,
|
|
970
|
-
isDisabled,
|
|
971
|
-
pushHistory,
|
|
972
|
-
updateFormattingState,
|
|
973
|
-
]);
|
|
974
|
-
// Italic and underline route through execCommand because it produces
|
|
975
|
-
// semantic <em>/<u> tags, handles toggle-on/off (including unwrap) without
|
|
976
|
-
// a hand-rolled negation-class scheme, and the editor's CSS already
|
|
977
|
-
// styles those tags via descendant selectors. execEditorCommand
|
|
978
|
-
// pre-snapshots history so undo/redo ordering is preserved.
|
|
979
|
-
const handleItalic = useCallback((event) => {
|
|
980
|
-
event === null || event === void 0 ? void 0 : event.preventDefault();
|
|
981
|
-
event === null || event === void 0 ? void 0 : event.stopPropagation();
|
|
982
|
-
execEditorCommand("italic");
|
|
983
|
-
}, [execEditorCommand]);
|
|
984
|
-
const handleUnderline = useCallback((event) => {
|
|
985
|
-
event === null || event === void 0 ? void 0 : event.preventDefault();
|
|
986
|
-
event === null || event === void 0 ? void 0 : event.stopPropagation();
|
|
987
|
-
execEditorCommand("underline");
|
|
988
|
-
}, [execEditorCommand]);
|
|
989
|
-
const handleFontSize = useCallback((sizeKey) => {
|
|
990
|
-
if (isDisabled || !editorRef.current)
|
|
56
|
+
if (!editor)
|
|
991
57
|
return;
|
|
992
|
-
|
|
993
|
-
const
|
|
994
|
-
|
|
995
|
-
if (!selection || selection.rangeCount === 0) {
|
|
996
|
-
closeAllPickers();
|
|
997
|
-
return;
|
|
998
|
-
}
|
|
999
|
-
pushHistory();
|
|
1000
|
-
// Tailwind's `text-*` utilities bundle font-size + a matching
|
|
1001
|
-
// line-height in one rule, so swapping the size class swaps both —
|
|
1002
|
-
// no separate flattening pass needed for the line-height.
|
|
1003
|
-
applyClassSwap(FONT_SIZE_CLASSES, option.className);
|
|
1004
|
-
requestAnimationFrame(() => {
|
|
1005
|
-
updateFormattingState();
|
|
1006
|
-
handleContentChange();
|
|
1007
|
-
});
|
|
1008
|
-
closeAllPickers();
|
|
1009
|
-
}, [
|
|
1010
|
-
applyClassSwap,
|
|
1011
|
-
closeAllPickers,
|
|
1012
|
-
focusEditor,
|
|
1013
|
-
handleContentChange,
|
|
1014
|
-
isDisabled,
|
|
1015
|
-
pushHistory,
|
|
1016
|
-
updateFormattingState,
|
|
1017
|
-
]);
|
|
1018
|
-
const applyToBlockAncestors = useCallback((apply) => {
|
|
1019
|
-
if (!editorRef.current)
|
|
58
|
+
editor.focus();
|
|
59
|
+
const range = savedRangeRef.current;
|
|
60
|
+
if (!range)
|
|
1020
61
|
return;
|
|
1021
62
|
const selection = window.getSelection();
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
const editor = editorRef.current;
|
|
1025
|
-
// Normalize: ensure no text node or inline element is a *direct*
|
|
1026
|
-
// child of the editor. Otherwise `findBlock` would fall back to the
|
|
1027
|
-
// editor itself, and any class set on it would live on the live DOM
|
|
1028
|
-
// but never serialize through `editor.innerHTML` — line-height
|
|
1029
|
-
// changes would silently disappear from saved HTML. Wrap each
|
|
1030
|
-
// contiguous run of inline-or-text top-level children in a fresh
|
|
1031
|
-
// `<div>`. Idempotent: blocks that are already block-level are left
|
|
1032
|
-
// alone, so calling this on every block-level mutation is cheap.
|
|
1033
|
-
const isInlineChild = (node) => {
|
|
1034
|
-
if (node.nodeType === Node.TEXT_NODE)
|
|
1035
|
-
return true;
|
|
1036
|
-
if (node instanceof HTMLElement) {
|
|
1037
|
-
const d = window.getComputedStyle(node).display;
|
|
1038
|
-
return d === "inline" || d === "inline-block";
|
|
1039
|
-
}
|
|
1040
|
-
return false;
|
|
1041
|
-
};
|
|
1042
|
-
const groups = [];
|
|
1043
|
-
let currentGroup = null;
|
|
1044
|
-
for (const child of Array.from(editor.childNodes)) {
|
|
1045
|
-
if (isInlineChild(child)) {
|
|
1046
|
-
if (!currentGroup) {
|
|
1047
|
-
currentGroup = [];
|
|
1048
|
-
groups.push(currentGroup);
|
|
1049
|
-
}
|
|
1050
|
-
currentGroup.push(child);
|
|
1051
|
-
}
|
|
1052
|
-
else {
|
|
1053
|
-
currentGroup = null;
|
|
1054
|
-
}
|
|
1055
|
-
}
|
|
1056
|
-
for (const group of groups) {
|
|
1057
|
-
if (group.length === 0)
|
|
1058
|
-
continue;
|
|
1059
|
-
const wrap = document.createElement("div");
|
|
1060
|
-
editor.insertBefore(wrap, group[0]);
|
|
1061
|
-
for (const node of group)
|
|
1062
|
-
wrap.appendChild(node);
|
|
1063
|
-
}
|
|
1064
|
-
// Re-read range after potential mutations — Range objects survive
|
|
1065
|
-
// moves but offsets into reparented text nodes stay valid, so
|
|
1066
|
-
// `selection.getRangeAt(0)` still points at the same characters.
|
|
1067
|
-
const range = selection.getRangeAt(0);
|
|
1068
|
-
const findBlock = (node) => {
|
|
1069
|
-
let n = node;
|
|
1070
|
-
if (n && n.nodeType === Node.TEXT_NODE)
|
|
1071
|
-
n = n.parentElement;
|
|
1072
|
-
while (n && n !== editor && editor.contains(n)) {
|
|
1073
|
-
if (n instanceof HTMLElement) {
|
|
1074
|
-
const d = window.getComputedStyle(n).display;
|
|
1075
|
-
if (d === "block" || d === "list-item" || d === "table-cell" || d === "flex" || d === "grid") {
|
|
1076
|
-
return n;
|
|
1077
|
-
}
|
|
1078
|
-
}
|
|
1079
|
-
n = n.parentElement;
|
|
1080
|
-
}
|
|
1081
|
-
// After normalization above, an inline node always has a non-editor
|
|
1082
|
-
// block ancestor; this branch is now only reached for malformed
|
|
1083
|
-
// cases. Returning null is safer than returning the editor (which
|
|
1084
|
-
// wouldn't serialize anyway).
|
|
1085
|
-
return null;
|
|
1086
|
-
};
|
|
1087
|
-
const blocks = new Set();
|
|
1088
|
-
if (selection.isCollapsed) {
|
|
1089
|
-
const block = findBlock(range.startContainer);
|
|
1090
|
-
if (block)
|
|
1091
|
-
blocks.add(block);
|
|
1092
|
-
}
|
|
1093
|
-
else if (range.commonAncestorContainer.nodeType === Node.TEXT_NODE) {
|
|
1094
|
-
const block = findBlock(range.commonAncestorContainer);
|
|
1095
|
-
if (block)
|
|
1096
|
-
blocks.add(block);
|
|
1097
|
-
}
|
|
1098
|
-
else {
|
|
1099
|
-
const walker = document.createTreeWalker(range.commonAncestorContainer, NodeFilter.SHOW_TEXT, {
|
|
1100
|
-
acceptNode: (n) => range.intersectsNode(n) ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_REJECT,
|
|
1101
|
-
});
|
|
1102
|
-
let current;
|
|
1103
|
-
while ((current = walker.nextNode())) {
|
|
1104
|
-
const block = findBlock(current);
|
|
1105
|
-
if (block)
|
|
1106
|
-
blocks.add(block);
|
|
1107
|
-
}
|
|
1108
|
-
}
|
|
1109
|
-
blocks.forEach((b) => apply(b));
|
|
63
|
+
selection === null || selection === void 0 ? void 0 : selection.removeAllRanges();
|
|
64
|
+
selection === null || selection === void 0 ? void 0 : selection.addRange(range);
|
|
1110
65
|
}, []);
|
|
1111
|
-
const
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
});
|
|
1125
|
-
requestAnimationFrame(() => {
|
|
1126
|
-
updateFormattingState();
|
|
1127
|
-
handleContentChange();
|
|
1128
|
-
});
|
|
1129
|
-
closeAllPickers();
|
|
1130
|
-
}, [
|
|
1131
|
-
applyStyleToSelection,
|
|
1132
|
-
closeAllPickers,
|
|
1133
|
-
focusEditor,
|
|
1134
|
-
fontFamilies,
|
|
1135
|
-
handleContentChange,
|
|
1136
|
-
isDisabled,
|
|
1137
|
-
loadFontsOnDemand,
|
|
1138
|
-
pushHistory,
|
|
1139
|
-
updateFormattingState,
|
|
1140
|
-
]);
|
|
1141
|
-
const handleFontWeight = useCallback((key) => {
|
|
1142
|
-
if (isDisabled || !editorRef.current)
|
|
1143
|
-
return;
|
|
1144
|
-
focusEditor();
|
|
1145
|
-
const option = getFontWeightOption(key);
|
|
1146
|
-
pushHistory();
|
|
1147
|
-
applyClassSwap(FONT_WEIGHT_CLASSES, option.className);
|
|
1148
|
-
requestAnimationFrame(() => {
|
|
1149
|
-
updateFormattingState();
|
|
1150
|
-
handleContentChange();
|
|
1151
|
-
});
|
|
1152
|
-
closeAllPickers();
|
|
1153
|
-
}, [
|
|
1154
|
-
applyClassSwap,
|
|
1155
|
-
closeAllPickers,
|
|
1156
|
-
focusEditor,
|
|
1157
|
-
handleContentChange,
|
|
1158
|
-
isDisabled,
|
|
1159
|
-
pushHistory,
|
|
1160
|
-
updateFormattingState,
|
|
1161
|
-
]);
|
|
1162
|
-
const handleLineHeight = useCallback((key) => {
|
|
1163
|
-
if (isDisabled || !editorRef.current)
|
|
1164
|
-
return;
|
|
1165
|
-
focusEditor();
|
|
1166
|
-
const option = getLineHeightOption(key);
|
|
1167
|
-
pushHistory();
|
|
1168
|
-
// Line height is paragraph-level, so swap the `leading-*` class on the
|
|
1169
|
-
// block ancestors that contain the selection rather than wrapping in a
|
|
1170
|
-
// span. Each block carries at most one `leading-*` class — clear any
|
|
1171
|
-
// existing one first, then add the new one.
|
|
1172
|
-
applyToBlockAncestors((el) => {
|
|
1173
|
-
for (const cls of LINE_HEIGHT_CLASSES) {
|
|
1174
|
-
if (cls !== option.className)
|
|
1175
|
-
el.classList.remove(cls);
|
|
1176
|
-
}
|
|
1177
|
-
el.classList.add(option.className);
|
|
1178
|
-
});
|
|
1179
|
-
requestAnimationFrame(() => {
|
|
1180
|
-
updateFormattingState();
|
|
1181
|
-
handleContentChange();
|
|
1182
|
-
});
|
|
1183
|
-
closeAllPickers();
|
|
1184
|
-
}, [
|
|
1185
|
-
applyToBlockAncestors,
|
|
1186
|
-
closeAllPickers,
|
|
1187
|
-
focusEditor,
|
|
1188
|
-
handleContentChange,
|
|
1189
|
-
isDisabled,
|
|
1190
|
-
pushHistory,
|
|
1191
|
-
updateFormattingState,
|
|
1192
|
-
]);
|
|
1193
|
-
const handleLetterSpacing = useCallback((key) => {
|
|
1194
|
-
if (isDisabled || !editorRef.current)
|
|
1195
|
-
return;
|
|
1196
|
-
focusEditor();
|
|
1197
|
-
const option = getLetterSpacingOption(key);
|
|
1198
|
-
pushHistory();
|
|
1199
|
-
applyClassSwap(LETTER_SPACING_CLASSES, option.className);
|
|
1200
|
-
requestAnimationFrame(() => {
|
|
1201
|
-
updateFormattingState();
|
|
1202
|
-
handleContentChange();
|
|
1203
|
-
});
|
|
1204
|
-
closeAllPickers();
|
|
1205
|
-
}, [
|
|
1206
|
-
applyClassSwap,
|
|
1207
|
-
closeAllPickers,
|
|
1208
|
-
focusEditor,
|
|
1209
|
-
handleContentChange,
|
|
1210
|
-
isDisabled,
|
|
1211
|
-
pushHistory,
|
|
1212
|
-
updateFormattingState,
|
|
1213
|
-
]);
|
|
1214
|
-
const handleTextColor = useCallback((token) => {
|
|
1215
|
-
if (isDisabled || !editorRef.current || !token)
|
|
1216
|
-
return;
|
|
1217
|
-
focusEditor();
|
|
1218
|
-
const selection = window.getSelection();
|
|
1219
|
-
if (!selection || selection.rangeCount === 0) {
|
|
1220
|
-
closeAllPickers();
|
|
1221
|
-
return;
|
|
1222
|
-
}
|
|
1223
|
-
pushHistory();
|
|
1224
|
-
applyClassSwap(TEXT_COLOR_CLASSES, `text-${token}`);
|
|
1225
|
-
addRecentColor(token);
|
|
1226
|
-
requestAnimationFrame(() => {
|
|
1227
|
-
updateFormattingState();
|
|
1228
|
-
handleContentChange();
|
|
1229
|
-
});
|
|
1230
|
-
closeAllPickers();
|
|
1231
|
-
}, [
|
|
1232
|
-
addRecentColor,
|
|
1233
|
-
applyClassSwap,
|
|
1234
|
-
closeAllPickers,
|
|
1235
|
-
focusEditor,
|
|
1236
|
-
handleContentChange,
|
|
1237
|
-
isDisabled,
|
|
1238
|
-
pushHistory,
|
|
1239
|
-
updateFormattingState,
|
|
1240
|
-
]);
|
|
1241
|
-
const handleMouseDown = useCallback((event) => {
|
|
1242
|
-
event.preventDefault();
|
|
1243
|
-
focusEditor();
|
|
1244
|
-
}, [focusEditor]);
|
|
1245
|
-
const fontSizeAnimations = useMemo(() => createDropdownAnimations(fontSizeIsFlipped), [fontSizeIsFlipped]);
|
|
1246
|
-
const fontWeightAnimations = useMemo(() => createDropdownAnimations(fontWeightIsFlipped), [fontWeightIsFlipped]);
|
|
1247
|
-
const fontFamilyAnimations = useMemo(() => createDropdownAnimations(fontFamilyIsFlipped), [fontFamilyIsFlipped]);
|
|
1248
|
-
const lineHeightAnimations = useMemo(() => createDropdownAnimations(lineHeightIsFlipped), [lineHeightIsFlipped]);
|
|
1249
|
-
const letterSpacingAnimations = useMemo(() => createDropdownAnimations(letterSpacingIsFlipped), [letterSpacingIsFlipped]);
|
|
1250
|
-
const colorAnimations = useMemo(() => createDropdownAnimations(colorIsFlipped), [colorIsFlipped]);
|
|
1251
|
-
const handleAlign = useCallback((alignment) => {
|
|
1252
|
-
const commandMap = {
|
|
1253
|
-
left: "justifyLeft",
|
|
1254
|
-
center: "justifyCenter",
|
|
1255
|
-
right: "justifyRight",
|
|
1256
|
-
};
|
|
1257
|
-
execEditorCommand(commandMap[alignment]);
|
|
1258
|
-
}, [execEditorCommand]);
|
|
1259
|
-
const handleSelection = useCallback(() => {
|
|
66
|
+
const syncFormatting = useCallback(() => {
|
|
67
|
+
setFormatting(readFormattingState(editorRef.current, window.getSelection()));
|
|
68
|
+
}, []);
|
|
69
|
+
/**
|
|
70
|
+
* Emit the current DOM as sanitized HTML.
|
|
71
|
+
*
|
|
72
|
+
* Note what this does NOT do: write the sanitized string back into the
|
|
73
|
+
* editor. Doing that mid-typing discards every live Range and moves the
|
|
74
|
+
* caret — the previous implementation's most user-visible bug. Foreign
|
|
75
|
+
* markup can only arrive via paste/drop/`value`, each sanitized at its own
|
|
76
|
+
* boundary, so the DOM is already clean by the time we read it.
|
|
77
|
+
*/
|
|
78
|
+
const emitChange = useCallback(() => {
|
|
1260
79
|
var _a;
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
updateFormattingState();
|
|
1264
|
-
if (toolbarConfig.position !== "floating")
|
|
1265
|
-
return;
|
|
1266
|
-
const selection = window.getSelection();
|
|
1267
|
-
if (!selection || selection.rangeCount === 0) {
|
|
1268
|
-
setShowToolbar(false);
|
|
1269
|
-
return;
|
|
1270
|
-
}
|
|
1271
|
-
const range = selection.getRangeAt(0);
|
|
1272
|
-
if (!editorRef.current.contains(range.commonAncestorContainer) || selection.isCollapsed) {
|
|
1273
|
-
setShowToolbar(false);
|
|
1274
|
-
return;
|
|
1275
|
-
}
|
|
1276
|
-
const rect = range.getBoundingClientRect();
|
|
1277
|
-
const editorRect = editorRef.current.getBoundingClientRect();
|
|
1278
|
-
if (rect.width === 0 && rect.height === 0) {
|
|
1279
|
-
setShowToolbar(false);
|
|
80
|
+
const editor = editorRef.current;
|
|
81
|
+
if (!editor)
|
|
1280
82
|
return;
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
// fall back to a realistic default for the very first frame.
|
|
1287
|
-
const toolbarWidth = ((_a = toolbarRef.current) === null || _a === void 0 ? void 0 : _a.offsetWidth) || 320;
|
|
1288
|
-
const left = Math.max(10, Math.min(rect.left - editorRect.left + rect.width / 2 - toolbarWidth / 2, editorRect.width - toolbarWidth - 10));
|
|
1289
|
-
setToolbarPosition({ top: rect.top - editorRect.top - 50, left });
|
|
1290
|
-
setShowToolbar(true);
|
|
1291
|
-
}, [isDisabled, toolbarConfig.position, updateFormattingState]);
|
|
83
|
+
setIsEmpty(((_a = editor.textContent) !== null && _a !== void 0 ? _a : "").trim().length === 0);
|
|
84
|
+
onChange === null || onChange === void 0 ? void 0 : onChange(sanitizeFn(editor.innerHTML));
|
|
85
|
+
}, [onChange, sanitizeFn]);
|
|
86
|
+
// Sync the controlled value in. Guarded on inequality so typing does not
|
|
87
|
+
// re-enter and reset the caret.
|
|
1292
88
|
useEffect(() => {
|
|
1293
|
-
|
|
1294
|
-
return;
|
|
89
|
+
var _a;
|
|
1295
90
|
const editor = editorRef.current;
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
pushHistory("typing");
|
|
1306
|
-
};
|
|
1307
|
-
// Intercept native undo/redo shortcuts so the browser's contentEditable
|
|
1308
|
-
// stack doesn't drift out of sync with our linear history. Without this
|
|
1309
|
-
// the browser would still honor Cmd/Ctrl+Z on its own internal stack.
|
|
1310
|
-
const handleKeyDown = (event) => {
|
|
1311
|
-
const mod = event.metaKey || event.ctrlKey;
|
|
1312
|
-
if (!mod)
|
|
1313
|
-
return;
|
|
1314
|
-
const key = event.key.toLowerCase();
|
|
1315
|
-
if (key === "z") {
|
|
1316
|
-
event.preventDefault();
|
|
1317
|
-
if (event.shiftKey)
|
|
1318
|
-
handleRedo(event);
|
|
1319
|
-
else
|
|
1320
|
-
handleUndo(event);
|
|
1321
|
-
}
|
|
1322
|
-
else if (key === "y" && !event.shiftKey) {
|
|
1323
|
-
event.preventDefault();
|
|
1324
|
-
handleRedo(event);
|
|
1325
|
-
}
|
|
1326
|
-
};
|
|
1327
|
-
editor.addEventListener("input", handleInput);
|
|
1328
|
-
editor.addEventListener("beforeinput", handleBeforeInput);
|
|
1329
|
-
editor.addEventListener("keydown", handleKeyDown);
|
|
1330
|
-
return () => {
|
|
1331
|
-
editor.removeEventListener("input", handleInput);
|
|
1332
|
-
editor.removeEventListener("beforeinput", handleBeforeInput);
|
|
1333
|
-
editor.removeEventListener("keydown", handleKeyDown);
|
|
1334
|
-
};
|
|
1335
|
-
}, [handleContentChange, handleRedo, handleUndo, pushHistory, updateFormattingState]);
|
|
91
|
+
if (!editor || value === undefined)
|
|
92
|
+
return;
|
|
93
|
+
const clean = sanitizeFn(value);
|
|
94
|
+
if (clean !== editor.innerHTML) {
|
|
95
|
+
editor.innerHTML = clean;
|
|
96
|
+
}
|
|
97
|
+
setIsEmpty(((_a = editor.textContent) !== null && _a !== void 0 ? _a : "").trim().length === 0);
|
|
98
|
+
}, [value, sanitizeFn]);
|
|
99
|
+
// Keep the toolbar's pressed states truthful as the caret moves.
|
|
1336
100
|
useEffect(() => {
|
|
1337
|
-
const
|
|
1338
|
-
if (!editorRef.current)
|
|
1339
|
-
return;
|
|
101
|
+
const handler = () => {
|
|
1340
102
|
const selection = window.getSelection();
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
updateFormattingState();
|
|
103
|
+
const editor = editorRef.current;
|
|
104
|
+
if (!selection || !editor || !selection.anchorNode)
|
|
1344
105
|
return;
|
|
106
|
+
if (editor.contains(selection.anchorNode)) {
|
|
107
|
+
saveSelection();
|
|
108
|
+
syncFormatting();
|
|
1345
109
|
}
|
|
1346
|
-
const range = selection.getRangeAt(0);
|
|
1347
|
-
if (!editorRef.current.contains(range.commonAncestorContainer)) {
|
|
1348
|
-
setShowToolbar(false);
|
|
1349
|
-
return;
|
|
1350
|
-
}
|
|
1351
|
-
handleSelection();
|
|
1352
110
|
};
|
|
1353
|
-
document.addEventListener("selectionchange",
|
|
1354
|
-
return () => document.removeEventListener("selectionchange",
|
|
1355
|
-
}, [
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
: tool === "letterSpacing"
|
|
1440
|
-
? formattingState.letterSpacing !== DEFAULT_FORMATTING_STATE.letterSpacing
|
|
1441
|
-
: tool === "alignLeft"
|
|
1442
|
-
? formattingState.textAlign === "left"
|
|
1443
|
-
: tool === "alignCenter"
|
|
1444
|
-
? formattingState.textAlign === "center"
|
|
1445
|
-
: tool === "alignRight"
|
|
1446
|
-
? formattingState.textAlign === "right"
|
|
1447
|
-
: false;
|
|
1448
|
-
switch (tool) {
|
|
1449
|
-
case "bold":
|
|
1450
|
-
return (jsx(Button, { size: "sm", color: "neutral", variant: "soft", iconOnly: true, className: cn(themeStyles.toolbarButtonStyle), "data-state": isActive ? "active" : "inactive", onMouseDown: handleMouseDown, onClick: handleBold, disabled: isDisabled, "aria-label": "Bold", "aria-pressed": isActive ? "true" : "false", children: jsx(BoldIcon, { className: "size-4" }) }, toolKey));
|
|
1451
|
-
case "italic":
|
|
1452
|
-
return (jsx(Button, { size: "sm", color: "neutral", variant: "soft", iconOnly: true, className: cn(themeStyles.toolbarButtonStyle), "data-state": isActive ? "active" : "inactive", onMouseDown: handleMouseDown, onClick: handleItalic, disabled: isDisabled, "aria-label": "Italic", "aria-pressed": isActive ? "true" : "false", children: jsx(ItalicIcon, { className: "size-4" }) }, toolKey));
|
|
1453
|
-
case "underline":
|
|
1454
|
-
return (jsx(Button, { size: "sm", color: "neutral", variant: "soft", iconOnly: true, className: cn(themeStyles.toolbarButtonStyle), "data-state": isActive ? "active" : "inactive", onMouseDown: handleMouseDown, onClick: handleUnderline, disabled: isDisabled, "aria-label": "Underline", "aria-pressed": isActive ? "true" : "false", children: jsx(UnderlineIcon, { className: "size-4" }) }, toolKey));
|
|
1455
|
-
case "fontFamily": {
|
|
1456
|
-
// The `fontFamily` tool is opt-in via toolbar config, but
|
|
1457
|
-
// `fontFamilies` is also a public prop — if a consumer passes an
|
|
1458
|
-
// empty array the dropdown has nothing to show and the trigger
|
|
1459
|
-
// can't compute a current option to display. Skip rendering the
|
|
1460
|
-
// tool entirely in that case rather than crashing on undefined.
|
|
1461
|
-
if (fontFamilies.length === 0) {
|
|
1462
|
-
if (process.env.NODE_ENV !== "production") {
|
|
1463
|
-
console.warn("[RichTextEditor]: `fontFamily` toolbar tool was requested but `fontFamilies` is empty — the tool will not render. Either remove `fontFamily` from toolbar.tools or supply at least one option.");
|
|
1464
|
-
}
|
|
1465
|
-
return null;
|
|
1466
|
-
}
|
|
1467
|
-
const currentOption = (_a = fontFamilies.find((opt) => opt.key === formattingState.fontFamily)) !== null && _a !== void 0 ? _a : fontFamilies[0];
|
|
1468
|
-
const portalStyle = {
|
|
1469
|
-
position: "fixed",
|
|
1470
|
-
top: fontFamilyPortalPosition.top,
|
|
1471
|
-
left: fontFamilyPortalPosition.left,
|
|
1472
|
-
zIndex: 1000,
|
|
1473
|
-
...fontFamilyPortalCssVars,
|
|
1474
|
-
};
|
|
1475
|
-
const renderedDropdown = isClient
|
|
1476
|
-
? createPortal(jsx(AnimatePresence, { mode: "wait", children: showFontFamilyPicker && (jsx(motion.div, { ref: (el) => {
|
|
1477
|
-
fontFamilyPortalContentRef.current = el;
|
|
1478
|
-
}, "data-flikkui-portal": "", role: "listbox", className: cn(surfaceClasses(surfaceLevel, { translucent: true, shadow: false }), themeStyles.dropdownStyle), style: portalStyle, variants: shouldReduceMotion ? undefined : fontFamilyAnimations, initial: "initial", animate: fontFamilyPortalReady ? "visible" : "initial", exit: "exit", children: jsx(SurfaceProvider, { level: surfaceLevel, children: fontFamilies.map((option) => {
|
|
1479
|
-
const selected = option.key === formattingState.fontFamily;
|
|
1480
|
-
return (jsx("button", { className: themeStyles.dropdownItemStyle, "data-active": selected ? "true" : "false", onMouseDown: (event) => {
|
|
1481
|
-
event.preventDefault();
|
|
1482
|
-
focusEditor();
|
|
1483
|
-
}, onClick: (event) => {
|
|
1484
|
-
event.preventDefault();
|
|
1485
|
-
event.stopPropagation();
|
|
1486
|
-
handleFontFamily(option.key);
|
|
1487
|
-
}, role: "option", "aria-selected": selected, children: jsxs("span", { className: "flex items-center justify-between gap-3", children: [jsx("span", { className: "text-sm", style: { fontFamily: option.stack || undefined }, children: option.label }), option.family && (jsx("span", { className: "text-xs text-[var(--color-text-muted)]", children: "Google" }))] }) }, option.key));
|
|
1488
|
-
}) }) })) }), document.body)
|
|
1489
|
-
: null;
|
|
1490
|
-
return (jsxs("div", { className: "relative", "data-tool": "fontFamily", children: [jsx(Button, { ref: fontFamilyTriggerRef, size: "sm", color: "neutral", variant: "soft", className: cn(themeStyles.toolbarButtonStyle, "!justify-start !w-auto px-2 min-w-[4rem] gap-1"), "data-state": isActive ? "active" : "inactive", onMouseDown: handleMouseDown, onClick: (event) => {
|
|
1491
|
-
event.preventDefault();
|
|
1492
|
-
event.stopPropagation();
|
|
1493
|
-
focusEditor();
|
|
1494
|
-
togglePicker("fontFamily");
|
|
1495
|
-
}, disabled: isDisabled, "aria-label": `Font family (${currentOption.label})`, "aria-expanded": showFontFamilyPicker, "aria-haspopup": "listbox", children: jsx("span", { className: "text-sm font-medium truncate text-left max-w-[5.5rem] block", style: { fontFamily: currentOption.stack || undefined }, children: currentOption.label }) }), renderedDropdown] }, toolKey));
|
|
1496
|
-
}
|
|
1497
|
-
case "fontWeight": {
|
|
1498
|
-
const currentOption = getFontWeightOption(formattingState.fontWeight);
|
|
1499
|
-
const portalStyle = {
|
|
1500
|
-
position: "fixed",
|
|
1501
|
-
top: fontWeightPortalPosition.top,
|
|
1502
|
-
left: fontWeightPortalPosition.left,
|
|
1503
|
-
zIndex: 1000,
|
|
1504
|
-
...fontWeightPortalCssVars,
|
|
1505
|
-
};
|
|
1506
|
-
const renderedDropdown = isClient
|
|
1507
|
-
? createPortal(jsx(AnimatePresence, { mode: "wait", children: showFontWeightPicker && (jsx(motion.div, { ref: (el) => {
|
|
1508
|
-
fontWeightPortalContentRef.current = el;
|
|
1509
|
-
}, "data-flikkui-portal": "", role: "listbox", className: cn(surfaceClasses(surfaceLevel, { translucent: true, shadow: false }), themeStyles.dropdownStyle), style: portalStyle, variants: shouldReduceMotion ? undefined : fontWeightAnimations, initial: "initial", animate: fontWeightPortalReady ? "visible" : "initial", exit: "exit", children: jsx(SurfaceProvider, { level: surfaceLevel, children: FONT_WEIGHT_OPTIONS.map((option) => {
|
|
1510
|
-
const selected = option.key === formattingState.fontWeight;
|
|
1511
|
-
return (jsx("button", { className: themeStyles.dropdownItemStyle, "data-active": selected ? "true" : "false", onMouseDown: (event) => {
|
|
1512
|
-
event.preventDefault();
|
|
1513
|
-
focusEditor();
|
|
1514
|
-
}, onClick: (event) => {
|
|
1515
|
-
event.preventDefault();
|
|
1516
|
-
event.stopPropagation();
|
|
1517
|
-
handleFontWeight(option.key);
|
|
1518
|
-
}, role: "option", "aria-selected": selected, children: jsxs("span", { className: "flex items-center justify-between gap-3", children: [jsx("span", { className: "text-sm", style: { fontWeight: option.value }, children: option.label }), jsx("span", { className: "text-sm text-[var(--color-text-muted)]", children: option.value })] }) }, option.key));
|
|
1519
|
-
}) }) })) }), document.body)
|
|
1520
|
-
: null;
|
|
1521
|
-
return (jsxs("div", { className: "relative", "data-tool": "fontWeight", children: [jsx(Button, { ref: fontWeightTriggerRef, size: "sm", color: "neutral", variant: "soft", iconOnly: true, className: cn(themeStyles.toolbarButtonStyle), "data-state": isActive ? "active" : "inactive", onMouseDown: handleMouseDown, onClick: (event) => {
|
|
1522
|
-
event.preventDefault();
|
|
1523
|
-
event.stopPropagation();
|
|
1524
|
-
focusEditor();
|
|
1525
|
-
togglePicker("fontWeight");
|
|
1526
|
-
}, disabled: isDisabled, "aria-label": `Font weight (${currentOption.label})`, "aria-expanded": showFontWeightPicker, "aria-haspopup": "listbox", children: jsx(BoldIcon, { className: "w-4 h-4" }) }), renderedDropdown] }, toolKey));
|
|
1527
|
-
}
|
|
1528
|
-
case "fontSize": {
|
|
1529
|
-
const currentOption = getFontSizeOption(formattingState.fontSize);
|
|
1530
|
-
const portalStyle = {
|
|
1531
|
-
position: "fixed",
|
|
1532
|
-
top: fontSizePortalPosition.top,
|
|
1533
|
-
left: fontSizePortalPosition.left,
|
|
1534
|
-
zIndex: 1000,
|
|
1535
|
-
...fontSizePortalCssVars,
|
|
1536
|
-
};
|
|
1537
|
-
const renderedDropdown = isClient
|
|
1538
|
-
? createPortal(jsx(AnimatePresence, { mode: "wait", children: showFontSizePicker && (jsx(motion.div, { ref: (el) => {
|
|
1539
|
-
fontSizePortalContentRef.current = el;
|
|
1540
|
-
}, "data-flikkui-portal": "", role: "listbox", className: cn(surfaceClasses(surfaceLevel, { translucent: true, shadow: false }), themeStyles.dropdownStyle), style: portalStyle, variants: shouldReduceMotion ? undefined : fontSizeAnimations, initial: "initial", animate: fontSizePortalReady ? "visible" : "initial", exit: "exit", children: jsx(SurfaceProvider, { level: surfaceLevel, children: FONT_SIZE_OPTIONS.map((option) => {
|
|
1541
|
-
const selected = option.key === formattingState.fontSize;
|
|
1542
|
-
return (jsx("button", { className: themeStyles.dropdownItemStyle, "data-active": selected ? "true" : "false", onMouseDown: (event) => {
|
|
1543
|
-
event.preventDefault();
|
|
1544
|
-
focusEditor();
|
|
1545
|
-
}, onClick: (event) => {
|
|
1546
|
-
event.preventDefault();
|
|
1547
|
-
event.stopPropagation();
|
|
1548
|
-
handleFontSize(option.key);
|
|
1549
|
-
}, role: "option", "aria-selected": selected, children: jsxs("span", { className: "flex items-center justify-between gap-3", children: [jsx("span", { className: "uppercase text-sm font-medium", children: option.label }), jsxs("span", { className: "text-sm text-[var(--color-text-muted)]", children: [option.px, "px"] })] }) }, option.key));
|
|
1550
|
-
}) }) })) }), document.body)
|
|
1551
|
-
: null;
|
|
1552
|
-
return (jsxs("div", { className: "relative", "data-tool": "fontSize", children: [jsx(Button, { ref: fontSizeTriggerRef, size: "sm", color: "neutral", variant: "soft", className: cn(themeStyles.toolbarButtonStyle, "px-2 min-w-[3rem] gap-1"), "data-state": isActive ? "active" : "inactive", onMouseDown: handleMouseDown, onClick: (event) => {
|
|
1553
|
-
event.preventDefault();
|
|
1554
|
-
event.stopPropagation();
|
|
1555
|
-
focusEditor();
|
|
1556
|
-
togglePicker("fontSize");
|
|
1557
|
-
}, disabled: isDisabled, "aria-label": "Font size", "aria-expanded": showFontSizePicker, "aria-haspopup": "listbox", children: jsx("span", { className: "text-sm font-medium tracking-wide uppercase", children: currentOption.label }) }), renderedDropdown] }, toolKey));
|
|
1558
|
-
}
|
|
1559
|
-
case "lineHeight": {
|
|
1560
|
-
const currentOption = getLineHeightOption(formattingState.lineHeight);
|
|
1561
|
-
const portalStyle = {
|
|
1562
|
-
position: "fixed",
|
|
1563
|
-
top: lineHeightPortalPosition.top,
|
|
1564
|
-
left: lineHeightPortalPosition.left,
|
|
1565
|
-
zIndex: 1000,
|
|
1566
|
-
...lineHeightPortalCssVars,
|
|
1567
|
-
};
|
|
1568
|
-
const renderedDropdown = isClient
|
|
1569
|
-
? createPortal(jsx(AnimatePresence, { mode: "wait", children: showLineHeightPicker && (jsx(motion.div, { ref: (el) => {
|
|
1570
|
-
lineHeightPortalContentRef.current = el;
|
|
1571
|
-
}, "data-flikkui-portal": "", role: "listbox", className: cn(surfaceClasses(surfaceLevel, { translucent: true, shadow: false }), themeStyles.dropdownStyle), style: portalStyle, variants: shouldReduceMotion ? undefined : lineHeightAnimations, initial: "initial", animate: lineHeightPortalReady ? "visible" : "initial", exit: "exit", children: jsx(SurfaceProvider, { level: surfaceLevel, children: LINE_HEIGHT_OPTIONS.map((option) => {
|
|
1572
|
-
const selected = option.key === formattingState.lineHeight;
|
|
1573
|
-
return (jsx("button", { className: themeStyles.dropdownItemStyle, "data-active": selected ? "true" : "false", onMouseDown: (event) => {
|
|
1574
|
-
event.preventDefault();
|
|
1575
|
-
focusEditor();
|
|
1576
|
-
}, onClick: (event) => {
|
|
1577
|
-
event.preventDefault();
|
|
1578
|
-
event.stopPropagation();
|
|
1579
|
-
handleLineHeight(option.key);
|
|
1580
|
-
}, role: "option", "aria-selected": selected, children: jsxs("span", { className: "flex items-center justify-between gap-3", children: [jsx("span", { className: "text-sm font-medium", children: option.label }), jsx("span", { className: "text-sm text-[var(--color-text-muted)]", children: option.value })] }) }, option.key));
|
|
1581
|
-
}) }) })) }), document.body)
|
|
1582
|
-
: null;
|
|
1583
|
-
return (jsxs("div", { className: "relative", "data-tool": "lineHeight", children: [jsx(Button, { ref: lineHeightTriggerRef, size: "sm", color: "neutral", variant: "soft", iconOnly: true, className: cn(themeStyles.toolbarButtonStyle), "data-state": isActive ? "active" : "inactive", onMouseDown: handleMouseDown, onClick: (event) => {
|
|
1584
|
-
event.preventDefault();
|
|
1585
|
-
event.stopPropagation();
|
|
1586
|
-
focusEditor();
|
|
1587
|
-
togglePicker("lineHeight");
|
|
1588
|
-
}, disabled: isDisabled, "aria-label": `Line height (${currentOption.label})`, "aria-expanded": showLineHeightPicker, "aria-haspopup": "listbox", children: jsx(ArrowsUpDownIcon, { className: "w-4 h-4" }) }), renderedDropdown] }, toolKey));
|
|
1589
|
-
}
|
|
1590
|
-
case "letterSpacing": {
|
|
1591
|
-
const currentOption = getLetterSpacingOption(formattingState.letterSpacing);
|
|
1592
|
-
const portalStyle = {
|
|
1593
|
-
position: "fixed",
|
|
1594
|
-
top: letterSpacingPortalPosition.top,
|
|
1595
|
-
left: letterSpacingPortalPosition.left,
|
|
1596
|
-
zIndex: 1000,
|
|
1597
|
-
...letterSpacingPortalCssVars,
|
|
1598
|
-
};
|
|
1599
|
-
const renderedDropdown = isClient
|
|
1600
|
-
? createPortal(jsx(AnimatePresence, { mode: "wait", children: showLetterSpacingPicker && (jsx(motion.div, { ref: (el) => {
|
|
1601
|
-
letterSpacingPortalContentRef.current = el;
|
|
1602
|
-
}, "data-flikkui-portal": "", role: "listbox", className: cn(surfaceClasses(surfaceLevel, { translucent: true, shadow: false }), themeStyles.dropdownStyle), style: portalStyle, variants: shouldReduceMotion ? undefined : letterSpacingAnimations, initial: "initial", animate: letterSpacingPortalReady ? "visible" : "initial", exit: "exit", children: jsx(SurfaceProvider, { level: surfaceLevel, children: LETTER_SPACING_OPTIONS.map((option) => {
|
|
1603
|
-
const selected = option.key === formattingState.letterSpacing;
|
|
1604
|
-
return (jsx("button", { className: themeStyles.dropdownItemStyle, "data-active": selected ? "true" : "false", onMouseDown: (event) => {
|
|
1605
|
-
event.preventDefault();
|
|
1606
|
-
focusEditor();
|
|
1607
|
-
}, onClick: (event) => {
|
|
1608
|
-
event.preventDefault();
|
|
1609
|
-
event.stopPropagation();
|
|
1610
|
-
handleLetterSpacing(option.key);
|
|
1611
|
-
}, role: "option", "aria-selected": selected, children: jsxs("span", { className: "flex items-center justify-between gap-3", children: [jsx("span", { className: "text-sm font-medium", children: option.label }), jsx("span", { className: "text-sm text-[var(--color-text-muted)]", children: option.value })] }) }, option.key));
|
|
1612
|
-
}) }) })) }), document.body)
|
|
1613
|
-
: null;
|
|
1614
|
-
return (jsxs("div", { className: "relative", "data-tool": "letterSpacing", children: [jsx(Button, { ref: letterSpacingTriggerRef, size: "sm", color: "neutral", variant: "soft", iconOnly: true, className: cn(themeStyles.toolbarButtonStyle), "data-state": isActive ? "active" : "inactive", onMouseDown: handleMouseDown, onClick: (event) => {
|
|
1615
|
-
event.preventDefault();
|
|
1616
|
-
event.stopPropagation();
|
|
1617
|
-
focusEditor();
|
|
1618
|
-
togglePicker("letterSpacing");
|
|
1619
|
-
}, disabled: isDisabled, "aria-label": `Letter spacing (${currentOption.label})`, "aria-expanded": showLetterSpacingPicker, "aria-haspopup": "listbox", children: jsx(ArrowsRightLeftIcon, { className: "w-4 h-4" }) }), renderedDropdown] }, toolKey));
|
|
1620
|
-
}
|
|
1621
|
-
case "textColor": {
|
|
1622
|
-
const portalStyle = {
|
|
1623
|
-
position: "fixed",
|
|
1624
|
-
top: colorPortalPosition.top,
|
|
1625
|
-
left: colorPortalPosition.left,
|
|
1626
|
-
zIndex: 1000,
|
|
1627
|
-
...colorPortalCssVars,
|
|
1628
|
-
};
|
|
1629
|
-
const currentToken = formattingState.textColor;
|
|
1630
|
-
// `bgValue` paints the swatch (resolved hex/oklch); `token` is the
|
|
1631
|
-
// Tailwind shade fragment (`rose-500`, `white`, …) used to build
|
|
1632
|
-
// the `text-{token}` class applied to the editor content.
|
|
1633
|
-
const renderSwatch = (bgValue, token, label) => {
|
|
1634
|
-
const isActiveSwatch = token === currentToken;
|
|
1635
|
-
return (jsx("button", { className: themeStyles.colorSwatchStyle, style: { backgroundColor: bgValue }, "data-active": isActiveSwatch ? "true" : "false", onMouseDown: (event) => {
|
|
1636
|
-
event.preventDefault();
|
|
1637
|
-
focusEditor();
|
|
1638
|
-
}, onClick: (event) => {
|
|
1639
|
-
event.preventDefault();
|
|
1640
|
-
event.stopPropagation();
|
|
1641
|
-
handleTextColor(token);
|
|
1642
|
-
}, "aria-label": `Set text color to ${label}` }, `swatch-${label}`));
|
|
1643
|
-
};
|
|
1644
|
-
const renderedColorDropdown = isClient
|
|
1645
|
-
? createPortal(jsx(AnimatePresence, { mode: "wait", children: showColorPicker && (jsx(motion.div, { ref: (el) => {
|
|
1646
|
-
colorPortalContentRef.current = el;
|
|
1647
|
-
}, "data-flikkui-portal": "", className: cn(surfaceClasses(surfaceLevel, { translucent: true, shadow: false }), themeStyles.dropdownStyle, "!max-h-none !overflow-visible"), style: portalStyle, variants: shouldReduceMotion ? undefined : colorAnimations, initial: "initial", animate: colorPortalReady ? "visible" : "initial", exit: "exit", children: jsx(SurfaceProvider, { level: surfaceLevel, children: jsx(ScrollArea, { type: "none", className: "max-h-[20rem]", children: jsxs("div", { className: themeStyles.colorPickerStyle, children: [recentColors.length > 0 && (jsxs("div", { className: "flex flex-col gap-1 pb-2 mb-1 border-b border-[var(--color-border)]", children: [jsx("span", { className: "text-[11px] font-medium uppercase tracking-wide text-[var(--color-text-muted)]", children: "Recent" }), jsx("div", { className: "flex gap-0.5", children: recentColors.map((token) => renderSwatch(resolveTailwindColor(token), token, `recent ${token}`)) })] })), jsxs("div", { className: "flex flex-col gap-1", children: [jsx("span", { className: "text-[11px] font-medium uppercase tracking-wide text-[var(--color-text-muted)]", children: "Mono" }), jsx("div", { className: "flex gap-0.5", children: MONO_COLORS.map((color) => renderSwatch(color.value, color.token, color.name)) }), jsx("span", { className: "mt-2 text-[11px] font-medium uppercase tracking-wide text-[var(--color-text-muted)]", children: "Spectrum" }), TAILWIND_COLOR_FAMILIES.map((family) => (jsx("div", { className: "flex gap-0.5", children: getTailwindShadeTokens(family).map((token) => renderSwatch(resolveTailwindColor(token), token, token)) }, family)))] })] }) }) }) })) }), document.body)
|
|
1648
|
-
: null;
|
|
1649
|
-
return (jsxs("div", { className: "relative", "data-tool": "textColor", children: [jsx(Button, { ref: colorTriggerRef, size: "sm", color: "neutral", variant: "soft", iconOnly: true, className: cn(themeStyles.toolbarButtonStyle), onMouseDown: handleMouseDown, onClick: (event) => {
|
|
1650
|
-
event.preventDefault();
|
|
1651
|
-
event.stopPropagation();
|
|
1652
|
-
focusEditor();
|
|
1653
|
-
togglePicker("color");
|
|
1654
|
-
}, disabled: isDisabled, "aria-label": "Text Color", "aria-expanded": showColorPicker, children: jsx(PaintBrushIcon, { className: "w-4 h-4" }) }), renderedColorDropdown] }, toolKey));
|
|
1655
|
-
}
|
|
1656
|
-
case "alignLeft":
|
|
1657
|
-
return (jsx(Button, { size: "sm", color: "neutral", variant: "soft", iconOnly: true, className: cn(themeStyles.toolbarButtonStyle), "data-state": isActive ? "active" : "inactive", onMouseDown: handleMouseDown, onClick: (event) => {
|
|
1658
|
-
event.preventDefault();
|
|
1659
|
-
event.stopPropagation();
|
|
1660
|
-
handleAlign("left");
|
|
1661
|
-
}, disabled: isDisabled, "aria-label": "Align Left", "aria-pressed": isActive ? "true" : "false", children: jsx(Bars3BottomLeftIcon, { className: "w-4 h-4" }) }, toolKey));
|
|
1662
|
-
case "alignCenter":
|
|
1663
|
-
return (jsx(Button, { size: "sm", color: "neutral", variant: "soft", iconOnly: true, className: cn(themeStyles.toolbarButtonStyle), "data-state": isActive ? "active" : "inactive", onMouseDown: handleMouseDown, onClick: (event) => {
|
|
1664
|
-
event.preventDefault();
|
|
1665
|
-
event.stopPropagation();
|
|
1666
|
-
handleAlign("center");
|
|
1667
|
-
}, disabled: isDisabled, "aria-label": "Align Center", "aria-pressed": isActive ? "true" : "false", children: jsx(Bars3Icon, { className: "w-4 h-4" }) }, toolKey));
|
|
1668
|
-
case "alignRight":
|
|
1669
|
-
return (jsx(Button, { size: "sm", color: "neutral", variant: "soft", iconOnly: true, className: cn(themeStyles.toolbarButtonStyle), "data-state": isActive ? "active" : "inactive", onMouseDown: handleMouseDown, onClick: (event) => {
|
|
1670
|
-
event.preventDefault();
|
|
1671
|
-
event.stopPropagation();
|
|
1672
|
-
handleAlign("right");
|
|
1673
|
-
}, disabled: isDisabled, "aria-label": "Align Right", "aria-pressed": isActive ? "true" : "false", children: jsx(Bars3BottomRightIcon, { className: "w-4 h-4" }) }, toolKey));
|
|
1674
|
-
case "undo":
|
|
1675
|
-
return (jsx(Button, { size: "sm", color: "neutral", variant: "soft", iconOnly: true, className: cn(themeStyles.toolbarButtonStyle), onMouseDown: handleMouseDown, onClick: handleUndo, disabled: isDisabled, "aria-label": "Undo", children: jsx(ArrowUturnLeftIcon, { className: "w-4 h-4" }) }, toolKey));
|
|
1676
|
-
case "redo":
|
|
1677
|
-
return (jsx(Button, { size: "sm", color: "neutral", variant: "soft", iconOnly: true, className: cn(themeStyles.toolbarButtonStyle), onMouseDown: handleMouseDown, onClick: handleRedo, disabled: isDisabled, "aria-label": "Redo", children: jsx(ArrowUturnRightIcon, { className: "w-4 h-4" }) }, toolKey));
|
|
1678
|
-
case "separator":
|
|
1679
|
-
return jsx("div", { className: themeStyles.separatorStyle }, toolKey);
|
|
1680
|
-
default:
|
|
1681
|
-
return null;
|
|
1682
|
-
}
|
|
1683
|
-
};
|
|
1684
|
-
const renderToolbar = () => {
|
|
1685
|
-
if (!toolbarConfig.show || !toolbarConfig.tools)
|
|
1686
|
-
return null;
|
|
1687
|
-
const tools = toolbarConfig.tools.map((tool, index) => renderToolbarTool(tool, `${tool}-${index}`));
|
|
1688
|
-
if (toolbarConfig.position === "floating") {
|
|
1689
|
-
return (jsx(AnimatePresence, { mode: "wait", children: showToolbar && (jsx(motion.div, { ref: toolbarRef, variants: shouldReduceMotion ? undefined : floatingToolbarVariants, initial: "initial", animate: "animate", exit: "exit", className: cn(surfaceClasses(surfaceLevel, { translucent: true }), themeStyles.floatingToolbarStyle, toolbarConfig.className), style: { position: "absolute", top: toolbarPosition.top, left: toolbarPosition.left, zIndex: 50 }, children: jsx(SurfaceProvider, { level: surfaceLevel, children: tools }) }, "floating-toolbar")) }));
|
|
1690
|
-
}
|
|
1691
|
-
return (jsx("div", { ref: toolbarRef, className: cn(themeStyles.toolbarStyle, toolbarConfig.className), children: tools }));
|
|
1692
|
-
};
|
|
1693
|
-
return (jsxs("div", { className: cn(themeStyles.wrapperStyle, className), ...props, children: [label && (typeof label === 'string' ? (jsx("div", { className: "mb-1", children: jsx(FormLabel, { htmlFor: editorId, className: labelClassName, state: state, required: required, children: label }) })) : (label)), jsxs("div", { className: "relative", children: [jsxs("div", { className: cn(themeStyles.containerStyle, themeStyles.states[state], themeStyles.sizes[size]), "data-state": getDataState(), children: [toolbarConfig.position === "top" && renderToolbar(), jsx("div", { ref: (el) => {
|
|
1694
|
-
if (el) {
|
|
1695
|
-
editorRef.current = el;
|
|
1696
|
-
if (typeof ref === "function") {
|
|
1697
|
-
ref(el);
|
|
1698
|
-
}
|
|
1699
|
-
else if (ref) {
|
|
1700
|
-
ref.current = el;
|
|
1701
|
-
}
|
|
1702
|
-
}
|
|
1703
|
-
}, id: editorId, contentEditable: !isDisabled, className: cn(themeStyles.editorStyle), "data-state": getDataState(), style: { minHeight: `${minHeight}px`, maxHeight: maxHeight ? `${maxHeight}px` : undefined }, "data-placeholder": placeholder, onFocus: () => {
|
|
1704
|
-
setIsFocused(true);
|
|
1705
|
-
updateFormattingState();
|
|
1706
|
-
}, onBlur: () => {
|
|
1707
|
-
setIsFocused(false);
|
|
1708
|
-
if (toolbarConfig.position === "floating") {
|
|
1709
|
-
setShowToolbar(false);
|
|
1710
|
-
}
|
|
1711
|
-
closeAllPickers();
|
|
1712
|
-
}, "aria-invalid": isInvalid, "aria-required": required, "aria-describedby": displayedMessage ? messageId : undefined, role: "textbox", "aria-multiline": "true", "aria-label": typeof label === 'string' ? label : "Rich text editor", suppressContentEditableWarning: true })] }), toolbarConfig.position === "floating" && renderToolbar()] }), displayedMessage && (typeof displayedMessage === 'string' ? (jsx("div", { id: messageId, role: isInvalid ? "alert" : undefined, className: cn(themeStyles.helperText, helperTextClassName), children: displayedMessage })) : (displayedMessage))] }));
|
|
111
|
+
document.addEventListener("selectionchange", handler);
|
|
112
|
+
return () => document.removeEventListener("selectionchange", handler);
|
|
113
|
+
}, [saveSelection, syncFormatting]);
|
|
114
|
+
const runCommand = useCallback((fn) => {
|
|
115
|
+
if (isDisabled)
|
|
116
|
+
return;
|
|
117
|
+
restoreSelection();
|
|
118
|
+
fn();
|
|
119
|
+
syncFormatting();
|
|
120
|
+
emitChange();
|
|
121
|
+
}, [isDisabled, restoreSelection, syncFormatting, emitChange]);
|
|
122
|
+
const handleToolCommand = useCallback((tool) => {
|
|
123
|
+
if (tool === "link") {
|
|
124
|
+
saveSelection();
|
|
125
|
+
setLinkFieldOpen((open) => !open);
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
runCommand(() => {
|
|
129
|
+
switch (tool) {
|
|
130
|
+
case "bold": return toggleBold();
|
|
131
|
+
case "italic": return toggleItalic();
|
|
132
|
+
case "underline": return toggleUnderline();
|
|
133
|
+
case "strikethrough": return toggleStrikethrough();
|
|
134
|
+
case "bulletList": return toggleBulletList();
|
|
135
|
+
case "orderedList": return toggleOrderedList();
|
|
136
|
+
case "blockquote": return toggleBlockquote(formatting.blockquote);
|
|
137
|
+
case "code": return toggleCodeBlock(formatting.code);
|
|
138
|
+
case "clearFormat": return clearFormatting();
|
|
139
|
+
case "undo": return undo();
|
|
140
|
+
case "redo": return redo();
|
|
141
|
+
default: return undefined;
|
|
142
|
+
}
|
|
143
|
+
});
|
|
144
|
+
}, [runCommand, saveSelection, formatting.blockquote, formatting.code]);
|
|
145
|
+
const handleBlockChange = useCallback((block) => runCommand(() => setBlockStyle(block)), [runCommand]);
|
|
146
|
+
/**
|
|
147
|
+
* Paste and drop are the two ways foreign markup reaches the document. Both
|
|
148
|
+
* are intercepted, sanitized, and re-inserted — the browser's default
|
|
149
|
+
* handler never runs, so nothing unsanitized touches the DOM.
|
|
150
|
+
*/
|
|
151
|
+
const insertForeignHtml = useCallback((html, text) => {
|
|
152
|
+
const clean = sanitizeFn(html);
|
|
153
|
+
if (clean)
|
|
154
|
+
insertSanitizedHtml(clean);
|
|
155
|
+
else if (text)
|
|
156
|
+
insertText(text);
|
|
157
|
+
syncFormatting();
|
|
158
|
+
emitChange();
|
|
159
|
+
}, [sanitizeFn, syncFormatting, emitChange]);
|
|
160
|
+
const handlePaste = useCallback((event) => {
|
|
161
|
+
if (isDisabled)
|
|
162
|
+
return;
|
|
163
|
+
event.preventDefault();
|
|
164
|
+
insertForeignHtml(event.clipboardData.getData("text/html"), event.clipboardData.getData("text/plain"));
|
|
165
|
+
}, [isDisabled, insertForeignHtml]);
|
|
166
|
+
const handleDrop = useCallback((event) => {
|
|
167
|
+
if (isDisabled)
|
|
168
|
+
return;
|
|
169
|
+
event.preventDefault();
|
|
170
|
+
insertForeignHtml(event.dataTransfer.getData("text/html"), event.dataTransfer.getData("text/plain"));
|
|
171
|
+
}, [isDisabled, insertForeignHtml]);
|
|
172
|
+
const applyLink = useCallback((href) => {
|
|
173
|
+
setLinkFieldOpen(false);
|
|
174
|
+
runCommand(() => createLink(href));
|
|
175
|
+
}, [runCommand]);
|
|
176
|
+
const removeLink$1 = useCallback(() => {
|
|
177
|
+
setLinkFieldOpen(false);
|
|
178
|
+
runCommand(() => removeLink());
|
|
179
|
+
}, [runCommand]);
|
|
180
|
+
const cancelLink = useCallback(() => {
|
|
181
|
+
setLinkFieldOpen(false);
|
|
182
|
+
restoreSelection();
|
|
183
|
+
}, [restoreSelection]);
|
|
184
|
+
const setRefs = useCallback((node) => {
|
|
185
|
+
editorRef.current = node;
|
|
186
|
+
if (typeof ref === "function")
|
|
187
|
+
ref(node);
|
|
188
|
+
else if (ref)
|
|
189
|
+
ref.current = node;
|
|
190
|
+
}, [ref]);
|
|
191
|
+
const showToolbar = (toolbar === null || toolbar === void 0 ? void 0 : toolbar.show) !== false;
|
|
192
|
+
const tools = (_a = toolbar === null || toolbar === void 0 ? void 0 : toolbar.tools) !== null && _a !== void 0 ? _a : DEFAULT_TOOLBAR_TOOLS;
|
|
193
|
+
const message = isInvalid && errorMessage ? errorMessage : helperText;
|
|
194
|
+
const hasMessage = Boolean(message);
|
|
195
|
+
const restAriaLabel = rest["aria-label"];
|
|
196
|
+
return (jsxs("div", { className: cn(theme.wrapperStyle, wrapperClassName), children: [label && (jsx(FormLabel, { htmlFor: generatedId, state: state, required: required, className: labelClassName, children: label })), jsxs("div", { className: cn(theme.containerStyle, theme.containerStates[state], containerClassName), children: [showToolbar && (jsx(RichTextToolbar, { tools: tools, formatting: formatting, disabled: isDisabled, size: size, className: cn(toolbar === null || toolbar === void 0 ? void 0 : toolbar.className, toolbarClassName), onCommand: handleToolCommand, onBlockChange: handleBlockChange })), isLinkFieldOpen && !isDisabled && (jsx(RichTextLinkField, { initialHref: formatting.link, onApply: applyLink, onRemove: removeLink$1, onCancel: cancelLink })), jsx("div", { ...rest, id: generatedId, ref: setRefs, role: "textbox", "aria-multiline": "true", "aria-label": typeof label === "string" ? label : restAriaLabel !== null && restAriaLabel !== void 0 ? restAriaLabel : "Rich text editor", "aria-required": required || undefined, "aria-invalid": isInvalid || undefined, "aria-describedby": hasMessage ? describedById : undefined, "aria-disabled": isDisabled || undefined, contentEditable: !isDisabled, suppressContentEditableWarning: true, "data-placeholder": placeholder, "data-empty": isEmpty || undefined, tabIndex: isDisabled ? -1 : 0, style: { minHeight, maxHeight }, className: cn(theme.editorStyle, theme.editorSizes[size], isEmpty && placeholder && theme.placeholderStyle, className), onInput: emitChange, onPaste: handlePaste, onDrop: handleDrop, onKeyUp: syncFormatting, onMouseUp: syncFormatting, onBlur: saveSelection })] }), hasMessage && (jsx("div", { id: describedById, className: cn(isInvalid && errorMessage ? theme.errorTextStyle : theme.helperTextStyle, helperTextClassName), children: message }))] }));
|
|
1713
197
|
});
|
|
1714
198
|
RichTextEditor.displayName = "RichTextEditor";
|
|
1715
199
|
|
|
1716
|
-
export {
|
|
200
|
+
export { RichTextEditor };
|