import { useUniqueId } from "@charcuterie/logic" import type { ClipboardEvent, DragEvent, KeyboardEvent, ReactNode, } from "react" import { Fragment, useRef, useState } from "react" import type { SlotProps } from "../slotProps.ts" import { mergeSlotProps } from "../slotProps.ts" import { Toolbar } from "../Toolbar/Toolbar.tsx" import { toClassName } from "../toClassName.ts" import type { MarkdownSelection } from "./markdownCommands.ts" import { continueList, getMinimalEdit, indentLines, insertLink, insertText, isLinkPaste, MARKDOWN_LINE_PREFIXES, outdentLines, toggleHeading, toggleInlineMarker, toggleLinePrefix, toMarkdownImage, wrapSelectionInLink, } from "./markdownCommands.ts" import type { MarkdownSpanKind } from "./markdownSpans.ts" import { toLineIndex, toMarkdownLines, } from "./markdownSpans.ts" export type MarkdownImageUpload = { /** Alt text. Defaults to the file's name when omitted. */ alt?: string /** Where the image now lives. Inserted verbatim. */ url: string } /** * The toolbar's glyphs, by action. * * **No default, and none shipped** — this package ships no icons, * and a `⋯`/`▨` default renders as nothing where the font lacks it * ([decision](../../../../docs/decisions/2026-07-29-ship-no-icons-and-no-symbol-glyphs.md)). * With no icons the toolbar is nine word buttons, which works * everywhere and collapses into the overflow menu early; an app * that brings lucide gets a compact bar and keeps the words as the * accessible names. * * `overflow` is the odd one out: it is not a markdown command, it * is the **bar's own** trigger. It lives here because an app that * passes the nine and not the tenth gets nine icons followed by * the words *"More actions"* — which is the one width where an * icon bar looks unfinished, and there was no prop to fix it with. */ export type MarkdownEditorIcons = Partial< Record< | "bold" | "bulletedList" | "code" | "heading" | "image" | "italic" | "link" | "numberedList" | "overflow" | "quote" | "taskList", ReactNode > > export type MarkdownEditorProps = SlotProps & { className?: string /** * **Initial** only. This control is uncontrolled, and the long * reason is in the doc comment below — it is the whole design. */ defaultValue?: string /** See `MarkdownEditorIcons`. */ icons?: MarkdownEditorIcons isDisabled?: boolean isReadOnly?: boolean /** * The accessible name, as `aria-label`. Omit it inside a `Field`, * which supplies a real `