import type * as React from 'react'; import type { PrimitivePropsWithRef } from '../utils/primitive.cjs'; /** The element `EditableInput` or `EditableTextarea` renders — whichever one the consumer mounted. */ export type EditableInputElement = HTMLInputElement | HTMLTextAreaElement; /** * How interacting with `EditablePreview` enters edit mode; keyboard activation (Enter/Space) always works, even under `'none'`, since the preview is a `role="button"` tab stop either way. */ export type EditableActivationMode = 'click' | 'dblclick' | 'focus' | 'none'; /** The payload of {@link EditableProps.onValueRevert}. */ export interface EditableRevertDetail { /** The restored text — what is now on screen. */ value: string; /** The draft that was thrown away — the only place the abandoned text still exists. */ previousValue: string; } /** Which gestures commit the draft. `'none'` leaves committing entirely to `EditableSubmitTrigger`. */ export type EditableSubmitMode = 'blur' | 'enter' | 'both' | 'none'; /** * Split placeholder for the preview (visible text, read as label) and editor * (hint, read as instruction) since one string cannot serve both purposes. */ export interface EditablePlaceholder { /** The editor's `placeholder` attribute — a hint, not a name. */ edit: string; /** The empty preview's text, which is also its accessible name — write it as a label. */ preview: string; } /** @internal Context state distributed from Editable to its parts. */ export interface EditableContextValue { value: string; editing: boolean; /** The editor's placeholder half, already resolved from the union. */ editPlaceholder: string | undefined; /** The empty preview's placeholder half, already resolved from the union. */ previewPlaceholder: string | undefined; disabled: boolean; readOnly: boolean; required: boolean; invalid: boolean; maxLength: number | undefined; activationMode: EditableActivationMode; submitMode: EditableSubmitMode; /** Set by whichever of `EditableInput`/`EditableTextarea` is mounted, so the root can focus it on entering edit mode. */ inputRef: React.RefObject; /** Set by `EditablePreview`, so the root can return focus to it on leaving edit mode. */ previewRef: React.RefObject; /** * True only for the instant the root is programmatically returning focus to * the preview; `activationMode="focus"` reads it to avoid re-entering edit mode on that focus. */ returningFocusRef: React.RefObject; /** Id of whichever editor is mounted — an `EditableLabel`'s `for` target. */ editorId: string; /** Id a mounted `EditableLabel` carries, and the preview points `aria-labelledby` at. */ labelId: string; /** Whether an `EditableLabel` is currently mounted — a dangling IDREF names nothing. */ labelPresent: boolean; /** Registers/unregisters an `EditableLabel`. @internal */ setLabelPresent: (present: boolean) => void; setValue: (value: string) => void; activate: () => void; submit: () => void; cancel: () => void; } /** Props for the {@link Editable} root. */ export interface EditableProps extends Omit, 'onChange'> { /** * Manually-composed parts for full control over the anatomy; omitted, the field derives its own preview and editor from `multiline`. * @default undefined — the field derives its anatomy. */ children?: React.ReactNode | undefined; /** * Current text, for controlled usage, tracking the live draft while editing; use `onValueCommit` for the submitted result only. * @default undefined — uncontrolled, tracks its own value internally. */ value?: string | undefined; /** * Initial text, for uncontrolled usage, seeding the first render. * @default undefined — starts empty, so the preview shows `placeholder` and the root carries `data-empty`. */ defaultValue?: string | undefined; /** * Fires with the new text on every keystroke while editing, and again with the restored text when an edit is cancelled or rejected. * @default undefined — no-op if omitted. */ onValueChange?: ((value: string) => void) | undefined; /** * Fires once with the final text when an edit is successfully submitted; * never for an Escape-cancel or a rejected `required` blank submit. * @default undefined — no-op if omitted. */ onValueCommit?: ((value: string) => void) | undefined; /** * Fires when an edit is abandoned rather than committed — Escape, an * `EditableCancelTrigger` click, or a `required` field submitted blank; * fires after the value is restored, so `value` is what's now on screen and * `previousValue` is the discarded draft. * @default undefined — no-op if omitted. */ onValueRevert?: ((detail: EditableRevertDetail) => void) | undefined; /** * Whether the input is showing instead of the preview, for controlled usage. Pair with `onEditChange`. * @default undefined — uncontrolled, tracks its own edit state internally. */ editing?: boolean | undefined; /** * Whether to start in edit mode, for uncontrolled usage; starting `true` focuses the input on mount. * @default false — starts showing the preview. */ defaultEditing?: boolean | undefined; /** * Fires with the new edit state whenever it flips, controlled or not. * @default undefined — no-op if omitted. */ onEditChange?: ((editing: boolean) => void) | undefined; /** * Text rendered by `EditablePreview` when the value is empty and set as the editor's `placeholder`; a plain string sets both, the `{ edit, preview }` object form sets them separately. * @default undefined — an empty value renders an empty preview. */ placeholder?: string | EditablePlaceholder | undefined; /** * Where focus goes when the edit ends, overriding the preview default; a function so the target is resolved when focus moves, not at render, with `null` falling back to the preview. * @default undefined — focus returns to `EditablePreview`. */ finalFocusEl?: (() => HTMLElement | null) | undefined; /** * Blocks editing entirely and takes the preview out of the tab order. * @default false */ disabled?: boolean | undefined; /** * Shows the value but blocks entering edit mode; the preview stays focusable, unlike `disabled`. * @default false */ readOnly?: boolean | undefined; /** * Rejects a submit that would leave the value blank (whitespace counts): * the pre-edit text is restored, `onValueCommit` does not fire, and edit mode closes. * @default false — an empty submit is committed like any other. */ required?: boolean | undefined; /** * Maximum number of characters the input accepts, forwarded to the native `maxLength` attribute. * @default undefined — no limit. */ maxLength?: number | undefined; /** * Form field name; when set, the root renders one hidden input carrying the committed value, not the live draft. * @default undefined — renders no hidden input; submits nothing. */ name?: string | undefined; /** * Id of the `
` to submit with, mirroring the native `form` attribute on the hidden input `name` renders. * @default undefined — associates with the nearest ancestor ``. */ form?: string | undefined; /** * Which pointer gesture on `EditablePreview` enters edit mode; `'none'` matches none, leaving `EditableEditTrigger` or a controlled `editing` prop as the way in. Enter/Space always opens the editor regardless. * @default 'click' — a single click on the preview starts editing. */ activationMode?: EditableActivationMode | undefined; /** * Which gestures commit the draft: `'enter'`, `'blur'`, `'both'`, or * `'none'` (leaving it to `EditableSubmitTrigger`). Escape always cancels regardless. * @default 'both' — Enter and blur each submit. */ submitMode?: EditableSubmitMode | undefined; /** * Marks the current value as invalid, published as `data-invalid` on root, preview and editor, and as `aria-invalid` on the editor only, since that state is deprecated on the preview's `role="button"`. * @default false */ invalid?: boolean | undefined; /** * Select the whole value when edit mode opens, so typing replaces it; `false` places the caret at the end instead. * @default true — entering edit mode selects everything. */ selectOnFocus?: boolean | undefined; /** * Whether the derived anatomy's editor is an `EditableTextarea` instead of an `EditableInput`; ignored when `children` is given. * @default false — a single-line `EditableInput`. */ multiline?: boolean | undefined; /** * Whether the derived anatomy renders an `EditableSubmitTrigger`/`EditableCancelTrigger` pair after the editor; ignored when `children` is given. * @default false */ showControls?: boolean | undefined; } /** Props for {@link EditablePreview}, the read-mode text that starts an edit. Renders nothing while editing. */ export interface EditablePreviewProps extends PrimitivePropsWithRef<'span'> { } /** Props for {@link EditableInput}, the single-line editor. Value, placeholder and constraints come from {@link Editable}'s context. */ export interface EditableInputProps extends Omit, 'value' | 'defaultValue'> { } /** Props for {@link EditableTextarea}, the multi-line editor. Value, placeholder and constraints come from {@link Editable}'s context. */ export interface EditableTextareaProps extends Omit, 'value' | 'defaultValue'> { /** * Grow the editor to fit its content instead of showing a scrollbar. * @default false — the editor keeps its `rows` height and scrolls. */ autoResize?: boolean | undefined; /** * Caps auto-resize growth to this many rows, based on computed line-height; past the cap the editor scrolls again. * @default undefined — grows without limit. */ maxRows?: number | undefined; /** * The editor's opening size and the floor auto-resize will not shrink * below; where `minRows` and `maxRows` disagree, `maxRows` wins. * @default 2 — the platform's own `rows` default. */ minRows?: number | undefined; } /** Props for {@link EditableLabel}, the `