{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "navigation-feedback-docs",
  "title": "Navigation Feedback",
  "description": "Flash a landed target after TOC, footnote, search, or custom navigation jumps.",
  "files": [
    {
      "path": "../../content/docs/(plugins)/(functionality)/navigation-feedback.mdx",
      "content": "---\ntitle: Navigation Feedback\ndescription: Flash a landed target after TOC, footnote, search, or custom navigation jumps.\n---\n\n<ComponentPreview name=\"toc-demo\" />\n\n<PackageInfo>\n\n## Features\n\n- Briefly highlight the landed node after a navigation jump.\n- Replace any previous flash deterministically — no stacked timers, no doubled animations.\n- Expose transforms for flash-only and full select-focus-scroll-flash flows.\n- Inject `data-nav-*` attributes so any render can style the active target.\n\n</PackageInfo>\n\nNavigation Feedback is a small core plugin for \"you landed here\" UX. It doesn't own navigation itself — it flashes the landed node so the reader can see where the editor just moved. Reach for it in TOC jumps, footnote navigation, search results, and custom outline surfaces.\n\n## Usage\n\n<Steps>\n\n### Core Plugin\n\n`NavigationFeedbackPlugin` is part of Plate core and is included by `createPlateEditor` automatically. You don't need to add it to the `plugins` array for the defaults to work.\n\n### Configure Duration\n\nThe default flash lasts 1.6 seconds. If you want it tighter or longer, use the top-level `navigationFeedback` editor option:\n\n```tsx\nimport { createPlateEditor } from 'platejs/react';\n\nconst editor = createPlateEditor({\n  navigationFeedback: {\n    duration: 1200,\n  },\n});\n```\n\n- `navigationFeedback.duration`: Default flash duration in milliseconds. **Default:** `1600`.\n\nDisable the plugin entirely if you don't want any landed-target flash:\n\n```tsx\nconst editor = createPlateEditor({\n  navigationFeedback: false,\n});\n```\n\n### Flash a Target\n\nWhen another action already handled scroll, focus, and selection, and you only want the visual confirmation, call `editor.tf.navigation.flashTarget`:\n\n```tsx\neditor.tf.navigation.flashTarget({\n  target: {\n    path: [12],\n    type: 'node',\n  },\n});\n```\n\nThe call returns `false` when the path doesn't resolve to a node, so you can branch on stale targets without throwing.\n\nOverride the duration or variant per call when this jump deserves its own look:\n\n```tsx\neditor.tf.navigation.flashTarget({\n  duration: 1500,\n  target: {\n    path: [12],\n    type: 'node',\n  },\n  variant: 'mention',\n});\n```\n\nThe `variant` string is written straight to `data-nav-highlight`, so you can key distinct CSS animations off it — `'navigated'`, `'mention'`, `'found'`, whatever you need.\n\n### Navigate and Flash\n\nMost navigation actions should do four things at once: move the selection, focus the editor, scroll the target into view, and flash the landed node. `editor.tf.navigation.navigate` does all four, and each step is independent — skip any of them with the flags below.\n\nHere's how the footnote plugin jumps from a reference to its definition:\n\n```tsx\neditor.tf.navigation.navigate({\n  focus: true,\n  scroll: true,\n  scrollTarget: point,\n  select: {\n    anchor: { offset: 0, path: firstTextPath },\n    focus: { offset: 0, path: firstTextPath },\n  },\n  target: {\n    path: definition[1],\n    type: 'node',\n  },\n});\n```\n\nSkip the flash when the jump should be silent:\n\n```tsx\neditor.tf.navigation.navigate({\n  flash: false,\n  select: point,\n  target: { path: [12], type: 'node' },\n});\n```\n\nOr tune the flash per call:\n\n```tsx\neditor.tf.navigation.navigate({\n  flash: { duration: 1200, variant: 'mention' },\n  select: point,\n  target: { path: [12], type: 'node' },\n});\n```\n\nIf you don't pass `scrollTarget`, Plate picks a scroll point in this order: `select.focus`, `select.anchor`, `select` (when it's a `Point`), then `editor.api.start(target.path)`.\n\n### Style the Landed Target\n\nWhenever a target is active, the plugin injects transient attributes onto that node's DOM and a CSS variable with the current duration:\n\n| Attribute | Value |\n| --- | --- |\n| `data-nav-target` | `\"true\"` on the active node. |\n| `data-nav-highlight` | Current `variant` (e.g. `\"navigated\"`). |\n| `data-nav-cycle` | `\"0\"` or `\"1\"` — alternates per flash so CSS animations restart cleanly. |\n| `data-nav-pulse` | Monotonic pulse counter. Useful for debugging repeat triggers. |\n| `--plate-nav-feedback-duration` | Inline CSS variable set to `${duration}ms`. |\n\nStyle them anywhere your editor styles live:\n\n```css\n.slate-editor [data-nav-highlight] {\n  border-radius: 0.375rem;\n}\n\n.slate-editor [data-nav-highlight][data-nav-cycle='0'] {\n  animation: plate-nav-highlight-a var(--plate-nav-feedback-duration, 900ms)\n    ease-out;\n}\n\n.slate-editor [data-nav-highlight][data-nav-cycle='1'] {\n  animation: plate-nav-highlight-b var(--plate-nav-feedback-duration, 900ms)\n    ease-out;\n}\n```\n\n<Callout type=\"info\">\n  **Why two animations?** Flashing the same node twice in a row on the same keyframe name wouldn't restart the animation. The plugin alternates `data-nav-cycle` between `0` and `1` so adjacent flashes run different animation names and the browser replays cleanly.\n</Callout>\n\n### Highlight Custom Renders\n\nAttribute injection fires through the plugin's `nodeProps` inject, so any standard `PlateElement` render that spreads its `attributes` onto the root DOM node picks up `data-nav-*` for free.\n\nFor atoms, inline voids, or components that need the highlight state inside JSX (e.g. on a nested button), read it with `useNavigationHighlight`:\n\n```tsx\nimport { useNavigationHighlight, usePath } from 'platejs/react';\n\nexport function FootnoteReferenceElement(props) {\n  const path = usePath();\n  const highlight = useNavigationHighlight(path);\n\n  return (\n    <PlateElement\n      {...props}\n      attributes={{\n        ...props.attributes,\n        'data-nav-cycle': highlight ? String(highlight.cycle) : undefined,\n        'data-nav-highlight': highlight?.variant,\n        'data-nav-pulse': highlight ? String(highlight.pulse) : undefined,\n        'data-nav-target': highlight ? 'true' : undefined,\n        style: {\n          ...props.attributes.style,\n          '--plate-nav-feedback-duration': highlight\n            ? `${highlight.duration}ms`\n            : undefined,\n        },\n      }}\n    >\n      {props.children}\n    </PlateElement>\n  );\n}\n```\n\nThe hook returns `null` when this node isn't the active target and the full target (`{ cycle, duration, path, pulse, type, variant }`) when it is. Pass a `Path`, a `TElement`, or a `TText` — paths go through, nodes get resolved via `editor.api.findPath`.\n\nDone. You now have a deterministic flash on every jump and the wiring to style or extend it.\n\n</Steps>\n\n## Plugins\n\n### `NavigationFeedbackPlugin`\n\nCore plugin for transient \"landed target\" feedback after successful navigation.\n\n<API name=\"NavigationFeedbackPlugin\">\n<APIOptions>\n  <APIItem name=\"duration\" type=\"number\">\n    Default flash duration in milliseconds.\n    - **Default:** `1600`\n  </APIItem>\n</APIOptions>\n</API>\n\n## API\n\n### `api.navigation.activeTarget`\n\nGet the current flashed target, or `null` when none is active. The returned target carries a resolved `path`, so later edits that shift the target keep the highlight on the right node.\n\n<API name=\"activeTarget\">\n<APIReturns>\n  <APIItem name=\"return\" type=\"NavigationFeedbackActiveTarget | null\">\n    Active target `{ cycle, duration, path, pulse, type, variant }`, or `null`.\n  </APIItem>\n</APIReturns>\n</API>\n\n### `api.navigation.clear`\n\nClear the current feedback target immediately. Safe to call when nothing is active.\n\n<API name=\"clear\" />\n\n### `api.navigation.isTarget`\n\nCheck whether a given path is the current flashed target.\n\n<API name=\"isTarget\">\n<APIParameters>\n  <APIItem name=\"path\" type=\"Path\">\n    Path to compare against the active target.\n  </APIItem>\n</APIParameters>\n<APIReturns>\n  <APIItem name=\"return\" type=\"boolean\">\n    `true` when there is an active target and its path equals `path`.\n  </APIItem>\n</APIReturns>\n</API>\n\n## Transforms\n\n### `tf.navigation.flashTarget`\n\nFlash a target node without changing selection, focus, or scroll. Replaces any active flash on the same editor.\n\n<API name=\"flashTarget\">\n<APIParameters>\n  <APIItem name=\"target\" type=\"{ path: Path; type: 'node' }\">\n    Node target to flash.\n  </APIItem>\n  <APIItem name=\"duration\" type=\"number\" optional>\n    Override the default duration for this call.\n  </APIItem>\n  <APIItem name=\"variant\" type=\"string\" optional>\n    Highlight variant stored in `data-nav-highlight`.\n    - **Default:** `navigated`\n  </APIItem>\n</APIParameters>\n<APIReturns>\n  <APIItem name=\"return\" type=\"boolean\">\n    `false` when the path doesn't resolve to a node, `true` otherwise.\n  </APIItem>\n</APIReturns>\n</API>\n\n### `tf.navigation.navigate`\n\nSelect, focus, scroll, and flash a target in one call. Each step is independent — skip any of them with the flags below.\n\n<API name=\"navigate\">\n<APIParameters>\n  <APIItem name=\"target\" type=\"{ path: Path; type: 'node' }\">\n    Node target to navigate to.\n  </APIItem>\n  <APIItem name=\"select\" type=\"Point | TRange\" optional>\n    Point (collapsed) or range to apply before scrolling and flashing.\n  </APIItem>\n  <APIItem name=\"focus\" type=\"boolean\" optional>\n    Focus the editor after selection.\n    - **Default:** `true`\n  </APIItem>\n  <APIItem name=\"scroll\" type=\"boolean\" optional>\n    Scroll the resolved point into view.\n    - **Default:** `true`\n  </APIItem>\n  <APIItem name=\"scrollTarget\" type=\"Point\" optional>\n    Explicit point to scroll into view. Falls back to `select.focus`, `select.anchor`, `select`, then `editor.api.start(target.path)`.\n  </APIItem>\n  <APIItem name=\"flash\" type=\"false | { duration?: number; variant?: string }\" optional>\n    Per-call flash config. Pass `false` to navigate without flashing.\n  </APIItem>\n</APIParameters>\n<APIReturns>\n  <APIItem name=\"return\" type=\"boolean\">\n    `false` when the path doesn't resolve to a node, `true` otherwise.\n  </APIItem>\n</APIReturns>\n</API>\n\n### `tf.navigation.clear`\n\nClear the current flashed target immediately. Same effect as `api.navigation.clear`.\n\n<API name=\"clear\" />\n\n## Hooks\n\n### `useNavigationHighlight`\n\nSubscribe a custom render to the active navigation target. Returns the target metadata when the given path/node matches, `null` otherwise.\n\n<API name=\"useNavigationHighlight\">\n<APIParameters>\n  <APIItem name=\"target\" type=\"Path | TElement | TText | null | undefined\">\n    Path to compare, or a node the hook resolves via `editor.api.findPath`.\n  </APIItem>\n</APIParameters>\n<APIReturns>\n  <APIItem name=\"return\" type=\"NavigationFeedbackActiveTarget | null\">\n    Active target metadata when this node is the current flashed target, `null` otherwise.\n  </APIItem>\n</APIReturns>\n</API>\n\n## Related Docs\n\n- [Table of Contents](/docs/toc)\n- [Footnote](/docs/footnote)\n- [Editor Methods](/docs/editor-methods)\n- [Plugin Configuration](/docs/plugin)\n",
      "type": "registry:file",
      "target": "content/docs/plate/(plugins)/(functionality)/navigation-feedback.mdx"
    }
  ],
  "type": "registry:file"
}