import { ReactNode } from 'react'; import { IPropertyAccessor, IPropertyValue } from '../../lib/properties'; /** * One editable property. * * `type` names a renderer in the field registry. The built-ins are listed in * {@link BUILT_IN_FIELD_TYPES}, and any string works once you register a * renderer for it. */ export interface IPropertyField extends IPropertyAccessor { /** Label shown above the control. */ label: string; /** Field type. Defaults to `"text"`. */ type?: string; /** One line under the label explaining the property. */ description?: string; /** Group heading this field sits under. Ungrouped fields come first. */ group?: string; /** Render read-only. */ disabled?: boolean; /** * Hide the field for the current selection — a property that only applies to * some node kinds, say. Receives the selected subjects. */ when?: (subjects: S[]) => boolean; /** Return a message to show the field as invalid. */ validate?: (value: unknown, subjects: S[]) => string | undefined; /** * Anything the renderer needs: `options` for a select, `rows` for a text * area, `unit` for a number, or whatever your own field type expects. */ props?: Record; } /** What a field renderer receives. */ export interface FieldRendererProps { field: IPropertyField; /** The shared value across the selection, and whether subjects disagree. */ state: IPropertyValue; /** Apply a value to every selected subject. */ onChange: (value: unknown) => void; /** The selected subjects, for a renderer that needs more than one value. */ subjects: S[]; /** Validation message, if `validate` returned one. */ error?: string; } /** Renders one field. Register your own to extend the panel. */ export type FieldRenderer = (props: FieldRendererProps) => ReactNode; /** * The field types the panel understands out of the box. * * Spread it to add your own, or replace an entry to change how a type renders * everywhere: * * ```tsx * * ``` */ export declare const BUILT_IN_FIELD_TYPES: Record>; export interface PropertyPanelProps { /** * The selected subjects. Zero renders the empty state, one edits normally, * several edit together with a mixed indicator where values differ. */ subjects: S[]; /** The properties to show. */ fields: IPropertyField[]; /** * Called with every subject updated. The panel produces new copies and never * mutates, so this is the whole edit — apply it to your state. */ onChange?: (updated: S[], field: IPropertyField, value: unknown) => void; /** * Field renderers by type. Defaults to {@link BUILT_IN_FIELD_TYPES}; spread * it to add your own without losing the built-ins. */ fieldTypes?: Record>; /** Shown when nothing is selected. */ emptyState?: ReactNode; /** * Heading above the fields. Receives the selection, so it can read * "3 nodes selected" as easily as a name. */ title?: ReactNode | ((subjects: S[]) => ReactNode); /** Group headings render in this order. Unlisted groups follow, in first-seen order. */ groupOrder?: string[]; /** Extra classes merged onto the root element. */ className?: string; /** Accessible label for the region. Defaults to `"Properties"`. */ 'aria-label'?: string; } /** * An inspector: the properties of whatever is selected, as editable fields. * * Give it the selection and a list of field descriptors. It reads each * property across every selected subject, renders the matching field, and hands * back new copies on edit — it holds no state and mutates nothing. * * Three selection states are handled for you: * * - **none** — the empty state. * - **one** — ordinary editing. * - **several** — shared values edit together; where subjects disagree the * field shows *Mixed* rather than one subject's value, and editing applies to * all of them. * * That last case is the one hand-rolled inspectors usually skip, and skipping * it is how a multi-selection edit silently flattens data. * * Every field is also exported on its own — reach for `TextField` and friends * when a property needs bespoke arrangement, and use `FieldShell` to make a * custom control look like the rest. * * @example * ```tsx * nodes.length > 1 ? `${nodes.length} nodes` : 'Node'} * fields={[ * { key: 'data.label', label: 'Label' }, * { key: 'kind', label: 'Type', type: 'select', * props: { options: [{ value: 'idea', label: 'Idea' }] } }, * { key: 'data.notes', label: 'Notes', type: 'textarea', group: 'Detail' }, * { key: 'data.tags', label: 'Tags', type: 'tags', group: 'Detail' }, * ]} * onChange={(updated) => setNodes(merge(nodes, updated))} * /> * ``` */ export declare const PropertyPanel: ({ subjects, fields, onChange, fieldTypes, emptyState, title, groupOrder, className, "aria-label": ariaLabel, }: PropertyPanelProps) => import("react/jsx-runtime").JSX.Element;