{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "link-cue",
  "type": "registry:ui",
  "title": "Link cue",
  "description": "A real URL host-chip link and a separate anchored source field, with an explicit footprint and one confirmed source-history transaction.",
  "dependencies": [
    "@base-ui/react@^1.8.0"
  ],
  "registryDependencies": [
    "https://metalui.dev/r/button.json",
    "https://metalui.dev/r/field.json",
    "https://metalui.dev/r/icons.json",
    "https://metalui.dev/r/mark.json",
    "https://metalui.dev/r/popover.json",
    "https://metalui.dev/r/tokens.json"
  ],
  "files": [
    {
      "path": "packages/metalui/src/components/link-cue/link-cue.tsx",
      "type": "registry:ui",
      "target": "components/metalui/components/link-cue/link-cue.tsx",
      "content": "'use client';\nimport * as React from 'react';\nimport { Button as BaseButton } from '@base-ui/react/button';\nimport { Icon } from '../../icons/Icon';\nimport { MarkUrl, useReadingLine } from '../mark/mark';\nimport { MARK_GLYPH_SIZE } from '../mark/identity.generated';\nimport { Popover } from '../popover/popover';\nimport { Field } from '../field/field';\nimport { Button } from '../button/button';\n\n/** Parse for navigation/display only. The authored source is never replaced by URL.href. */\nfunction destination(words: string) {\n  if (/\\s/.test(words)) return null;\n  try { const url = new URL(words); return ['http:', 'https:'].includes(url.protocol) && !!url.hostname ? url : null; } catch { return null; }\n}\nexport interface LinkCueProps extends Omit<React.AnchorHTMLAttributes<HTMLAnchorElement>, 'value' | 'onChange' | 'children' | 'href'> {\n  value: string;\n  label: string;\n  /** Widest permitted source words AND resolved host alternatives, reserved once before editing. */\n  footprint: readonly string[];\n  disabled?: boolean;\n  readOnly?: boolean;\n  raw?: boolean;\n  editing?: boolean;\n  editProps?: Omit<React.ButtonHTMLAttributes<HTMLButtonElement>, 'children' | 'value' | 'disabled'>;\n  inputAria?: Pick<React.InputHTMLAttributes<HTMLInputElement>, 'aria-describedby' | 'aria-description'>;\n  /** Optional host constraint after absolute HTTP(S) URL and fixed-footprint validation. */\n  validate?: (words: string) => string | undefined;\n  onBegin?: () => boolean | void;\n  onChange: (words: string) => boolean | void;\n  onCommit?: () => void;\n  onCancel?: () => void;\n}\n\n/* LINK CUE STORYBOARD\n * rest    real host-chip anchor follows its exact URL; a host-declared source/host footprint is paid\n * hover   the separate edit glyph appears in the reserved meaning clearance; Enter on link follows\n * edit    the shared field owns a local draft, one host range snapshot, and no source previews\n * apply   validate full source and footprint, then write the exact words and commit once\n * cancel  Escape/outside/unmount/stale source restores the snapshot; a refused draft stays editable\n * Reduced motion is the donor popover fade. There is no rest clock or independently tuned motion.\n */\nexport const LinkCue = React.forwardRef<HTMLAnchorElement, LinkCueProps>(function LinkCue(props, ref) {\n  const reading = useReadingLine();\n  const { value, label, footprint, disabled = false, readOnly = false, raw = false, editing: _editing,\n    editProps, inputAria, validate: _validate, onBegin: _begin, onChange: _change, onCommit: _commit, onCancel: _cancel, className, ...linkProps } = props;\n  const root = React.useRef<HTMLSpanElement>(null);\n  const reserve = React.useRef<HTMLSpanElement>(null);\n  const [open, setOpen] = React.useState(false);\n  const [draft, setDraft] = React.useState(value);\n  const [error, setError] = React.useState<string>();\n  const active = React.useRef<{ original: string; footprint: string } | null>(null);\n  const callbacks = React.useRef(props); callbacks.current = props;\n  const plan = JSON.stringify(footprint);\n  const url = destination(value);\n  const mutable = !disabled && !readOnly && footprint.length > 0;\n  function cancel(render = true) {\n    if (!active.current) return;\n    active.current = null;\n    if (render) { setOpen(false); setError(undefined); }\n    callbacks.current.onCancel?.();\n  }\n  const latestCancel = React.useRef(cancel); latestCancel.current = cancel;\n  React.useEffect(() => {\n    const held = active.current;\n    if (held && (value !== held.original || props.editing === false || !mutable || held.footprint !== plan)) latestCancel.current();\n  }, [value, props.editing, mutable, plan]);\n  React.useEffect(() => () => latestCancel.current(false), []);\n  function apply() {\n    if (!active.current || !mutable) return;\n    const nextDestination = destination(draft);\n    if (!nextDestination) { setError('Enter an absolute HTTP or HTTPS URL.'); return; }\n    // One input operation measures the reserved source font; no animation-frame or rest reads.\n    const element = root.current, reserved = reserve.current;\n    const context = document.createElement('canvas').getContext('2d');\n    if (!element || !reserved || !context) { setError('The source footprint is not available yet.'); return; }\n    const font = getComputedStyle(element); context.font = `${font.fontStyle} ${font.fontWeight} ${font.fontSize} ${font.fontFamily}`;\n    const spacing = parseFloat(font.letterSpacing) || 0;\n    const sourceWidth = context.measureText(draft).width + Math.max(0, draft.length - 1) * spacing;\n    const chip = reserved.querySelector<HTMLAnchorElement>('.mu-cue-url');\n    if (!chip) { setError('The source footprint is not available yet.'); return; }\n    const chipFont = getComputedStyle(chip);\n    context.font = `${chipFont.fontStyle} ${chipFont.fontWeight} ${chipFont.fontSize} ${chipFont.fontFamily}`;\n    const chipWidth = context.measureText(nextDestination.hostname).width + Math.max(0, nextDestination.hostname.length - 1) * (parseFloat(chipFont.letterSpacing) || 0)\n      + MARK_GLYPH_SIZE + (parseFloat(chipFont.columnGap) || 0) + (parseFloat(chipFont.paddingLeft) || 0) + (parseFloat(chipFont.paddingRight) || 0);\n    if (Math.ceil(Math.max(sourceWidth, chipWidth)) > Math.ceil(reserved.getBoundingClientRect().width)) { setError('This URL exceeds the source footprint allowed by the document.'); return; }\n    const refused = callbacks.current.validate?.(draft);\n    if (refused) { setError(refused); return; }\n    if (draft !== value && callbacks.current.onChange(draft) === false) { cancel(); return; }\n    active.current = null; setOpen(false); setError(undefined); callbacks.current.onCommit?.();\n  }\n  const description = [...new Set(['Enter follows this destination. The separate edit key changes its exact URL.', linkProps['aria-description']].filter(Boolean))].join(' ');\n  const shared = { ...linkProps, ref, href: !disabled && url ? value : undefined, role: disabled || !url ? 'link' : linkProps.role,\n    tabIndex: disabled || !url ? linkProps.tabIndex ?? 0 : linkProps.tabIndex, 'aria-disabled': disabled || !url || undefined,\n    'aria-label': `${label}: ${value}`, 'aria-description': description, title: value,\n    onClick: (event: React.MouseEvent<HTMLAnchorElement>) => { if (disabled || !url) event.preventDefault(); else linkProps.onClick?.(event); },\n    onAuxClick: (event: React.MouseEvent<HTMLAnchorElement>) => { if (disabled || !url) event.preventDefault(); else linkProps.onAuxClick?.(event); },\n    onKeyDown: (event: React.KeyboardEvent<HTMLAnchorElement>) => { if ((disabled || !url) && event.key === 'Enter') event.preventDefault(); else linkProps.onKeyDown?.(event); },\n  };\n  return <span ref={root} className={`mu-link-cue group relative ${reading ? 'inline-flex items-center gap-mu-space-4' : 'inline-grid'} align-baseline type-content text-ink${className ? ` ${className}` : ''}`} data-value={value} data-editing={open || undefined}>\n    <span ref={reserve} aria-hidden className={`invisible col-start-1 row-start-1 inline-grid whitespace-nowrap${reading ? ' absolute end-0 pointer-events-none' : ''}`}>\n      {footprint.map((words, index) => [<span key={`${index}-source-${words}`} className=\"col-start-1 row-start-1\">{words}</span>, <span key={`${index}-host-${words}`} className=\"col-start-1 row-start-1\"><MarkUrl tabIndex={-1} host={destination(words)?.hostname ?? words} glyph={<Icon name=\"link\" size={MARK_GLYPH_SIZE} />} /></span>])}\n    </span>\n    <span className=\"col-start-1 row-start-1 justify-self-start\">\n      {raw ? <a {...shared} className=\"mu-link-cue-link outline-none underline focus-visible:focus-ring\">{value}</a>\n        : <MarkUrl {...shared} host={url?.hostname ?? value} glyph={<Icon name=\"link\" size={MARK_GLYPH_SIZE} />} />}\n    </span>\n    <Popover open={open} onOpenChange={next => {\n      if (!next) { cancel(); setOpen(false); return; }\n      if (!mutable || callbacks.current.onBegin?.() === false) return;\n      active.current = { original: value, footprint: plan }; setDraft(value); setError(undefined); setOpen(true);\n    }}>\n      <Popover.Trigger><BaseButton {...editProps} disabled={disabled} focusableWhenDisabled={readOnly} aria-disabled={!mutable || undefined}\n        aria-label={`Edit ${label} URL`} aria-description={['Enter opens the URL field. Enter applies; Escape cancels.', editProps?.['aria-description'], linkProps['aria-description']].filter(Boolean).join(' ')}\n        className={`mu-link-cue-edit inline-flex flex-none border-0 p-0 bg-transparent text-ink3 pointer-hover:text-ink2 outline-none focus-visible:focus-ring${reading ? '' : ' absolute end-0 bottom-full mb-mu-space-2 opacity-0 group-pointer-hover:opacity-100 group-focus-within:opacity-100'}`}>\n        <Icon name=\"pen\" size={MARK_GLYPH_SIZE} />\n      </BaseButton></Popover.Trigger>\n      <Popover.Content aria-label={`Edit ${label} URL`} align=\"start\">\n        <Popover.Title>{label}</Popover.Title>\n        <Popover.Description>Write the full URL. The document keeps your exact words.</Popover.Description>\n        <Popover.Body><form className=\"mu-stack gap-mu-related\" onSubmit={event => { event.preventDefault(); apply(); }}>\n          <Field size=\"regular\" invalid={!!error}><Field.Icon><Icon name=\"link\" /></Field.Icon><Field.Input {...inputAria} aria-label={`${label} URL`} value={draft} onChange={event => { setDraft(event.target.value); setError(undefined); }} /></Field>\n          {error && <p role=\"alert\" className=\"type-meta text-red m-0\">{error}</p>}\n          <div className=\"mu-cluster gap-mu-related\"><Button size=\"compact\" cap=\"primary\" type=\"submit\" icon={<Icon name=\"check\" />}>Apply URL</Button><Button size=\"compact\" cap=\"standard\" onClick={() => cancel()} icon={<Icon name=\"close\" />}>Cancel URL edit</Button></div>\n        </form></Popover.Body>\n      </Popover.Content>\n    </Popover>\n  </span>;\n});\n"
    },
    {
      "path": "packages/metalui/src/components/link-cue/link-cue.css",
      "type": "registry:file",
      "target": "components/metalui/components/link-cue/link-cue.css",
      "content": "/* LinkCue composes the existing MarkUrl host pill, source content type, meaning clearance, Popover,\n   regular Field and canonical pen/check/close keys. The host supplies the fixed source footprint. */\n"
    }
  ],
  "docs": "Agent guide: https://metalui.dev/r/link-cue.md. SwiftUI: MetalLinkCue in the MetalUI Swift package."
}
