{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "rename-editor",
  "type": "registry:ui",
  "title": "Rename editor",
  "description": "A name field and one async rename confirm: basename selection, host validation, pending lock, glyph/drum result and captured-original Undo callback.",
  "dependencies": [
    "@base-ui/react@^1.8.0"
  ],
  "registryDependencies": [
    "https://metalui.dev/r/button.json",
    "https://metalui.dev/r/field.json",
    "https://metalui.dev/r/form-field.json",
    "https://metalui.dev/r/icons.json",
    "https://metalui.dev/r/morph-icons.json",
    "https://metalui.dev/r/motion.json",
    "https://metalui.dev/r/tokens.json"
  ],
  "files": [
    {
      "path": "packages/metalui/src/components/rename-editor/rename-editor.tsx",
      "type": "registry:ui",
      "target": "components/metalui/components/rename-editor/rename-editor.tsx",
      "content": "'use client';\nimport * as React from 'react';\nimport { Button } from '../button/button';\nimport { Field } from '../field/field';\nimport { Form, FormField } from '../form-field/form-field';\nimport { Icon } from '../../icons/Icon';\nimport { MorphIcon } from '../../icons/MorphIcon';\nimport { useWaiting } from '../../motion/waiting';\nimport { SPRINGS } from '../../motion/springs.generated';\nimport { WAITING_TIMING } from '../../motion/waiting.generated';\nimport { useEditRequest } from './edit-request';\n\nexport interface RenameEditorProps {\n  /** Name captured when this editor opens. Remount for a new editing session. */\n  value: string;\n  /** Select the basename, excluding only a nonempty final extension. Dotfiles remain whole. */\n  file?: boolean;\n  label?: string;\n  validate?: (name: string) => string | null | undefined;\n  onRename: (name: string) => void | Promise<void>;\n  /** Successful request, with the captured original for the host's Undo. */\n  onRenamed?: (name: string, original: string) => void;\n  /** Result has been visible for the shared minimum beat; the host may close now. */\n  onDone?: () => void;\n  onCancel: () => void;\n  /** Includes the result beat. Refuse host dismissal while true. */\n  onPendingChange?: (pending: boolean) => void;\n  className?: string;\n}\nconst ROOT = 'mu-rename-editor mu-stack gap-mu-related';\nconst ACTIONS = 'mu-cluster justify-end gap-mu-related';\n\n/** Text plus one confirm operation; the host owns validation, persistence, dismissal and Undo. */\nexport const RenameEditor = React.forwardRef<HTMLInputElement, RenameEditorProps>(function RenameEditor({\n  value, file = false, label = 'Name', validate, onRename, onRenamed, onDone, onCancel, onPendingChange, className,\n}, ref) {\n  const failureId = React.useId();\n  const original = React.useRef(value);\n  const [draft, setDraft] = React.useState(value);\n  const selected = React.useRef(false);\n  const root = React.useRef<HTMLFormElement>(null);\n  const request = useEditRequest({ onCommit: onRename, onCommitted: onRenamed, onPendingChange });\n  const { phase } = useWaiting(request.state, root);\n  const callbacks = React.useRef({ onDone, finish: request.finish }); callbacks.current = { onDone, finish: request.finish };\n  React.useEffect(() => {\n    if (phase !== 'done') return;\n    const close = setTimeout(() => { callbacks.current.finish(); callbacks.current.onDone?.(); }, SPRINGS.settle.duration * 1000 + WAITING_TIMING.minimumVisible);\n    return () => clearTimeout(close);\n  }, [phase]);\n  const name = draft.trim();\n  const validation = name ? validate?.(name) : null;\n  const error = validation || request.failure;\n  const locked = request.state === 'waiting' || request.state === 'done';\n  const unchanged = name === original.current.trim();\n  return <Form ref={root} className={`${ROOT} ${className ?? ''}`} data-state={request.state}\n    onFormSubmit={() => { if (!locked && name && !unchanged && !validation) void request.commit(name, original.current); }}>\n    <FormField name=\"name\" invalid={!!validation}>\n      <FormField.Label>{label}</FormField.Label>\n      <Field size=\"regular\" invalid={!!error} disabled={locked} aria-busy={request.state === 'waiting'}>\n        <Field.Input ref={ref} aria-invalid={!!error || undefined} aria-describedby={request.failure ? failureId : undefined} value={draft} onChange={e => { setDraft(e.target.value); request.reset(); }}\n          onFocus={e => {\n            if (selected.current) return; selected.current = true;\n            const dot = original.current.lastIndexOf('.');\n            e.currentTarget.setSelectionRange(0, file && dot > 0 && dot < original.current.length - 1 ? dot : original.current.length);\n          }} />\n      </Field>\n      {validation && <FormField.Error match>{validation}</FormField.Error>}\n      {request.failure && <p id={failureId} className=\"m-0 type-meta text-form-field-error-ink\" role=\"alert\">{request.failure}</p>}\n    </FormField>\n    <div className={ACTIONS}>\n      <Button type=\"button\" disabled={locked} onClick={onCancel}>Cancel</Button>\n      <Button cap=\"primary\" type=\"submit\"\n        icon={<span className=\"relative inline-flex\"><MorphIcon name={phase === 'done' ? 'check' : phase === 'error' ? 'sync-error' : 'pen'} className={phase === 'idle' ? 'invisible' : undefined} /><Icon name=\"pen\" className={phase === 'idle' ? 'absolute inset-0' : 'hidden'} /></span>} state={request.state}\n        waitingLabel=\"Renaming…\" doneLabel=\"Renamed\" errorLabel=\"Try again\"\n        disabled={!name || unchanged || !!validation}>Rename</Button>\n    </div>\n  </Form>;\n});\n"
    },
    {
      "path": "packages/metalui/src/components/rename-editor/edit-request.ts",
      "type": "registry:ui",
      "target": "components/metalui/components/rename-editor/edit-request.ts",
      "content": "'use client';\nimport * as React from 'react';\nimport type { ButtonState } from '../button/button';\n\n/** Internal request lifecycle shared by small edit recipes; not a public form framework. */\nexport function useEditRequest<Value, Original = Value>(options: {\n  onCommit: (value: Value) => void | Promise<void>;\n  onCommitted?: (value: Value, original: Original) => void;\n  onPendingChange?: (pending: boolean) => void;\n}) {\n  const [state, setState] = React.useState<ButtonState>('idle');\n  const [failure, setFailure] = React.useState<string | null>(null);\n  const running = React.useRef(false);\n  const generation = React.useRef(0);\n  const callbacks = React.useRef(options); callbacks.current = options;\n  React.useEffect(() => () => { generation.current++; callbacks.current.onPendingChange?.(false); }, []);\n  const commit = async (value: Value, original: Original) => {\n    if (running.current) return;\n    running.current = true; const request = ++generation.current;\n    callbacks.current.onPendingChange?.(true); setFailure(null); setState('waiting');\n    try {\n      await callbacks.current.onCommit(value);\n      if (generation.current !== request) return;\n      setState('done'); callbacks.current.onCommitted?.(value, original);\n    } catch (error) {\n      if (generation.current !== request) return;\n      setFailure(error instanceof Error ? error.message : 'Could not save. Try again.');\n      setState('error'); callbacks.current.onPendingChange?.(false); running.current = false;\n    }\n  };\n  const reset = () => { if (!running.current) { setFailure(null); setState('idle'); } };\n  const finish = () => { callbacks.current.onPendingChange?.(false); running.current = false; };\n  return { state, failure, commit, reset, finish };\n}\n"
    }
  ],
  "docs": "Agent guide: https://metalui.dev/r/rename-editor.md. SwiftUI: MetalRenameEditor in the MetalUI Swift package."
}
