@open-mercato/ui 0.6.8-develop.7071.1.58862d0c9f → 0.6.8-develop.7072.1.19c2a8bbe0
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.
|
@@ -1181,16 +1181,23 @@ function RichEditorAutoToolbar({
|
|
|
1181
1181
|
const hasOverflow = hiddenItems.length > 0;
|
|
1182
1182
|
const showMoreButton = hasOverflow || !!moreMenu;
|
|
1183
1183
|
return /* @__PURE__ */ jsxs("div", { className: "relative w-full", children: [
|
|
1184
|
-
/* @__PURE__ */
|
|
1184
|
+
/* @__PURE__ */ jsx(
|
|
1185
1185
|
"div",
|
|
1186
1186
|
{
|
|
1187
|
-
ref: measureRef,
|
|
1188
1187
|
"aria-hidden": "true",
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1188
|
+
"data-slot": "rich-editor-toolbar-measure-clip",
|
|
1189
|
+
className: "pointer-events-none absolute inset-0 overflow-hidden",
|
|
1190
|
+
children: /* @__PURE__ */ jsxs(
|
|
1191
|
+
"div",
|
|
1192
|
+
{
|
|
1193
|
+
ref: measureRef,
|
|
1194
|
+
className: "invisible absolute left-0 top-0 flex flex-row flex-nowrap items-center gap-0.5",
|
|
1195
|
+
children: [
|
|
1196
|
+
items.map(renderToolbarItem),
|
|
1197
|
+
/* @__PURE__ */ jsx(RichEditorDropdownButton, { icon: /* @__PURE__ */ jsx(MoreVertical, {}), ariaLabel: labels.more, showChevron: false, menu: /* @__PURE__ */ jsx("div", {}) })
|
|
1198
|
+
]
|
|
1199
|
+
}
|
|
1200
|
+
)
|
|
1194
1201
|
}
|
|
1195
1202
|
),
|
|
1196
1203
|
/* @__PURE__ */ jsxs(RichEditorToolbar, { ref: containerRef, className: "w-full flex-nowrap overflow-hidden", children: [
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/primitives/rich-editor.tsx"],
|
|
4
|
-
"sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport {\n AlignCenter,\n AlignJustify,\n AlignLeft,\n AlignRight,\n AtSign,\n Bold,\n ChevronDown,\n Code,\n FileCode,\n HelpCircle,\n Image as ImageIcon,\n Italic,\n Link,\n List,\n ListChecks,\n ListOrdered,\n Maximize2,\n MessageCircle,\n Minus,\n MoreVertical,\n Quote,\n Strikethrough,\n Table as TableIcon,\n Underline,\n} from 'lucide-react'\nimport { cva, type VariantProps } from 'class-variance-authority'\n\nimport { cn } from '@open-mercato/shared/lib/utils'\nimport {\n sanitizeHtmlRichText,\n sanitizeRichTextHref,\n sanitizeRichTextPasteContent,\n} from '@open-mercato/ui/backend/utils/richTextSanitizer'\nimport { Popover, PopoverContent, PopoverTrigger } from './popover'\nimport {\n Tooltip,\n TooltipContent,\n TooltipProvider,\n TooltipTrigger,\n} from './tooltip'\nimport {\n Dialog,\n DialogContent,\n DialogFooter,\n DialogHeader,\n DialogTitle,\n} from './dialog'\nimport { Input } from './input'\nimport { Button } from './button'\n\n// Figma `Rich Editor Colors [1.1]` palette (166331:4100) \u2014 12 tokens.\n// Order matches the Figma frame: gray / black / white / blue / orange / red /\n// green / yellow / purple / sky / pink / teal.\nexport const RICH_EDITOR_COLOR_PALETTE = {\n gray: '#7b7b7b',\n black: '#171717',\n white: '#ffffff',\n blue: '#6366f1',\n orange: '#f59e0b',\n red: '#ef4444',\n green: '#22c55e',\n yellow: '#f6b51e',\n purple: '#7d52f4',\n sky: '#47c2ff',\n pink: '#fb4ba3',\n teal: '#22d3bb',\n} as const\n\nexport type RichEditorColorKey = keyof typeof RICH_EDITOR_COLOR_PALETTE\nconst COLOR_KEYS: RichEditorColorKey[] = [\n 'gray', 'black', 'white', 'blue', 'orange', 'red', 'green', 'yellow', 'purple', 'sky', 'pink', 'teal',\n]\n\nconst COLOR_LABELS_EN: Record<RichEditorColorKey, string> = {\n gray: 'Gray',\n black: 'Black',\n white: 'White',\n blue: 'Blue',\n orange: 'Orange',\n red: 'Red',\n green: 'Green',\n yellow: 'Yellow',\n purple: 'Purple',\n sky: 'Sky',\n pink: 'Pink',\n teal: 'Teal',\n}\n\nexport type RichEditorVariant = 'full' | 'standard' | 'basic' | 'minimal' | 'custom'\n\nexport type RichEditorAlign = 'left' | 'center' | 'right' | 'justify'\nexport type RichEditorFontSize = '12px' | '14px' | '16px' | '18px' | '20px' | '24px'\n\nconst ALIGN_ICONS: Record<RichEditorAlign, React.ComponentType<{ className?: string; 'aria-hidden'?: 'true' }>> = {\n left: AlignLeft,\n center: AlignCenter,\n right: AlignRight,\n justify: AlignJustify,\n}\n\nconst FONT_SIZE_OPTIONS: RichEditorFontSize[] = ['12px', '14px', '16px', '18px', '20px', '24px']\n\ntype SelectionState = {\n bold: boolean\n italic: boolean\n underline: boolean\n strikethrough: boolean\n unorderedList: boolean\n orderedList: boolean\n blockquote: boolean\n code: boolean\n heading: '' | 'h1' | 'h2' | 'h3'\n align: RichEditorAlign\n}\n\nconst EMPTY_SELECTION_STATE: SelectionState = {\n bold: false,\n italic: false,\n underline: false,\n strikethrough: false,\n unorderedList: false,\n orderedList: false,\n blockquote: false,\n code: false,\n heading: '',\n align: 'left',\n}\n\ntype RichEditorContextValue = {\n exec: (command: string, arg?: string) => void\n selection: SelectionState\n disabled: boolean\n /**\n * Open a DS-styled URL prompt (replaces the native `window.prompt`\n * popup). Pre-saves the current selection so the caller's `onConfirm`\n * handler can re-target the same range when running `execCommand`.\n */\n requestUrlPrompt: (request: {\n kind: 'link' | 'image'\n title: string\n placeholder?: string\n onConfirm: (url: string) => void\n }) => void\n}\n\nconst RichEditorContext = React.createContext<RichEditorContextValue | null>(null)\n\nfunction useRichEditorContext(component: string): RichEditorContextValue {\n const ctx = React.useContext(RichEditorContext)\n if (!ctx) {\n throw new Error(`${component} must be rendered inside <RichEditor>`)\n }\n return ctx\n}\n\nexport type RichEditorLabels = {\n bold: string\n italic: string\n underline: string\n strikethrough: string\n unorderedList: string\n orderedList: string\n checklist: string\n blockquote: string\n code: string\n inlineCode: string\n codeBlock: string\n horizontalRule: string\n image: string\n imageUrlPrompt: string\n table: string\n heading: string\n heading1: string\n heading2: string\n heading3: string\n paragraph: string\n link: string\n linkUrlPrompt: string\n color: string\n textColor: string\n fontSize: string\n align: string\n alignLeft: string\n alignCenter: string\n alignRight: string\n alignJustify: string\n comment: string\n mention: string\n more: string\n help: string\n fullscreen: string\n placeholder: string\n colors: Record<RichEditorColorKey, string>\n}\n\nconst DEFAULT_LABELS: RichEditorLabels = {\n bold: 'Bold',\n italic: 'Italic',\n underline: 'Underline',\n strikethrough: 'Strikethrough',\n unorderedList: 'Bullet list',\n orderedList: 'Numbered list',\n checklist: 'Checklist',\n blockquote: 'Quote',\n code: 'Code',\n inlineCode: 'Inline code',\n codeBlock: 'Code block',\n horizontalRule: 'Horizontal rule',\n image: 'Image',\n imageUrlPrompt: 'Enter image URL',\n table: 'Table',\n heading: 'Header',\n heading1: 'Heading 1',\n heading2: 'Heading 2',\n heading3: 'Heading 3',\n paragraph: 'Paragraph',\n link: 'Link',\n linkUrlPrompt: 'Enter URL',\n color: 'Text color',\n textColor: 'Color',\n fontSize: 'Font size',\n align: 'Align',\n alignLeft: 'Align left',\n alignCenter: 'Align center',\n alignRight: 'Align right',\n alignJustify: 'Justify',\n comment: 'Add comment',\n mention: 'Mention',\n more: 'More',\n help: 'Help',\n fullscreen: 'Toggle fullscreen',\n placeholder: 'Placeholder text...',\n colors: COLOR_LABELS_EN,\n}\n\nexport type RichEditorProps = {\n /** HTML string value (sanitized by `sanitizeHtmlRichText`). */\n value?: string\n /** Called with sanitized HTML on blur. */\n onChange: (html: string) => void\n /**\n * Toolbar preset:\n * - `'full'` \u2014 Figma 166331:4006 reference: heading + font size + color + B/I/U/S + lists + align + comment + link + mention + more\n * - `'standard'` \u2014 heading + bold/italic/underline + lists + link (CrudForm `editor='html'` default)\n * - `'basic'` \u2014 bold/italic/underline + bullet list + link\n * - `'minimal'` \u2014 bold/italic/underline\n * - `'custom'` \u2014 render `<RichEditorToolbar>{...}</RichEditorToolbar>` + `<RichEditorContent />` children manually\n */\n variant?: RichEditorVariant\n placeholder?: string\n minRows?: number\n disabled?: boolean\n className?: string\n contentClassName?: string\n labels?: Partial<RichEditorLabels>\n /** Optional handler for the comment button (only rendered in `full` variant). */\n onComment?: () => void\n /** Optional handler for the mention `@` button. */\n onMention?: () => void\n /** Optional menu rendered inside the More `\u22EE` dropdown popover. */\n moreMenu?: React.ReactNode\n /**\n * Optional handler for the fullscreen toggle. When provided, the\n * `full` variant renders the fullscreen icon button as the trailing\n * action \u2014 the caller is responsible for switching the editor into a\n * full-window layout (modal / portal / dialog).\n */\n onFullscreen?: () => void\n /**\n * Optional handler for the image insert button. When provided,\n * overrides the default `window.prompt(labels.imageUrlPrompt)` URL\n * picker so consumers can wire a file upload pipeline instead.\n */\n onImageInsert?: () => void\n /**\n * Optional max length for the rich text content. When provided, renders a\n * `<currentLength>/<maxLength>` counter in the bottom-right corner of the\n * content card (Figma 166331:6589 reference). Counts the plaintext length\n * of the sanitized HTML so users see a meaningful character budget.\n */\n maxLength?: number\n children?: React.ReactNode\n id?: string\n name?: string\n 'aria-invalid'?: boolean | 'true' | 'false'\n}\n\nfunction deriveSelectionState(): SelectionState {\n if (typeof document === 'undefined' || !document.queryCommandState) {\n return EMPTY_SELECTION_STATE\n }\n try {\n const block = document.queryCommandValue?.('formatBlock') ?? ''\n const lower = String(block).toLowerCase().replace(/[<>]/g, '')\n const heading: SelectionState['heading'] =\n lower === 'h1' || lower === 'h2' || lower === 'h3' ? lower : ''\n const align: RichEditorAlign = document.queryCommandState('justifyCenter')\n ? 'center'\n : document.queryCommandState('justifyRight')\n ? 'right'\n : document.queryCommandState('justifyFull')\n ? 'justify'\n : 'left'\n return {\n bold: document.queryCommandState('bold'),\n italic: document.queryCommandState('italic'),\n underline: document.queryCommandState('underline'),\n strikethrough: document.queryCommandState('strikeThrough'),\n unorderedList: document.queryCommandState('insertUnorderedList'),\n orderedList: document.queryCommandState('insertOrderedList'),\n blockquote: lower === 'blockquote',\n code: lower === 'pre',\n heading,\n align,\n }\n } catch {\n return EMPTY_SELECTION_STATE\n }\n}\n\nexport const RichEditor = React.memo(function RichEditor({\n value = '',\n onChange,\n variant = 'standard',\n placeholder,\n minRows = 4,\n disabled = false,\n className,\n contentClassName,\n labels: labelsProp,\n onComment,\n onMention,\n moreMenu,\n onFullscreen,\n onImageInsert,\n maxLength,\n children,\n id,\n name,\n 'aria-invalid': ariaInvalid,\n}: RichEditorProps) {\n const labels = React.useMemo<RichEditorLabels>(\n () => ({\n ...DEFAULT_LABELS,\n ...(labelsProp ?? {}),\n colors: { ...DEFAULT_LABELS.colors, ...(labelsProp?.colors ?? {}) },\n }),\n [labelsProp],\n )\n\n const editorRef = React.useRef<HTMLDivElement | null>(null)\n const applyingExternal = React.useRef(false)\n const typingRef = React.useRef(false)\n const savedSelectionRef = React.useRef<Range | null>(null)\n const [selection, setSelection] = React.useState<SelectionState>(EMPTY_SELECTION_STATE)\n const [promptDialog, setPromptDialog] = React.useState<{\n kind: 'link' | 'image'\n title: string\n placeholder?: string\n value: string\n onConfirm: (url: string) => void\n } | null>(null)\n\n React.useEffect(() => {\n const el = editorRef.current\n if (!el) return\n const sanitizedValue = sanitizeHtmlRichText(value)\n if (!typingRef.current && el.innerHTML !== sanitizedValue) {\n applyingExternal.current = true\n el.innerHTML = sanitizedValue\n requestAnimationFrame(() => {\n applyingExternal.current = false\n })\n }\n }, [value])\n\n const refreshSelectionState = React.useCallback(() => {\n setSelection(deriveSelectionState())\n }, [])\n\n const exec = React.useCallback(\n (command: string, arg?: string) => {\n const el = editorRef.current\n if (!el || disabled) return\n el.focus()\n try {\n // Tell the browser to emit modern <span style=\"\u2026\"> output (forecolor,\n // backColor, fontSize, etc.) instead of the deprecated <font> tag \u2014\n // the sanitizer drops <font> outright, so without this colour /\n // font-size changes silently disappear on the next blur.\n document.execCommand('styleWithCSS', false, 'true' as never)\n } catch {\n // ignore \u2014 older browsers without styleWithCSS support\n }\n try {\n document.execCommand(command, false, arg)\n } catch {\n // ignore unsupported commands\n }\n refreshSelectionState()\n },\n [disabled, refreshSelectionState],\n )\n\n const requestUrlPrompt = React.useCallback<RichEditorContextValue['requestUrlPrompt']>(\n (request) => {\n // Snapshot the current selection so we can restore the same range\n // after the Dialog steals focus.\n if (typeof window !== 'undefined') {\n const sel = window.getSelection()\n if (sel && sel.rangeCount > 0 && editorRef.current?.contains(sel.anchorNode)) {\n savedSelectionRef.current = sel.getRangeAt(0).cloneRange()\n } else {\n savedSelectionRef.current = null\n }\n }\n setPromptDialog({ ...request, value: '' })\n },\n [],\n )\n\n const ctx = React.useMemo<RichEditorContextValue>(\n () => ({ exec, selection, disabled, requestUrlPrompt }),\n [exec, selection, disabled, requestUrlPrompt],\n )\n\n React.useEffect(() => {\n if (typeof document === 'undefined') return\n const onSelectionChange = () => {\n if (!editorRef.current) return\n const sel = window.getSelection()\n if (!sel || !sel.anchorNode) return\n if (!editorRef.current.contains(sel.anchorNode)) return\n refreshSelectionState()\n }\n document.addEventListener('selectionchange', onSelectionChange)\n return () => {\n document.removeEventListener('selectionchange', onSelectionChange)\n }\n }, [refreshSelectionState])\n\n const onKeyDown = React.useCallback(\n (e: React.KeyboardEvent<HTMLDivElement>) => {\n const isMod = e.metaKey || e.ctrlKey\n if (!isMod) return\n const k = e.key.toLowerCase()\n if (k === 'b') {\n e.preventDefault()\n exec('bold')\n } else if (k === 'i') {\n e.preventDefault()\n exec('italic')\n } else if (k === 'u') {\n e.preventDefault()\n exec('underline')\n }\n },\n [exec],\n )\n\n const onPaste = React.useCallback(\n (e: React.ClipboardEvent<HTMLDivElement>) => {\n const html = e.clipboardData.getData('text/html')\n const text = e.clipboardData.getData('text/plain')\n const sanitizedPaste = sanitizeRichTextPasteContent(html, text)\n if (!sanitizedPaste) return\n e.preventDefault()\n exec(sanitizedPaste.command, sanitizedPaste.value)\n },\n [exec],\n )\n\n const onBlur = React.useCallback(() => {\n const el = editorRef.current\n if (!el) return\n typingRef.current = false\n const sanitized = sanitizeHtmlRichText(el.innerHTML)\n if (el.innerHTML !== sanitized) {\n applyingExternal.current = true\n el.innerHTML = sanitized\n requestAnimationFrame(() => {\n applyingExternal.current = false\n })\n }\n onChange(sanitized)\n }, [onChange])\n\n const presetItems = usePresetToolbarItems({\n variant,\n labels,\n onComment,\n onMention,\n onFullscreen,\n onImageInsert,\n exec,\n selection,\n requestUrlPrompt,\n })\n\n const plaintextLength = React.useMemo(() => {\n if (!maxLength) return 0\n if (typeof document === 'undefined') return 0\n const tmp = document.createElement('div')\n tmp.innerHTML = sanitizeHtmlRichText(value)\n return (tmp.textContent ?? '').length\n }, [value, maxLength])\n\n return (\n <RichEditorContext.Provider value={ctx}>\n <TooltipProvider delayDuration={400}>\n <div\n className={cn('w-full space-y-2', disabled && 'opacity-60', className)}\n data-slot=\"rich-editor\"\n data-disabled={disabled ? 'true' : 'false'}\n aria-invalid={ariaInvalid}\n >\n {variant === 'custom'\n ? children\n : (\n <>\n <RichEditorAutoToolbar items={presetItems} labels={labels} moreMenu={moreMenu} />\n <div className=\"relative\">\n <RichEditorContent\n ref={editorRef}\n placeholder={placeholder ?? labels.placeholder}\n minRows={minRows}\n disabled={disabled}\n className={contentClassName}\n onKeyDown={onKeyDown}\n onPaste={onPaste}\n onBlur={onBlur}\n onInput={() => {\n if (!applyingExternal.current) typingRef.current = true\n }}\n id={id}\n name={name}\n />\n {maxLength ? (\n <span\n className={cn(\n 'pointer-events-none absolute bottom-2 right-3 select-none text-xs leading-4 text-muted-foreground',\n plaintextLength > maxLength && 'text-destructive',\n )}\n data-slot=\"rich-editor-counter\"\n aria-live=\"polite\"\n >\n {plaintextLength}/{maxLength}\n </span>\n ) : null}\n </div>\n </>\n )}\n </div>\n {promptDialog !== null ? (\n <Dialog\n open\n onOpenChange={(open) => {\n if (!open) setPromptDialog(null)\n }}\n >\n <DialogContent\n className=\"sm:max-w-md\"\n onKeyDown={(e) => {\n if (e.key === 'Enter' && promptDialog) {\n e.preventDefault()\n const url = sanitizeRichTextHref(promptDialog.value)\n const onConfirm = promptDialog.onConfirm\n setPromptDialog(null)\n if (!url) return\n const el = editorRef.current\n const range = savedSelectionRef.current\n if (el) {\n el.focus()\n if (range) {\n const sel = window.getSelection()\n sel?.removeAllRanges()\n sel?.addRange(range)\n }\n }\n onConfirm(url)\n }\n }}\n >\n <DialogHeader>\n <DialogTitle>{promptDialog?.title ?? ''}</DialogTitle>\n </DialogHeader>\n <Input\n autoFocus\n type=\"url\"\n value={promptDialog?.value ?? ''}\n placeholder={promptDialog?.placeholder ?? 'https://\u2026'}\n onChange={(e) => {\n const next = e.target.value\n setPromptDialog((prev) => (prev ? { ...prev, value: next } : prev))\n }}\n />\n <DialogFooter>\n <Button\n type=\"button\"\n variant=\"outline\"\n onClick={() => setPromptDialog(null)}\n >\n Cancel\n </Button>\n <Button\n type=\"button\"\n onClick={() => {\n if (!promptDialog) return\n const url = sanitizeRichTextHref(promptDialog.value)\n const onConfirm = promptDialog.onConfirm\n setPromptDialog(null)\n if (!url) return\n const el = editorRef.current\n const range = savedSelectionRef.current\n if (el) {\n el.focus()\n if (range) {\n const sel = window.getSelection()\n sel?.removeAllRanges()\n sel?.addRange(range)\n }\n }\n onConfirm(url)\n }}\n >\n OK\n </Button>\n </DialogFooter>\n </DialogContent>\n </Dialog>\n ) : null}\n </TooltipProvider>\n </RichEditorContext.Provider>\n )\n}, (prev, next) =>\n prev.value === next.value &&\n prev.disabled === next.disabled &&\n prev.variant === next.variant &&\n prev.maxLength === next.maxLength,\n)\nRichEditor.displayName = 'RichEditor'\n\nexport type RichEditorToolbarProps = React.HTMLAttributes<HTMLDivElement>\n\nexport const RichEditorToolbar = React.forwardRef<HTMLDivElement, RichEditorToolbarProps>(\n ({ className, children, ...props }, ref) => (\n <div\n ref={ref}\n role=\"toolbar\"\n aria-label=\"Rich text formatting\"\n className={cn(\n // Figma 166331:4006 reference: standalone card with rounded-8 + border + shadow-xs.\n // 2px outer padding, 2px gap between items.\n 'flex w-fit max-w-full flex-wrap items-center gap-0.5 overflow-x-auto rounded-lg border border-border bg-card p-0.5 shadow-xs',\n className,\n )}\n data-slot=\"rich-editor-toolbar\"\n {...props}\n >\n {children}\n </div>\n ),\n)\nRichEditorToolbar.displayName = 'RichEditorToolbar'\n\nconst richEditorItemVariants = cva(\n // Figma Rich Editor Items (166251 family): 28\u00D7h, rounded-6, bg-white default,\n // bg-weak-50 (= bg-muted token) on hover, on toggle-active (data-active=true,\n // e.g. Bold inside a bold selection), and on dropdown-open\n // (data-state=open from Radix PopoverTrigger). Text / icon colours move from\n // sub-600 (= text-muted-foreground, #5c5c5c) in the default state to\n // strong-950 (= text-foreground, #171717) in the active / open states per\n // Figma 166261:22214 / 166261:22217 reference cells.\n 'group/rich-editor-item inline-flex h-7 shrink-0 items-center justify-center rounded-md bg-transparent text-sm font-medium leading-5 text-muted-foreground transition-colors hover:bg-muted focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring/40 disabled:pointer-events-none disabled:opacity-40 data-[active=true]:bg-muted data-[active=true]:text-foreground data-[state=open]:bg-muted data-[state=open]:text-foreground',\n {\n variants: {\n type: {\n // Figma 166251:2698 \u2014 pl-[10px] pr-[4px] py-[4px], gap-[2px].\n text: 'min-w-0 gap-0.5 pl-2.5 pr-1 py-1 tracking-[-0.084px] [&_svg]:size-4',\n // Figma 166251:2700 \u2014 square icon button p-[4px].\n icon: 'aspect-square p-1 [&_svg]:size-4',\n // Figma 166251:3337 \u2014 icon + caret p-[4px] gap-[2px].\n dropdown: 'gap-0.5 p-1 [&_svg]:size-4',\n // Figma 166331:4437 \u2014 color swatch + caret pl-[8px] pr-[4px] py-[4px].\n color: 'gap-0.5 pl-2 pr-1 py-1 [&_svg]:size-4',\n },\n },\n defaultVariants: { type: 'icon' },\n },\n)\n\ntype ToolbarButtonBaseProps = Omit<React.ButtonHTMLAttributes<HTMLButtonElement>, 'type' | 'children'> &\n VariantProps<typeof richEditorItemVariants> & {\n active?: boolean\n tooltipLabel?: string\n }\n\nconst RichEditorButton = React.forwardRef<HTMLButtonElement, ToolbarButtonBaseProps & { children?: React.ReactNode }>(\n ({ className, type, active, tooltipLabel, children, onMouseDown, ...props }, ref) => {\n const button = (\n <button\n ref={ref}\n type=\"button\"\n onMouseDown={(e) => {\n e.preventDefault()\n onMouseDown?.(e)\n }}\n data-active={active ? 'true' : 'false'}\n aria-pressed={active}\n className={cn(richEditorItemVariants({ type }), className)}\n {...props}\n >\n {children}\n </button>\n )\n // Wrap in DS Tooltip when tooltipLabel is provided \u2014 matches Figma\n // 166331:4027 (dark rounded popover above the trigger).\n if (!tooltipLabel) return button\n return (\n <Tooltip>\n <TooltipTrigger asChild>{button}</TooltipTrigger>\n <TooltipContent side=\"top\" sideOffset={6}>{tooltipLabel}</TooltipContent>\n </Tooltip>\n )\n },\n)\nRichEditorButton.displayName = 'RichEditorButton'\n\nexport type RichEditorIconButtonProps = Omit<ToolbarButtonBaseProps, 'type'> & {\n icon: React.ReactNode\n ariaLabel: string\n command?: string\n commandArg?: string\n onActivate?: () => void\n}\n\nexport const RichEditorIconButton = React.forwardRef<HTMLButtonElement, RichEditorIconButtonProps>(\n ({ icon, ariaLabel, command, commandArg, onActivate, active, onClick, ...props }, ref) => {\n const { exec, disabled } = useRichEditorContext('RichEditorIconButton')\n return (\n <RichEditorButton\n ref={ref}\n type=\"icon\"\n active={active}\n aria-label={ariaLabel}\n disabled={disabled || props.disabled}\n onClick={(e) => {\n if (command) exec(command, commandArg)\n onActivate?.()\n onClick?.(e)\n }}\n {...props}\n >\n <span data-slot=\"rich-editor-item-icon\" aria-hidden=\"true\">\n {icon}\n </span>\n </RichEditorButton>\n )\n },\n)\nRichEditorIconButton.displayName = 'RichEditorIconButton'\n\nexport type RichEditorTextDropdownProps = Omit<ToolbarButtonBaseProps, 'type' | 'children'> & {\n label: React.ReactNode\n ariaLabel: string\n menu?: React.ReactNode\n onActivate?: () => void\n}\n\nexport const RichEditorTextDropdown = React.forwardRef<HTMLButtonElement, RichEditorTextDropdownProps>(\n ({ label, ariaLabel, menu, onActivate, ...props }, ref) => {\n const { disabled } = useRichEditorContext('RichEditorTextDropdown')\n const button = (\n <RichEditorButton\n ref={ref}\n type=\"text\"\n aria-label={ariaLabel}\n disabled={disabled || props.disabled}\n onClick={onActivate}\n {...props}\n >\n <span className=\"truncate\">{label}</span>\n <ChevronDown className=\"opacity-60 transition-transform group-data-[state=open]/rich-editor-item:rotate-180\" aria-hidden=\"true\" />\n </RichEditorButton>\n )\n if (!menu) return button\n return (\n <Popover>\n <PopoverTrigger asChild>{button}</PopoverTrigger>\n <PopoverContent align=\"start\" sideOffset={6} className=\"w-44 p-1\">\n {menu}\n </PopoverContent>\n </Popover>\n )\n },\n)\nRichEditorTextDropdown.displayName = 'RichEditorTextDropdown'\n\nexport type RichEditorDropdownButtonProps = Omit<ToolbarButtonBaseProps, 'type' | 'children'> & {\n icon: React.ReactNode\n ariaLabel: string\n menu: React.ReactNode\n /** When false, the trailing chevron is omitted (e.g. for the `\u22EE More` overflow button). */\n showChevron?: boolean\n}\n\nexport const RichEditorDropdownButton = React.forwardRef<HTMLButtonElement, RichEditorDropdownButtonProps>(\n ({ icon, ariaLabel, menu, showChevron, ...props }, ref) => {\n const { disabled } = useRichEditorContext('RichEditorDropdownButton')\n return (\n <Popover>\n <PopoverTrigger asChild>\n <RichEditorButton\n ref={ref}\n type=\"dropdown\"\n aria-label={ariaLabel}\n disabled={disabled || props.disabled}\n {...props}\n >\n <span data-slot=\"rich-editor-item-icon\" aria-hidden=\"true\">\n {icon}\n </span>\n {showChevron === false ? null : (\n <ChevronDown className=\"opacity-60 transition-transform group-data-[state=open]/rich-editor-item:rotate-180\" aria-hidden=\"true\" />\n )}\n </RichEditorButton>\n </PopoverTrigger>\n <PopoverContent align=\"start\" sideOffset={6} className=\"w-44 p-1\">\n {menu}\n </PopoverContent>\n </Popover>\n )\n },\n)\nRichEditorDropdownButton.displayName = 'RichEditorDropdownButton'\n\nexport type RichEditorColorButtonProps = Omit<ToolbarButtonBaseProps, 'type' | 'children' | 'value'> & {\n colorValue?: RichEditorColorKey | null\n ariaLabel: string\n command?: string\n onSelect?: (color: RichEditorColorKey | null) => void\n /** Optional translated labels per palette key (defaults to English names). */\n colorLabels?: Partial<Record<RichEditorColorKey, string>>\n /**\n * Restrict the popover palette to a subset of colour keys. Defaults to all\n * 10 colours from the Figma `Rich Editor Colors [1.1]` master frame\n * (166331:4100); pass a 5-key subset for the compact popover variant seen\n * in some Figma mockups.\n */\n palette?: RichEditorColorKey[]\n}\n\nexport const RichEditorColorButton = React.forwardRef<HTMLButtonElement, RichEditorColorButtonProps>(\n ({ colorValue, ariaLabel, command = 'foreColor', onSelect, colorLabels, palette = COLOR_KEYS, ...props }, ref) => {\n const { exec, disabled } = useRichEditorContext('RichEditorColorButton')\n // Track the most recently selected colour internally so the trigger swatch\n // reflects the latest pick even when the consumer doesn't pass a\n // controlled `colorValue`. The `colorValue` prop still wins in controlled\n // mode (e.g. when the consumer wants the swatch to mirror a saved value).\n const [lastSelected, setLastSelected] = React.useState<RichEditorColorKey | null>(null)\n const effectiveValue = colorValue ?? lastSelected\n const swatchColor = effectiveValue ? RICH_EDITOR_COLOR_PALETTE[effectiveValue] : RICH_EDITOR_COLOR_PALETTE.blue\n return (\n <Popover>\n <PopoverTrigger asChild>\n <RichEditorButton\n ref={ref}\n type=\"color\"\n aria-label={ariaLabel}\n disabled={disabled || props.disabled}\n {...props}\n >\n <span\n className=\"inline-block size-4 shrink-0 rounded-full border border-black/10\"\n style={{ backgroundColor: swatchColor }}\n data-slot=\"rich-editor-color-swatch\"\n aria-hidden=\"true\"\n />\n <ChevronDown className=\"opacity-60 transition-transform group-data-[state=open]/rich-editor-item:rotate-180\" aria-hidden=\"true\" />\n </RichEditorButton>\n </PopoverTrigger>\n <PopoverContent align=\"start\" sideOffset={6} className=\"w-36 p-1\">\n <RichEditorColorPalette\n value={effectiveValue}\n labels={colorLabels}\n palette={palette}\n onChange={(key) => {\n if (key) exec(command, RICH_EDITOR_COLOR_PALETTE[key])\n setLastSelected(key)\n onSelect?.(key)\n }}\n />\n </PopoverContent>\n </Popover>\n )\n },\n)\nRichEditorColorButton.displayName = 'RichEditorColorButton'\n\nexport type RichEditorColorPaletteProps = {\n value?: RichEditorColorKey | null\n onChange?: (next: RichEditorColorKey | null) => void\n /** Translatable labels per palette key (defaults to English). */\n labels?: Partial<Record<RichEditorColorKey, string>>\n /**\n * Restrict the palette to a subset of colour keys (defaults to all 10). Use\n * the same subset list on `RichEditorColorButton` and standalone palette\n * popovers so the trigger swatch and the popover stay in sync.\n */\n palette?: RichEditorColorKey[]\n className?: string\n}\n\nexport function RichEditorColorPalette({ value, onChange, labels, palette, className }: RichEditorColorPaletteProps) {\n const resolvedLabels = { ...COLOR_LABELS_EN, ...(labels ?? {}) }\n const keys = palette && palette.length > 0 ? palette : COLOR_KEYS\n return (\n <div\n className={cn('flex flex-col gap-0.5', className)}\n data-slot=\"rich-editor-color-palette\"\n role=\"listbox\"\n aria-label=\"Color palette\"\n >\n {keys.map((key) => {\n const isActive = value === key\n return (\n <button\n key={key}\n type=\"button\"\n role=\"option\"\n aria-selected={isActive}\n aria-label={resolvedLabels[key]}\n onMouseDown={(e) => e.preventDefault()}\n onClick={() => onChange?.(key)}\n className={cn(\n 'flex items-center gap-2 rounded-md px-2 py-1.5 text-left text-sm transition-colors hover:bg-muted focus:outline-none focus-visible:ring-2 focus-visible:ring-ring/40',\n isActive && 'font-medium text-foreground',\n )}\n data-color-key={key}\n >\n <span\n className=\"size-4 shrink-0 rounded-full border border-black/10\"\n style={{ backgroundColor: RICH_EDITOR_COLOR_PALETTE[key] }}\n aria-hidden=\"true\"\n />\n <span className=\"truncate\">{resolvedLabels[key]}</span>\n </button>\n )\n })}\n </div>\n )\n}\n\nexport type RichEditorMenuItemProps = Omit<React.ButtonHTMLAttributes<HTMLButtonElement>, 'children'> & {\n /** Optional 16\u00D716 leading icon (typically a Lucide glyph). */\n icon?: React.ReactNode\n /** Item label (also forwarded as the accessible name). */\n children: React.ReactNode\n /** Destructive variant \u2014 flips the label colour to `text-destructive`. */\n destructive?: boolean\n}\n\n/**\n * Menu item helper for `RichEditor` popovers (More `\u22EE` kebab, custom heading\n * menus, etc.). Renders a Figma `Profile Dropdown Items`-style row: a 16\u00D716\n * icon slot, gap-2, full-width hover background. Use inside the `moreMenu`\n * prop of `<RichEditor>` or the `menu` prop of a compound `RichEditorTextDropdown`\n * / `RichEditorDropdownButton`.\n */\nexport const RichEditorMenuItem = React.forwardRef<HTMLButtonElement, RichEditorMenuItemProps>(\n ({ className, icon, children, destructive, onMouseDown, ...props }, ref) => (\n <button\n ref={ref}\n type=\"button\"\n role=\"menuitem\"\n onMouseDown={(e) => {\n e.preventDefault()\n onMouseDown?.(e)\n }}\n className={cn(\n 'flex w-full cursor-pointer items-center gap-2 rounded px-2 py-1.5 text-left text-sm text-foreground transition-colors hover:bg-muted focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring/40 disabled:pointer-events-none disabled:opacity-50',\n destructive && 'text-destructive hover:bg-destructive/10',\n className,\n )}\n data-slot=\"rich-editor-menu-item\"\n {...props}\n >\n {icon ? (\n <span\n className=\"inline-flex size-4 shrink-0 items-center justify-center text-muted-foreground [&_svg]:size-4\"\n data-slot=\"rich-editor-menu-item-icon\"\n aria-hidden=\"true\"\n >\n {icon}\n </span>\n ) : null}\n <span className=\"min-w-0 flex-1 truncate\">{children}</span>\n </button>\n ),\n)\nRichEditorMenuItem.displayName = 'RichEditorMenuItem'\n\nexport function RichEditorDivider({ className }: { className?: string }) {\n return (\n <span\n role=\"separator\"\n aria-orientation=\"vertical\"\n // Figma 166416:52475 \u2014 4\u00D716 container, 1px line. Toolbar `gap-0.5`\n // already provides the surrounding 2px gap on each side, so the divider\n // itself only carries the 1px line + 1.5px horizontal padding (mx-px).\n className={cn('mx-px inline-block h-4 w-px shrink-0 bg-border', className)}\n data-slot=\"rich-editor-divider\"\n />\n )\n}\n\nexport type RichEditorContentProps = Omit<React.HTMLAttributes<HTMLDivElement>, 'contentEditable' | 'children'> & {\n placeholder?: string\n minRows?: number\n disabled?: boolean\n id?: string\n name?: string\n}\n\nexport const RichEditorContent = React.forwardRef<HTMLDivElement, RichEditorContentProps>(\n ({ className, placeholder, minRows = 4, disabled, onBlur, onInput, onKeyDown, onPaste, id, name, ...props }, ref) => {\n const minHeight = `${Math.max(1, minRows) * 1.5}rem`\n return (\n <div\n ref={ref}\n contentEditable={!disabled}\n suppressContentEditableWarning\n spellCheck\n role=\"textbox\"\n aria-multiline=\"true\"\n data-slot=\"rich-editor-content\"\n data-placeholder={placeholder ?? ''}\n data-name={name}\n id={id}\n className={cn(\n // Figma reference: content lives in its own bordered card with rounded-8 + shadow-xs,\n // visually separated from the toolbar by the small vertical gap from the parent\n // wrapper's `space-y-2`.\n 'w-full max-w-none rounded-lg border border-border bg-card px-3 py-3 text-sm leading-6 text-foreground shadow-xs focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring/40',\n // The project does not include `@tailwindcss/typography`, so we have to\n // re-implement the rich text rendering ourselves via descendant-arbitrary\n // selectors. Cover every tag the toolbar can produce so the user sees\n // the formatting instantly after each execCommand.\n '[&_h1]:mt-4 [&_h1]:mb-2 [&_h1]:text-2xl [&_h1]:font-bold',\n '[&_h2]:mt-3 [&_h2]:mb-2 [&_h2]:text-xl [&_h2]:font-semibold',\n '[&_h3]:mt-3 [&_h3]:mb-1 [&_h3]:text-lg [&_h3]:font-semibold',\n '[&_p]:my-1',\n '[&_ul]:my-2 [&_ul]:list-disc [&_ul]:pl-6',\n '[&_ol]:my-2 [&_ol]:list-decimal [&_ol]:pl-6',\n '[&_li]:my-0.5',\n // Task list checklist override \u2014 when <ul data-task-list> we drop the\n // disc bullet so the <input type=checkbox> child stands alone.\n '[&_ul[data-task-list]]:list-none [&_ul[data-task-list]]:pl-0',\n '[&_ul[data-task-list]_input[type=checkbox]]:mr-2',\n '[&_blockquote]:my-2 [&_blockquote]:border-l-4 [&_blockquote]:border-border [&_blockquote]:pl-4 [&_blockquote]:italic [&_blockquote]:text-muted-foreground',\n '[&_pre]:my-2 [&_pre]:rounded [&_pre]:bg-muted [&_pre]:p-3 [&_pre]:font-mono [&_pre]:text-xs',\n '[&_code]:rounded [&_code]:bg-muted [&_code]:px-1 [&_code]:font-mono [&_code]:text-xs',\n '[&_a]:text-primary [&_a]:underline',\n '[&_hr]:my-3 [&_hr]:border-border',\n '[&_img]:my-2 [&_img]:h-auto [&_img]:max-w-full [&_img]:rounded',\n '[&_table]:my-2 [&_table]:border-collapse',\n '[&_td]:border [&_td]:border-border [&_td]:px-2 [&_td]:py-1',\n '[&_th]:border [&_th]:border-border [&_th]:bg-muted [&_th]:px-2 [&_th]:py-1',\n 'data-[placeholder]:before:pointer-events-none data-[placeholder]:empty:before:content-[attr(data-placeholder)] data-[placeholder]:empty:before:text-muted-foreground',\n className,\n )}\n style={{ minHeight }}\n onBlur={onBlur}\n onInput={onInput}\n onKeyDown={onKeyDown}\n onPaste={onPaste}\n {...props}\n />\n )\n },\n)\nRichEditorContent.displayName = 'RichEditorContent'\n\n// \u2500\u2500 Preset toolbar \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\ntype ToolbarItem =\n | { kind: 'divider'; key: string }\n | {\n kind: 'iconButton'\n key: string\n icon: React.ReactNode\n ariaLabel: string\n tooltipLabel?: string\n active?: boolean\n command?: string\n commandArg?: string\n onActivate?: () => void\n }\n | {\n kind: 'textDropdown'\n key: string\n ariaLabel: string\n label: React.ReactNode\n menu: React.ReactNode\n }\n | {\n kind: 'dropdownButton'\n key: string\n icon: React.ReactNode\n ariaLabel: string\n menu: React.ReactNode\n showChevron?: boolean\n }\n | {\n kind: 'colorButton'\n key: string\n ariaLabel: string\n colorLabels: Record<RichEditorColorKey, string>\n }\n\nfunction renderToolbarItem(item: ToolbarItem): React.ReactNode {\n switch (item.kind) {\n case 'divider':\n return <RichEditorDivider key={item.key} />\n case 'iconButton':\n return (\n <RichEditorIconButton\n key={item.key}\n icon={item.icon}\n ariaLabel={item.ariaLabel}\n tooltipLabel={item.tooltipLabel}\n active={item.active}\n command={item.command}\n commandArg={item.commandArg}\n onActivate={item.onActivate}\n />\n )\n case 'textDropdown':\n return (\n <RichEditorTextDropdown\n key={item.key}\n ariaLabel={item.ariaLabel}\n label={item.label}\n menu={item.menu}\n />\n )\n case 'dropdownButton':\n return (\n <RichEditorDropdownButton\n key={item.key}\n icon={item.icon}\n ariaLabel={item.ariaLabel}\n menu={item.menu}\n showChevron={item.showChevron}\n />\n )\n case 'colorButton':\n return (\n <RichEditorColorButton\n key={item.key}\n ariaLabel={item.ariaLabel}\n colorLabels={item.colorLabels}\n />\n )\n }\n}\n\nfunction trimTrailingDividers(items: ToolbarItem[]): ToolbarItem[] {\n let end = items.length\n while (end > 0 && items[end - 1].kind === 'divider') end -= 1\n return items.slice(0, end)\n}\n\nfunction trimLeadingDividers(items: ToolbarItem[]): ToolbarItem[] {\n let start = 0\n while (start < items.length && items[start].kind === 'divider') start += 1\n return items.slice(start)\n}\n\nfunction usePresetToolbarItems({\n variant,\n labels,\n onComment,\n onMention,\n onFullscreen,\n onImageInsert,\n exec,\n selection,\n requestUrlPrompt,\n}: {\n variant: RichEditorVariant\n labels: RichEditorLabels\n onComment?: () => void\n onMention?: () => void\n onFullscreen?: () => void\n onImageInsert?: () => void\n exec: RichEditorContextValue['exec']\n selection: RichEditorContextValue['selection']\n requestUrlPrompt: RichEditorContextValue['requestUrlPrompt']\n}): ToolbarItem[] {\n const onLink = React.useCallback(() => {\n requestUrlPrompt({\n kind: 'link',\n title: labels.linkUrlPrompt,\n placeholder: 'https://example.com',\n onConfirm: (url) => exec('createLink', url),\n })\n }, [requestUrlPrompt, exec, labels.linkUrlPrompt])\n\n const onImage = React.useCallback(() => {\n if (onImageInsert) {\n onImageInsert()\n return\n }\n requestUrlPrompt({\n kind: 'image',\n title: labels.imageUrlPrompt,\n placeholder: 'https://example.com/image.png',\n onConfirm: (url) => exec('insertImage', url),\n })\n }, [requestUrlPrompt, exec, labels.imageUrlPrompt, onImageInsert])\n\n const onInsertTable = React.useCallback(() => {\n const html = '<table style=\"border-collapse:collapse;width:100%\"><tbody>'\n + '<tr><td style=\"border:1px solid #ebebeb;padding:8px\"> </td><td style=\"border:1px solid #ebebeb;padding:8px\"> </td><td style=\"border:1px solid #ebebeb;padding:8px\"> </td></tr>'\n + '<tr><td style=\"border:1px solid #ebebeb;padding:8px\"> </td><td style=\"border:1px solid #ebebeb;padding:8px\"> </td><td style=\"border:1px solid #ebebeb;padding:8px\"> </td></tr>'\n + '<tr><td style=\"border:1px solid #ebebeb;padding:8px\"> </td><td style=\"border:1px solid #ebebeb;padding:8px\"> </td><td style=\"border:1px solid #ebebeb;padding:8px\"> </td></tr>'\n + '</tbody></table><p> </p>'\n exec('insertHTML', html)\n }, [exec])\n\n const onInsertChecklist = React.useCallback(() => {\n const html = '<ul data-task-list=\"true\" style=\"list-style:none;padding-left:0\">'\n + '<li><label><input type=\"checkbox\" /> </label></li>'\n + '</ul>'\n exec('insertHTML', html)\n }, [exec])\n\n const onInsertInlineCode = React.useCallback(() => {\n const selectionText = typeof window !== 'undefined' ? window.getSelection()?.toString() ?? '' : ''\n if (!selectionText) {\n exec('insertHTML', '<code> </code>')\n return\n }\n exec('insertHTML', `<code>${selectionText}</code>`)\n }, [exec])\n\n const showHeading = variant === 'full' || variant === 'standard'\n const showFontSize = variant === 'full'\n const showColor = variant === 'full'\n const showStrike = variant === 'full'\n const showHr = variant === 'full'\n const showQuoteAndCode = variant === 'full'\n const showImageTable = variant === 'full'\n const showChecklist = variant === 'full'\n const showOrdered = variant === 'full' || variant === 'standard' || variant === 'basic'\n const showAlign = variant === 'full'\n // Comment + Mention are always available in the `full` variant \u2014 when the\n // consumer doesn't wire a custom popover we fall back to inserting a\n // sensible plain-text marker at the caret so the buttons never feel dead.\n // Consumers can still pass `onComment` / `onMention` to fully override and\n // open their own popover / picker / inline UI.\n const showComment = variant === 'full'\n const commentHandler = onComment ?? (() => exec('insertText', '[comment: ]'))\n const showMention = variant === 'full'\n const mentionHandler = onMention ?? (() => exec('insertText', '@'))\n const showLink = variant === 'full' || variant === 'standard' || variant === 'basic'\n const showLists = variant !== 'minimal'\n const showHelp = variant === 'full'\n const showFullscreen = variant === 'full' && (onFullscreen !== undefined)\n\n const keyboardShortcutsHelp = (\n <div className=\"flex w-64 flex-col gap-1 p-1 text-sm\" role=\"menu\">\n <div className=\"px-2 py-1 text-xs font-semibold uppercase tracking-wide text-muted-foreground\">Keyboard</div>\n <div className=\"flex items-center justify-between rounded px-2 py-1 hover:bg-muted\">\n <span>{labels.bold}</span>\n <kbd className=\"rounded border bg-card px-1.5 py-0.5 text-xs\">\u2318 B</kbd>\n </div>\n <div className=\"flex items-center justify-between rounded px-2 py-1 hover:bg-muted\">\n <span>{labels.italic}</span>\n <kbd className=\"rounded border bg-card px-1.5 py-0.5 text-xs\">\u2318 I</kbd>\n </div>\n <div className=\"flex items-center justify-between rounded px-2 py-1 hover:bg-muted\">\n <span>{labels.underline}</span>\n <kbd className=\"rounded border bg-card px-1.5 py-0.5 text-xs\">\u2318 U</kbd>\n </div>\n </div>\n )\n\n const headingMenu = (\n <div className=\"flex flex-col gap-0.5\" role=\"menu\">\n <button\n type=\"button\"\n role=\"menuitem\"\n className=\"cursor-pointer rounded px-2 py-1 text-left text-sm hover:bg-muted\"\n onMouseDown={(e) => e.preventDefault()}\n onClick={() => exec('formatBlock', '<p>')}\n >\n {labels.paragraph}\n </button>\n <button\n type=\"button\"\n role=\"menuitem\"\n className=\"cursor-pointer rounded px-2 py-1 text-left text-lg font-semibold hover:bg-muted\"\n onMouseDown={(e) => e.preventDefault()}\n onClick={() => exec('formatBlock', '<h1>')}\n >\n {labels.heading1}\n </button>\n <button\n type=\"button\"\n role=\"menuitem\"\n className=\"cursor-pointer rounded px-2 py-1 text-left text-base font-semibold hover:bg-muted\"\n onMouseDown={(e) => e.preventDefault()}\n onClick={() => exec('formatBlock', '<h2>')}\n >\n {labels.heading2}\n </button>\n <button\n type=\"button\"\n role=\"menuitem\"\n className=\"cursor-pointer rounded px-2 py-1 text-left text-sm font-semibold hover:bg-muted\"\n onMouseDown={(e) => e.preventDefault()}\n onClick={() => exec('formatBlock', '<h3>')}\n >\n {labels.heading3}\n </button>\n </div>\n )\n\n const headingLabel: string = (() => {\n switch (selection.heading) {\n case 'h1':\n return labels.heading1\n case 'h2':\n return labels.heading2\n case 'h3':\n return labels.heading3\n default:\n return labels.heading\n }\n })()\n\n const fontSizeMenu = (\n <div className=\"flex flex-col gap-0.5\" role=\"menu\">\n {FONT_SIZE_OPTIONS.map((size) => (\n <button\n key={size}\n type=\"button\"\n role=\"menuitem\"\n className=\"cursor-pointer rounded px-2 py-1 text-left text-sm hover:bg-muted\"\n onMouseDown={(e) => e.preventDefault()}\n onClick={() => {\n // execCommand('fontSize', \u2026) requires 1\u20137 \u2014 translate the px label\n // through a CSS-driven span instead.\n exec('removeFormat')\n const px = parseInt(size, 10)\n if (px > 0) {\n exec('insertHTML', `<span style=\"font-size:${px}px\">${window.getSelection()?.toString() ?? ''}</span>`)\n }\n }}\n >\n {size}\n </button>\n ))}\n </div>\n )\n\n const alignMenu = (\n <div className=\"flex flex-col gap-0.5\" role=\"menu\">\n {(['left', 'center', 'right', 'justify'] as RichEditorAlign[]).map((option) => {\n const Icon = ALIGN_ICONS[option]\n const command = option === 'justify' ? 'justifyFull' : `justify${option[0].toUpperCase()}${option.slice(1)}`\n const label = option === 'left'\n ? labels.alignLeft\n : option === 'center'\n ? labels.alignCenter\n : option === 'right'\n ? labels.alignRight\n : labels.alignJustify\n return (\n <button\n key={option}\n type=\"button\"\n role=\"menuitem\"\n className=\"flex cursor-pointer items-center gap-2 rounded px-2 py-1 text-left text-sm hover:bg-muted\"\n onMouseDown={(e) => e.preventDefault()}\n onClick={() => exec(command)}\n >\n <Icon className=\"size-4\" aria-hidden=\"true\" />\n <span>{label}</span>\n </button>\n )\n })}\n </div>\n )\n\n const AlignActiveIcon = ALIGN_ICONS[selection.align]\n\n if (variant === 'custom') return []\n\n const items: ToolbarItem[] = []\n if (showHeading) {\n items.push({ kind: 'textDropdown', key: 'heading', ariaLabel: labels.heading, label: headingLabel, menu: headingMenu })\n items.push({ kind: 'divider', key: 'd-heading' })\n }\n if (showFontSize) {\n items.push({ kind: 'textDropdown', key: 'fontSize', ariaLabel: labels.fontSize, label: '14px', menu: fontSizeMenu })\n items.push({ kind: 'divider', key: 'd-fontSize' })\n }\n if (showColor) {\n items.push({ kind: 'colorButton', key: 'color', ariaLabel: labels.color, colorLabels: labels.colors })\n items.push({ kind: 'divider', key: 'd-color' })\n }\n items.push({ kind: 'iconButton', key: 'bold', icon: <Bold />, command: 'bold', ariaLabel: labels.bold, tooltipLabel: labels.bold, active: selection.bold })\n items.push({ kind: 'iconButton', key: 'italic', icon: <Italic />, command: 'italic', ariaLabel: labels.italic, tooltipLabel: labels.italic, active: selection.italic })\n items.push({ kind: 'iconButton', key: 'underline', icon: <Underline />, command: 'underline', ariaLabel: labels.underline, tooltipLabel: labels.underline, active: selection.underline })\n if (showStrike) {\n items.push({ kind: 'iconButton', key: 'strike', icon: <Strikethrough />, command: 'strikeThrough', ariaLabel: labels.strikethrough, tooltipLabel: labels.strikethrough, active: selection.strikethrough })\n }\n if (showLists) {\n items.push({ kind: 'divider', key: 'd-lists' })\n items.push({ kind: 'iconButton', key: 'ul', icon: <List />, command: 'insertUnorderedList', ariaLabel: labels.unorderedList, tooltipLabel: labels.unorderedList, active: selection.unorderedList })\n if (showOrdered) {\n items.push({ kind: 'iconButton', key: 'ol', icon: <ListOrdered />, command: 'insertOrderedList', ariaLabel: labels.orderedList, tooltipLabel: labels.orderedList, active: selection.orderedList })\n }\n }\n if (showHr || showQuoteAndCode) {\n items.push({ kind: 'divider', key: 'd-structure' })\n if (showHr) {\n items.push({ kind: 'iconButton', key: 'hr', icon: <Minus />, ariaLabel: labels.horizontalRule, tooltipLabel: labels.horizontalRule, onActivate: () => exec('insertHorizontalRule') })\n }\n if (showQuoteAndCode) {\n items.push({ kind: 'iconButton', key: 'quote', icon: <Quote />, ariaLabel: labels.blockquote, tooltipLabel: labels.blockquote, active: selection.blockquote, onActivate: () => exec('formatBlock', selection.blockquote ? '<p>' : '<blockquote>') })\n items.push({ kind: 'iconButton', key: 'inlineCode', icon: <Code />, ariaLabel: labels.inlineCode, tooltipLabel: labels.inlineCode, onActivate: onInsertInlineCode })\n items.push({ kind: 'iconButton', key: 'codeBlock', icon: <FileCode />, ariaLabel: labels.codeBlock, tooltipLabel: labels.codeBlock, active: selection.code, onActivate: () => exec('formatBlock', selection.code ? '<p>' : '<pre>') })\n }\n }\n if (showImageTable) {\n items.push({ kind: 'divider', key: 'd-media' })\n items.push({ kind: 'iconButton', key: 'image', icon: <ImageIcon />, ariaLabel: labels.image, tooltipLabel: labels.image, onActivate: onImage })\n items.push({ kind: 'iconButton', key: 'table', icon: <TableIcon />, ariaLabel: labels.table, tooltipLabel: labels.table, onActivate: onInsertTable })\n }\n if (showChecklist) {\n items.push({ kind: 'iconButton', key: 'checklist', icon: <ListChecks />, ariaLabel: labels.checklist, tooltipLabel: labels.checklist, onActivate: onInsertChecklist })\n }\n if (showAlign) {\n items.push({ kind: 'divider', key: 'd-align' })\n items.push({ kind: 'dropdownButton', key: 'align', icon: <AlignActiveIcon />, ariaLabel: labels.align, menu: alignMenu })\n }\n if (showLink || showComment || showMention) {\n items.push({ kind: 'divider', key: 'd-anchors' })\n }\n if (showComment) {\n items.push({ kind: 'iconButton', key: 'comment', icon: <MessageCircle />, ariaLabel: labels.comment, tooltipLabel: labels.comment, onActivate: commentHandler })\n }\n if (showLink) {\n items.push({ kind: 'iconButton', key: 'link', icon: <Link />, ariaLabel: labels.link, tooltipLabel: labels.link, onActivate: onLink })\n }\n if (showMention) {\n items.push({ kind: 'iconButton', key: 'mention', icon: <AtSign />, ariaLabel: labels.mention, tooltipLabel: labels.mention, onActivate: mentionHandler })\n }\n if (showHelp || showFullscreen) {\n items.push({ kind: 'divider', key: 'd-trailing' })\n if (showHelp) {\n items.push({ kind: 'dropdownButton', key: 'help', icon: <HelpCircle />, ariaLabel: labels.help, menu: keyboardShortcutsHelp, showChevron: false })\n }\n if (showFullscreen && onFullscreen) {\n items.push({ kind: 'iconButton', key: 'fullscreen', icon: <Maximize2 />, ariaLabel: labels.fullscreen, tooltipLabel: labels.fullscreen, onActivate: onFullscreen })\n }\n }\n\n return trimTrailingDividers(items)\n}\n\nfunction RichEditorAutoToolbar({\n items,\n labels,\n moreMenu,\n}: {\n items: ToolbarItem[]\n labels: RichEditorLabels\n moreMenu?: React.ReactNode\n}) {\n const measureRef = React.useRef<HTMLDivElement>(null)\n const containerRef = React.useRef<HTMLDivElement>(null)\n const [firstHidden, setFirstHidden] = React.useState<number | null>(null)\n\n React.useLayoutEffect(() => {\n if (typeof window === 'undefined') return\n if (typeof ResizeObserver === 'undefined') return\n const container = containerRef.current\n const measureEl = measureRef.current\n if (!container || !measureEl) return\n\n const measure = () => {\n const containerWidth = container.clientWidth\n if (containerWidth === 0) return\n\n const allEls = Array.from(measureEl.children) as HTMLElement[]\n if (allEls.length === 0) return\n // Last child is the reserved \u22EE placeholder for budget calculations.\n const moreEl = allEls[allEls.length - 1]\n const itemEls = allEls.slice(0, -1)\n const moreReserved = moreEl.offsetWidth + 4\n const containerPadding = 4 // p-0.5 left + right\n\n let totalWidth = 0\n for (const el of itemEls) totalWidth += el.offsetWidth + 2\n\n if (totalWidth + containerPadding <= containerWidth + 2) {\n setFirstHidden(null)\n return\n }\n\n let accumulated = 0\n for (let i = 0; i < itemEls.length; i++) {\n accumulated += itemEls[i].offsetWidth + 2\n if (accumulated > containerWidth - moreReserved - containerPadding) {\n setFirstHidden(i)\n return\n }\n }\n setFirstHidden(null)\n }\n\n measure()\n const ro = new ResizeObserver(measure)\n ro.observe(container)\n return () => ro.disconnect()\n }, [items])\n\n const visibleItems = firstHidden === null ? items : trimTrailingDividers(items.slice(0, firstHidden))\n const hiddenItems = firstHidden === null ? [] : trimLeadingDividers(items.slice(firstHidden))\n\n const hasOverflow = hiddenItems.length > 0\n const showMoreButton = hasOverflow || !!moreMenu\n\n return (\n <div className=\"relative w-full\">\n <div\n ref={measureRef}\n aria-hidden=\"true\"\n className=\"pointer-events-none invisible absolute left-0 top-0 flex flex-row flex-nowrap items-center gap-0.5\"\n >\n {items.map(renderToolbarItem)}\n <RichEditorDropdownButton icon={<MoreVertical />} ariaLabel={labels.more} showChevron={false} menu={<div />} />\n </div>\n <RichEditorToolbar ref={containerRef} className=\"w-full flex-nowrap overflow-hidden\">\n {visibleItems.map(renderToolbarItem)}\n {showMoreButton ? (\n <RichEditorDropdownButton\n icon={<MoreVertical />}\n ariaLabel={labels.more}\n showChevron={false}\n menu={\n <div className=\"flex max-w-[320px] flex-row flex-wrap items-center gap-0.5 p-1\">\n {moreMenu ? (\n <>\n <div className=\"w-full\">{moreMenu}</div>\n {hiddenItems.length > 0 ? <div className=\"my-1 h-px w-full bg-border\" /> : null}\n </>\n ) : null}\n {hiddenItems.map(renderToolbarItem)}\n </div>\n }\n />\n ) : null}\n </RichEditorToolbar>\n </div>\n )\n}\n\nexport { richEditorItemVariants }\n"],
|
|
5
|
-
"mappings": ";AA2gBc,mBACE,KAkBI,YAnBN;AAzgBd,YAAY,WAAW;AACvB;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,SAAS;AAAA,EACT;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,SAAS;AAAA,EACT;AAAA,OACK;AACP,SAAS,WAA8B;AAEvC,SAAS,UAAU;AACnB;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP,SAAS,SAAS,gBAAgB,sBAAsB;AACxD;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP,SAAS,aAAa;AACtB,SAAS,cAAc;AAKhB,MAAM,4BAA4B;AAAA,EACvC,MAAM;AAAA,EACN,OAAO;AAAA,EACP,OAAO;AAAA,EACP,MAAM;AAAA,EACN,QAAQ;AAAA,EACR,KAAK;AAAA,EACL,OAAO;AAAA,EACP,QAAQ;AAAA,EACR,QAAQ;AAAA,EACR,KAAK;AAAA,EACL,MAAM;AAAA,EACN,MAAM;AACR;AAGA,MAAM,aAAmC;AAAA,EACvC;AAAA,EAAQ;AAAA,EAAS;AAAA,EAAS;AAAA,EAAQ;AAAA,EAAU;AAAA,EAAO;AAAA,EAAS;AAAA,EAAU;AAAA,EAAU;AAAA,EAAO;AAAA,EAAQ;AACjG;AAEA,MAAM,kBAAsD;AAAA,EAC1D,MAAM;AAAA,EACN,OAAO;AAAA,EACP,OAAO;AAAA,EACP,MAAM;AAAA,EACN,QAAQ;AAAA,EACR,KAAK;AAAA,EACL,OAAO;AAAA,EACP,QAAQ;AAAA,EACR,QAAQ;AAAA,EACR,KAAK;AAAA,EACL,MAAM;AAAA,EACN,MAAM;AACR;AAOA,MAAM,cAA4G;AAAA,EAChH,MAAM;AAAA,EACN,QAAQ;AAAA,EACR,OAAO;AAAA,EACP,SAAS;AACX;AAEA,MAAM,oBAA0C,CAAC,QAAQ,QAAQ,QAAQ,QAAQ,QAAQ,MAAM;AAe/F,MAAM,wBAAwC;AAAA,EAC5C,MAAM;AAAA,EACN,QAAQ;AAAA,EACR,WAAW;AAAA,EACX,eAAe;AAAA,EACf,eAAe;AAAA,EACf,aAAa;AAAA,EACb,YAAY;AAAA,EACZ,MAAM;AAAA,EACN,SAAS;AAAA,EACT,OAAO;AACT;AAmBA,MAAM,oBAAoB,MAAM,cAA6C,IAAI;AAEjF,SAAS,qBAAqB,WAA2C;AACvE,QAAM,MAAM,MAAM,WAAW,iBAAiB;AAC9C,MAAI,CAAC,KAAK;AACR,UAAM,IAAI,MAAM,GAAG,SAAS,uCAAuC;AAAA,EACrE;AACA,SAAO;AACT;AA0CA,MAAM,iBAAmC;AAAA,EACvC,MAAM;AAAA,EACN,QAAQ;AAAA,EACR,WAAW;AAAA,EACX,eAAe;AAAA,EACf,eAAe;AAAA,EACf,aAAa;AAAA,EACb,WAAW;AAAA,EACX,YAAY;AAAA,EACZ,MAAM;AAAA,EACN,YAAY;AAAA,EACZ,WAAW;AAAA,EACX,gBAAgB;AAAA,EAChB,OAAO;AAAA,EACP,gBAAgB;AAAA,EAChB,OAAO;AAAA,EACP,SAAS;AAAA,EACT,UAAU;AAAA,EACV,UAAU;AAAA,EACV,UAAU;AAAA,EACV,WAAW;AAAA,EACX,MAAM;AAAA,EACN,eAAe;AAAA,EACf,OAAO;AAAA,EACP,WAAW;AAAA,EACX,UAAU;AAAA,EACV,OAAO;AAAA,EACP,WAAW;AAAA,EACX,aAAa;AAAA,EACb,YAAY;AAAA,EACZ,cAAc;AAAA,EACd,SAAS;AAAA,EACT,SAAS;AAAA,EACT,MAAM;AAAA,EACN,MAAM;AAAA,EACN,YAAY;AAAA,EACZ,aAAa;AAAA,EACb,QAAQ;AACV;AAsDA,SAAS,uBAAuC;AAC9C,MAAI,OAAO,aAAa,eAAe,CAAC,SAAS,mBAAmB;AAClE,WAAO;AAAA,EACT;AACA,MAAI;AACF,UAAM,QAAQ,SAAS,oBAAoB,aAAa,KAAK;AAC7D,UAAM,QAAQ,OAAO,KAAK,EAAE,YAAY,EAAE,QAAQ,SAAS,EAAE;AAC7D,UAAM,UACJ,UAAU,QAAQ,UAAU,QAAQ,UAAU,OAAO,QAAQ;AAC/D,UAAM,QAAyB,SAAS,kBAAkB,eAAe,IACrE,WACA,SAAS,kBAAkB,cAAc,IACvC,UACA,SAAS,kBAAkB,aAAa,IACtC,YACA;AACR,WAAO;AAAA,MACL,MAAM,SAAS,kBAAkB,MAAM;AAAA,MACvC,QAAQ,SAAS,kBAAkB,QAAQ;AAAA,MAC3C,WAAW,SAAS,kBAAkB,WAAW;AAAA,MACjD,eAAe,SAAS,kBAAkB,eAAe;AAAA,MACzD,eAAe,SAAS,kBAAkB,qBAAqB;AAAA,MAC/D,aAAa,SAAS,kBAAkB,mBAAmB;AAAA,MAC3D,YAAY,UAAU;AAAA,MACtB,MAAM,UAAU;AAAA,MAChB;AAAA,MACA;AAAA,IACF;AAAA,EACF,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEO,MAAM,aAAa,MAAM;AAAA,EAAK,SAASA,YAAW;AAAA,IACvD,QAAQ;AAAA,IACR;AAAA,IACA,UAAU;AAAA,IACV;AAAA,IACA,UAAU;AAAA,IACV,WAAW;AAAA,IACX;AAAA,IACA;AAAA,IACA,QAAQ;AAAA,IACR;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,gBAAgB;AAAA,EAClB,GAAoB;AAClB,UAAM,SAAS,MAAM;AAAA,MACnB,OAAO;AAAA,QACL,GAAG;AAAA,QACH,GAAI,cAAc,CAAC;AAAA,QACnB,QAAQ,EAAE,GAAG,eAAe,QAAQ,GAAI,YAAY,UAAU,CAAC,EAAG;AAAA,MACpE;AAAA,MACA,CAAC,UAAU;AAAA,IACb;AAEA,UAAM,YAAY,MAAM,OAA8B,IAAI;AAC1D,UAAM,mBAAmB,MAAM,OAAO,KAAK;AAC3C,UAAM,YAAY,MAAM,OAAO,KAAK;AACpC,UAAM,oBAAoB,MAAM,OAAqB,IAAI;AACzD,UAAM,CAAC,WAAW,YAAY,IAAI,MAAM,SAAyB,qBAAqB;AACtF,UAAM,CAAC,cAAc,eAAe,IAAI,MAAM,SAMpC,IAAI;AAEd,UAAM,UAAU,MAAM;AACpB,YAAM,KAAK,UAAU;AACrB,UAAI,CAAC,GAAI;AACT,YAAM,iBAAiB,qBAAqB,KAAK;AACjD,UAAI,CAAC,UAAU,WAAW,GAAG,cAAc,gBAAgB;AACzD,yBAAiB,UAAU;AAC3B,WAAG,YAAY;AACf,8BAAsB,MAAM;AAC1B,2BAAiB,UAAU;AAAA,QAC7B,CAAC;AAAA,MACH;AAAA,IACF,GAAG,CAAC,KAAK,CAAC;AAEV,UAAM,wBAAwB,MAAM,YAAY,MAAM;AACpD,mBAAa,qBAAqB,CAAC;AAAA,IACrC,GAAG,CAAC,CAAC;AAEL,UAAM,OAAO,MAAM;AAAA,MACjB,CAAC,SAAiB,QAAiB;AACjC,cAAM,KAAK,UAAU;AACrB,YAAI,CAAC,MAAM,SAAU;AACrB,WAAG,MAAM;AACT,YAAI;AAKF,mBAAS,YAAY,gBAAgB,OAAO,MAAe;AAAA,QAC7D,QAAQ;AAAA,QAER;AACA,YAAI;AACF,mBAAS,YAAY,SAAS,OAAO,GAAG;AAAA,QAC1C,QAAQ;AAAA,QAER;AACA,8BAAsB;AAAA,MACxB;AAAA,MACA,CAAC,UAAU,qBAAqB;AAAA,IAClC;AAEA,UAAM,mBAAmB,MAAM;AAAA,MAC7B,CAAC,YAAY;AAGX,YAAI,OAAO,WAAW,aAAa;AACjC,gBAAM,MAAM,OAAO,aAAa;AAChC,cAAI,OAAO,IAAI,aAAa,KAAK,UAAU,SAAS,SAAS,IAAI,UAAU,GAAG;AAC5E,8BAAkB,UAAU,IAAI,WAAW,CAAC,EAAE,WAAW;AAAA,UAC3D,OAAO;AACL,8BAAkB,UAAU;AAAA,UAC9B;AAAA,QACF;AACA,wBAAgB,EAAE,GAAG,SAAS,OAAO,GAAG,CAAC;AAAA,MAC3C;AAAA,MACA,CAAC;AAAA,IACH;AAEA,UAAM,MAAM,MAAM;AAAA,MAChB,OAAO,EAAE,MAAM,WAAW,UAAU,iBAAiB;AAAA,MACrD,CAAC,MAAM,WAAW,UAAU,gBAAgB;AAAA,IAC9C;AAEA,UAAM,UAAU,MAAM;AACpB,UAAI,OAAO,aAAa,YAAa;AACrC,YAAM,oBAAoB,MAAM;AAC9B,YAAI,CAAC,UAAU,QAAS;AACxB,cAAM,MAAM,OAAO,aAAa;AAChC,YAAI,CAAC,OAAO,CAAC,IAAI,WAAY;AAC7B,YAAI,CAAC,UAAU,QAAQ,SAAS,IAAI,UAAU,EAAG;AACjD,8BAAsB;AAAA,MACxB;AACA,eAAS,iBAAiB,mBAAmB,iBAAiB;AAC9D,aAAO,MAAM;AACX,iBAAS,oBAAoB,mBAAmB,iBAAiB;AAAA,MACnE;AAAA,IACF,GAAG,CAAC,qBAAqB,CAAC;AAE1B,UAAM,YAAY,MAAM;AAAA,MACtB,CAAC,MAA2C;AAC1C,cAAM,QAAQ,EAAE,WAAW,EAAE;AAC7B,YAAI,CAAC,MAAO;AACZ,cAAM,IAAI,EAAE,IAAI,YAAY;AAC5B,YAAI,MAAM,KAAK;AACb,YAAE,eAAe;AACjB,eAAK,MAAM;AAAA,QACb,WAAW,MAAM,KAAK;AACpB,YAAE,eAAe;AACjB,eAAK,QAAQ;AAAA,QACf,WAAW,MAAM,KAAK;AACpB,YAAE,eAAe;AACjB,eAAK,WAAW;AAAA,QAClB;AAAA,MACF;AAAA,MACA,CAAC,IAAI;AAAA,IACP;AAEA,UAAM,UAAU,MAAM;AAAA,MACpB,CAAC,MAA4C;AAC3C,cAAM,OAAO,EAAE,cAAc,QAAQ,WAAW;AAChD,cAAM,OAAO,EAAE,cAAc,QAAQ,YAAY;AACjD,cAAM,iBAAiB,6BAA6B,MAAM,IAAI;AAC9D,YAAI,CAAC,eAAgB;AACrB,UAAE,eAAe;AACjB,aAAK,eAAe,SAAS,eAAe,KAAK;AAAA,MACnD;AAAA,MACA,CAAC,IAAI;AAAA,IACP;AAEA,UAAM,SAAS,MAAM,YAAY,MAAM;AACrC,YAAM,KAAK,UAAU;AACrB,UAAI,CAAC,GAAI;AACT,gBAAU,UAAU;AACpB,YAAM,YAAY,qBAAqB,GAAG,SAAS;AACnD,UAAI,GAAG,cAAc,WAAW;AAC9B,yBAAiB,UAAU;AAC3B,WAAG,YAAY;AACf,8BAAsB,MAAM;AAC1B,2BAAiB,UAAU;AAAA,QAC7B,CAAC;AAAA,MACH;AACA,eAAS,SAAS;AAAA,IACpB,GAAG,CAAC,QAAQ,CAAC;AAEb,UAAM,cAAc,sBAAsB;AAAA,MACxC;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF,CAAC;AAED,UAAM,kBAAkB,MAAM,QAAQ,MAAM;AAC1C,UAAI,CAAC,UAAW,QAAO;AACvB,UAAI,OAAO,aAAa,YAAa,QAAO;AAC5C,YAAM,MAAM,SAAS,cAAc,KAAK;AACxC,UAAI,YAAY,qBAAqB,KAAK;AAC1C,cAAQ,IAAI,eAAe,IAAI;AAAA,IACjC,GAAG,CAAC,OAAO,SAAS,CAAC;AAErB,WACE,oBAAC,kBAAkB,UAAlB,EAA2B,OAAO,KACjC,+BAAC,mBAAgB,eAAe,KAC9B;AAAA;AAAA,QAAC;AAAA;AAAA,UACC,WAAW,GAAG,oBAAoB,YAAY,cAAc,SAAS;AAAA,UACrE,aAAU;AAAA,UACV,iBAAe,WAAW,SAAS;AAAA,UACnC,gBAAc;AAAA,UAEb,sBAAY,WACT,WAEA,iCACE;AAAA,gCAAC,yBAAsB,OAAO,aAAa,QAAgB,UAAoB;AAAA,YAC/E,qBAAC,SAAI,WAAU,YACb;AAAA;AAAA,gBAAC;AAAA;AAAA,kBACC,KAAK;AAAA,kBACL,aAAa,eAAe,OAAO;AAAA,kBACnC;AAAA,kBACA;AAAA,kBACA,WAAW;AAAA,kBACX;AAAA,kBACA;AAAA,kBACA;AAAA,kBACA,SAAS,MAAM;AACb,wBAAI,CAAC,iBAAiB,QAAS,WAAU,UAAU;AAAA,kBACrD;AAAA,kBACA;AAAA,kBACA;AAAA;AAAA,cACF;AAAA,cACC,YACC;AAAA,gBAAC;AAAA;AAAA,kBACC,WAAW;AAAA,oBACT;AAAA,oBACA,kBAAkB,aAAa;AAAA,kBACjC;AAAA,kBACA,aAAU;AAAA,kBACV,aAAU;AAAA,kBAET;AAAA;AAAA,oBAAgB;AAAA,oBAAE;AAAA;AAAA;AAAA,cACrB,IACE;AAAA,eACN;AAAA,aACF;AAAA;AAAA,MAEN;AAAA,MACC,iBAAiB,OAClB;AAAA,QAAC;AAAA;AAAA,UACC,MAAI;AAAA,UACJ,cAAc,CAAC,SAAS;AACtB,gBAAI,CAAC,KAAM,iBAAgB,IAAI;AAAA,UACjC;AAAA,UAEA;AAAA,YAAC;AAAA;AAAA,cACC,WAAU;AAAA,cACV,WAAW,CAAC,MAAM;AAChB,oBAAI,EAAE,QAAQ,WAAW,cAAc;AACrC,oBAAE,eAAe;AACjB,wBAAM,MAAM,qBAAqB,aAAa,KAAK;AACnD,wBAAM,YAAY,aAAa;AAC/B,kCAAgB,IAAI;AACpB,sBAAI,CAAC,IAAK;AACV,wBAAM,KAAK,UAAU;AACrB,wBAAM,QAAQ,kBAAkB;AAChC,sBAAI,IAAI;AACN,uBAAG,MAAM;AACT,wBAAI,OAAO;AACT,4BAAM,MAAM,OAAO,aAAa;AAChC,2BAAK,gBAAgB;AACrB,2BAAK,SAAS,KAAK;AAAA,oBACrB;AAAA,kBACF;AACA,4BAAU,GAAG;AAAA,gBACf;AAAA,cACF;AAAA,cAEA;AAAA,oCAAC,gBACC,8BAAC,eAAa,wBAAc,SAAS,IAAG,GAC1C;AAAA,gBACA;AAAA,kBAAC;AAAA;AAAA,oBACC,WAAS;AAAA,oBACT,MAAK;AAAA,oBACL,OAAO,cAAc,SAAS;AAAA,oBAC9B,aAAa,cAAc,eAAe;AAAA,oBAC1C,UAAU,CAAC,MAAM;AACf,4BAAM,OAAO,EAAE,OAAO;AACtB,sCAAgB,CAAC,SAAU,OAAO,EAAE,GAAG,MAAM,OAAO,KAAK,IAAI,IAAK;AAAA,oBACpE;AAAA;AAAA,gBACF;AAAA,gBACA,qBAAC,gBACC;AAAA;AAAA,oBAAC;AAAA;AAAA,sBACC,MAAK;AAAA,sBACL,SAAQ;AAAA,sBACR,SAAS,MAAM,gBAAgB,IAAI;AAAA,sBACpC;AAAA;AAAA,kBAED;AAAA,kBACA;AAAA,oBAAC;AAAA;AAAA,sBACC,MAAK;AAAA,sBACL,SAAS,MAAM;AACb,4BAAI,CAAC,aAAc;AACnB,8BAAM,MAAM,qBAAqB,aAAa,KAAK;AACnD,8BAAM,YAAY,aAAa;AAC/B,wCAAgB,IAAI;AACpB,4BAAI,CAAC,IAAK;AACV,8BAAM,KAAK,UAAU;AACrB,8BAAM,QAAQ,kBAAkB;AAChC,4BAAI,IAAI;AACN,6BAAG,MAAM;AACT,8BAAI,OAAO;AACT,kCAAM,MAAM,OAAO,aAAa;AAChC,iCAAK,gBAAgB;AACrB,iCAAK,SAAS,KAAK;AAAA,0BACrB;AAAA,wBACF;AACA,kCAAU,GAAG;AAAA,sBACf;AAAA,sBACD;AAAA;AAAA,kBAED;AAAA,mBACF;AAAA;AAAA;AAAA,UACF;AAAA;AAAA,MACF,IACI;AAAA,OACN,GACF;AAAA,EAEJ;AAAA,EAAG,CAAC,MAAM,SACR,KAAK,UAAU,KAAK,SACpB,KAAK,aAAa,KAAK,YACvB,KAAK,YAAY,KAAK,WACtB,KAAK,cAAc,KAAK;AAC1B;AACA,WAAW,cAAc;AAIlB,MAAM,oBAAoB,MAAM;AAAA,EACrC,CAAC,EAAE,WAAW,UAAU,GAAG,MAAM,GAAG,QAClC;AAAA,IAAC;AAAA;AAAA,MACC;AAAA,MACA,MAAK;AAAA,MACL,cAAW;AAAA,MACX,WAAW;AAAA;AAAA;AAAA,QAGT;AAAA,QACA;AAAA,MACF;AAAA,MACA,aAAU;AAAA,MACT,GAAG;AAAA,MAEH;AAAA;AAAA,EACH;AAEJ;AACA,kBAAkB,cAAc;AAEhC,MAAM,yBAAyB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQ7B;AAAA,EACA;AAAA,IACE,UAAU;AAAA,MACR,MAAM;AAAA;AAAA,QAEJ,MAAM;AAAA;AAAA,QAEN,MAAM;AAAA;AAAA,QAEN,UAAU;AAAA;AAAA,QAEV,OAAO;AAAA,MACT;AAAA,IACF;AAAA,IACA,iBAAiB,EAAE,MAAM,OAAO;AAAA,EAClC;AACF;AAQA,MAAM,mBAAmB,MAAM;AAAA,EAC7B,CAAC,EAAE,WAAW,MAAM,QAAQ,cAAc,UAAU,aAAa,GAAG,MAAM,GAAG,QAAQ;AACnF,UAAM,SACJ;AAAA,MAAC;AAAA;AAAA,QACC;AAAA,QACA,MAAK;AAAA,QACL,aAAa,CAAC,MAAM;AAClB,YAAE,eAAe;AACjB,wBAAc,CAAC;AAAA,QACjB;AAAA,QACA,eAAa,SAAS,SAAS;AAAA,QAC/B,gBAAc;AAAA,QACd,WAAW,GAAG,uBAAuB,EAAE,KAAK,CAAC,GAAG,SAAS;AAAA,QACxD,GAAG;AAAA,QAEH;AAAA;AAAA,IACH;AAIF,QAAI,CAAC,aAAc,QAAO;AAC1B,WACE,qBAAC,WACC;AAAA,0BAAC,kBAAe,SAAO,MAAE,kBAAO;AAAA,MAChC,oBAAC,kBAAe,MAAK,OAAM,YAAY,GAAI,wBAAa;AAAA,OAC1D;AAAA,EAEJ;AACF;AACA,iBAAiB,cAAc;AAUxB,MAAM,uBAAuB,MAAM;AAAA,EACxC,CAAC,EAAE,MAAM,WAAW,SAAS,YAAY,YAAY,QAAQ,SAAS,GAAG,MAAM,GAAG,QAAQ;AACxF,UAAM,EAAE,MAAM,SAAS,IAAI,qBAAqB,sBAAsB;AACtE,WACE;AAAA,MAAC;AAAA;AAAA,QACC;AAAA,QACA,MAAK;AAAA,QACL;AAAA,QACA,cAAY;AAAA,QACZ,UAAU,YAAY,MAAM;AAAA,QAC5B,SAAS,CAAC,MAAM;AACd,cAAI,QAAS,MAAK,SAAS,UAAU;AACrC,uBAAa;AACb,oBAAU,CAAC;AAAA,QACb;AAAA,QACC,GAAG;AAAA,QAEJ,8BAAC,UAAK,aAAU,yBAAwB,eAAY,QACjD,gBACH;AAAA;AAAA,IACF;AAAA,EAEJ;AACF;AACA,qBAAqB,cAAc;AAS5B,MAAM,yBAAyB,MAAM;AAAA,EAC1C,CAAC,EAAE,OAAO,WAAW,MAAM,YAAY,GAAG,MAAM,GAAG,QAAQ;AACzD,UAAM,EAAE,SAAS,IAAI,qBAAqB,wBAAwB;AAClE,UAAM,SACJ;AAAA,MAAC;AAAA;AAAA,QACC;AAAA,QACA,MAAK;AAAA,QACL,cAAY;AAAA,QACZ,UAAU,YAAY,MAAM;AAAA,QAC5B,SAAS;AAAA,QACR,GAAG;AAAA,QAEJ;AAAA,8BAAC,UAAK,WAAU,YAAY,iBAAM;AAAA,UAClC,oBAAC,eAAY,WAAU,uFAAsF,eAAY,QAAO;AAAA;AAAA;AAAA,IAClI;AAEF,QAAI,CAAC,KAAM,QAAO;AAClB,WACE,qBAAC,WACC;AAAA,0BAAC,kBAAe,SAAO,MAAE,kBAAO;AAAA,MAChC,oBAAC,kBAAe,OAAM,SAAQ,YAAY,GAAG,WAAU,YACpD,gBACH;AAAA,OACF;AAAA,EAEJ;AACF;AACA,uBAAuB,cAAc;AAU9B,MAAM,2BAA2B,MAAM;AAAA,EAC5C,CAAC,EAAE,MAAM,WAAW,MAAM,aAAa,GAAG,MAAM,GAAG,QAAQ;AACzD,UAAM,EAAE,SAAS,IAAI,qBAAqB,0BAA0B;AACpE,WACE,qBAAC,WACC;AAAA,0BAAC,kBAAe,SAAO,MACrB;AAAA,QAAC;AAAA;AAAA,UACC;AAAA,UACA,MAAK;AAAA,UACL,cAAY;AAAA,UACZ,UAAU,YAAY,MAAM;AAAA,UAC3B,GAAG;AAAA,UAEJ;AAAA,gCAAC,UAAK,aAAU,yBAAwB,eAAY,QACjD,gBACH;AAAA,YACC,gBAAgB,QAAQ,OACvB,oBAAC,eAAY,WAAU,uFAAsF,eAAY,QAAO;AAAA;AAAA;AAAA,MAEpI,GACF;AAAA,MACA,oBAAC,kBAAe,OAAM,SAAQ,YAAY,GAAG,WAAU,YACpD,gBACH;AAAA,OACF;AAAA,EAEJ;AACF;AACA,yBAAyB,cAAc;AAkBhC,MAAM,wBAAwB,MAAM;AAAA,EACzC,CAAC,EAAE,YAAY,WAAW,UAAU,aAAa,UAAU,aAAa,UAAU,YAAY,GAAG,MAAM,GAAG,QAAQ;AAChH,UAAM,EAAE,MAAM,SAAS,IAAI,qBAAqB,uBAAuB;AAKvE,UAAM,CAAC,cAAc,eAAe,IAAI,MAAM,SAAoC,IAAI;AACtF,UAAM,iBAAiB,cAAc;AACrC,UAAM,cAAc,iBAAiB,0BAA0B,cAAc,IAAI,0BAA0B;AAC3G,WACE,qBAAC,WACC;AAAA,0BAAC,kBAAe,SAAO,MACrB;AAAA,QAAC;AAAA;AAAA,UACC;AAAA,UACA,MAAK;AAAA,UACL,cAAY;AAAA,UACZ,UAAU,YAAY,MAAM;AAAA,UAC3B,GAAG;AAAA,UAEJ;AAAA;AAAA,cAAC;AAAA;AAAA,gBACC,WAAU;AAAA,gBACV,OAAO,EAAE,iBAAiB,YAAY;AAAA,gBACtC,aAAU;AAAA,gBACV,eAAY;AAAA;AAAA,YACd;AAAA,YACA,oBAAC,eAAY,WAAU,uFAAsF,eAAY,QAAO;AAAA;AAAA;AAAA,MAClI,GACF;AAAA,MACA,oBAAC,kBAAe,OAAM,SAAQ,YAAY,GAAG,WAAU,YACrD;AAAA,QAAC;AAAA;AAAA,UACC,OAAO;AAAA,UACP,QAAQ;AAAA,UACR;AAAA,UACA,UAAU,CAAC,QAAQ;AACjB,gBAAI,IAAK,MAAK,SAAS,0BAA0B,GAAG,CAAC;AACrD,4BAAgB,GAAG;AACnB,uBAAW,GAAG;AAAA,UAChB;AAAA;AAAA,MACF,GACF;AAAA,OACF;AAAA,EAEJ;AACF;AACA,sBAAsB,cAAc;AAgB7B,SAAS,uBAAuB,EAAE,OAAO,UAAU,QAAQ,SAAS,UAAU,GAAgC;AACnH,QAAM,iBAAiB,EAAE,GAAG,iBAAiB,GAAI,UAAU,CAAC,EAAG;AAC/D,QAAM,OAAO,WAAW,QAAQ,SAAS,IAAI,UAAU;AACvD,SACE;AAAA,IAAC;AAAA;AAAA,MACC,WAAW,GAAG,yBAAyB,SAAS;AAAA,MAChD,aAAU;AAAA,MACV,MAAK;AAAA,MACL,cAAW;AAAA,MAEV,eAAK,IAAI,CAAC,QAAQ;AACjB,cAAM,WAAW,UAAU;AAC3B,eACE;AAAA,UAAC;AAAA;AAAA,YAEC,MAAK;AAAA,YACL,MAAK;AAAA,YACL,iBAAe;AAAA,YACf,cAAY,eAAe,GAAG;AAAA,YAC9B,aAAa,CAAC,MAAM,EAAE,eAAe;AAAA,YACrC,SAAS,MAAM,WAAW,GAAG;AAAA,YAC7B,WAAW;AAAA,cACT;AAAA,cACA,YAAY;AAAA,YACd;AAAA,YACA,kBAAgB;AAAA,YAEhB;AAAA;AAAA,gBAAC;AAAA;AAAA,kBACC,WAAU;AAAA,kBACV,OAAO,EAAE,iBAAiB,0BAA0B,GAAG,EAAE;AAAA,kBACzD,eAAY;AAAA;AAAA,cACd;AAAA,cACA,oBAAC,UAAK,WAAU,YAAY,yBAAe,GAAG,GAAE;AAAA;AAAA;AAAA,UAlB3C;AAAA,QAmBP;AAAA,MAEJ,CAAC;AAAA;AAAA,EACH;AAEJ;AAkBO,MAAM,qBAAqB,MAAM;AAAA,EACtC,CAAC,EAAE,WAAW,MAAM,UAAU,aAAa,aAAa,GAAG,MAAM,GAAG,QAClE;AAAA,IAAC;AAAA;AAAA,MACC;AAAA,MACA,MAAK;AAAA,MACL,MAAK;AAAA,MACL,aAAa,CAAC,MAAM;AAClB,UAAE,eAAe;AACjB,sBAAc,CAAC;AAAA,MACjB;AAAA,MACA,WAAW;AAAA,QACT;AAAA,QACA,eAAe;AAAA,QACf;AAAA,MACF;AAAA,MACA,aAAU;AAAA,MACT,GAAG;AAAA,MAEH;AAAA,eACC;AAAA,UAAC;AAAA;AAAA,YACC,WAAU;AAAA,YACV,aAAU;AAAA,YACV,eAAY;AAAA,YAEX;AAAA;AAAA,QACH,IACE;AAAA,QACJ,oBAAC,UAAK,WAAU,2BAA2B,UAAS;AAAA;AAAA;AAAA,EACtD;AAEJ;AACA,mBAAmB,cAAc;AAE1B,SAAS,kBAAkB,EAAE,UAAU,GAA2B;AACvE,SACE;AAAA,IAAC;AAAA;AAAA,MACC,MAAK;AAAA,MACL,oBAAiB;AAAA,MAIjB,WAAW,GAAG,kDAAkD,SAAS;AAAA,MACzE,aAAU;AAAA;AAAA,EACZ;AAEJ;AAUO,MAAM,oBAAoB,MAAM;AAAA,EACrC,CAAC,EAAE,WAAW,aAAa,UAAU,GAAG,UAAU,QAAQ,SAAS,WAAW,SAAS,IAAI,MAAM,GAAG,MAAM,GAAG,QAAQ;AACnH,UAAM,YAAY,GAAG,KAAK,IAAI,GAAG,OAAO,IAAI,GAAG;AAC/C,WACE;AAAA,MAAC;AAAA;AAAA,QACC;AAAA,QACA,iBAAiB,CAAC;AAAA,QAClB,gCAA8B;AAAA,QAC9B,YAAU;AAAA,QACV,MAAK;AAAA,QACL,kBAAe;AAAA,QACf,aAAU;AAAA,QACV,oBAAkB,eAAe;AAAA,QACjC,aAAW;AAAA,QACX;AAAA,QACA,WAAW;AAAA;AAAA;AAAA;AAAA,UAIT;AAAA;AAAA;AAAA;AAAA;AAAA,UAKA;AAAA,UACA;AAAA,UACA;AAAA,UACA;AAAA,UACA;AAAA,UACA;AAAA,UACA;AAAA;AAAA;AAAA,UAGA;AAAA,UACA;AAAA,UACA;AAAA,UACA;AAAA,UACA;AAAA,UACA;AAAA,UACA;AAAA,UACA;AAAA,UACA;AAAA,UACA;AAAA,UACA;AAAA,UACA;AAAA,UACA;AAAA,QACF;AAAA,QACA,OAAO,EAAE,UAAU;AAAA,QACnB;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACC,GAAG;AAAA;AAAA,IACN;AAAA,EAEJ;AACF;AACA,kBAAkB,cAAc;AAuChC,SAAS,kBAAkB,MAAoC;AAC7D,UAAQ,KAAK,MAAM;AAAA,IACjB,KAAK;AACH,aAAO,oBAAC,uBAAuB,KAAK,GAAK;AAAA,IAC3C,KAAK;AACH,aACE;AAAA,QAAC;AAAA;AAAA,UAEC,MAAM,KAAK;AAAA,UACX,WAAW,KAAK;AAAA,UAChB,cAAc,KAAK;AAAA,UACnB,QAAQ,KAAK;AAAA,UACb,SAAS,KAAK;AAAA,UACd,YAAY,KAAK;AAAA,UACjB,YAAY,KAAK;AAAA;AAAA,QAPZ,KAAK;AAAA,MAQZ;AAAA,IAEJ,KAAK;AACH,aACE;AAAA,QAAC;AAAA;AAAA,UAEC,WAAW,KAAK;AAAA,UAChB,OAAO,KAAK;AAAA,UACZ,MAAM,KAAK;AAAA;AAAA,QAHN,KAAK;AAAA,MAIZ;AAAA,IAEJ,KAAK;AACH,aACE;AAAA,QAAC;AAAA;AAAA,UAEC,MAAM,KAAK;AAAA,UACX,WAAW,KAAK;AAAA,UAChB,MAAM,KAAK;AAAA,UACX,aAAa,KAAK;AAAA;AAAA,QAJb,KAAK;AAAA,MAKZ;AAAA,IAEJ,KAAK;AACH,aACE;AAAA,QAAC;AAAA;AAAA,UAEC,WAAW,KAAK;AAAA,UAChB,aAAa,KAAK;AAAA;AAAA,QAFb,KAAK;AAAA,MAGZ;AAAA,EAEN;AACF;AAEA,SAAS,qBAAqB,OAAqC;AACjE,MAAI,MAAM,MAAM;AAChB,SAAO,MAAM,KAAK,MAAM,MAAM,CAAC,EAAE,SAAS,UAAW,QAAO;AAC5D,SAAO,MAAM,MAAM,GAAG,GAAG;AAC3B;AAEA,SAAS,oBAAoB,OAAqC;AAChE,MAAI,QAAQ;AACZ,SAAO,QAAQ,MAAM,UAAU,MAAM,KAAK,EAAE,SAAS,UAAW,UAAS;AACzE,SAAO,MAAM,MAAM,KAAK;AAC1B;AAEA,SAAS,sBAAsB;AAAA,EAC7B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAUkB;AAChB,QAAM,SAAS,MAAM,YAAY,MAAM;AACrC,qBAAiB;AAAA,MACf,MAAM;AAAA,MACN,OAAO,OAAO;AAAA,MACd,aAAa;AAAA,MACb,WAAW,CAAC,QAAQ,KAAK,cAAc,GAAG;AAAA,IAC5C,CAAC;AAAA,EACH,GAAG,CAAC,kBAAkB,MAAM,OAAO,aAAa,CAAC;AAEjD,QAAM,UAAU,MAAM,YAAY,MAAM;AACtC,QAAI,eAAe;AACjB,oBAAc;AACd;AAAA,IACF;AACA,qBAAiB;AAAA,MACf,MAAM;AAAA,MACN,OAAO,OAAO;AAAA,MACd,aAAa;AAAA,MACb,WAAW,CAAC,QAAQ,KAAK,eAAe,GAAG;AAAA,IAC7C,CAAC;AAAA,EACH,GAAG,CAAC,kBAAkB,MAAM,OAAO,gBAAgB,aAAa,CAAC;AAEjE,QAAM,gBAAgB,MAAM,YAAY,MAAM;AAC5C,UAAM,OAAO;AAKb,SAAK,cAAc,IAAI;AAAA,EACzB,GAAG,CAAC,IAAI,CAAC;AAET,QAAM,oBAAoB,MAAM,YAAY,MAAM;AAChD,UAAM,OAAO;AAGb,SAAK,cAAc,IAAI;AAAA,EACzB,GAAG,CAAC,IAAI,CAAC;AAET,QAAM,qBAAqB,MAAM,YAAY,MAAM;AACjD,UAAM,gBAAgB,OAAO,WAAW,cAAc,OAAO,aAAa,GAAG,SAAS,KAAK,KAAK;AAChG,QAAI,CAAC,eAAe;AAClB,WAAK,cAAc,qBAAqB;AACxC;AAAA,IACF;AACA,SAAK,cAAc,SAAS,aAAa,SAAS;AAAA,EACpD,GAAG,CAAC,IAAI,CAAC;AAET,QAAM,cAAc,YAAY,UAAU,YAAY;AACtD,QAAM,eAAe,YAAY;AACjC,QAAM,YAAY,YAAY;AAC9B,QAAM,aAAa,YAAY;AAC/B,QAAM,SAAS,YAAY;AAC3B,QAAM,mBAAmB,YAAY;AACrC,QAAM,iBAAiB,YAAY;AACnC,QAAM,gBAAgB,YAAY;AAClC,QAAM,cAAc,YAAY,UAAU,YAAY,cAAc,YAAY;AAChF,QAAM,YAAY,YAAY;AAM9B,QAAM,cAAc,YAAY;AAChC,QAAM,iBAAiB,cAAc,MAAM,KAAK,cAAc,aAAa;AAC3E,QAAM,cAAc,YAAY;AAChC,QAAM,iBAAiB,cAAc,MAAM,KAAK,cAAc,GAAG;AACjE,QAAM,WAAW,YAAY,UAAU,YAAY,cAAc,YAAY;AAC7E,QAAM,YAAY,YAAY;AAC9B,QAAM,WAAW,YAAY;AAC7B,QAAM,iBAAiB,YAAY,UAAW,iBAAiB;AAE/D,QAAM,wBACJ,qBAAC,SAAI,WAAU,wCAAuC,MAAK,QACzD;AAAA,wBAAC,SAAI,WAAU,iFAAgF,sBAAQ;AAAA,IACvG,qBAAC,SAAI,WAAU,sEACb;AAAA,0BAAC,UAAM,iBAAO,MAAK;AAAA,MACnB,oBAAC,SAAI,WAAU,gDAA+C,sBAAG;AAAA,OACnE;AAAA,IACA,qBAAC,SAAI,WAAU,sEACb;AAAA,0BAAC,UAAM,iBAAO,QAAO;AAAA,MACrB,oBAAC,SAAI,WAAU,gDAA+C,sBAAG;AAAA,OACnE;AAAA,IACA,qBAAC,SAAI,WAAU,sEACb;AAAA,0BAAC,UAAM,iBAAO,WAAU;AAAA,MACxB,oBAAC,SAAI,WAAU,gDAA+C,sBAAG;AAAA,OACnE;AAAA,KACF;AAGF,QAAM,cACJ,qBAAC,SAAI,WAAU,yBAAwB,MAAK,QAC1C;AAAA;AAAA,MAAC;AAAA;AAAA,QACC,MAAK;AAAA,QACL,MAAK;AAAA,QACL,WAAU;AAAA,QACV,aAAa,CAAC,MAAM,EAAE,eAAe;AAAA,QACrC,SAAS,MAAM,KAAK,eAAe,KAAK;AAAA,QAEvC,iBAAO;AAAA;AAAA,IACV;AAAA,IACA;AAAA,MAAC;AAAA;AAAA,QACC,MAAK;AAAA,QACL,MAAK;AAAA,QACL,WAAU;AAAA,QACV,aAAa,CAAC,MAAM,EAAE,eAAe;AAAA,QACrC,SAAS,MAAM,KAAK,eAAe,MAAM;AAAA,QAExC,iBAAO;AAAA;AAAA,IACV;AAAA,IACA;AAAA,MAAC;AAAA;AAAA,QACC,MAAK;AAAA,QACL,MAAK;AAAA,QACL,WAAU;AAAA,QACV,aAAa,CAAC,MAAM,EAAE,eAAe;AAAA,QACrC,SAAS,MAAM,KAAK,eAAe,MAAM;AAAA,QAExC,iBAAO;AAAA;AAAA,IACV;AAAA,IACA;AAAA,MAAC;AAAA;AAAA,QACC,MAAK;AAAA,QACL,MAAK;AAAA,QACL,WAAU;AAAA,QACV,aAAa,CAAC,MAAM,EAAE,eAAe;AAAA,QACrC,SAAS,MAAM,KAAK,eAAe,MAAM;AAAA,QAExC,iBAAO;AAAA;AAAA,IACV;AAAA,KACF;AAGF,QAAM,gBAAwB,MAAM;AAClC,YAAQ,UAAU,SAAS;AAAA,MACzB,KAAK;AACH,eAAO,OAAO;AAAA,MAChB,KAAK;AACH,eAAO,OAAO;AAAA,MAChB,KAAK;AACH,eAAO,OAAO;AAAA,MAChB;AACE,eAAO,OAAO;AAAA,IAClB;AAAA,EACF,GAAG;AAEH,QAAM,eACJ,oBAAC,SAAI,WAAU,yBAAwB,MAAK,QACzC,4BAAkB,IAAI,CAAC,SACtB;AAAA,IAAC;AAAA;AAAA,MAEC,MAAK;AAAA,MACL,MAAK;AAAA,MACL,WAAU;AAAA,MACV,aAAa,CAAC,MAAM,EAAE,eAAe;AAAA,MACrC,SAAS,MAAM;AAGb,aAAK,cAAc;AACnB,cAAM,KAAK,SAAS,MAAM,EAAE;AAC5B,YAAI,KAAK,GAAG;AACV,eAAK,cAAc,0BAA0B,EAAE,OAAO,OAAO,aAAa,GAAG,SAAS,KAAK,EAAE,SAAS;AAAA,QACxG;AAAA,MACF;AAAA,MAEC;AAAA;AAAA,IAfI;AAAA,EAgBP,CACD,GACH;AAGF,QAAM,YACJ,oBAAC,SAAI,WAAU,yBAAwB,MAAK,QACxC,WAAC,QAAQ,UAAU,SAAS,SAAS,EAAwB,IAAI,CAAC,WAAW;AAC7E,UAAM,OAAO,YAAY,MAAM;AAC/B,UAAM,UAAU,WAAW,YAAY,gBAAgB,UAAU,OAAO,CAAC,EAAE,YAAY,CAAC,GAAG,OAAO,MAAM,CAAC,CAAC;AAC1G,UAAM,QAAQ,WAAW,SACrB,OAAO,YACP,WAAW,WACT,OAAO,cACP,WAAW,UACT,OAAO,aACP,OAAO;AACf,WACE;AAAA,MAAC;AAAA;AAAA,QAEC,MAAK;AAAA,QACL,MAAK;AAAA,QACL,WAAU;AAAA,QACV,aAAa,CAAC,MAAM,EAAE,eAAe;AAAA,QACrC,SAAS,MAAM,KAAK,OAAO;AAAA,QAE3B;AAAA,8BAAC,QAAK,WAAU,UAAS,eAAY,QAAO;AAAA,UAC5C,oBAAC,UAAM,iBAAM;AAAA;AAAA;AAAA,MARR;AAAA,IASP;AAAA,EAEJ,CAAC,GACH;AAGF,QAAM,kBAAkB,YAAY,UAAU,KAAK;AAEnD,MAAI,YAAY,SAAU,QAAO,CAAC;AAElC,QAAM,QAAuB,CAAC;AAC9B,MAAI,aAAa;AACf,UAAM,KAAK,EAAE,MAAM,gBAAgB,KAAK,WAAW,WAAW,OAAO,SAAS,OAAO,cAAc,MAAM,YAAY,CAAC;AACtH,UAAM,KAAK,EAAE,MAAM,WAAW,KAAK,YAAY,CAAC;AAAA,EAClD;AACA,MAAI,cAAc;AAChB,UAAM,KAAK,EAAE,MAAM,gBAAgB,KAAK,YAAY,WAAW,OAAO,UAAU,OAAO,QAAQ,MAAM,aAAa,CAAC;AACnH,UAAM,KAAK,EAAE,MAAM,WAAW,KAAK,aAAa,CAAC;AAAA,EACnD;AACA,MAAI,WAAW;AACb,UAAM,KAAK,EAAE,MAAM,eAAe,KAAK,SAAS,WAAW,OAAO,OAAO,aAAa,OAAO,OAAO,CAAC;AACrG,UAAM,KAAK,EAAE,MAAM,WAAW,KAAK,UAAU,CAAC;AAAA,EAChD;AACA,QAAM,KAAK,EAAE,MAAM,cAAc,KAAK,QAAQ,MAAM,oBAAC,QAAK,GAAI,SAAS,QAAQ,WAAW,OAAO,MAAM,cAAc,OAAO,MAAM,QAAQ,UAAU,KAAK,CAAC;AAC1J,QAAM,KAAK,EAAE,MAAM,cAAc,KAAK,UAAU,MAAM,oBAAC,UAAO,GAAI,SAAS,UAAU,WAAW,OAAO,QAAQ,cAAc,OAAO,QAAQ,QAAQ,UAAU,OAAO,CAAC;AACtK,QAAM,KAAK,EAAE,MAAM,cAAc,KAAK,aAAa,MAAM,oBAAC,aAAU,GAAI,SAAS,aAAa,WAAW,OAAO,WAAW,cAAc,OAAO,WAAW,QAAQ,UAAU,UAAU,CAAC;AACxL,MAAI,YAAY;AACd,UAAM,KAAK,EAAE,MAAM,cAAc,KAAK,UAAU,MAAM,oBAAC,iBAAc,GAAI,SAAS,iBAAiB,WAAW,OAAO,eAAe,cAAc,OAAO,eAAe,QAAQ,UAAU,cAAc,CAAC;AAAA,EAC3M;AACA,MAAI,WAAW;AACb,UAAM,KAAK,EAAE,MAAM,WAAW,KAAK,UAAU,CAAC;AAC9C,UAAM,KAAK,EAAE,MAAM,cAAc,KAAK,MAAM,MAAM,oBAAC,QAAK,GAAI,SAAS,uBAAuB,WAAW,OAAO,eAAe,cAAc,OAAO,eAAe,QAAQ,UAAU,cAAc,CAAC;AAClM,QAAI,aAAa;AACf,YAAM,KAAK,EAAE,MAAM,cAAc,KAAK,MAAM,MAAM,oBAAC,eAAY,GAAI,SAAS,qBAAqB,WAAW,OAAO,aAAa,cAAc,OAAO,aAAa,QAAQ,UAAU,YAAY,CAAC;AAAA,IACnM;AAAA,EACF;AACA,MAAI,UAAU,kBAAkB;AAC9B,UAAM,KAAK,EAAE,MAAM,WAAW,KAAK,cAAc,CAAC;AAClD,QAAI,QAAQ;AACV,YAAM,KAAK,EAAE,MAAM,cAAc,KAAK,MAAM,MAAM,oBAAC,SAAM,GAAI,WAAW,OAAO,gBAAgB,cAAc,OAAO,gBAAgB,YAAY,MAAM,KAAK,sBAAsB,EAAE,CAAC;AAAA,IACtL;AACA,QAAI,kBAAkB;AACpB,YAAM,KAAK,EAAE,MAAM,cAAc,KAAK,SAAS,MAAM,oBAAC,SAAM,GAAI,WAAW,OAAO,YAAY,cAAc,OAAO,YAAY,QAAQ,UAAU,YAAY,YAAY,MAAM,KAAK,eAAe,UAAU,aAAa,QAAQ,cAAc,EAAE,CAAC;AACnP,YAAM,KAAK,EAAE,MAAM,cAAc,KAAK,cAAc,MAAM,oBAAC,QAAK,GAAI,WAAW,OAAO,YAAY,cAAc,OAAO,YAAY,YAAY,mBAAmB,CAAC;AACnK,YAAM,KAAK,EAAE,MAAM,cAAc,KAAK,aAAa,MAAM,oBAAC,YAAS,GAAI,WAAW,OAAO,WAAW,cAAc,OAAO,WAAW,QAAQ,UAAU,MAAM,YAAY,MAAM,KAAK,eAAe,UAAU,OAAO,QAAQ,OAAO,EAAE,CAAC;AAAA,IACvO;AAAA,EACF;AACA,MAAI,gBAAgB;AAClB,UAAM,KAAK,EAAE,MAAM,WAAW,KAAK,UAAU,CAAC;AAC9C,UAAM,KAAK,EAAE,MAAM,cAAc,KAAK,SAAS,MAAM,oBAAC,aAAU,GAAI,WAAW,OAAO,OAAO,cAAc,OAAO,OAAO,YAAY,QAAQ,CAAC;AAC9I,UAAM,KAAK,EAAE,MAAM,cAAc,KAAK,SAAS,MAAM,oBAAC,aAAU,GAAI,WAAW,OAAO,OAAO,cAAc,OAAO,OAAO,YAAY,cAAc,CAAC;AAAA,EACtJ;AACA,MAAI,eAAe;AACjB,UAAM,KAAK,EAAE,MAAM,cAAc,KAAK,aAAa,MAAM,oBAAC,cAAW,GAAI,WAAW,OAAO,WAAW,cAAc,OAAO,WAAW,YAAY,kBAAkB,CAAC;AAAA,EACvK;AACA,MAAI,WAAW;AACb,UAAM,KAAK,EAAE,MAAM,WAAW,KAAK,UAAU,CAAC;AAC9C,UAAM,KAAK,EAAE,MAAM,kBAAkB,KAAK,SAAS,MAAM,oBAAC,mBAAgB,GAAI,WAAW,OAAO,OAAO,MAAM,UAAU,CAAC;AAAA,EAC1H;AACA,MAAI,YAAY,eAAe,aAAa;AAC1C,UAAM,KAAK,EAAE,MAAM,WAAW,KAAK,YAAY,CAAC;AAAA,EAClD;AACA,MAAI,aAAa;AACf,UAAM,KAAK,EAAE,MAAM,cAAc,KAAK,WAAW,MAAM,oBAAC,iBAAc,GAAI,WAAW,OAAO,SAAS,cAAc,OAAO,SAAS,YAAY,eAAe,CAAC;AAAA,EACjK;AACA,MAAI,UAAU;AACZ,UAAM,KAAK,EAAE,MAAM,cAAc,KAAK,QAAQ,MAAM,oBAAC,QAAK,GAAI,WAAW,OAAO,MAAM,cAAc,OAAO,MAAM,YAAY,OAAO,CAAC;AAAA,EACvI;AACA,MAAI,aAAa;AACf,UAAM,KAAK,EAAE,MAAM,cAAc,KAAK,WAAW,MAAM,oBAAC,UAAO,GAAI,WAAW,OAAO,SAAS,cAAc,OAAO,SAAS,YAAY,eAAe,CAAC;AAAA,EAC1J;AACA,MAAI,YAAY,gBAAgB;AAC9B,UAAM,KAAK,EAAE,MAAM,WAAW,KAAK,aAAa,CAAC;AACjD,QAAI,UAAU;AACZ,YAAM,KAAK,EAAE,MAAM,kBAAkB,KAAK,QAAQ,MAAM,oBAAC,cAAW,GAAI,WAAW,OAAO,MAAM,MAAM,uBAAuB,aAAa,MAAM,CAAC;AAAA,IACnJ;AACA,QAAI,kBAAkB,cAAc;AAClC,YAAM,KAAK,EAAE,MAAM,cAAc,KAAK,cAAc,MAAM,oBAAC,aAAU,GAAI,WAAW,OAAO,YAAY,cAAc,OAAO,YAAY,YAAY,aAAa,CAAC;AAAA,IACpK;AAAA,EACF;AAEA,SAAO,qBAAqB,KAAK;AACnC;AAEA,SAAS,sBAAsB;AAAA,EAC7B;AAAA,EACA;AAAA,EACA;AACF,GAIG;AACD,QAAM,aAAa,MAAM,OAAuB,IAAI;AACpD,QAAM,eAAe,MAAM,OAAuB,IAAI;AACtD,QAAM,CAAC,aAAa,cAAc,IAAI,MAAM,SAAwB,IAAI;AAExE,QAAM,gBAAgB,MAAM;AAC1B,QAAI,OAAO,WAAW,YAAa;AACnC,QAAI,OAAO,mBAAmB,YAAa;AAC3C,UAAM,YAAY,aAAa;AAC/B,UAAM,YAAY,WAAW;AAC7B,QAAI,CAAC,aAAa,CAAC,UAAW;AAE9B,UAAM,UAAU,MAAM;AACpB,YAAM,iBAAiB,UAAU;AACjC,UAAI,mBAAmB,EAAG;AAE1B,YAAM,SAAS,MAAM,KAAK,UAAU,QAAQ;AAC5C,UAAI,OAAO,WAAW,EAAG;AAEzB,YAAM,SAAS,OAAO,OAAO,SAAS,CAAC;AACvC,YAAM,UAAU,OAAO,MAAM,GAAG,EAAE;AAClC,YAAM,eAAe,OAAO,cAAc;AAC1C,YAAM,mBAAmB;AAEzB,UAAI,aAAa;AACjB,iBAAW,MAAM,QAAS,eAAc,GAAG,cAAc;AAEzD,UAAI,aAAa,oBAAoB,iBAAiB,GAAG;AACvD,uBAAe,IAAI;AACnB;AAAA,MACF;AAEA,UAAI,cAAc;AAClB,eAAS,IAAI,GAAG,IAAI,QAAQ,QAAQ,KAAK;AACvC,uBAAe,QAAQ,CAAC,EAAE,cAAc;AACxC,YAAI,cAAc,iBAAiB,eAAe,kBAAkB;AAClE,yBAAe,CAAC;AAChB;AAAA,QACF;AAAA,MACF;AACA,qBAAe,IAAI;AAAA,IACrB;AAEA,YAAQ;AACR,UAAM,KAAK,IAAI,eAAe,OAAO;AACrC,OAAG,QAAQ,SAAS;AACpB,WAAO,MAAM,GAAG,WAAW;AAAA,EAC7B,GAAG,CAAC,KAAK,CAAC;AAEV,QAAM,eAAe,gBAAgB,OAAO,QAAQ,qBAAqB,MAAM,MAAM,GAAG,WAAW,CAAC;AACpG,QAAM,cAAc,gBAAgB,OAAO,CAAC,IAAI,oBAAoB,MAAM,MAAM,WAAW,CAAC;AAE5F,QAAM,cAAc,YAAY,SAAS;AACzC,QAAM,iBAAiB,eAAe,CAAC,CAAC;AAExC,SACE,qBAAC,SAAI,WAAU,mBACb;AAAA;AAAA,MAAC;AAAA;AAAA,QACC,KAAK;AAAA,QACL,eAAY;AAAA,QACZ,WAAU;AAAA,QAET;AAAA,gBAAM,IAAI,iBAAiB;AAAA,UAC5B,oBAAC,4BAAyB,MAAM,oBAAC,gBAAa,GAAI,WAAW,OAAO,MAAM,aAAa,OAAO,MAAM,oBAAC,SAAI,GAAI;AAAA;AAAA;AAAA,IAC/G;AAAA,IACA,qBAAC,qBAAkB,KAAK,cAAc,WAAU,sCAC7C;AAAA,mBAAa,IAAI,iBAAiB;AAAA,MAClC,iBACC;AAAA,QAAC;AAAA;AAAA,UACC,MAAM,oBAAC,gBAAa;AAAA,UACpB,WAAW,OAAO;AAAA,UAClB,aAAa;AAAA,UACb,MACE,qBAAC,SAAI,WAAU,kEACZ;AAAA,uBACC,iCACE;AAAA,kCAAC,SAAI,WAAU,UAAU,oBAAS;AAAA,cACjC,YAAY,SAAS,IAAI,oBAAC,SAAI,WAAU,8BAA6B,IAAK;AAAA,eAC7E,IACE;AAAA,YACH,YAAY,IAAI,iBAAiB;AAAA,aACpC;AAAA;AAAA,MAEJ,IACE;AAAA,OACN;AAAA,KACF;AAEJ;",
|
|
4
|
+
"sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport {\n AlignCenter,\n AlignJustify,\n AlignLeft,\n AlignRight,\n AtSign,\n Bold,\n ChevronDown,\n Code,\n FileCode,\n HelpCircle,\n Image as ImageIcon,\n Italic,\n Link,\n List,\n ListChecks,\n ListOrdered,\n Maximize2,\n MessageCircle,\n Minus,\n MoreVertical,\n Quote,\n Strikethrough,\n Table as TableIcon,\n Underline,\n} from 'lucide-react'\nimport { cva, type VariantProps } from 'class-variance-authority'\n\nimport { cn } from '@open-mercato/shared/lib/utils'\nimport {\n sanitizeHtmlRichText,\n sanitizeRichTextHref,\n sanitizeRichTextPasteContent,\n} from '@open-mercato/ui/backend/utils/richTextSanitizer'\nimport { Popover, PopoverContent, PopoverTrigger } from './popover'\nimport {\n Tooltip,\n TooltipContent,\n TooltipProvider,\n TooltipTrigger,\n} from './tooltip'\nimport {\n Dialog,\n DialogContent,\n DialogFooter,\n DialogHeader,\n DialogTitle,\n} from './dialog'\nimport { Input } from './input'\nimport { Button } from './button'\n\n// Figma `Rich Editor Colors [1.1]` palette (166331:4100) \u2014 12 tokens.\n// Order matches the Figma frame: gray / black / white / blue / orange / red /\n// green / yellow / purple / sky / pink / teal.\nexport const RICH_EDITOR_COLOR_PALETTE = {\n gray: '#7b7b7b',\n black: '#171717',\n white: '#ffffff',\n blue: '#6366f1',\n orange: '#f59e0b',\n red: '#ef4444',\n green: '#22c55e',\n yellow: '#f6b51e',\n purple: '#7d52f4',\n sky: '#47c2ff',\n pink: '#fb4ba3',\n teal: '#22d3bb',\n} as const\n\nexport type RichEditorColorKey = keyof typeof RICH_EDITOR_COLOR_PALETTE\nconst COLOR_KEYS: RichEditorColorKey[] = [\n 'gray', 'black', 'white', 'blue', 'orange', 'red', 'green', 'yellow', 'purple', 'sky', 'pink', 'teal',\n]\n\nconst COLOR_LABELS_EN: Record<RichEditorColorKey, string> = {\n gray: 'Gray',\n black: 'Black',\n white: 'White',\n blue: 'Blue',\n orange: 'Orange',\n red: 'Red',\n green: 'Green',\n yellow: 'Yellow',\n purple: 'Purple',\n sky: 'Sky',\n pink: 'Pink',\n teal: 'Teal',\n}\n\nexport type RichEditorVariant = 'full' | 'standard' | 'basic' | 'minimal' | 'custom'\n\nexport type RichEditorAlign = 'left' | 'center' | 'right' | 'justify'\nexport type RichEditorFontSize = '12px' | '14px' | '16px' | '18px' | '20px' | '24px'\n\nconst ALIGN_ICONS: Record<RichEditorAlign, React.ComponentType<{ className?: string; 'aria-hidden'?: 'true' }>> = {\n left: AlignLeft,\n center: AlignCenter,\n right: AlignRight,\n justify: AlignJustify,\n}\n\nconst FONT_SIZE_OPTIONS: RichEditorFontSize[] = ['12px', '14px', '16px', '18px', '20px', '24px']\n\ntype SelectionState = {\n bold: boolean\n italic: boolean\n underline: boolean\n strikethrough: boolean\n unorderedList: boolean\n orderedList: boolean\n blockquote: boolean\n code: boolean\n heading: '' | 'h1' | 'h2' | 'h3'\n align: RichEditorAlign\n}\n\nconst EMPTY_SELECTION_STATE: SelectionState = {\n bold: false,\n italic: false,\n underline: false,\n strikethrough: false,\n unorderedList: false,\n orderedList: false,\n blockquote: false,\n code: false,\n heading: '',\n align: 'left',\n}\n\ntype RichEditorContextValue = {\n exec: (command: string, arg?: string) => void\n selection: SelectionState\n disabled: boolean\n /**\n * Open a DS-styled URL prompt (replaces the native `window.prompt`\n * popup). Pre-saves the current selection so the caller's `onConfirm`\n * handler can re-target the same range when running `execCommand`.\n */\n requestUrlPrompt: (request: {\n kind: 'link' | 'image'\n title: string\n placeholder?: string\n onConfirm: (url: string) => void\n }) => void\n}\n\nconst RichEditorContext = React.createContext<RichEditorContextValue | null>(null)\n\nfunction useRichEditorContext(component: string): RichEditorContextValue {\n const ctx = React.useContext(RichEditorContext)\n if (!ctx) {\n throw new Error(`${component} must be rendered inside <RichEditor>`)\n }\n return ctx\n}\n\nexport type RichEditorLabels = {\n bold: string\n italic: string\n underline: string\n strikethrough: string\n unorderedList: string\n orderedList: string\n checklist: string\n blockquote: string\n code: string\n inlineCode: string\n codeBlock: string\n horizontalRule: string\n image: string\n imageUrlPrompt: string\n table: string\n heading: string\n heading1: string\n heading2: string\n heading3: string\n paragraph: string\n link: string\n linkUrlPrompt: string\n color: string\n textColor: string\n fontSize: string\n align: string\n alignLeft: string\n alignCenter: string\n alignRight: string\n alignJustify: string\n comment: string\n mention: string\n more: string\n help: string\n fullscreen: string\n placeholder: string\n colors: Record<RichEditorColorKey, string>\n}\n\nconst DEFAULT_LABELS: RichEditorLabels = {\n bold: 'Bold',\n italic: 'Italic',\n underline: 'Underline',\n strikethrough: 'Strikethrough',\n unorderedList: 'Bullet list',\n orderedList: 'Numbered list',\n checklist: 'Checklist',\n blockquote: 'Quote',\n code: 'Code',\n inlineCode: 'Inline code',\n codeBlock: 'Code block',\n horizontalRule: 'Horizontal rule',\n image: 'Image',\n imageUrlPrompt: 'Enter image URL',\n table: 'Table',\n heading: 'Header',\n heading1: 'Heading 1',\n heading2: 'Heading 2',\n heading3: 'Heading 3',\n paragraph: 'Paragraph',\n link: 'Link',\n linkUrlPrompt: 'Enter URL',\n color: 'Text color',\n textColor: 'Color',\n fontSize: 'Font size',\n align: 'Align',\n alignLeft: 'Align left',\n alignCenter: 'Align center',\n alignRight: 'Align right',\n alignJustify: 'Justify',\n comment: 'Add comment',\n mention: 'Mention',\n more: 'More',\n help: 'Help',\n fullscreen: 'Toggle fullscreen',\n placeholder: 'Placeholder text...',\n colors: COLOR_LABELS_EN,\n}\n\nexport type RichEditorProps = {\n /** HTML string value (sanitized by `sanitizeHtmlRichText`). */\n value?: string\n /** Called with sanitized HTML on blur. */\n onChange: (html: string) => void\n /**\n * Toolbar preset:\n * - `'full'` \u2014 Figma 166331:4006 reference: heading + font size + color + B/I/U/S + lists + align + comment + link + mention + more\n * - `'standard'` \u2014 heading + bold/italic/underline + lists + link (CrudForm `editor='html'` default)\n * - `'basic'` \u2014 bold/italic/underline + bullet list + link\n * - `'minimal'` \u2014 bold/italic/underline\n * - `'custom'` \u2014 render `<RichEditorToolbar>{...}</RichEditorToolbar>` + `<RichEditorContent />` children manually\n */\n variant?: RichEditorVariant\n placeholder?: string\n minRows?: number\n disabled?: boolean\n className?: string\n contentClassName?: string\n labels?: Partial<RichEditorLabels>\n /** Optional handler for the comment button (only rendered in `full` variant). */\n onComment?: () => void\n /** Optional handler for the mention `@` button. */\n onMention?: () => void\n /** Optional menu rendered inside the More `\u22EE` dropdown popover. */\n moreMenu?: React.ReactNode\n /**\n * Optional handler for the fullscreen toggle. When provided, the\n * `full` variant renders the fullscreen icon button as the trailing\n * action \u2014 the caller is responsible for switching the editor into a\n * full-window layout (modal / portal / dialog).\n */\n onFullscreen?: () => void\n /**\n * Optional handler for the image insert button. When provided,\n * overrides the default `window.prompt(labels.imageUrlPrompt)` URL\n * picker so consumers can wire a file upload pipeline instead.\n */\n onImageInsert?: () => void\n /**\n * Optional max length for the rich text content. When provided, renders a\n * `<currentLength>/<maxLength>` counter in the bottom-right corner of the\n * content card (Figma 166331:6589 reference). Counts the plaintext length\n * of the sanitized HTML so users see a meaningful character budget.\n */\n maxLength?: number\n children?: React.ReactNode\n id?: string\n name?: string\n 'aria-invalid'?: boolean | 'true' | 'false'\n}\n\nfunction deriveSelectionState(): SelectionState {\n if (typeof document === 'undefined' || !document.queryCommandState) {\n return EMPTY_SELECTION_STATE\n }\n try {\n const block = document.queryCommandValue?.('formatBlock') ?? ''\n const lower = String(block).toLowerCase().replace(/[<>]/g, '')\n const heading: SelectionState['heading'] =\n lower === 'h1' || lower === 'h2' || lower === 'h3' ? lower : ''\n const align: RichEditorAlign = document.queryCommandState('justifyCenter')\n ? 'center'\n : document.queryCommandState('justifyRight')\n ? 'right'\n : document.queryCommandState('justifyFull')\n ? 'justify'\n : 'left'\n return {\n bold: document.queryCommandState('bold'),\n italic: document.queryCommandState('italic'),\n underline: document.queryCommandState('underline'),\n strikethrough: document.queryCommandState('strikeThrough'),\n unorderedList: document.queryCommandState('insertUnorderedList'),\n orderedList: document.queryCommandState('insertOrderedList'),\n blockquote: lower === 'blockquote',\n code: lower === 'pre',\n heading,\n align,\n }\n } catch {\n return EMPTY_SELECTION_STATE\n }\n}\n\nexport const RichEditor = React.memo(function RichEditor({\n value = '',\n onChange,\n variant = 'standard',\n placeholder,\n minRows = 4,\n disabled = false,\n className,\n contentClassName,\n labels: labelsProp,\n onComment,\n onMention,\n moreMenu,\n onFullscreen,\n onImageInsert,\n maxLength,\n children,\n id,\n name,\n 'aria-invalid': ariaInvalid,\n}: RichEditorProps) {\n const labels = React.useMemo<RichEditorLabels>(\n () => ({\n ...DEFAULT_LABELS,\n ...(labelsProp ?? {}),\n colors: { ...DEFAULT_LABELS.colors, ...(labelsProp?.colors ?? {}) },\n }),\n [labelsProp],\n )\n\n const editorRef = React.useRef<HTMLDivElement | null>(null)\n const applyingExternal = React.useRef(false)\n const typingRef = React.useRef(false)\n const savedSelectionRef = React.useRef<Range | null>(null)\n const [selection, setSelection] = React.useState<SelectionState>(EMPTY_SELECTION_STATE)\n const [promptDialog, setPromptDialog] = React.useState<{\n kind: 'link' | 'image'\n title: string\n placeholder?: string\n value: string\n onConfirm: (url: string) => void\n } | null>(null)\n\n React.useEffect(() => {\n const el = editorRef.current\n if (!el) return\n const sanitizedValue = sanitizeHtmlRichText(value)\n if (!typingRef.current && el.innerHTML !== sanitizedValue) {\n applyingExternal.current = true\n el.innerHTML = sanitizedValue\n requestAnimationFrame(() => {\n applyingExternal.current = false\n })\n }\n }, [value])\n\n const refreshSelectionState = React.useCallback(() => {\n setSelection(deriveSelectionState())\n }, [])\n\n const exec = React.useCallback(\n (command: string, arg?: string) => {\n const el = editorRef.current\n if (!el || disabled) return\n el.focus()\n try {\n // Tell the browser to emit modern <span style=\"\u2026\"> output (forecolor,\n // backColor, fontSize, etc.) instead of the deprecated <font> tag \u2014\n // the sanitizer drops <font> outright, so without this colour /\n // font-size changes silently disappear on the next blur.\n document.execCommand('styleWithCSS', false, 'true' as never)\n } catch {\n // ignore \u2014 older browsers without styleWithCSS support\n }\n try {\n document.execCommand(command, false, arg)\n } catch {\n // ignore unsupported commands\n }\n refreshSelectionState()\n },\n [disabled, refreshSelectionState],\n )\n\n const requestUrlPrompt = React.useCallback<RichEditorContextValue['requestUrlPrompt']>(\n (request) => {\n // Snapshot the current selection so we can restore the same range\n // after the Dialog steals focus.\n if (typeof window !== 'undefined') {\n const sel = window.getSelection()\n if (sel && sel.rangeCount > 0 && editorRef.current?.contains(sel.anchorNode)) {\n savedSelectionRef.current = sel.getRangeAt(0).cloneRange()\n } else {\n savedSelectionRef.current = null\n }\n }\n setPromptDialog({ ...request, value: '' })\n },\n [],\n )\n\n const ctx = React.useMemo<RichEditorContextValue>(\n () => ({ exec, selection, disabled, requestUrlPrompt }),\n [exec, selection, disabled, requestUrlPrompt],\n )\n\n React.useEffect(() => {\n if (typeof document === 'undefined') return\n const onSelectionChange = () => {\n if (!editorRef.current) return\n const sel = window.getSelection()\n if (!sel || !sel.anchorNode) return\n if (!editorRef.current.contains(sel.anchorNode)) return\n refreshSelectionState()\n }\n document.addEventListener('selectionchange', onSelectionChange)\n return () => {\n document.removeEventListener('selectionchange', onSelectionChange)\n }\n }, [refreshSelectionState])\n\n const onKeyDown = React.useCallback(\n (e: React.KeyboardEvent<HTMLDivElement>) => {\n const isMod = e.metaKey || e.ctrlKey\n if (!isMod) return\n const k = e.key.toLowerCase()\n if (k === 'b') {\n e.preventDefault()\n exec('bold')\n } else if (k === 'i') {\n e.preventDefault()\n exec('italic')\n } else if (k === 'u') {\n e.preventDefault()\n exec('underline')\n }\n },\n [exec],\n )\n\n const onPaste = React.useCallback(\n (e: React.ClipboardEvent<HTMLDivElement>) => {\n const html = e.clipboardData.getData('text/html')\n const text = e.clipboardData.getData('text/plain')\n const sanitizedPaste = sanitizeRichTextPasteContent(html, text)\n if (!sanitizedPaste) return\n e.preventDefault()\n exec(sanitizedPaste.command, sanitizedPaste.value)\n },\n [exec],\n )\n\n const onBlur = React.useCallback(() => {\n const el = editorRef.current\n if (!el) return\n typingRef.current = false\n const sanitized = sanitizeHtmlRichText(el.innerHTML)\n if (el.innerHTML !== sanitized) {\n applyingExternal.current = true\n el.innerHTML = sanitized\n requestAnimationFrame(() => {\n applyingExternal.current = false\n })\n }\n onChange(sanitized)\n }, [onChange])\n\n const presetItems = usePresetToolbarItems({\n variant,\n labels,\n onComment,\n onMention,\n onFullscreen,\n onImageInsert,\n exec,\n selection,\n requestUrlPrompt,\n })\n\n const plaintextLength = React.useMemo(() => {\n if (!maxLength) return 0\n if (typeof document === 'undefined') return 0\n const tmp = document.createElement('div')\n tmp.innerHTML = sanitizeHtmlRichText(value)\n return (tmp.textContent ?? '').length\n }, [value, maxLength])\n\n return (\n <RichEditorContext.Provider value={ctx}>\n <TooltipProvider delayDuration={400}>\n <div\n className={cn('w-full space-y-2', disabled && 'opacity-60', className)}\n data-slot=\"rich-editor\"\n data-disabled={disabled ? 'true' : 'false'}\n aria-invalid={ariaInvalid}\n >\n {variant === 'custom'\n ? children\n : (\n <>\n <RichEditorAutoToolbar items={presetItems} labels={labels} moreMenu={moreMenu} />\n <div className=\"relative\">\n <RichEditorContent\n ref={editorRef}\n placeholder={placeholder ?? labels.placeholder}\n minRows={minRows}\n disabled={disabled}\n className={contentClassName}\n onKeyDown={onKeyDown}\n onPaste={onPaste}\n onBlur={onBlur}\n onInput={() => {\n if (!applyingExternal.current) typingRef.current = true\n }}\n id={id}\n name={name}\n />\n {maxLength ? (\n <span\n className={cn(\n 'pointer-events-none absolute bottom-2 right-3 select-none text-xs leading-4 text-muted-foreground',\n plaintextLength > maxLength && 'text-destructive',\n )}\n data-slot=\"rich-editor-counter\"\n aria-live=\"polite\"\n >\n {plaintextLength}/{maxLength}\n </span>\n ) : null}\n </div>\n </>\n )}\n </div>\n {promptDialog !== null ? (\n <Dialog\n open\n onOpenChange={(open) => {\n if (!open) setPromptDialog(null)\n }}\n >\n <DialogContent\n className=\"sm:max-w-md\"\n onKeyDown={(e) => {\n if (e.key === 'Enter' && promptDialog) {\n e.preventDefault()\n const url = sanitizeRichTextHref(promptDialog.value)\n const onConfirm = promptDialog.onConfirm\n setPromptDialog(null)\n if (!url) return\n const el = editorRef.current\n const range = savedSelectionRef.current\n if (el) {\n el.focus()\n if (range) {\n const sel = window.getSelection()\n sel?.removeAllRanges()\n sel?.addRange(range)\n }\n }\n onConfirm(url)\n }\n }}\n >\n <DialogHeader>\n <DialogTitle>{promptDialog?.title ?? ''}</DialogTitle>\n </DialogHeader>\n <Input\n autoFocus\n type=\"url\"\n value={promptDialog?.value ?? ''}\n placeholder={promptDialog?.placeholder ?? 'https://\u2026'}\n onChange={(e) => {\n const next = e.target.value\n setPromptDialog((prev) => (prev ? { ...prev, value: next } : prev))\n }}\n />\n <DialogFooter>\n <Button\n type=\"button\"\n variant=\"outline\"\n onClick={() => setPromptDialog(null)}\n >\n Cancel\n </Button>\n <Button\n type=\"button\"\n onClick={() => {\n if (!promptDialog) return\n const url = sanitizeRichTextHref(promptDialog.value)\n const onConfirm = promptDialog.onConfirm\n setPromptDialog(null)\n if (!url) return\n const el = editorRef.current\n const range = savedSelectionRef.current\n if (el) {\n el.focus()\n if (range) {\n const sel = window.getSelection()\n sel?.removeAllRanges()\n sel?.addRange(range)\n }\n }\n onConfirm(url)\n }}\n >\n OK\n </Button>\n </DialogFooter>\n </DialogContent>\n </Dialog>\n ) : null}\n </TooltipProvider>\n </RichEditorContext.Provider>\n )\n}, (prev, next) =>\n prev.value === next.value &&\n prev.disabled === next.disabled &&\n prev.variant === next.variant &&\n prev.maxLength === next.maxLength,\n)\nRichEditor.displayName = 'RichEditor'\n\nexport type RichEditorToolbarProps = React.HTMLAttributes<HTMLDivElement>\n\nexport const RichEditorToolbar = React.forwardRef<HTMLDivElement, RichEditorToolbarProps>(\n ({ className, children, ...props }, ref) => (\n <div\n ref={ref}\n role=\"toolbar\"\n aria-label=\"Rich text formatting\"\n className={cn(\n // Figma 166331:4006 reference: standalone card with rounded-8 + border + shadow-xs.\n // 2px outer padding, 2px gap between items.\n 'flex w-fit max-w-full flex-wrap items-center gap-0.5 overflow-x-auto rounded-lg border border-border bg-card p-0.5 shadow-xs',\n className,\n )}\n data-slot=\"rich-editor-toolbar\"\n {...props}\n >\n {children}\n </div>\n ),\n)\nRichEditorToolbar.displayName = 'RichEditorToolbar'\n\nconst richEditorItemVariants = cva(\n // Figma Rich Editor Items (166251 family): 28\u00D7h, rounded-6, bg-white default,\n // bg-weak-50 (= bg-muted token) on hover, on toggle-active (data-active=true,\n // e.g. Bold inside a bold selection), and on dropdown-open\n // (data-state=open from Radix PopoverTrigger). Text / icon colours move from\n // sub-600 (= text-muted-foreground, #5c5c5c) in the default state to\n // strong-950 (= text-foreground, #171717) in the active / open states per\n // Figma 166261:22214 / 166261:22217 reference cells.\n 'group/rich-editor-item inline-flex h-7 shrink-0 items-center justify-center rounded-md bg-transparent text-sm font-medium leading-5 text-muted-foreground transition-colors hover:bg-muted focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring/40 disabled:pointer-events-none disabled:opacity-40 data-[active=true]:bg-muted data-[active=true]:text-foreground data-[state=open]:bg-muted data-[state=open]:text-foreground',\n {\n variants: {\n type: {\n // Figma 166251:2698 \u2014 pl-[10px] pr-[4px] py-[4px], gap-[2px].\n text: 'min-w-0 gap-0.5 pl-2.5 pr-1 py-1 tracking-[-0.084px] [&_svg]:size-4',\n // Figma 166251:2700 \u2014 square icon button p-[4px].\n icon: 'aspect-square p-1 [&_svg]:size-4',\n // Figma 166251:3337 \u2014 icon + caret p-[4px] gap-[2px].\n dropdown: 'gap-0.5 p-1 [&_svg]:size-4',\n // Figma 166331:4437 \u2014 color swatch + caret pl-[8px] pr-[4px] py-[4px].\n color: 'gap-0.5 pl-2 pr-1 py-1 [&_svg]:size-4',\n },\n },\n defaultVariants: { type: 'icon' },\n },\n)\n\ntype ToolbarButtonBaseProps = Omit<React.ButtonHTMLAttributes<HTMLButtonElement>, 'type' | 'children'> &\n VariantProps<typeof richEditorItemVariants> & {\n active?: boolean\n tooltipLabel?: string\n }\n\nconst RichEditorButton = React.forwardRef<HTMLButtonElement, ToolbarButtonBaseProps & { children?: React.ReactNode }>(\n ({ className, type, active, tooltipLabel, children, onMouseDown, ...props }, ref) => {\n const button = (\n <button\n ref={ref}\n type=\"button\"\n onMouseDown={(e) => {\n e.preventDefault()\n onMouseDown?.(e)\n }}\n data-active={active ? 'true' : 'false'}\n aria-pressed={active}\n className={cn(richEditorItemVariants({ type }), className)}\n {...props}\n >\n {children}\n </button>\n )\n // Wrap in DS Tooltip when tooltipLabel is provided \u2014 matches Figma\n // 166331:4027 (dark rounded popover above the trigger).\n if (!tooltipLabel) return button\n return (\n <Tooltip>\n <TooltipTrigger asChild>{button}</TooltipTrigger>\n <TooltipContent side=\"top\" sideOffset={6}>{tooltipLabel}</TooltipContent>\n </Tooltip>\n )\n },\n)\nRichEditorButton.displayName = 'RichEditorButton'\n\nexport type RichEditorIconButtonProps = Omit<ToolbarButtonBaseProps, 'type'> & {\n icon: React.ReactNode\n ariaLabel: string\n command?: string\n commandArg?: string\n onActivate?: () => void\n}\n\nexport const RichEditorIconButton = React.forwardRef<HTMLButtonElement, RichEditorIconButtonProps>(\n ({ icon, ariaLabel, command, commandArg, onActivate, active, onClick, ...props }, ref) => {\n const { exec, disabled } = useRichEditorContext('RichEditorIconButton')\n return (\n <RichEditorButton\n ref={ref}\n type=\"icon\"\n active={active}\n aria-label={ariaLabel}\n disabled={disabled || props.disabled}\n onClick={(e) => {\n if (command) exec(command, commandArg)\n onActivate?.()\n onClick?.(e)\n }}\n {...props}\n >\n <span data-slot=\"rich-editor-item-icon\" aria-hidden=\"true\">\n {icon}\n </span>\n </RichEditorButton>\n )\n },\n)\nRichEditorIconButton.displayName = 'RichEditorIconButton'\n\nexport type RichEditorTextDropdownProps = Omit<ToolbarButtonBaseProps, 'type' | 'children'> & {\n label: React.ReactNode\n ariaLabel: string\n menu?: React.ReactNode\n onActivate?: () => void\n}\n\nexport const RichEditorTextDropdown = React.forwardRef<HTMLButtonElement, RichEditorTextDropdownProps>(\n ({ label, ariaLabel, menu, onActivate, ...props }, ref) => {\n const { disabled } = useRichEditorContext('RichEditorTextDropdown')\n const button = (\n <RichEditorButton\n ref={ref}\n type=\"text\"\n aria-label={ariaLabel}\n disabled={disabled || props.disabled}\n onClick={onActivate}\n {...props}\n >\n <span className=\"truncate\">{label}</span>\n <ChevronDown className=\"opacity-60 transition-transform group-data-[state=open]/rich-editor-item:rotate-180\" aria-hidden=\"true\" />\n </RichEditorButton>\n )\n if (!menu) return button\n return (\n <Popover>\n <PopoverTrigger asChild>{button}</PopoverTrigger>\n <PopoverContent align=\"start\" sideOffset={6} className=\"w-44 p-1\">\n {menu}\n </PopoverContent>\n </Popover>\n )\n },\n)\nRichEditorTextDropdown.displayName = 'RichEditorTextDropdown'\n\nexport type RichEditorDropdownButtonProps = Omit<ToolbarButtonBaseProps, 'type' | 'children'> & {\n icon: React.ReactNode\n ariaLabel: string\n menu: React.ReactNode\n /** When false, the trailing chevron is omitted (e.g. for the `\u22EE More` overflow button). */\n showChevron?: boolean\n}\n\nexport const RichEditorDropdownButton = React.forwardRef<HTMLButtonElement, RichEditorDropdownButtonProps>(\n ({ icon, ariaLabel, menu, showChevron, ...props }, ref) => {\n const { disabled } = useRichEditorContext('RichEditorDropdownButton')\n return (\n <Popover>\n <PopoverTrigger asChild>\n <RichEditorButton\n ref={ref}\n type=\"dropdown\"\n aria-label={ariaLabel}\n disabled={disabled || props.disabled}\n {...props}\n >\n <span data-slot=\"rich-editor-item-icon\" aria-hidden=\"true\">\n {icon}\n </span>\n {showChevron === false ? null : (\n <ChevronDown className=\"opacity-60 transition-transform group-data-[state=open]/rich-editor-item:rotate-180\" aria-hidden=\"true\" />\n )}\n </RichEditorButton>\n </PopoverTrigger>\n <PopoverContent align=\"start\" sideOffset={6} className=\"w-44 p-1\">\n {menu}\n </PopoverContent>\n </Popover>\n )\n },\n)\nRichEditorDropdownButton.displayName = 'RichEditorDropdownButton'\n\nexport type RichEditorColorButtonProps = Omit<ToolbarButtonBaseProps, 'type' | 'children' | 'value'> & {\n colorValue?: RichEditorColorKey | null\n ariaLabel: string\n command?: string\n onSelect?: (color: RichEditorColorKey | null) => void\n /** Optional translated labels per palette key (defaults to English names). */\n colorLabels?: Partial<Record<RichEditorColorKey, string>>\n /**\n * Restrict the popover palette to a subset of colour keys. Defaults to all\n * 10 colours from the Figma `Rich Editor Colors [1.1]` master frame\n * (166331:4100); pass a 5-key subset for the compact popover variant seen\n * in some Figma mockups.\n */\n palette?: RichEditorColorKey[]\n}\n\nexport const RichEditorColorButton = React.forwardRef<HTMLButtonElement, RichEditorColorButtonProps>(\n ({ colorValue, ariaLabel, command = 'foreColor', onSelect, colorLabels, palette = COLOR_KEYS, ...props }, ref) => {\n const { exec, disabled } = useRichEditorContext('RichEditorColorButton')\n // Track the most recently selected colour internally so the trigger swatch\n // reflects the latest pick even when the consumer doesn't pass a\n // controlled `colorValue`. The `colorValue` prop still wins in controlled\n // mode (e.g. when the consumer wants the swatch to mirror a saved value).\n const [lastSelected, setLastSelected] = React.useState<RichEditorColorKey | null>(null)\n const effectiveValue = colorValue ?? lastSelected\n const swatchColor = effectiveValue ? RICH_EDITOR_COLOR_PALETTE[effectiveValue] : RICH_EDITOR_COLOR_PALETTE.blue\n return (\n <Popover>\n <PopoverTrigger asChild>\n <RichEditorButton\n ref={ref}\n type=\"color\"\n aria-label={ariaLabel}\n disabled={disabled || props.disabled}\n {...props}\n >\n <span\n className=\"inline-block size-4 shrink-0 rounded-full border border-black/10\"\n style={{ backgroundColor: swatchColor }}\n data-slot=\"rich-editor-color-swatch\"\n aria-hidden=\"true\"\n />\n <ChevronDown className=\"opacity-60 transition-transform group-data-[state=open]/rich-editor-item:rotate-180\" aria-hidden=\"true\" />\n </RichEditorButton>\n </PopoverTrigger>\n <PopoverContent align=\"start\" sideOffset={6} className=\"w-36 p-1\">\n <RichEditorColorPalette\n value={effectiveValue}\n labels={colorLabels}\n palette={palette}\n onChange={(key) => {\n if (key) exec(command, RICH_EDITOR_COLOR_PALETTE[key])\n setLastSelected(key)\n onSelect?.(key)\n }}\n />\n </PopoverContent>\n </Popover>\n )\n },\n)\nRichEditorColorButton.displayName = 'RichEditorColorButton'\n\nexport type RichEditorColorPaletteProps = {\n value?: RichEditorColorKey | null\n onChange?: (next: RichEditorColorKey | null) => void\n /** Translatable labels per palette key (defaults to English). */\n labels?: Partial<Record<RichEditorColorKey, string>>\n /**\n * Restrict the palette to a subset of colour keys (defaults to all 10). Use\n * the same subset list on `RichEditorColorButton` and standalone palette\n * popovers so the trigger swatch and the popover stay in sync.\n */\n palette?: RichEditorColorKey[]\n className?: string\n}\n\nexport function RichEditorColorPalette({ value, onChange, labels, palette, className }: RichEditorColorPaletteProps) {\n const resolvedLabels = { ...COLOR_LABELS_EN, ...(labels ?? {}) }\n const keys = palette && palette.length > 0 ? palette : COLOR_KEYS\n return (\n <div\n className={cn('flex flex-col gap-0.5', className)}\n data-slot=\"rich-editor-color-palette\"\n role=\"listbox\"\n aria-label=\"Color palette\"\n >\n {keys.map((key) => {\n const isActive = value === key\n return (\n <button\n key={key}\n type=\"button\"\n role=\"option\"\n aria-selected={isActive}\n aria-label={resolvedLabels[key]}\n onMouseDown={(e) => e.preventDefault()}\n onClick={() => onChange?.(key)}\n className={cn(\n 'flex items-center gap-2 rounded-md px-2 py-1.5 text-left text-sm transition-colors hover:bg-muted focus:outline-none focus-visible:ring-2 focus-visible:ring-ring/40',\n isActive && 'font-medium text-foreground',\n )}\n data-color-key={key}\n >\n <span\n className=\"size-4 shrink-0 rounded-full border border-black/10\"\n style={{ backgroundColor: RICH_EDITOR_COLOR_PALETTE[key] }}\n aria-hidden=\"true\"\n />\n <span className=\"truncate\">{resolvedLabels[key]}</span>\n </button>\n )\n })}\n </div>\n )\n}\n\nexport type RichEditorMenuItemProps = Omit<React.ButtonHTMLAttributes<HTMLButtonElement>, 'children'> & {\n /** Optional 16\u00D716 leading icon (typically a Lucide glyph). */\n icon?: React.ReactNode\n /** Item label (also forwarded as the accessible name). */\n children: React.ReactNode\n /** Destructive variant \u2014 flips the label colour to `text-destructive`. */\n destructive?: boolean\n}\n\n/**\n * Menu item helper for `RichEditor` popovers (More `\u22EE` kebab, custom heading\n * menus, etc.). Renders a Figma `Profile Dropdown Items`-style row: a 16\u00D716\n * icon slot, gap-2, full-width hover background. Use inside the `moreMenu`\n * prop of `<RichEditor>` or the `menu` prop of a compound `RichEditorTextDropdown`\n * / `RichEditorDropdownButton`.\n */\nexport const RichEditorMenuItem = React.forwardRef<HTMLButtonElement, RichEditorMenuItemProps>(\n ({ className, icon, children, destructive, onMouseDown, ...props }, ref) => (\n <button\n ref={ref}\n type=\"button\"\n role=\"menuitem\"\n onMouseDown={(e) => {\n e.preventDefault()\n onMouseDown?.(e)\n }}\n className={cn(\n 'flex w-full cursor-pointer items-center gap-2 rounded px-2 py-1.5 text-left text-sm text-foreground transition-colors hover:bg-muted focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring/40 disabled:pointer-events-none disabled:opacity-50',\n destructive && 'text-destructive hover:bg-destructive/10',\n className,\n )}\n data-slot=\"rich-editor-menu-item\"\n {...props}\n >\n {icon ? (\n <span\n className=\"inline-flex size-4 shrink-0 items-center justify-center text-muted-foreground [&_svg]:size-4\"\n data-slot=\"rich-editor-menu-item-icon\"\n aria-hidden=\"true\"\n >\n {icon}\n </span>\n ) : null}\n <span className=\"min-w-0 flex-1 truncate\">{children}</span>\n </button>\n ),\n)\nRichEditorMenuItem.displayName = 'RichEditorMenuItem'\n\nexport function RichEditorDivider({ className }: { className?: string }) {\n return (\n <span\n role=\"separator\"\n aria-orientation=\"vertical\"\n // Figma 166416:52475 \u2014 4\u00D716 container, 1px line. Toolbar `gap-0.5`\n // already provides the surrounding 2px gap on each side, so the divider\n // itself only carries the 1px line + 1.5px horizontal padding (mx-px).\n className={cn('mx-px inline-block h-4 w-px shrink-0 bg-border', className)}\n data-slot=\"rich-editor-divider\"\n />\n )\n}\n\nexport type RichEditorContentProps = Omit<React.HTMLAttributes<HTMLDivElement>, 'contentEditable' | 'children'> & {\n placeholder?: string\n minRows?: number\n disabled?: boolean\n id?: string\n name?: string\n}\n\nexport const RichEditorContent = React.forwardRef<HTMLDivElement, RichEditorContentProps>(\n ({ className, placeholder, minRows = 4, disabled, onBlur, onInput, onKeyDown, onPaste, id, name, ...props }, ref) => {\n const minHeight = `${Math.max(1, minRows) * 1.5}rem`\n return (\n <div\n ref={ref}\n contentEditable={!disabled}\n suppressContentEditableWarning\n spellCheck\n role=\"textbox\"\n aria-multiline=\"true\"\n data-slot=\"rich-editor-content\"\n data-placeholder={placeholder ?? ''}\n data-name={name}\n id={id}\n className={cn(\n // Figma reference: content lives in its own bordered card with rounded-8 + shadow-xs,\n // visually separated from the toolbar by the small vertical gap from the parent\n // wrapper's `space-y-2`.\n 'w-full max-w-none rounded-lg border border-border bg-card px-3 py-3 text-sm leading-6 text-foreground shadow-xs focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring/40',\n // The project does not include `@tailwindcss/typography`, so we have to\n // re-implement the rich text rendering ourselves via descendant-arbitrary\n // selectors. Cover every tag the toolbar can produce so the user sees\n // the formatting instantly after each execCommand.\n '[&_h1]:mt-4 [&_h1]:mb-2 [&_h1]:text-2xl [&_h1]:font-bold',\n '[&_h2]:mt-3 [&_h2]:mb-2 [&_h2]:text-xl [&_h2]:font-semibold',\n '[&_h3]:mt-3 [&_h3]:mb-1 [&_h3]:text-lg [&_h3]:font-semibold',\n '[&_p]:my-1',\n '[&_ul]:my-2 [&_ul]:list-disc [&_ul]:pl-6',\n '[&_ol]:my-2 [&_ol]:list-decimal [&_ol]:pl-6',\n '[&_li]:my-0.5',\n // Task list checklist override \u2014 when <ul data-task-list> we drop the\n // disc bullet so the <input type=checkbox> child stands alone.\n '[&_ul[data-task-list]]:list-none [&_ul[data-task-list]]:pl-0',\n '[&_ul[data-task-list]_input[type=checkbox]]:mr-2',\n '[&_blockquote]:my-2 [&_blockquote]:border-l-4 [&_blockquote]:border-border [&_blockquote]:pl-4 [&_blockquote]:italic [&_blockquote]:text-muted-foreground',\n '[&_pre]:my-2 [&_pre]:rounded [&_pre]:bg-muted [&_pre]:p-3 [&_pre]:font-mono [&_pre]:text-xs',\n '[&_code]:rounded [&_code]:bg-muted [&_code]:px-1 [&_code]:font-mono [&_code]:text-xs',\n '[&_a]:text-primary [&_a]:underline',\n '[&_hr]:my-3 [&_hr]:border-border',\n '[&_img]:my-2 [&_img]:h-auto [&_img]:max-w-full [&_img]:rounded',\n '[&_table]:my-2 [&_table]:border-collapse',\n '[&_td]:border [&_td]:border-border [&_td]:px-2 [&_td]:py-1',\n '[&_th]:border [&_th]:border-border [&_th]:bg-muted [&_th]:px-2 [&_th]:py-1',\n 'data-[placeholder]:before:pointer-events-none data-[placeholder]:empty:before:content-[attr(data-placeholder)] data-[placeholder]:empty:before:text-muted-foreground',\n className,\n )}\n style={{ minHeight }}\n onBlur={onBlur}\n onInput={onInput}\n onKeyDown={onKeyDown}\n onPaste={onPaste}\n {...props}\n />\n )\n },\n)\nRichEditorContent.displayName = 'RichEditorContent'\n\n// \u2500\u2500 Preset toolbar \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\ntype ToolbarItem =\n | { kind: 'divider'; key: string }\n | {\n kind: 'iconButton'\n key: string\n icon: React.ReactNode\n ariaLabel: string\n tooltipLabel?: string\n active?: boolean\n command?: string\n commandArg?: string\n onActivate?: () => void\n }\n | {\n kind: 'textDropdown'\n key: string\n ariaLabel: string\n label: React.ReactNode\n menu: React.ReactNode\n }\n | {\n kind: 'dropdownButton'\n key: string\n icon: React.ReactNode\n ariaLabel: string\n menu: React.ReactNode\n showChevron?: boolean\n }\n | {\n kind: 'colorButton'\n key: string\n ariaLabel: string\n colorLabels: Record<RichEditorColorKey, string>\n }\n\nfunction renderToolbarItem(item: ToolbarItem): React.ReactNode {\n switch (item.kind) {\n case 'divider':\n return <RichEditorDivider key={item.key} />\n case 'iconButton':\n return (\n <RichEditorIconButton\n key={item.key}\n icon={item.icon}\n ariaLabel={item.ariaLabel}\n tooltipLabel={item.tooltipLabel}\n active={item.active}\n command={item.command}\n commandArg={item.commandArg}\n onActivate={item.onActivate}\n />\n )\n case 'textDropdown':\n return (\n <RichEditorTextDropdown\n key={item.key}\n ariaLabel={item.ariaLabel}\n label={item.label}\n menu={item.menu}\n />\n )\n case 'dropdownButton':\n return (\n <RichEditorDropdownButton\n key={item.key}\n icon={item.icon}\n ariaLabel={item.ariaLabel}\n menu={item.menu}\n showChevron={item.showChevron}\n />\n )\n case 'colorButton':\n return (\n <RichEditorColorButton\n key={item.key}\n ariaLabel={item.ariaLabel}\n colorLabels={item.colorLabels}\n />\n )\n }\n}\n\nfunction trimTrailingDividers(items: ToolbarItem[]): ToolbarItem[] {\n let end = items.length\n while (end > 0 && items[end - 1].kind === 'divider') end -= 1\n return items.slice(0, end)\n}\n\nfunction trimLeadingDividers(items: ToolbarItem[]): ToolbarItem[] {\n let start = 0\n while (start < items.length && items[start].kind === 'divider') start += 1\n return items.slice(start)\n}\n\nfunction usePresetToolbarItems({\n variant,\n labels,\n onComment,\n onMention,\n onFullscreen,\n onImageInsert,\n exec,\n selection,\n requestUrlPrompt,\n}: {\n variant: RichEditorVariant\n labels: RichEditorLabels\n onComment?: () => void\n onMention?: () => void\n onFullscreen?: () => void\n onImageInsert?: () => void\n exec: RichEditorContextValue['exec']\n selection: RichEditorContextValue['selection']\n requestUrlPrompt: RichEditorContextValue['requestUrlPrompt']\n}): ToolbarItem[] {\n const onLink = React.useCallback(() => {\n requestUrlPrompt({\n kind: 'link',\n title: labels.linkUrlPrompt,\n placeholder: 'https://example.com',\n onConfirm: (url) => exec('createLink', url),\n })\n }, [requestUrlPrompt, exec, labels.linkUrlPrompt])\n\n const onImage = React.useCallback(() => {\n if (onImageInsert) {\n onImageInsert()\n return\n }\n requestUrlPrompt({\n kind: 'image',\n title: labels.imageUrlPrompt,\n placeholder: 'https://example.com/image.png',\n onConfirm: (url) => exec('insertImage', url),\n })\n }, [requestUrlPrompt, exec, labels.imageUrlPrompt, onImageInsert])\n\n const onInsertTable = React.useCallback(() => {\n const html = '<table style=\"border-collapse:collapse;width:100%\"><tbody>'\n + '<tr><td style=\"border:1px solid #ebebeb;padding:8px\"> </td><td style=\"border:1px solid #ebebeb;padding:8px\"> </td><td style=\"border:1px solid #ebebeb;padding:8px\"> </td></tr>'\n + '<tr><td style=\"border:1px solid #ebebeb;padding:8px\"> </td><td style=\"border:1px solid #ebebeb;padding:8px\"> </td><td style=\"border:1px solid #ebebeb;padding:8px\"> </td></tr>'\n + '<tr><td style=\"border:1px solid #ebebeb;padding:8px\"> </td><td style=\"border:1px solid #ebebeb;padding:8px\"> </td><td style=\"border:1px solid #ebebeb;padding:8px\"> </td></tr>'\n + '</tbody></table><p> </p>'\n exec('insertHTML', html)\n }, [exec])\n\n const onInsertChecklist = React.useCallback(() => {\n const html = '<ul data-task-list=\"true\" style=\"list-style:none;padding-left:0\">'\n + '<li><label><input type=\"checkbox\" /> </label></li>'\n + '</ul>'\n exec('insertHTML', html)\n }, [exec])\n\n const onInsertInlineCode = React.useCallback(() => {\n const selectionText = typeof window !== 'undefined' ? window.getSelection()?.toString() ?? '' : ''\n if (!selectionText) {\n exec('insertHTML', '<code> </code>')\n return\n }\n exec('insertHTML', `<code>${selectionText}</code>`)\n }, [exec])\n\n const showHeading = variant === 'full' || variant === 'standard'\n const showFontSize = variant === 'full'\n const showColor = variant === 'full'\n const showStrike = variant === 'full'\n const showHr = variant === 'full'\n const showQuoteAndCode = variant === 'full'\n const showImageTable = variant === 'full'\n const showChecklist = variant === 'full'\n const showOrdered = variant === 'full' || variant === 'standard' || variant === 'basic'\n const showAlign = variant === 'full'\n // Comment + Mention are always available in the `full` variant \u2014 when the\n // consumer doesn't wire a custom popover we fall back to inserting a\n // sensible plain-text marker at the caret so the buttons never feel dead.\n // Consumers can still pass `onComment` / `onMention` to fully override and\n // open their own popover / picker / inline UI.\n const showComment = variant === 'full'\n const commentHandler = onComment ?? (() => exec('insertText', '[comment: ]'))\n const showMention = variant === 'full'\n const mentionHandler = onMention ?? (() => exec('insertText', '@'))\n const showLink = variant === 'full' || variant === 'standard' || variant === 'basic'\n const showLists = variant !== 'minimal'\n const showHelp = variant === 'full'\n const showFullscreen = variant === 'full' && (onFullscreen !== undefined)\n\n const keyboardShortcutsHelp = (\n <div className=\"flex w-64 flex-col gap-1 p-1 text-sm\" role=\"menu\">\n <div className=\"px-2 py-1 text-xs font-semibold uppercase tracking-wide text-muted-foreground\">Keyboard</div>\n <div className=\"flex items-center justify-between rounded px-2 py-1 hover:bg-muted\">\n <span>{labels.bold}</span>\n <kbd className=\"rounded border bg-card px-1.5 py-0.5 text-xs\">\u2318 B</kbd>\n </div>\n <div className=\"flex items-center justify-between rounded px-2 py-1 hover:bg-muted\">\n <span>{labels.italic}</span>\n <kbd className=\"rounded border bg-card px-1.5 py-0.5 text-xs\">\u2318 I</kbd>\n </div>\n <div className=\"flex items-center justify-between rounded px-2 py-1 hover:bg-muted\">\n <span>{labels.underline}</span>\n <kbd className=\"rounded border bg-card px-1.5 py-0.5 text-xs\">\u2318 U</kbd>\n </div>\n </div>\n )\n\n const headingMenu = (\n <div className=\"flex flex-col gap-0.5\" role=\"menu\">\n <button\n type=\"button\"\n role=\"menuitem\"\n className=\"cursor-pointer rounded px-2 py-1 text-left text-sm hover:bg-muted\"\n onMouseDown={(e) => e.preventDefault()}\n onClick={() => exec('formatBlock', '<p>')}\n >\n {labels.paragraph}\n </button>\n <button\n type=\"button\"\n role=\"menuitem\"\n className=\"cursor-pointer rounded px-2 py-1 text-left text-lg font-semibold hover:bg-muted\"\n onMouseDown={(e) => e.preventDefault()}\n onClick={() => exec('formatBlock', '<h1>')}\n >\n {labels.heading1}\n </button>\n <button\n type=\"button\"\n role=\"menuitem\"\n className=\"cursor-pointer rounded px-2 py-1 text-left text-base font-semibold hover:bg-muted\"\n onMouseDown={(e) => e.preventDefault()}\n onClick={() => exec('formatBlock', '<h2>')}\n >\n {labels.heading2}\n </button>\n <button\n type=\"button\"\n role=\"menuitem\"\n className=\"cursor-pointer rounded px-2 py-1 text-left text-sm font-semibold hover:bg-muted\"\n onMouseDown={(e) => e.preventDefault()}\n onClick={() => exec('formatBlock', '<h3>')}\n >\n {labels.heading3}\n </button>\n </div>\n )\n\n const headingLabel: string = (() => {\n switch (selection.heading) {\n case 'h1':\n return labels.heading1\n case 'h2':\n return labels.heading2\n case 'h3':\n return labels.heading3\n default:\n return labels.heading\n }\n })()\n\n const fontSizeMenu = (\n <div className=\"flex flex-col gap-0.5\" role=\"menu\">\n {FONT_SIZE_OPTIONS.map((size) => (\n <button\n key={size}\n type=\"button\"\n role=\"menuitem\"\n className=\"cursor-pointer rounded px-2 py-1 text-left text-sm hover:bg-muted\"\n onMouseDown={(e) => e.preventDefault()}\n onClick={() => {\n // execCommand('fontSize', \u2026) requires 1\u20137 \u2014 translate the px label\n // through a CSS-driven span instead.\n exec('removeFormat')\n const px = parseInt(size, 10)\n if (px > 0) {\n exec('insertHTML', `<span style=\"font-size:${px}px\">${window.getSelection()?.toString() ?? ''}</span>`)\n }\n }}\n >\n {size}\n </button>\n ))}\n </div>\n )\n\n const alignMenu = (\n <div className=\"flex flex-col gap-0.5\" role=\"menu\">\n {(['left', 'center', 'right', 'justify'] as RichEditorAlign[]).map((option) => {\n const Icon = ALIGN_ICONS[option]\n const command = option === 'justify' ? 'justifyFull' : `justify${option[0].toUpperCase()}${option.slice(1)}`\n const label = option === 'left'\n ? labels.alignLeft\n : option === 'center'\n ? labels.alignCenter\n : option === 'right'\n ? labels.alignRight\n : labels.alignJustify\n return (\n <button\n key={option}\n type=\"button\"\n role=\"menuitem\"\n className=\"flex cursor-pointer items-center gap-2 rounded px-2 py-1 text-left text-sm hover:bg-muted\"\n onMouseDown={(e) => e.preventDefault()}\n onClick={() => exec(command)}\n >\n <Icon className=\"size-4\" aria-hidden=\"true\" />\n <span>{label}</span>\n </button>\n )\n })}\n </div>\n )\n\n const AlignActiveIcon = ALIGN_ICONS[selection.align]\n\n if (variant === 'custom') return []\n\n const items: ToolbarItem[] = []\n if (showHeading) {\n items.push({ kind: 'textDropdown', key: 'heading', ariaLabel: labels.heading, label: headingLabel, menu: headingMenu })\n items.push({ kind: 'divider', key: 'd-heading' })\n }\n if (showFontSize) {\n items.push({ kind: 'textDropdown', key: 'fontSize', ariaLabel: labels.fontSize, label: '14px', menu: fontSizeMenu })\n items.push({ kind: 'divider', key: 'd-fontSize' })\n }\n if (showColor) {\n items.push({ kind: 'colorButton', key: 'color', ariaLabel: labels.color, colorLabels: labels.colors })\n items.push({ kind: 'divider', key: 'd-color' })\n }\n items.push({ kind: 'iconButton', key: 'bold', icon: <Bold />, command: 'bold', ariaLabel: labels.bold, tooltipLabel: labels.bold, active: selection.bold })\n items.push({ kind: 'iconButton', key: 'italic', icon: <Italic />, command: 'italic', ariaLabel: labels.italic, tooltipLabel: labels.italic, active: selection.italic })\n items.push({ kind: 'iconButton', key: 'underline', icon: <Underline />, command: 'underline', ariaLabel: labels.underline, tooltipLabel: labels.underline, active: selection.underline })\n if (showStrike) {\n items.push({ kind: 'iconButton', key: 'strike', icon: <Strikethrough />, command: 'strikeThrough', ariaLabel: labels.strikethrough, tooltipLabel: labels.strikethrough, active: selection.strikethrough })\n }\n if (showLists) {\n items.push({ kind: 'divider', key: 'd-lists' })\n items.push({ kind: 'iconButton', key: 'ul', icon: <List />, command: 'insertUnorderedList', ariaLabel: labels.unorderedList, tooltipLabel: labels.unorderedList, active: selection.unorderedList })\n if (showOrdered) {\n items.push({ kind: 'iconButton', key: 'ol', icon: <ListOrdered />, command: 'insertOrderedList', ariaLabel: labels.orderedList, tooltipLabel: labels.orderedList, active: selection.orderedList })\n }\n }\n if (showHr || showQuoteAndCode) {\n items.push({ kind: 'divider', key: 'd-structure' })\n if (showHr) {\n items.push({ kind: 'iconButton', key: 'hr', icon: <Minus />, ariaLabel: labels.horizontalRule, tooltipLabel: labels.horizontalRule, onActivate: () => exec('insertHorizontalRule') })\n }\n if (showQuoteAndCode) {\n items.push({ kind: 'iconButton', key: 'quote', icon: <Quote />, ariaLabel: labels.blockquote, tooltipLabel: labels.blockquote, active: selection.blockquote, onActivate: () => exec('formatBlock', selection.blockquote ? '<p>' : '<blockquote>') })\n items.push({ kind: 'iconButton', key: 'inlineCode', icon: <Code />, ariaLabel: labels.inlineCode, tooltipLabel: labels.inlineCode, onActivate: onInsertInlineCode })\n items.push({ kind: 'iconButton', key: 'codeBlock', icon: <FileCode />, ariaLabel: labels.codeBlock, tooltipLabel: labels.codeBlock, active: selection.code, onActivate: () => exec('formatBlock', selection.code ? '<p>' : '<pre>') })\n }\n }\n if (showImageTable) {\n items.push({ kind: 'divider', key: 'd-media' })\n items.push({ kind: 'iconButton', key: 'image', icon: <ImageIcon />, ariaLabel: labels.image, tooltipLabel: labels.image, onActivate: onImage })\n items.push({ kind: 'iconButton', key: 'table', icon: <TableIcon />, ariaLabel: labels.table, tooltipLabel: labels.table, onActivate: onInsertTable })\n }\n if (showChecklist) {\n items.push({ kind: 'iconButton', key: 'checklist', icon: <ListChecks />, ariaLabel: labels.checklist, tooltipLabel: labels.checklist, onActivate: onInsertChecklist })\n }\n if (showAlign) {\n items.push({ kind: 'divider', key: 'd-align' })\n items.push({ kind: 'dropdownButton', key: 'align', icon: <AlignActiveIcon />, ariaLabel: labels.align, menu: alignMenu })\n }\n if (showLink || showComment || showMention) {\n items.push({ kind: 'divider', key: 'd-anchors' })\n }\n if (showComment) {\n items.push({ kind: 'iconButton', key: 'comment', icon: <MessageCircle />, ariaLabel: labels.comment, tooltipLabel: labels.comment, onActivate: commentHandler })\n }\n if (showLink) {\n items.push({ kind: 'iconButton', key: 'link', icon: <Link />, ariaLabel: labels.link, tooltipLabel: labels.link, onActivate: onLink })\n }\n if (showMention) {\n items.push({ kind: 'iconButton', key: 'mention', icon: <AtSign />, ariaLabel: labels.mention, tooltipLabel: labels.mention, onActivate: mentionHandler })\n }\n if (showHelp || showFullscreen) {\n items.push({ kind: 'divider', key: 'd-trailing' })\n if (showHelp) {\n items.push({ kind: 'dropdownButton', key: 'help', icon: <HelpCircle />, ariaLabel: labels.help, menu: keyboardShortcutsHelp, showChevron: false })\n }\n if (showFullscreen && onFullscreen) {\n items.push({ kind: 'iconButton', key: 'fullscreen', icon: <Maximize2 />, ariaLabel: labels.fullscreen, tooltipLabel: labels.fullscreen, onActivate: onFullscreen })\n }\n }\n\n return trimTrailingDividers(items)\n}\n\nfunction RichEditorAutoToolbar({\n items,\n labels,\n moreMenu,\n}: {\n items: ToolbarItem[]\n labels: RichEditorLabels\n moreMenu?: React.ReactNode\n}) {\n const measureRef = React.useRef<HTMLDivElement>(null)\n const containerRef = React.useRef<HTMLDivElement>(null)\n const [firstHidden, setFirstHidden] = React.useState<number | null>(null)\n\n React.useLayoutEffect(() => {\n if (typeof window === 'undefined') return\n if (typeof ResizeObserver === 'undefined') return\n const container = containerRef.current\n const measureEl = measureRef.current\n if (!container || !measureEl) return\n\n const measure = () => {\n const containerWidth = container.clientWidth\n if (containerWidth === 0) return\n\n const allEls = Array.from(measureEl.children) as HTMLElement[]\n if (allEls.length === 0) return\n // Last child is the reserved \u22EE placeholder for budget calculations.\n const moreEl = allEls[allEls.length - 1]\n const itemEls = allEls.slice(0, -1)\n const moreReserved = moreEl.offsetWidth + 4\n const containerPadding = 4 // p-0.5 left + right\n\n let totalWidth = 0\n for (const el of itemEls) totalWidth += el.offsetWidth + 2\n\n if (totalWidth + containerPadding <= containerWidth + 2) {\n setFirstHidden(null)\n return\n }\n\n let accumulated = 0\n for (let i = 0; i < itemEls.length; i++) {\n accumulated += itemEls[i].offsetWidth + 2\n if (accumulated > containerWidth - moreReserved - containerPadding) {\n setFirstHidden(i)\n return\n }\n }\n setFirstHidden(null)\n }\n\n measure()\n const ro = new ResizeObserver(measure)\n ro.observe(container)\n return () => ro.disconnect()\n }, [items])\n\n const visibleItems = firstHidden === null ? items : trimTrailingDividers(items.slice(0, firstHidden))\n const hiddenItems = firstHidden === null ? [] : trimLeadingDividers(items.slice(firstHidden))\n\n const hasOverflow = hiddenItems.length > 0\n const showMoreButton = hasOverflow || !!moreMenu\n\n return (\n <div className=\"relative w-full\">\n <div\n aria-hidden=\"true\"\n data-slot=\"rich-editor-toolbar-measure-clip\"\n className=\"pointer-events-none absolute inset-0 overflow-hidden\"\n >\n <div\n ref={measureRef}\n className=\"invisible absolute left-0 top-0 flex flex-row flex-nowrap items-center gap-0.5\"\n >\n {items.map(renderToolbarItem)}\n <RichEditorDropdownButton icon={<MoreVertical />} ariaLabel={labels.more} showChevron={false} menu={<div />} />\n </div>\n </div>\n <RichEditorToolbar ref={containerRef} className=\"w-full flex-nowrap overflow-hidden\">\n {visibleItems.map(renderToolbarItem)}\n {showMoreButton ? (\n <RichEditorDropdownButton\n icon={<MoreVertical />}\n ariaLabel={labels.more}\n showChevron={false}\n menu={\n <div className=\"flex max-w-[320px] flex-row flex-wrap items-center gap-0.5 p-1\">\n {moreMenu ? (\n <>\n <div className=\"w-full\">{moreMenu}</div>\n {hiddenItems.length > 0 ? <div className=\"my-1 h-px w-full bg-border\" /> : null}\n </>\n ) : null}\n {hiddenItems.map(renderToolbarItem)}\n </div>\n }\n />\n ) : null}\n </RichEditorToolbar>\n </div>\n )\n}\n\nexport { richEditorItemVariants }\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": ["RichEditor"]
|
|
7
7
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@open-mercato/ui",
|
|
3
|
-
"version": "0.6.8-develop.
|
|
3
|
+
"version": "0.6.8-develop.7072.1.19c2a8bbe0",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -155,14 +155,14 @@
|
|
|
155
155
|
"remark-gfm": "^4.0.1"
|
|
156
156
|
},
|
|
157
157
|
"peerDependencies": {
|
|
158
|
-
"@open-mercato/shared": "0.6.8-develop.
|
|
158
|
+
"@open-mercato/shared": "0.6.8-develop.7072.1.19c2a8bbe0",
|
|
159
159
|
"react": ">=18.0.0",
|
|
160
160
|
"react-dom": ">=18.0.0",
|
|
161
161
|
"react-is": ">=18.0.0"
|
|
162
162
|
},
|
|
163
163
|
"devDependencies": {
|
|
164
164
|
"@figma/code-connect": "^1.3.4",
|
|
165
|
-
"@open-mercato/shared": "0.6.8-develop.
|
|
165
|
+
"@open-mercato/shared": "0.6.8-develop.7072.1.19c2a8bbe0",
|
|
166
166
|
"@testing-library/dom": "^10.4.1",
|
|
167
167
|
"@testing-library/jest-dom": "^7.0.0",
|
|
168
168
|
"@testing-library/react": "^16.3.1",
|
|
@@ -347,6 +347,49 @@ describe('RichEditor — custom variant + compound API', () => {
|
|
|
347
347
|
})
|
|
348
348
|
})
|
|
349
349
|
|
|
350
|
+
// jsdom has no layout engine, so the page-overflow bug this guards against
|
|
351
|
+
// (the hidden measurement row is wider than its column and grows the document)
|
|
352
|
+
// cannot be reproduced by measuring here. What these tests pin instead is the
|
|
353
|
+
// containment structure the fix relies on: the measurement row sits inside its
|
|
354
|
+
// own clipping layer, and the visible toolbar sits outside it so the clip never
|
|
355
|
+
// swallows the toolbar's own drop shadow.
|
|
356
|
+
describe('RichEditor — auto toolbar measurement row containment', () => {
|
|
357
|
+
const renderAutoToolbar = () => {
|
|
358
|
+
const { container } = render(<RichEditor value="" onChange={jest.fn()} variant="full" />)
|
|
359
|
+
const clip = container.querySelector('[data-slot="rich-editor-toolbar-measure-clip"]')!
|
|
360
|
+
const toolbar = container.querySelector('[data-slot="rich-editor-toolbar"]')!
|
|
361
|
+
return { clip, toolbar }
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
it('clips the hidden measurement row in its own layer so it cannot widen the page', () => {
|
|
365
|
+
const { clip } = renderAutoToolbar()
|
|
366
|
+
expect(clip).not.toBeNull()
|
|
367
|
+
expect(clip.getAttribute('aria-hidden')).toBe('true')
|
|
368
|
+
expect(clip.className).toContain('absolute')
|
|
369
|
+
expect(clip.className).toContain('inset-0')
|
|
370
|
+
expect(clip.className).toContain('overflow-hidden')
|
|
371
|
+
|
|
372
|
+
const measureRow = clip.firstElementChild!
|
|
373
|
+
expect(measureRow.className).toContain('invisible')
|
|
374
|
+
expect(measureRow.className).toContain('absolute')
|
|
375
|
+
expect(measureRow.className).toContain('flex-nowrap')
|
|
376
|
+
expect(measureRow.childElementCount).toBeGreaterThan(0)
|
|
377
|
+
})
|
|
378
|
+
|
|
379
|
+
it('keeps the visible toolbar outside the clipping layer so shadow-xs still paints', () => {
|
|
380
|
+
const { clip, toolbar } = renderAutoToolbar()
|
|
381
|
+
expect(clip.contains(toolbar)).toBe(false)
|
|
382
|
+
expect(toolbar.className).toContain('shadow-xs')
|
|
383
|
+
})
|
|
384
|
+
|
|
385
|
+
it('leaves the toolbar wrapper unclipped', () => {
|
|
386
|
+
const { toolbar } = renderAutoToolbar()
|
|
387
|
+
const wrapper = toolbar.parentElement!
|
|
388
|
+
expect(wrapper.className).toContain('relative')
|
|
389
|
+
expect(wrapper.className).not.toContain('overflow-hidden')
|
|
390
|
+
})
|
|
391
|
+
})
|
|
392
|
+
|
|
350
393
|
describe('RichEditorDropdownButton + RichEditorTextDropdown', () => {
|
|
351
394
|
it("RichEditorDropdownButton renders inside <RichEditor variant='custom'>", () => {
|
|
352
395
|
render(
|
|
@@ -1543,12 +1543,17 @@ function RichEditorAutoToolbar({
|
|
|
1543
1543
|
return (
|
|
1544
1544
|
<div className="relative w-full">
|
|
1545
1545
|
<div
|
|
1546
|
-
ref={measureRef}
|
|
1547
1546
|
aria-hidden="true"
|
|
1548
|
-
|
|
1547
|
+
data-slot="rich-editor-toolbar-measure-clip"
|
|
1548
|
+
className="pointer-events-none absolute inset-0 overflow-hidden"
|
|
1549
1549
|
>
|
|
1550
|
-
|
|
1551
|
-
|
|
1550
|
+
<div
|
|
1551
|
+
ref={measureRef}
|
|
1552
|
+
className="invisible absolute left-0 top-0 flex flex-row flex-nowrap items-center gap-0.5"
|
|
1553
|
+
>
|
|
1554
|
+
{items.map(renderToolbarItem)}
|
|
1555
|
+
<RichEditorDropdownButton icon={<MoreVertical />} ariaLabel={labels.more} showChevron={false} menu={<div />} />
|
|
1556
|
+
</div>
|
|
1552
1557
|
</div>
|
|
1553
1558
|
<RichEditorToolbar ref={containerRef} className="w-full flex-nowrap overflow-hidden">
|
|
1554
1559
|
{visibleItems.map(renderToolbarItem)}
|