{"version":3,"sources":["../src/lib/BaseTabbablePlugin.ts","../src/lib/findTabDestination.ts"],"sourcesContent":["import { type PluginConfig, createTSlatePlugin, KEYS } from '@udecode/plate';\n\nimport type { TabbableEntry } from './types';\n\nexport type TabblableConfig = PluginConfig<\n  'tabbable',\n  {\n    /**\n     * When true, the plugin will add its event listener to the document instead\n     * of the editor, allowing it to capture events from outside the editor.\n     *\n     * @default: false\n     */\n    globalEventListener?: boolean;\n    /**\n     * Add additional tabbables to the list of tabbables. Useful for adding\n     * tabbables that are not contained within the editor. Ignores\n     * `isTabbable`.\n     *\n     * @default: () => []\n     */\n    insertTabbableEntries?: (event: KeyboardEvent) => TabbableEntry[];\n    /**\n     * Determine whether an element should be included in the tabbable list.\n     *\n     * @default: (editor, tabbableEntry) => editor.api.isVoid(tabbableEntry.slateNode)\n     */\n    isTabbable?: (entry: TabbableEntry) => boolean;\n    /**\n     * Dynamically enable or disable the plugin.\n     *\n     * @default: () => true\n     */\n    query?: (event: KeyboardEvent) => boolean;\n  }\n>;\n\nexport const BaseTabbablePlugin = createTSlatePlugin<TabblableConfig>({\n  key: KEYS.tabbable,\n  options: {\n    globalEventListener: false,\n    insertTabbableEntries: () => [],\n    query: () => true,\n  },\n}).extend(({ editor }) => ({\n  options: {\n    isTabbable: (tabbableEntry) => editor.api.isVoid(tabbableEntry.slateNode),\n  },\n}));\n","import { type SlateEditor, PathApi } from '@udecode/plate';\n\nimport type { TabbableEntry, TabDestination } from './types';\n\nexport interface FindTabDestinationOptions {\n  activeTabbableEntry: TabbableEntry | null;\n  direction: 'backward' | 'forward';\n  tabbableEntries: TabbableEntry[];\n}\n\nexport const findTabDestination = (\n  editor: SlateEditor,\n  { activeTabbableEntry, direction, tabbableEntries }: FindTabDestinationOptions\n): TabDestination | null => {\n  // Case 1: A tabbable entry was active before tab was pressed\n  if (activeTabbableEntry) {\n    // Find the next tabbable entry after the active one\n    const activeTabbableEntryIndex =\n      tabbableEntries.indexOf(activeTabbableEntry);\n    const nextTabbableEntryIndex =\n      activeTabbableEntryIndex + (direction === 'forward' ? 1 : -1);\n    const nextTabbableEntry = tabbableEntries[nextTabbableEntryIndex];\n\n    /**\n     * If the next tabbable entry originated from the same path as the active\n     * tabbable entry, focus it.\n     *\n     * Examples of when this is true:\n     *\n     * - We're inside a void node and there is an additional tabbable inside the\n     *   same void node.\n     * - We're inside a popover containing multiple tabbable elements all anchored\n     *   to the same slate node, and there is an additional tabbable inside the\n     *   same popover.\n     *\n     * Examples of when this is false:\n     *\n     * - We're inside a void node and the next tabbable is outside the void node.\n     * - We're in the last tabbable element of a popover.\n     * - There is no next tabbable element.\n     */\n    if (\n      nextTabbableEntry &&\n      PathApi.equals(activeTabbableEntry.path, nextTabbableEntry.path)\n    ) {\n      return {\n        domNode: nextTabbableEntry.domNode,\n        type: 'dom-node',\n      };\n    }\n    /**\n     * Otherwise, return the focus to the editor. If we're moving forward, focus\n     * the first point after the active tabbable's path. If we're moving\n     * backward, focus the point of the active tabbable's path. TODO: Let a\n     * tabbable entry specify custom before and after points.\n     */\n    if (direction === 'forward') {\n      const pointAfter = editor.api.after(activeTabbableEntry.path);\n\n      if (!pointAfter) return null;\n\n      return {\n        path: pointAfter.path,\n        type: 'path',\n      };\n    }\n\n    return {\n      path: editor.api.point(activeTabbableEntry.path)!.path,\n      type: 'path',\n    };\n  }\n\n  // Case 2: No tabbable entry was active before tab was pressed\n\n  const selectionPath = editor.selection?.anchor?.path || [];\n\n  // Find the first tabbable entry after the selection\n  const nextTabbableEntry =\n    direction === 'forward'\n      ? tabbableEntries.find(\n          (entry) => !PathApi.isBefore(entry.path, selectionPath)\n        )\n      : [...tabbableEntries]\n          .reverse()\n          .find((entry) => PathApi.isBefore(entry.path, selectionPath));\n\n  // If it exists, focus it\n  if (nextTabbableEntry) {\n    return {\n      domNode: nextTabbableEntry.domNode,\n      type: 'dom-node',\n    };\n  }\n\n  // Otherwise, use the default behaviour\n  return null;\n};\n"],"mappings":";AAAA,SAA4B,oBAAoB,YAAY;AAqCrD,IAAM,qBAAqB,mBAAoC;AAAA,EACpE,KAAK,KAAK;AAAA,EACV,SAAS;AAAA,IACP,qBAAqB;AAAA,IACrB,uBAAuB,MAAM,CAAC;AAAA,IAC9B,OAAO,MAAM;AAAA,EACf;AACF,CAAC,EAAE,OAAO,CAAC,EAAE,OAAO,OAAO;AAAA,EACzB,SAAS;AAAA,IACP,YAAY,CAAC,kBAAkB,OAAO,IAAI,OAAO,cAAc,SAAS;AAAA,EAC1E;AACF,EAAE;;;AChDF,SAA2B,eAAe;AAUnC,IAAM,qBAAqB,CAChC,QACA,EAAE,qBAAqB,WAAW,gBAAgB,MACxB;AAE1B,MAAI,qBAAqB;AAEvB,UAAM,2BACJ,gBAAgB,QAAQ,mBAAmB;AAC7C,UAAM,yBACJ,4BAA4B,cAAc,YAAY,IAAI;AAC5D,UAAMA,qBAAoB,gBAAgB,sBAAsB;AAoBhE,QACEA,sBACA,QAAQ,OAAO,oBAAoB,MAAMA,mBAAkB,IAAI,GAC/D;AACA,aAAO;AAAA,QACL,SAASA,mBAAkB;AAAA,QAC3B,MAAM;AAAA,MACR;AAAA,IACF;AAOA,QAAI,cAAc,WAAW;AAC3B,YAAM,aAAa,OAAO,IAAI,MAAM,oBAAoB,IAAI;AAE5D,UAAI,CAAC,WAAY,QAAO;AAExB,aAAO;AAAA,QACL,MAAM,WAAW;AAAA,QACjB,MAAM;AAAA,MACR;AAAA,IACF;AAEA,WAAO;AAAA,MACL,MAAM,OAAO,IAAI,MAAM,oBAAoB,IAAI,EAAG;AAAA,MAClD,MAAM;AAAA,IACR;AAAA,EACF;AAIA,QAAM,gBAAgB,OAAO,WAAW,QAAQ,QAAQ,CAAC;AAGzD,QAAM,oBACJ,cAAc,YACV,gBAAgB;AAAA,IACd,CAAC,UAAU,CAAC,QAAQ,SAAS,MAAM,MAAM,aAAa;AAAA,EACxD,IACA,CAAC,GAAG,eAAe,EAChB,QAAQ,EACR,KAAK,CAAC,UAAU,QAAQ,SAAS,MAAM,MAAM,aAAa,CAAC;AAGpE,MAAI,mBAAmB;AACrB,WAAO;AAAA,MACL,SAAS,kBAAkB;AAAA,MAC3B,MAAM;AAAA,IACR;AAAA,EACF;AAGA,SAAO;AACT;","names":["nextTabbableEntry"]}