'use client';
import { useCallback, useMemo, useRef } from 'react';
import {
LazyMarkdownEditor as MarkdownEditor,
type MarkdownEditorHandle,
type MentionConfig,
type SlashCommandInfo,
} from '@djangocfg/ui-tools/markdown-editor';
import { useRegisterComposer } from '../hooks/useAutoFocusOnStreamEnd';
import type { ComposerSize, ComposerTextareaProps } from './types';
/**
* Single-row min-height FLOOR per composer size (px).
*
* This is a floor, NOT the row height — the empty `
`'s own line box
* defines the real single-row height. The floor must stay BELOW one line
* box (15px text × line-height) so it can never inflate the editor past
* one line: when a host overrides the editor leading (e.g. cmdop pins
* `.ProseMirror` to `leading-normal` ≈ 22.5px), a 24/28px min-height used
* to sit ABOVE the line box and the empty composer read as having a
* phantom extra line under the caret. Keeping every floor ≤ the tightest
* expected single line (≈22px) means the line box always wins and the
* single-row composer is exactly one line tall in every host.
*/
const MIN_HEIGHT: Record = {
sm: 18,
md: 20,
lg: 22,
};
export interface ComposerRichTextareaProps extends ComposerTextareaProps {
/** `@`-mention autocomplete config. Omit for a plain rich textarea. */
mentions?: MentionConfig;
/**
* Slash-command verb list. When set, a leading `/verb` in the editor
* is painted as an atom chip (same primary-tinted styling as the
* plain `` mirror). The chat composer's slash
* menu already lives outside the editor and keeps working without
* this prop — pass the verb list here when you also want the
* in-editor highlight to match.
*
* Structurally compatible with `SlashCommand` from
* `@djangocfg/ui-tools/chat` — passing the same array you wire into
* `composerSlots.slashCommands` is the normal pattern.
*
* Note: like `mentions`, the underlying TipTap extension is captured
* once on first render. If you may register slash commands later,
* pass `[]` from the very first render and mutate the list in place.
*/
slashCommands?: readonly SlashCommandInfo[];
}
/**
* Drop-in TipTap-backed textarea for the composer's Tier B `Textarea`
* slot. Pre-wired with chat defaults — `unstyled` (the composer surface
* draws the frame), no toolbar, size-matched single-row height, and
* Enter-to-send. The host passes only `mentions` if it needs `@`
* autocomplete; everything else flows from `ComposerTextareaProps`.
*
* @example
* }}
* />
*/
export function ComposerRichTextarea({
composer,
placeholder,
disabled,
size,
className,
mentions,
slashCommands,
}: ComposerRichTextareaProps) {
// Tiptap captures the mentions object once on first render — keep its
// identity stable so the suggestion plugin stays wired.
const stableMentions = useMemo(() => mentions, [mentions]);
// `slashCommands` flows through every render — the editor's
// SlashCommandNode is registered once on first mount (gated on
// `slashCommands !== undefined`) and the current list is read via a
// ref by the value-sync effect, so runtime additions / removals
// still light up the chip.
// The TipTap editor has no `