@elabs-ai/components-viewer 4.1.0 → 5.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (207) hide show
  1. package/dist/{code-adapter-ADZ4UOGN.js → adapters/code/code-adapter.js} +15 -64
  2. package/dist/adapters/code/code-adapter.js.map +1 -0
  3. package/dist/adapters/code/code-language.js +10 -0
  4. package/dist/adapters/code/code-language.js.map +1 -0
  5. package/dist/adapters/code/code-manifest.js +10 -0
  6. package/dist/adapters/code/code-manifest.js.map +1 -0
  7. package/dist/adapters/code/code-theme.js +8 -0
  8. package/dist/adapters/code/code-theme.js.map +1 -0
  9. package/dist/{csv-adapter-7HOFZ5Z6.js → adapters/csv/csv-adapter.js} +13 -9
  10. package/dist/adapters/csv/csv-adapter.js.map +1 -0
  11. package/dist/adapters/csv/csv-manifest.js +9 -0
  12. package/dist/adapters/csv/csv-manifest.js.map +1 -0
  13. package/dist/{docx-adapter-5CQDHWTD.js → adapters/docx/docx-adapter.js} +17 -175
  14. package/dist/adapters/docx/docx-adapter.js.map +1 -0
  15. package/dist/adapters/docx/docx-manifest.js +9 -0
  16. package/dist/adapters/docx/docx-manifest.js.map +1 -0
  17. package/dist/adapters/docx/docx-model.js +21 -0
  18. package/dist/adapters/docx/docx-model.js.map +1 -0
  19. package/dist/{image-adapter-WOHZR24J.js → adapters/image/image-adapter.js} +4 -3
  20. package/dist/adapters/image/image-adapter.js.map +1 -0
  21. package/dist/adapters/image/image-manifest.js +9 -0
  22. package/dist/adapters/image/image-manifest.js.map +1 -0
  23. package/dist/adapters/index.js +56 -0
  24. package/dist/adapters/index.js.map +1 -0
  25. package/dist/{json-adapter-ZUW5GQHE.js → adapters/json/json-adapter.js} +4 -3
  26. package/dist/adapters/json/json-adapter.js.map +1 -0
  27. package/dist/adapters/json/json-manifest.js +9 -0
  28. package/dist/adapters/json/json-manifest.js.map +1 -0
  29. package/dist/{markdown-adapter-YC6WTBS4.js → adapters/markdown/markdown-adapter.js} +14 -36
  30. package/dist/adapters/markdown/markdown-adapter.js.map +1 -0
  31. package/dist/adapters/markdown/markdown-manifest.js +9 -0
  32. package/dist/adapters/markdown/markdown-manifest.js.map +1 -0
  33. package/dist/adapters/markdown/markdown-marks.js +10 -0
  34. package/dist/adapters/markdown/markdown-marks.js.map +1 -0
  35. package/dist/{media-adapter-MCTB4GBH.js → adapters/media/media-adapter.js} +4 -3
  36. package/dist/adapters/media/media-adapter.js.map +1 -0
  37. package/dist/adapters/media/media-manifest.js +9 -0
  38. package/dist/adapters/media/media-manifest.js.map +1 -0
  39. package/dist/{pdf-adapter-5PMKEXUD.js → adapters/pdf/pdf-adapter.js} +31 -81
  40. package/dist/adapters/pdf/pdf-adapter.js.map +1 -0
  41. package/dist/adapters/pdf/pdf-engine.js +14 -0
  42. package/dist/adapters/pdf/pdf-engine.js.map +1 -0
  43. package/dist/adapters/pdf/pdf-manifest.js +9 -0
  44. package/dist/adapters/pdf/pdf-manifest.js.map +1 -0
  45. package/dist/adapters/pdf/pdf-text.js +20 -0
  46. package/dist/adapters/pdf/pdf-text.js.map +1 -0
  47. package/dist/{pptx-adapter-BONWFHYT.js → adapters/pptx/pptx-adapter.js} +34 -135
  48. package/dist/adapters/pptx/pptx-adapter.js.map +1 -0
  49. package/dist/adapters/pptx/pptx-manifest.js +9 -0
  50. package/dist/adapters/pptx/pptx-manifest.js.map +1 -0
  51. package/dist/adapters/pptx/pptx-model.js +37 -0
  52. package/dist/adapters/pptx/pptx-model.js.map +1 -0
  53. package/dist/{text-adapter-NFNWB5W3.js → adapters/text/text-adapter.js} +8 -5
  54. package/dist/adapters/text/text-adapter.js.map +1 -0
  55. package/dist/adapters/text/text-manifest.js +9 -0
  56. package/dist/adapters/text/text-manifest.js.map +1 -0
  57. package/dist/{xlsx-adapter-O3XIVLFC.js → adapters/xlsx/xlsx-adapter.js} +12 -8
  58. package/dist/adapters/xlsx/xlsx-adapter.js.map +1 -0
  59. package/dist/adapters/xlsx/xlsx-manifest.js +9 -0
  60. package/dist/adapters/xlsx/xlsx-manifest.js.map +1 -0
  61. package/dist/chunk-3HKQFFZW.js +31 -0
  62. package/dist/chunk-3HKQFFZW.js.map +1 -0
  63. package/dist/chunk-5VJWILZ7.js +182 -0
  64. package/dist/chunk-5VJWILZ7.js.map +1 -0
  65. package/dist/{chunk-UL43NGUG.js → chunk-5YE2MUS2.js} +1 -1
  66. package/dist/{chunk-5OMONA2G.js → chunk-6FVJ4D7W.js} +3 -15
  67. package/dist/chunk-6FVJ4D7W.js.map +1 -0
  68. package/dist/{chunk-RGWDIQJR.js → chunk-7EASJBQM.js} +2 -2
  69. package/dist/chunk-7PGGKQVY.js +26 -0
  70. package/dist/chunk-7PGGKQVY.js.map +1 -0
  71. package/dist/{chunk-XBOR2YXS.js → chunk-AK7F3IZE.js} +9 -30
  72. package/dist/chunk-AK7F3IZE.js.map +1 -0
  73. package/dist/chunk-AOYEL7O3.js +34 -0
  74. package/dist/chunk-AOYEL7O3.js.map +1 -0
  75. package/dist/chunk-BDUFH66C.js +61 -0
  76. package/dist/chunk-BDUFH66C.js.map +1 -0
  77. package/dist/chunk-BWMXMHVZ.js +107 -0
  78. package/dist/chunk-BWMXMHVZ.js.map +1 -0
  79. package/dist/chunk-CIT2HHRI.js +9 -0
  80. package/dist/chunk-CIT2HHRI.js.map +1 -0
  81. package/dist/chunk-CLOI4HBA.js +18 -0
  82. package/dist/chunk-CLOI4HBA.js.map +1 -0
  83. package/dist/chunk-CMYBLW7D.js +21 -0
  84. package/dist/chunk-CMYBLW7D.js.map +1 -0
  85. package/dist/chunk-CO2VJRSU.js +18 -0
  86. package/dist/chunk-CO2VJRSU.js.map +1 -0
  87. package/dist/{chunk-3U3MESBU.js → chunk-EA72HA4I.js} +2 -2
  88. package/dist/{chunk-KOMDRWOU.js → chunk-FDT4QOIG.js} +2 -2
  89. package/dist/chunk-FQHZEQUH.js +90 -0
  90. package/dist/chunk-FQHZEQUH.js.map +1 -0
  91. package/dist/chunk-G27UJZWH.js +82 -0
  92. package/dist/chunk-G27UJZWH.js.map +1 -0
  93. package/dist/{chunk-BCF3RWRL.js → chunk-GH4UBYST.js} +2 -2
  94. package/dist/{chunk-2NQ4RSJ3.js → chunk-GNBD53AT.js} +2 -2
  95. package/dist/{chunk-VO2273Z2.js → chunk-HOQZ7OPH.js} +2 -18
  96. package/dist/chunk-HOQZ7OPH.js.map +1 -0
  97. package/dist/{chunk-GGR7BNW4.js → chunk-HUUJIFFT.js} +2 -2
  98. package/dist/{chunk-FELIAGKI.js → chunk-I5XD2NJ6.js} +2 -2
  99. package/dist/chunk-IB4N72FA.js +21 -0
  100. package/dist/chunk-IB4N72FA.js.map +1 -0
  101. package/dist/{chunk-4OFQYALG.js → chunk-IQ6Z4X3K.js} +8 -68
  102. package/dist/chunk-IQ6Z4X3K.js.map +1 -0
  103. package/dist/{chunk-AORNMH77.js → chunk-JHFC2KBI.js} +1 -5
  104. package/dist/chunk-JHFC2KBI.js.map +1 -0
  105. package/dist/chunk-LRPZ373O.js +93 -0
  106. package/dist/chunk-LRPZ373O.js.map +1 -0
  107. package/dist/chunk-M77ICVWP.js +24 -0
  108. package/dist/chunk-M77ICVWP.js.map +1 -0
  109. package/dist/chunk-MJUH32ZK.js +24 -0
  110. package/dist/chunk-MJUH32ZK.js.map +1 -0
  111. package/dist/chunk-PAIVK4N6.js +162 -0
  112. package/dist/chunk-PAIVK4N6.js.map +1 -0
  113. package/dist/chunk-PKYDKXCO.js +129 -0
  114. package/dist/chunk-PKYDKXCO.js.map +1 -0
  115. package/dist/chunk-QIPKBOZJ.js +59 -0
  116. package/dist/chunk-QIPKBOZJ.js.map +1 -0
  117. package/dist/chunk-SASY77ZL.js +29 -0
  118. package/dist/chunk-SASY77ZL.js.map +1 -0
  119. package/dist/{chunk-SLXRUYT3.js → chunk-SBG7WPGO.js} +2 -44
  120. package/dist/chunk-SBG7WPGO.js.map +1 -0
  121. package/dist/chunk-T4Y4YRHJ.js +733 -0
  122. package/dist/chunk-T4Y4YRHJ.js.map +1 -0
  123. package/dist/chunk-TLCNGQKL.js +37 -0
  124. package/dist/chunk-TLCNGQKL.js.map +1 -0
  125. package/dist/chunk-US5KT3TR.js +141 -0
  126. package/dist/chunk-US5KT3TR.js.map +1 -0
  127. package/dist/{chunk-HMG2ERXH.js → chunk-YA72QE57.js} +2 -2
  128. package/dist/{chunk-H3OAON3D.js → chunk-YWHFMCS4.js} +2 -2
  129. package/dist/{chunk-UJIJECEF.js → chunk-ZAVXZRIO.js} +2 -2
  130. package/dist/components/grid-text.js +19 -0
  131. package/dist/components/grid-text.js.map +1 -0
  132. package/dist/components/marked-text.js +10 -0
  133. package/dist/components/marked-text.js.map +1 -0
  134. package/dist/components/sheet-table.js +14 -0
  135. package/dist/components/sheet-table.js.map +1 -0
  136. package/dist/core/errors.js +18 -0
  137. package/dist/core/errors.js.map +1 -0
  138. package/dist/core/highlight-marks.js +10 -0
  139. package/dist/core/highlight-marks.js.map +1 -0
  140. package/dist/core/highlight-resolve.js +10 -0
  141. package/dist/core/highlight-resolve.js.map +1 -0
  142. package/dist/core/highlight.js +12 -0
  143. package/dist/core/highlight.js.map +1 -0
  144. package/dist/core/registry.js +12 -0
  145. package/dist/core/registry.js.map +1 -0
  146. package/dist/core/scroll-host.js +10 -0
  147. package/dist/core/scroll-host.js.map +1 -0
  148. package/dist/core/text-index.js +14 -0
  149. package/dist/core/text-index.js.map +1 -0
  150. package/dist/core/types.js +8 -0
  151. package/dist/core/types.js.map +1 -0
  152. package/dist/core/use-highlight-scroll.js +11 -0
  153. package/dist/core/use-highlight-scroll.js.map +1 -0
  154. package/dist/core/use-page-control.js +9 -0
  155. package/dist/core/use-page-control.js.map +1 -0
  156. package/dist/core/use-paged-scroll.js +10 -0
  157. package/dist/core/use-paged-scroll.js.map +1 -0
  158. package/dist/core/use-viewport-size.js +10 -0
  159. package/dist/core/use-viewport-size.js.map +1 -0
  160. package/dist/core/zoom.js +16 -0
  161. package/dist/core/zoom.js.map +1 -0
  162. package/dist/file-viewer/file-viewer-context.js +11 -0
  163. package/dist/file-viewer/file-viewer-context.js.map +1 -0
  164. package/dist/file-viewer/file-viewer-find.js +13 -0
  165. package/dist/file-viewer/file-viewer-find.js.map +1 -0
  166. package/dist/file-viewer/file-viewer-pager.js +10 -0
  167. package/dist/file-viewer/file-viewer-pager.js.map +1 -0
  168. package/dist/file-viewer/file-viewer-zoom.js +13 -0
  169. package/dist/file-viewer/file-viewer-zoom.js.map +1 -0
  170. package/dist/file-viewer/file-viewer.js +48 -0
  171. package/dist/file-viewer/file-viewer.js.map +1 -0
  172. package/dist/index.js +78 -1268
  173. package/dist/index.js.map +1 -1
  174. package/package.json +6 -6
  175. package/src/__contract__/file-viewer.contract.test.tsx +49 -0
  176. package/src/file-viewer/file-viewer-recipes.stories.tsx +406 -0
  177. package/src/file-viewer/file-viewer.stories.tsx +2 -2
  178. package/src/file-viewer/file-viewer.test.tsx +35 -1
  179. package/src/file-viewer/file-viewer.tsx +16 -3
  180. package/dist/chunk-4OFQYALG.js.map +0 -1
  181. package/dist/chunk-5OMONA2G.js.map +0 -1
  182. package/dist/chunk-AORNMH77.js.map +0 -1
  183. package/dist/chunk-SLXRUYT3.js.map +0 -1
  184. package/dist/chunk-VO2273Z2.js.map +0 -1
  185. package/dist/chunk-XBOR2YXS.js.map +0 -1
  186. package/dist/code-adapter-ADZ4UOGN.js.map +0 -1
  187. package/dist/csv-adapter-7HOFZ5Z6.js.map +0 -1
  188. package/dist/docx-adapter-5CQDHWTD.js.map +0 -1
  189. package/dist/image-adapter-WOHZR24J.js.map +0 -1
  190. package/dist/json-adapter-ZUW5GQHE.js.map +0 -1
  191. package/dist/markdown-adapter-YC6WTBS4.js.map +0 -1
  192. package/dist/media-adapter-MCTB4GBH.js.map +0 -1
  193. package/dist/pdf-adapter-5PMKEXUD.js.map +0 -1
  194. package/dist/pptx-adapter-BONWFHYT.js.map +0 -1
  195. package/dist/text-adapter-NFNWB5W3.js.map +0 -1
  196. package/dist/xlsx-adapter-O3XIVLFC.js.map +0 -1
  197. /package/dist/{chunk-UL43NGUG.js.map → chunk-5YE2MUS2.js.map} +0 -0
  198. /package/dist/{chunk-RGWDIQJR.js.map → chunk-7EASJBQM.js.map} +0 -0
  199. /package/dist/{chunk-3U3MESBU.js.map → chunk-EA72HA4I.js.map} +0 -0
  200. /package/dist/{chunk-KOMDRWOU.js.map → chunk-FDT4QOIG.js.map} +0 -0
  201. /package/dist/{chunk-BCF3RWRL.js.map → chunk-GH4UBYST.js.map} +0 -0
  202. /package/dist/{chunk-2NQ4RSJ3.js.map → chunk-GNBD53AT.js.map} +0 -0
  203. /package/dist/{chunk-GGR7BNW4.js.map → chunk-HUUJIFFT.js.map} +0 -0
  204. /package/dist/{chunk-FELIAGKI.js.map → chunk-I5XD2NJ6.js.map} +0 -0
  205. /package/dist/{chunk-HMG2ERXH.js.map → chunk-YA72QE57.js.map} +0 -0
  206. /package/dist/{chunk-H3OAON3D.js.map → chunk-YWHFMCS4.js.map} +0 -0
  207. /package/dist/{chunk-UJIJECEF.js.map → chunk-ZAVXZRIO.js.map} +0 -0
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/file-viewer/file-viewer.tsx"],"sourcesContent":["\"use client\";\n\n/**\n * `FileViewer` — the compound shell.\n *\n * Every part is composed from `@elabs-ai/components-ui` primitives;\n * this package contributes file LOGIC (detection, parsing, the page model) and\n * never a parallel widget set. There is no `showToolbar` boolean: compose the\n * parts you want, or render `<FileViewer>` for the batteries-included default.\n *\n * Surface separation (`styling-and-tokens.md`): the frame is one raised `card`\n * with a border, the toolbar is divided from the content by a single `border-b`\n * — the sole structural cue between two same-fill regions, so it takes the\n * strong rung.\n */\n\nimport {\n Button,\n cn,\n downloadBlob,\n fileIconFor,\n IconButton,\n normalizeFileSource,\n normalizeQuoteTextWithOffsets,\n queryToRanges,\n resolveFileKind,\n Separator,\n Skeleton,\n StatePanel,\n Text,\n useLocale,\n type FileSource,\n type ProseHeadingLevel,\n} from \"@elabs-ai/components-ui\";\nimport { DownloadIcon, EyeOffIcon, SearchXIcon } from \"lucide-react\";\nimport {\n forwardRef,\n useCallback,\n useDeferredValue,\n useEffect,\n useMemo,\n useRef,\n useState,\n type HTMLAttributes,\n type KeyboardEvent as ReactKeyboardEvent,\n type ReactNode,\n} from \"react\";\n\nimport { createDefaultRegistry } from \"../adapters\";\nimport {\n ViewerError,\n isAbort,\n isModuleNotFound,\n parserMissingError,\n toViewerError,\n type ViewerErrorCode,\n} from \"../core/errors\";\nimport {\n FIND_MATCH_LIMIT,\n findMatchId,\n type DocumentHighlight,\n type HighlightSupport,\n type ResolvedHighlight,\n} from \"../core/highlight\";\nimport { resolveHighlights } from \"../core/highlight-resolve\";\nimport type { ViewerRegistry } from \"../core/registry\";\nimport type { DocumentRotation, ZoomLevel } from \"../core/types\";\nimport { DEFAULT_ZOOM, stepZoom } from \"../core/zoom\";\nimport { FileViewerFind, isFindShortcut } from \"./file-viewer-find\";\nimport { FileViewerPager } from \"./file-viewer-pager\";\nimport { FileViewerRotate, FileViewerZoom } from \"./file-viewer-zoom\";\nimport {\n FileViewerContext,\n useFileViewer,\n type FileViewerContextValue,\n type FileViewerFindState,\n type FileViewerLoadState,\n} from \"./file-viewer-context\";\n\n/** Stable empties, so a provider with no citations does not re-render on identity. */\nconst NO_HIGHLIGHTS: readonly DocumentHighlight[] = [];\nconst NO_RESOLVED: readonly ResolvedHighlight[] = [];\nconst NO_SUPPORT: HighlightSupport = [];\n\n/* -------------------------------------------------------------------------- */\n/* Provider */\n/* -------------------------------------------------------------------------- */\n\nexport interface FileViewerProviderProps {\n /** The file to show. `undefined` is the empty state, not an error. */\n source?: FileSource;\n /**\n * Adapters available to this viewer. Defaults to the built-ins.\n * Pass your own to add a format, drop one, or override a built-in.\n */\n registry?: ViewerRegistry;\n /**\n * Force the not-ready state while a parent fetches the source itself.\n * ORed with the viewer's own loading — a parent can add loading, never remove it.\n */\n loading?: boolean;\n /**\n * The rung a viewed document's own top-level heading renders at. Default `2`.\n *\n * A file carries its OWN heading tree, and that tree is only correct relative\n * to the page hosting it: a README's `#` rendered as an `<h1>` inside an app\n * that already has one puts two `h1`s in a screen reader's flat heading list,\n * and the frame's `<section aria-label>` does not fix that — most screen\n * readers list headings flat, not per landmark. The default assumes the\n * common case, a viewer embedded BELOW the page's own heading; pass `1` when\n * the viewer genuinely is the page.\n *\n * Adapters that render no headings ignore it. Same seam as\n * `@elabs-ai/components-ai`'s `MarkdownView baseHeadingLevel`.\n */\n baseHeadingLevel?: ProseHeadingLevel;\n /**\n * The parts of the document to point at — an answer's citations, a search\n * result's context, anything the app already knows about the file.\n *\n * A PROP rather than provider-only state because citations originate outside\n * the viewer entirely: the chat pane that produced them usually lives in\n * another route, and it owns which one the reader clicked. Pass\n * `defaultHighlights` instead to let the viewer own them.\n */\n highlights?: readonly DocumentHighlight[];\n /** Uncontrolled initial citations. Ignored when `highlights` is supplied. */\n defaultHighlights?: readonly DocumentHighlight[];\n onHighlightsChange?: (highlights: readonly DocumentHighlight[]) => void;\n /**\n * Which citation the viewer is pointed at. `null` is \"none\" explicitly;\n * `undefined` is what selects uncontrolled mode, so the two are not\n * interchangeable here.\n */\n activeHighlightId?: string | null;\n defaultActiveHighlightId?: string | null;\n onActiveHighlightChange?: (id: string | null) => void;\n /**\n * Which page the viewer is on, 1-based — controlled.\n *\n * A trio (`component-api.md`) rather than provider-only state because the page\n * is routinely something the APP owns: a deep link to page 7, a URL the reader\n * can share, a position restored from a \"continue reading\" record. Clamped to\n * the document on read, so an out-of-range value degrades to the nearest real\n * page instead of blanking the canvas.\n */\n pageNumber?: number;\n /** Uncontrolled initial page. Ignored when `pageNumber` is supplied. */\n defaultPageNumber?: number;\n onPageNumberChange?: (page: number) => void;\n /**\n * The scale to draw at, or a fit mode — controlled. Persisting a reader's\n * preferred zoom across files and sessions is the reason this is a prop.\n */\n zoom?: ZoomLevel;\n /**\n * Uncontrolled initial zoom. Ignored when `zoom` is supplied.\n *\n * Defaults to `\"fit-width\"`, not `1`: a viewer's job on open is to show the\n * document, and a 4000px scan or an A4 page at 100% in a 600px pane shows its\n * top-left corner. Every reader-facing PDF viewer opens fitted for the same\n * reason. Pass `1` for true 100%.\n */\n defaultZoom?: ZoomLevel;\n onZoomChange?: (zoom: ZoomLevel) => void;\n /** Quarter-turns clockwise — controlled. */\n rotation?: DocumentRotation;\n /** Uncontrolled initial rotation. Ignored when `rotation` is supplied. Default `0`. */\n defaultRotation?: DocumentRotation;\n onRotationChange?: (rotation: DocumentRotation) => void;\n children: ReactNode;\n}\n\n/** Matches for the current query, plus whether the cap swallowed any. */\ninterface FindMatches {\n highlights: readonly DocumentHighlight[];\n total: number;\n truncated: boolean;\n}\n\nconst NO_MATCHES: FindMatches = { highlights: NO_HIGHLIGHTS, total: 0, truncated: false };\n\nexport function FileViewerProvider({\n source,\n registry: registryProp,\n loading = false,\n baseHeadingLevel = 2,\n highlights: highlightsProp,\n defaultHighlights,\n onHighlightsChange,\n activeHighlightId: activeHighlightIdProp,\n defaultActiveHighlightId = null,\n onActiveHighlightChange,\n pageNumber: pageNumberProp,\n defaultPageNumber = 1,\n onPageNumberChange,\n zoom: zoomProp,\n defaultZoom = \"fit-width\",\n onZoomChange,\n rotation: rotationProp,\n defaultRotation = 0,\n onRotationChange,\n children,\n}: FileViewerProviderProps) {\n // A default registry per provider, not per module: one screen's `register()`\n // override must not leak into another's.\n const fallbackRegistry = useMemo(() => createDefaultRegistry(), []);\n const registry = registryProp ?? fallbackRegistry;\n\n const [attempt, setAttempt] = useState(0);\n const [state, setState] = useState<FileViewerLoadState>({ status: \"empty\", capabilities: {} });\n\n const resolved = useMemo(() => (source ? normalizeFileSource(source) : undefined), [source]);\n\n useEffect(() => {\n if (!resolved) {\n setState({ status: \"empty\", capabilities: {} });\n return;\n }\n\n const controller = new AbortController();\n let instance: { dispose?: () => void } | undefined;\n let cancelled = false;\n\n const kind = resolveFileKind(resolved.name, resolved.mediaType);\n const manifest = registry.detect(kind);\n\n // Capabilities come from the eager manifest, so the chrome is correct from\n // the first frame — before a single byte of parser is fetched.\n setState({\n status: \"loading\",\n source: resolved,\n capabilities: manifest?.capabilities ?? {},\n });\n\n void (async () => {\n try {\n if (!manifest) {\n throw new ViewerError(\"unsupported-format\", `No adapter can open \"${resolved.name}\".`, {\n fileName: resolved.name,\n });\n }\n const adapter = await registry.load(manifest.id);\n // A fresh instance per document — adapters may hold per-document state.\n const parser = adapter.create();\n instance = parser;\n const document = await parser.load(resolved, { signal: controller.signal });\n if (cancelled) return;\n setState({\n status: \"ready\",\n source: resolved,\n document,\n adapter,\n capabilities: manifest.capabilities ?? {},\n });\n } catch (error) {\n // A cancelled load is a superseded one — reporting it would flash an\n // error every time the user picks a different file.\n if (cancelled || isAbort(error)) return;\n // Every parser engine is reached by a dynamic `import()` INSIDE the\n // adapter's own `load()`, so \"the optional peer is not installed\" lands\n // here, not on `registry.load()` above. Falling through to `parse-failed`\n // would tell the reader their file is damaged and offer a retry that can\n // never succeed.\n const missingPeer =\n isModuleNotFound(error) && manifest\n ? parserMissingError(manifest.id, manifest.requires ?? [], {\n fileName: resolved.name,\n cause: error,\n })\n : undefined;\n setState({\n status: \"error\",\n source: resolved,\n capabilities: manifest?.capabilities ?? {},\n error: missingPeer ?? toViewerError(error, \"parse-failed\", { fileName: resolved.name }),\n });\n }\n })();\n\n return () => {\n cancelled = true;\n controller.abort();\n instance?.dispose?.();\n // Releases any object URL the source minted for this load.\n resolved.revoke();\n };\n }, [resolved, registry, attempt]);\n\n /* ---- Citations: a prop the app may control, or the viewer's own --------- */\n\n const highlightsControlled = highlightsProp !== undefined;\n const [ownHighlights, setOwnHighlights] = useState(defaultHighlights ?? NO_HIGHLIGHTS);\n const highlights = highlightsControlled ? highlightsProp : ownHighlights;\n const setHighlights = useCallback(\n (next: readonly DocumentHighlight[]) => {\n // Mirroring the platform: a controlled value is never written locally, so\n // the component can't drift from the prop that owns it.\n if (!highlightsControlled) setOwnHighlights(next);\n onHighlightsChange?.(next);\n },\n [highlightsControlled, onHighlightsChange],\n );\n\n const activeControlled = activeHighlightIdProp !== undefined;\n const [ownActiveId, setOwnActiveId] = useState<string | null>(defaultActiveHighlightId);\n const activeHighlightId = activeControlled ? activeHighlightIdProp : ownActiveId;\n const setActiveHighlight = useCallback(\n (id: string | null) => {\n if (!activeControlled) setOwnActiveId(id);\n onActiveHighlightChange?.(id);\n },\n [activeControlled, onActiveHighlightChange],\n );\n\n /* ---- View: which page, at what scale, turned which way ------------------ */\n\n const pageCount = state.document?.pageCount ?? 0;\n\n const pageControlled = pageNumberProp !== undefined;\n const [ownPage, setOwnPage] = useState(defaultPageNumber);\n const rawPage = pageControlled ? pageNumberProp : ownPage;\n // Clamped on READ. Writing a clamped value back at a controlled owner would\n // fight it, and a document whose page count shrank (a different file, same\n // provider) must not blank the canvas while the owner catches up.\n const pageNumber =\n pageCount > 0 ? Math.min(Math.max(1, rawPage), pageCount) : Math.max(1, rawPage);\n const goToPage = useCallback(\n (page: number) => {\n const next = Math.max(1, Math.round(page));\n if (!pageControlled) setOwnPage(next);\n onPageNumberChange?.(next);\n },\n [pageControlled, onPageNumberChange],\n );\n\n const zoomControlled = zoomProp !== undefined;\n const [ownZoom, setOwnZoom] = useState<ZoomLevel>(defaultZoom);\n const zoom = zoomControlled ? zoomProp : ownZoom;\n const setZoom = useCallback(\n (next: ZoomLevel) => {\n if (!zoomControlled) setOwnZoom(next);\n onZoomChange?.(next);\n },\n [zoomControlled, onZoomChange],\n );\n\n // What a fit mode actually became. Only the renderer can know it — it is the\n // one measuring its viewport — so this is a report, not a derivation.\n const [reportedZoom, setReportedZoom] = useState(DEFAULT_ZOOM);\n const effectiveZoom = typeof zoom === \"number\" ? zoom : reportedZoom;\n\n const rotationControlled = rotationProp !== undefined;\n const [ownRotation, setOwnRotation] = useState<DocumentRotation>(defaultRotation);\n const rotation = rotationControlled ? rotationProp : ownRotation;\n const setRotation = useCallback(\n (next: DocumentRotation) => {\n if (!rotationControlled) setOwnRotation(next);\n onRotationChange?.(next);\n },\n [rotationControlled, onRotationChange],\n );\n\n // Opening a DIFFERENT file starts at its first page, the right way up. Zoom\n // deliberately survives: a reader who zoomed in is reading at that size, and\n // snapping back to 100% on every file fights them.\n //\n // Compared against the previous source rather than run on mount, so a\n // `defaultPageNumber` (deep link, restored position) is not clobbered by its\n // own first render.\n const previousSource = useRef(resolved);\n useEffect(() => {\n if (previousSource.current === resolved) return;\n previousSource.current = resolved;\n setOwnPage(1);\n setOwnRotation(0);\n }, [resolved]);\n\n /* ---- Find-in-document: entirely the viewer's own ------------------------ */\n\n const [find, setFind] = useState({\n open: false,\n query: \"\",\n caseSensitive: false,\n activeIndex: 0,\n });\n\n // Whether a `FileViewerFind` part is actually composed into this viewer.\n //\n // The frame swallows Ctrl/Cmd+F, and swallowing it without a box to show is\n // strictly worse than not intercepting at all: the reader loses the browser's\n // own find and gets nothing in return. The parts are composable by design, so\n // \"the adapter could paint a match\" is not the same question as \"there is\n // somewhere to type\" — the find part answers the second by registering itself.\n const [findParts, setFindParts] = useState(0);\n const registerFind = useCallback(() => {\n setFindParts((count) => count + 1);\n return () => setFindParts((count) => count - 1);\n }, []);\n\n const text = state.document?.text;\n const capabilities = state.capabilities;\n const support = capabilities.highlight ?? NO_SUPPORT;\n\n // The declared `search` flag is an override in the OFF direction only. Read\n // as the source of truth it would deny the find box to PDF — the format\n // readers expect it on most — purely because that manifest predates the\n // feature. Read as \"can this adapter paint what find produces\" it is right by\n // construction: find emits `range` addresses, so `range` support is the bar.\n const canFind = (capabilities.search ?? true) && text !== undefined && support.includes(\"range\");\n\n // Deferred so a keystroke paints immediately and the (potentially large)\n // match scan lands a frame later, instead of blocking the caret.\n const query = useDeferredValue(find.query);\n const findMatches = useMemo<FindMatches>(() => {\n if (!find.open || !canFind || text === undefined || query.length === 0) return NO_MATCHES;\n // Deliberately NOT run through `normalizeRanges`: it merges ADJACENT\n // ranges, which is right for a fuzzy matcher painting one contiguous mark\n // and wrong here — searching \"l\" in \"hello\" finds two matches, and merging\n // them into one would make the counter disagree with what a reader counts.\n // A single needle's matches never overlap, so there is nothing to merge.\n const ranges = queryToRanges(text, query, find.caseSensitive);\n return {\n highlights: ranges.slice(0, FIND_MATCH_LIMIT).map(([start, end], index) => ({\n id: findMatchId(index),\n address: { kind: \"range\" as const, start, end },\n source: \"search\" as const,\n })),\n total: ranges.length,\n truncated: ranges.length > FIND_MATCH_LIMIT,\n };\n }, [find.open, find.caseSensitive, canFind, text, query]);\n\n /* ---- Locate: the one step that runs outside the adapter ----------------- */\n\n // Folded once per document, not once per keystroke: find re-resolves on every\n // character, and folding a 2 MB projection each time is what turns a search\n // box into a stutter.\n const normalized = useMemo(\n () => (text === undefined ? undefined : normalizeQuoteTextWithOffsets(text)),\n [text],\n );\n\n // Two things can claim to be \"the current one\": a citation the app pointed at,\n // and the match the reader is stepping through. The precedence is stated once,\n // here — while the find box is open and matching, find wins, because it is what\n // the reader's own keystrokes are moving. Leaving both active would paint the\n // reader's match like every other match while a citation kept the active plate.\n const activeFindId =\n find.open && findMatches.highlights.length > 0 ? findMatchId(find.activeIndex) : undefined;\n\n /**\n * The id the RENDERER is pointed at — the effective one, not the citation knob.\n *\n * A renderer that owns a pager or a tab strip navigates on this, and the shared\n * scroll hook fires on it. Handing it `state.activeHighlightId` instead would\n * leave find-in-document unable to scroll to, page to, or switch sheet to its\n * own match whenever no citation happened to be active.\n */\n const currentHighlightId = activeFindId ?? activeHighlightId;\n\n const resolvedHighlights = useMemo<readonly ResolvedHighlight[]>(() => {\n if (highlights.length === 0 && findMatches.highlights.length === 0) return NO_RESOLVED;\n return resolveHighlights([...highlights, ...findMatches.highlights], {\n normalized,\n textLength: text?.length,\n truncated: state.document?.textTruncated,\n supported: support,\n activeId: currentHighlightId,\n });\n }, [\n highlights,\n findMatches.highlights,\n normalized,\n text?.length,\n state.document?.textTruncated,\n support,\n currentHighlightId,\n ]);\n\n /* ---- Stepping ---------------------------------------------------------- */\n\n const stepCitation = useCallback(\n (delta: 1 | -1) => {\n const list = resolvedHighlights.filter(\n (highlight) => highlight.source === \"citation\" && highlight.status === \"resolved\",\n );\n if (list.length === 0) return;\n const at = list.findIndex((highlight) => highlight.id === activeHighlightId);\n // Nothing active yet: \"next\" starts at the top of the document and\n // \"previous\" at the bottom, rather than both landing on the first.\n const to =\n at === -1 ? (delta === 1 ? 0 : list.length - 1) : (at + delta + list.length) % list.length;\n setActiveHighlight((list[to] as ResolvedHighlight).id);\n },\n [resolvedHighlights, activeHighlightId, setActiveHighlight],\n );\n\n const stepFind = useCallback(\n (delta: 1 | -1) => {\n const count = findMatches.highlights.length;\n if (count === 0) return;\n setFind((current) => ({\n ...current,\n activeIndex: (current.activeIndex + delta + count) % count,\n }));\n },\n [findMatches.highlights.length],\n );\n\n const findState = useMemo<FileViewerFindState>(\n () => ({\n open: find.open,\n query: find.query,\n caseSensitive: find.caseSensitive,\n matches: findMatches.total,\n truncated: findMatches.truncated,\n activeIndex: find.activeIndex,\n }),\n [find, findMatches.total, findMatches.truncated],\n );\n\n const actions = useMemo(\n () => ({\n reload: () => setAttempt((n) => n + 1),\n setHighlights,\n setActiveHighlight,\n nextHighlight: () => stepCitation(1),\n previousHighlight: () => stepCitation(-1),\n openFind: () => setFind((current) => ({ ...current, open: true })),\n // The query survives a close, so re-opening resumes where the reader was;\n // only the box goes away.\n closeFind: () => setFind((current) => ({ ...current, open: false })),\n setFindQuery: (next: string) =>\n setFind((current) => ({ ...current, query: next, activeIndex: 0 })),\n setFindCaseSensitive: (next: boolean) =>\n setFind((current) => ({ ...current, caseSensitive: next, activeIndex: 0 })),\n nextFindMatch: () => stepFind(1),\n previousFindMatch: () => stepFind(-1),\n goToPage,\n // Stop at the ends rather than wrap: a document is not a carousel, and a\n // reader who holds \"next\" past the last page expects to stay there.\n nextPage: () => goToPage(Math.min(pageNumber + 1, pageCount || pageNumber + 1)),\n previousPage: () => goToPage(pageNumber - 1),\n setZoom,\n // Stepping from `effectiveZoom`, not from `zoom`: after a fit the ladder\n // has to continue from what is actually on screen.\n zoomIn: () => setZoom(stepZoom(effectiveZoom, 1)),\n zoomOut: () => setZoom(stepZoom(effectiveZoom, -1)),\n setRotation,\n rotate: (quarterTurns: 1 | -1) =>\n setRotation(((((rotation + quarterTurns * 90) % 360) + 360) % 360) as DocumentRotation),\n reportZoom: setReportedZoom,\n registerFind,\n }),\n [\n setHighlights,\n setActiveHighlight,\n stepCitation,\n stepFind,\n registerFind,\n goToPage,\n pageNumber,\n pageCount,\n setZoom,\n effectiveZoom,\n setRotation,\n rotation,\n ],\n );\n\n const value = useMemo<FileViewerContextValue>(\n () => ({\n state: {\n // A parent's `loading` can add the not-ready state but never clear a\n // real error — losing a failure to a stale prop is worse than a late\n // spinner.\n ...(loading && state.status !== \"error\" ? { ...state, status: \"loading\" as const } : state),\n highlights,\n activeHighlightId,\n find: findState,\n pageNumber,\n pageCount,\n zoom,\n effectiveZoom,\n rotation,\n },\n actions,\n registry,\n meta: {\n baseHeadingLevel,\n resolvedHighlights,\n currentHighlightId,\n highlightSupport: support,\n canFind,\n hasFind: findParts > 0,\n },\n }),\n [\n state,\n loading,\n registry,\n baseHeadingLevel,\n highlights,\n activeHighlightId,\n findState,\n actions,\n resolvedHighlights,\n currentHighlightId,\n support,\n canFind,\n findParts,\n pageNumber,\n pageCount,\n zoom,\n effectiveZoom,\n rotation,\n ],\n );\n\n return <FileViewerContext value={value}>{children}</FileViewerContext>;\n}\n\n/* -------------------------------------------------------------------------- */\n/* Frame */\n/* -------------------------------------------------------------------------- */\n\nexport type FileViewerFrameProps = HTMLAttributes<HTMLDivElement>;\n\n/**\n * The bordered surface the toolbar and content sit in, and the scope of the\n * viewer's find shortcut.\n *\n * Ctrl/Cmd+F is handled HERE rather than on `document`: a page may hold several\n * viewers, or a viewer beside an editor that has its own find, and a\n * document-level listener would let whichever mounted last win. Bound to the\n * frame, the shortcut belongs to whichever viewer the reader is actually inside,\n * and the browser's own find is untouched everywhere else on the page.\n */\nexport const FileViewerFrame = forwardRef<HTMLDivElement, FileViewerFrameProps>(\n function FileViewerFrame({ className, children, onKeyDown, ...props }, ref) {\n const { state, actions, meta } = useFileViewer();\n const { t } = useLocale();\n const frame = useRef<HTMLElement | null>(null);\n const wasOpen = useRef(false);\n\n // Closing the box must hand the caret back, or a keyboard reader is left\n // with focus on nothing at the top of the document.\n const findOpen = state.find.open && meta.canFind;\n useEffect(() => {\n if (wasOpen.current && !findOpen) {\n frame.current\n ?.querySelector<HTMLElement>('[data-slot=\"file-viewer-content\"]')\n ?.focus({ preventScroll: true });\n }\n wasOpen.current = findOpen;\n }, [findOpen]);\n\n return (\n <section\n ref={(node) => {\n frame.current = node;\n if (typeof ref === \"function\") ref(node as HTMLDivElement | null);\n else if (ref) ref.current = node as HTMLDivElement | null;\n }}\n data-slot=\"file-viewer\"\n aria-label={t(\"viewer.label\")}\n className={cn(\n \"bg-card text-card-foreground border-border flex h-full min-h-0 flex-col overflow-hidden rounded-lg border shadow-sm\",\n className,\n )}\n onKeyDown={(event) => {\n // The frame renders a `<section>` while its public props type says\n // `HTMLDivElement` — a pre-existing signature. One cast here beats\n // widening the exported ref type and breaking every caller's\n // `useRef<HTMLDivElement>`.\n onKeyDown?.(event as ReactKeyboardEvent<HTMLDivElement>);\n // Only intercept the browser's shortcut when this viewer can actually\n // honour it — otherwise the reader loses their browser find and gets\n // nothing back. Both halves are needed: an adapter that can paint a\n // match (`canFind`) AND a find part composed in to type into\n // (`hasFind`). A hand-composed frame that omits the part keeps the\n // browser's own find, which is the right answer for it.\n if (!event.defaultPrevented && meta.canFind && meta.hasFind && isFindShortcut(event)) {\n event.preventDefault();\n actions.openFind();\n }\n }}\n {...props}\n >\n {children}\n </section>\n );\n },\n);\n\n/* -------------------------------------------------------------------------- */\n/* Toolbar */\n/* -------------------------------------------------------------------------- */\n\nexport interface FileViewerToolbarProps extends HTMLAttributes<HTMLDivElement> {\n /** Extra controls, placed after the built-in actions. */\n actions?: ReactNode;\n}\n\n/**\n * The identity row: glyph, name, actions.\n *\n * No `role=\"toolbar\"` — that role promises roving-tabindex arrow-key navigation,\n * which this row does not implement. The same decision `ViewToolbar` made, and\n * the reason the P1 `Toolbar` primitive exists (ADR 0024 §5a).\n *\n * With no file it renders NOTHING: a row holding a generic glyph and a blank\n * name reads as a broken render, not as chrome. A screen that needs a permanent\n * header composes its own row around `FileViewerFrame` — that is what the parts\n * are for.\n */\nexport const FileViewerToolbar = forwardRef<HTMLDivElement, FileViewerToolbarProps>(\n function FileViewerToolbar({ className, actions, children, ...props }, ref) {\n const { state } = useFileViewer();\n const { t } = useLocale();\n const source = state.source;\n const Glyph = fileIconFor(source?.name ?? \"\", source?.mediaType);\n const [downloadFailed, setDownloadFailed] = useState(false);\n\n if (!source) return null;\n\n const download = () => {\n setDownloadFailed(false);\n void source\n .bytes()\n .then((bytes) => {\n downloadBlob(new Blob([bytes], { type: source.mediaType }), source.name);\n })\n .catch((error: unknown) => {\n console.error(\"FileViewer: download failed\", error);\n setDownloadFailed(true);\n });\n };\n\n return (\n <div\n ref={ref}\n data-slot=\"file-viewer-toolbar\"\n className={cn(\n // The divider is the ONLY cue between toolbar and content (same fill,\n // no elevation change) — WCAG 1.4.11, so the strong rung.\n \"border-border-strong flex shrink-0 items-center gap-2 border-b px-3 py-2\",\n className,\n )}\n {...props}\n >\n <Glyph aria-hidden=\"true\" className=\"text-muted-foreground size-4 shrink-0\" />\n {/* min-w-0 is what actually lets the name truncate inside a flex row. */}\n <Text className=\"min-w-0 flex-1 truncate\" title={source.name}>\n {source.name}\n </Text>\n {children}\n {actions}\n {downloadFailed ? (\n <Text role=\"alert\" className=\"text-destructive-text shrink-0 truncate\">\n {t(\"viewer.error.readFailedBody\", { name: source.name, format: source.mediaType })}\n </Text>\n ) : null}\n <Separator orientation=\"vertical\" className=\"h-4\" />\n {/* `label` is IconButton's single source of truth — it becomes both the\n accessible name and the tooltip, so the two cannot drift. */}\n <IconButton\n variant=\"ghost\"\n label={t(\"viewer.download\", { name: source.name })}\n icon={<DownloadIcon aria-hidden=\"true\" />}\n onClick={download}\n />\n </div>\n );\n },\n);\n\n/* -------------------------------------------------------------------------- */\n/* Highlight status */\n/* -------------------------------------------------------------------------- */\n\nexport type FileViewerHighlightStatusProps = HTMLAttributes<HTMLDivElement>;\n\n/**\n * \"We couldn't find that passage.\"\n *\n * A citation that fails to locate must not fail SILENTLY: the reader clicked a\n * source link and got a document that looks untouched, with no way to tell\n * whether the viewer is broken, the passage moved, or they mis-clicked. The\n * request survives resolution precisely so this line has something to say.\n *\n * Three different pieces of news, three sentences:\n * - `not-found` / `absent` — searched the whole projection; it is not in it.\n * - `not-found` / `truncated` — the projection is capped, so it may lie past it.\n * - `unsupported` — a CAPABILITY GAP: this build cannot point at part of that\n * format. Not the reader's fault and not retryable, same call the error panel\n * already makes for `unsupported-format`.\n *\n * Search misses are excluded: the find bar already counts its own matches, and\n * \"No matches\" there says it better than a second line here would.\n */\nexport const FileViewerHighlightStatus = forwardRef<HTMLDivElement, FileViewerHighlightStatusProps>(\n function FileViewerHighlightStatus({ className, ...props }, ref) {\n const { state, meta } = useFileViewer();\n const { t } = useLocale();\n\n const missed = meta.resolvedHighlights.filter(\n (highlight) =>\n highlight.source === \"citation\" &&\n (highlight.status === \"not-found\" || highlight.status === \"unsupported\"),\n );\n if (missed.length === 0) return null;\n\n const first = missed[0] as ResolvedHighlight;\n const message =\n first.status === \"unsupported\"\n ? t(\"viewer.highlight.unsupported\", {\n format: state.source?.extension.toUpperCase() || \"\",\n })\n : first.reason === \"truncated\"\n ? t(\"viewer.highlight.notFoundTruncated\")\n : t(\"viewer.highlight.notFound\");\n\n return (\n <div\n ref={ref}\n data-slot=\"file-viewer-highlight-status\"\n role=\"status\"\n aria-live=\"polite\"\n className={cn(\n // Information, not a failure — a neutral muted row, never the\n // destructive tone. The divider is the sole cue between it and the\n // content below (WCAG 1.4.11, strong rung).\n \"border-border-strong text-muted-foreground flex shrink-0 items-center gap-2 border-b px-3 py-2\",\n className,\n )}\n {...props}\n >\n <SearchXIcon aria-hidden=\"true\" className=\"size-4 shrink-0\" />\n <span className=\"text-meta min-w-0 flex-1 truncate\">{message}</span>\n </div>\n );\n },\n);\n\n/* -------------------------------------------------------------------------- */\n/* States */\n/* -------------------------------------------------------------------------- */\n\n/**\n * A layout-shaped skeleton, not a spinner: it occupies the box the real content\n * will, so nothing shifts when the file arrives (`loading-states.md`).\n * `aria-hidden` because `Skeleton` is decorative — the single live region on the\n * wrapper is what AT hears.\n */\nexport const FileViewerSkeleton = forwardRef<HTMLDivElement, HTMLAttributes<HTMLDivElement>>(\n function FileViewerSkeleton({ className, ...props }, ref) {\n const { state } = useFileViewer();\n const { t } = useLocale();\n return (\n <div\n ref={ref}\n data-slot=\"file-viewer-skeleton\"\n role=\"status\"\n aria-live=\"polite\"\n className={cn(\"flex h-full flex-col gap-3 p-4\", className)}\n {...props}\n >\n <span className=\"sr-only\">{t(\"viewer.loading\", { name: state.source?.name ?? \"\" })}</span>\n <Skeleton aria-hidden=\"true\" className=\"h-4 w-2/5\" />\n <Skeleton aria-hidden=\"true\" className=\"h-4 w-4/5\" />\n <Skeleton aria-hidden=\"true\" className=\"h-4 w-3/5\" />\n <Skeleton aria-hidden=\"true\" className=\"min-h-24 flex-1\" />\n </div>\n );\n },\n);\n\n/** Message keys per failure code — the code is the contract, the prose is not. */\nconst ERROR_MESSAGES: Record<ViewerErrorCode, { title: string; body: string }> = {\n \"unsupported-format\": {\n title: \"viewer.error.unsupportedFormat\",\n body: \"viewer.error.unsupportedFormatBody\",\n },\n \"parser-missing\": {\n title: \"viewer.error.parserMissing\",\n body: \"viewer.error.parserMissingBody\",\n },\n \"read-failed\": { title: \"viewer.error.readFailed\", body: \"viewer.error.readFailedBody\" },\n \"parse-failed\": { title: \"viewer.error.parseFailed\", body: \"viewer.error.parseFailedBody\" },\n // Neither should ever reach the UI: a protocol mismatch throws at registration\n // and an abort is swallowed as a superseded load. Mapped anyway so the panel\n // can never render an empty title.\n \"protocol-mismatch\": { title: \"viewer.error.parseFailed\", body: \"viewer.error.parseFailedBody\" },\n aborted: { title: \"viewer.error.readFailed\", body: \"viewer.error.readFailedBody\" },\n};\n\n/**\n * A gap in what this build can SHOW — the file is fine, we just cannot draw it.\n * Neither is retryable, and neither is the user's fault, so both are presented\n * as information rather than as a failure (see `FileViewerError`).\n */\nconst CAPABILITY_GAPS = new Set<ViewerErrorCode>([\"unsupported-format\", \"parser-missing\"]);\n\nexport const FileViewerError = forwardRef<HTMLDivElement, HTMLAttributes<HTMLDivElement>>(\n function FileViewerError({ className, ...props }, ref) {\n const { state } = useFileViewer();\n const { t } = useLocale();\n const error = state.error;\n if (!error) return null;\n\n const message = ERROR_MESSAGES[error.code];\n const vars = {\n name: state.source?.name ?? \"\",\n format: state.source?.extension.toUpperCase() || t(\"viewer.error.parseFailed\"),\n packages: error.packages?.join(\", \") ?? \"\",\n };\n\n // \"This build can't draw PDFs\" and \"the network dropped\" are different news.\n // A red alarm on the first one blames the reader for a capability we never\n // shipped, so the gap gets a neutral panel and `status`; a real failure keeps\n // the destructive panel and the `alert` StatePanel already sets.\n const isGap = CAPABILITY_GAPS.has(error.code);\n\n return (\n <div\n ref={ref}\n data-slot=\"file-viewer-error\"\n // min-h-full (not h-full) so a tall panel grows and scrolls instead of\n // being clipped at the top by `justify-center`.\n className={cn(\"flex min-h-full flex-col justify-center p-4\", className)}\n {...(isGap ? { role: \"status\" as const, \"aria-live\": \"polite\" as const } : {})}\n {...props}\n >\n <StatePanel\n kind={isGap ? \"empty\" : \"error\"}\n // A dashed edge invites a drop; this panel accepts nothing. Solid.\n className={isGap ? \"border-solid\" : undefined}\n icon={isGap ? <EyeOffIcon aria-hidden=\"true\" /> : undefined}\n title={t(message.title)}\n description={t(message.body, vars)}\n // `unsupported-format` and `parser-missing` are not retryable — the\n // file has not changed and neither has what is installed. Offering\n // \"Try again\" there teaches users the button does nothing.\n actions={\n error.code === \"read-failed\" || error.code === \"parse-failed\" ? (\n <RetryButton />\n ) : undefined\n }\n />\n </div>\n );\n },\n);\n\n/**\n * The real `Button`, not a styled `<button>`.\n *\n * A hand-rolled `text-primary` link measured 4.14:1 against `StatePanel`'s error\n * wash and failed axe — the brand hue is tuned against `--background`/`--card`,\n * not against a tinted panel. `outline` puts the label on ordinary ink over its\n * own surface, which is contrast-safe on any panel and gives the control a\n * visible hit target besides.\n */\nfunction RetryButton() {\n const { actions } = useFileViewer();\n const { t } = useLocale();\n return (\n <Button variant=\"outline\" size=\"sm\" onClick={actions.reload}>\n {t(\"viewer.retry\")}\n </Button>\n );\n}\n\nexport const FileViewerEmpty = forwardRef<HTMLDivElement, HTMLAttributes<HTMLDivElement>>(\n function FileViewerEmpty({ className, ...props }, ref) {\n const { t } = useLocale();\n return (\n <div\n ref={ref}\n data-slot=\"file-viewer-empty\"\n className={cn(\"flex min-h-full flex-col justify-center p-4\", className)}\n {...props}\n >\n <StatePanel kind=\"empty\" title={t(\"viewer.empty\")} description={t(\"viewer.emptyBody\")} />\n </div>\n );\n },\n);\n\n/* -------------------------------------------------------------------------- */\n/* Content */\n/* -------------------------------------------------------------------------- */\n\nexport type FileViewerContentProps = HTMLAttributes<HTMLDivElement>;\n\n/**\n * The state switch: empty · loading · error · the adapter's own renderer.\n *\n * The adapter supplies its `Renderer` alongside its parser, so this component\n * never grows a per-format `switch` — that is what keeps formats additive.\n *\n * **This is THE scroll boundary** for any adapter that does not manage its own\n * viewport. Two nested `overflow-auto` boxes do not compose: the inner one clips\n * while the outer one's padding stays put, so a long document ends flush against\n * a band of whitespace and reads as a failed render rather than as \"scroll for\n * more\". Adapters whose content simply flows (text, code, markdown, Word) let\n * this scroll; the ones with a fixed sub-control and a scrolling body of their\n * own — PDF pages, PowerPoint slides, a sheet under its tab bar — keep theirs\n * and label it the same way.\n *\n * It is also a **focusable, named region**: a pane that scrolls but contains\n * nothing focusable cannot be reached from a keyboard at all (WCAG 2.1.1), and\n * a plain-text file contains nothing focusable by definition.\n */\nexport const FileViewerContent = forwardRef<HTMLDivElement, FileViewerContentProps>(\n function FileViewerContent({ className, ...props }, ref) {\n const { state, actions, meta } = useFileViewer();\n const { t } = useLocale();\n\n return (\n <div\n ref={ref}\n data-slot=\"file-viewer-content\"\n role=\"region\"\n aria-label={t(\"viewer.content\")}\n tabIndex={0}\n className={cn(\"focus-ring min-h-0 flex-1 overflow-auto p-4\", className)}\n {...props}\n >\n {state.status === \"empty\" && <FileViewerEmpty />}\n {state.status === \"loading\" && <FileViewerSkeleton />}\n {state.status === \"error\" && <FileViewerError />}\n {state.status === \"ready\" && state.adapter && state.document && state.source && (\n <state.adapter.Renderer\n document={state.document}\n source={state.source}\n baseHeadingLevel={meta.baseHeadingLevel}\n highlights={meta.resolvedHighlights}\n // `meta.currentHighlightId`, NOT `state.activeHighlightId`: the\n // renderer must follow the find match too, or find can never scroll,\n // page or switch sheet.\n activeHighlightId={meta.currentHighlightId}\n // The view half (ADR 0026). A renderer that ignores these keeps\n // working — they are optional, and a format with no pages or no\n // scaling never reads them.\n pageNumber={state.pageNumber}\n onPageChange={actions.goToPage}\n zoom={state.zoom}\n onZoomResolved={actions.reportZoom}\n rotation={state.rotation}\n />\n )}\n </div>\n );\n },\n);\n\n/* -------------------------------------------------------------------------- */\n/* Batteries-included */\n/* -------------------------------------------------------------------------- */\n\nexport interface FileViewerProps\n extends\n Omit<FileViewerProviderProps, \"children\">,\n Omit<HTMLAttributes<HTMLDivElement>, \"children\"> {\n /** Replace the default composition. Rendered inside the provider AND the frame. */\n children?: ReactNode;\n}\n\n/**\n * The default composition — provider + frame + toolbar + content.\n *\n * Reach for the parts when you need a different arrangement; this covers the\n * common case in one element.\n */\nexport const FileViewer = forwardRef<HTMLDivElement, FileViewerProps>(function FileViewer(\n {\n // Every provider prop is destructured BY NAME, and the rest goes to the\n // frame. Peeling off only some of them and spreading the remainder was a\n // silent bug: `baseHeadingLevel` type-checked, never reached the provider,\n // and landed on the `<section>` as an unknown DOM attribute. Adding a\n // provider prop above without adding it here re-creates that exactly.\n source,\n registry,\n loading,\n baseHeadingLevel,\n highlights,\n defaultHighlights,\n onHighlightsChange,\n activeHighlightId,\n defaultActiveHighlightId,\n onActiveHighlightChange,\n pageNumber,\n defaultPageNumber,\n onPageNumberChange,\n zoom,\n defaultZoom,\n onZoomChange,\n rotation,\n defaultRotation,\n onRotationChange,\n children,\n ...props\n },\n ref,\n) {\n return (\n <FileViewerProvider\n source={source}\n registry={registry}\n loading={loading}\n baseHeadingLevel={baseHeadingLevel}\n highlights={highlights}\n defaultHighlights={defaultHighlights}\n onHighlightsChange={onHighlightsChange}\n activeHighlightId={activeHighlightId}\n defaultActiveHighlightId={defaultActiveHighlightId}\n onActiveHighlightChange={onActiveHighlightChange}\n pageNumber={pageNumber}\n defaultPageNumber={defaultPageNumber}\n onPageNumberChange={onPageNumberChange}\n zoom={zoom}\n defaultZoom={defaultZoom}\n onZoomChange={onZoomChange}\n rotation={rotation}\n defaultRotation={defaultRotation}\n onRotationChange={onRotationChange}\n >\n <FileViewerFrame ref={ref} {...props}>\n {children ?? (\n <>\n {/* The page and scale controls live in the identity row, not inside\n the canvas: one row of chrome per viewer, and an app that wants\n them somewhere else composes the parts itself. Each renders\n nothing for a format whose manifest does not claim it. */}\n <FileViewerToolbar>\n <FileViewerPager />\n <FileViewerZoom />\n <FileViewerRotate />\n </FileViewerToolbar>\n {/* Renders nothing until Ctrl/Cmd+F, and nothing at all for a\n format whose adapter cannot paint a range. */}\n <FileViewerFind />\n <FileViewerHighlightStatus />\n <FileViewerContent />\n </>\n )}\n </FileViewerFrame>\n </FileViewerProvider>\n );\n});\n"],"mappings":"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","names":["FileViewerFrame","FileViewerToolbar","FileViewerHighlightStatus","FileViewerSkeleton","FileViewerError","FileViewerEmpty","FileViewerContent","FileViewer"]}
@@ -0,0 +1,37 @@
1
+ "use client";
2
+ import {
3
+ createTextIndexBuilder
4
+ } from "./chunk-5YE2MUS2.js";
5
+
6
+ // src/components/grid-text.ts
7
+ var GRID_CELL_SEPARATOR = " ";
8
+ var GRID_ROW_SEPARATOR = "\n";
9
+ var GRID_SHEET_SEPARATOR = "\n\n";
10
+ var GRID_NAME_ROW = -2;
11
+ var GRID_HEAD_ROW = -1;
12
+ function gridToText(sheets) {
13
+ const builder = createTextIndexBuilder({ separator: GRID_ROW_SEPARATOR });
14
+ sheets.forEach((sheet, index) => {
15
+ let separator = index === 0 ? void 0 : GRID_SHEET_SEPARATOR;
16
+ const push = (chunk, row) => {
17
+ builder.push(chunk, { sheet: index, row }, separator);
18
+ if (chunk.length > 0) separator = void 0;
19
+ };
20
+ if (sheet.name) push(sheet.name, GRID_NAME_ROW);
21
+ push(sheet.columns.join(GRID_CELL_SEPARATOR), GRID_HEAD_ROW);
22
+ sheet.rows.forEach((row, rowIndex) => {
23
+ push(row.join(GRID_CELL_SEPARATOR), rowIndex);
24
+ });
25
+ });
26
+ return builder.build();
27
+ }
28
+
29
+ export {
30
+ GRID_CELL_SEPARATOR,
31
+ GRID_ROW_SEPARATOR,
32
+ GRID_SHEET_SEPARATOR,
33
+ GRID_NAME_ROW,
34
+ GRID_HEAD_ROW,
35
+ gridToText
36
+ };
37
+ //# sourceMappingURL=chunk-TLCNGQKL.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/components/grid-text.ts"],"sourcesContent":["/**\n * The one text projection every tabular format is addressed against.\n *\n * CSV and XLSX arrive through different parsers and render into the same\n * `SheetTable`; they should also be *addressable* the same way, or a citation\n * that resolves in a workbook would miss in the CSV export of the same data.\n * So the projection is written once here and both adapters call it.\n *\n * Shape — a sheet's name (when it has one), then its header row, then its body\n * rows; cells joined by a tab, rows by a newline, sheets by a blank line:\n *\n * ```\n * Q3\n * Region\\tRevenue\n * EMEA\\t4.2M\n *\n * Q4\n * …\n * ```\n *\n * **Rows, not cells, are the finest ref** — the same trade the Word adapter\n * makes. A cell-granular index would need a span per cell and a separator per\n * level; a row-granular one needs neither, because a cell's offset is the sum of\n * the cells before it ({@link chunkOffset}). The granularity of the REF is the\n * row; the granularity of the MARK is still the character.\n */\n\nimport { createTextIndexBuilder, type TextIndex } from \"../core/text-index\";\n\n/** Between two cells of the same row. */\nexport const GRID_CELL_SEPARATOR = \"\\t\";\n/** Between two rows of the same sheet. */\nexport const GRID_ROW_SEPARATOR = \"\\n\";\n/** Between two sheets of the same workbook. */\nexport const GRID_SHEET_SEPARATOR = \"\\n\\n\";\n\n/** The `row` of a sheet's own name line. */\nexport const GRID_NAME_ROW = -2;\n/** The `row` of a sheet's header row. */\nexport const GRID_HEAD_ROW = -1;\n\n/** Where a stretch of the projection came from in the grid. */\nexport interface GridRef {\n /** Index into the sheets. `0` for a single-sheet format like CSV. */\n sheet: number;\n /** Body row index, or {@link GRID_HEAD_ROW} / {@link GRID_NAME_ROW}. */\n row: number;\n}\n\n/** What {@link gridToText} needs from a sheet — the shape both adapters already hold. */\nexport interface GridSheetInput {\n /** The tab's name. Absent for a single-sheet format, which has no tab. */\n name?: string;\n columns: readonly string[];\n rows: readonly (readonly string[])[];\n}\n\n/** Project a workbook (or a one-sheet CSV) to text, with the map back to it. */\nexport function gridToText(sheets: readonly GridSheetInput[]): TextIndex<GridRef> {\n const builder = createTextIndexBuilder<GridRef>({ separator: GRID_ROW_SEPARATOR });\n sheets.forEach((sheet, index) => {\n // The blank line goes before whatever this sheet's FIRST line turns out to\n // be — its name, or its header when it has no name.\n let separator = index === 0 ? undefined : GRID_SHEET_SEPARATOR;\n const push = (chunk: string, row: number) => {\n builder.push(chunk, { sheet: index, row }, separator);\n if (chunk.length > 0) separator = undefined;\n };\n\n if (sheet.name) push(sheet.name, GRID_NAME_ROW);\n push(sheet.columns.join(GRID_CELL_SEPARATOR), GRID_HEAD_ROW);\n sheet.rows.forEach((row, rowIndex) => {\n push(row.join(GRID_CELL_SEPARATOR), rowIndex);\n });\n });\n return builder.build();\n}\n"],"mappings":";;;;;;AA8BO,IAAM,sBAAsB;AAE5B,IAAM,qBAAqB;AAE3B,IAAM,uBAAuB;AAG7B,IAAM,gBAAgB;AAEtB,IAAM,gBAAgB;AAmBtB,SAAS,WAAW,QAAuD;AAChF,QAAM,UAAU,uBAAgC,EAAE,WAAW,mBAAmB,CAAC;AACjF,SAAO,QAAQ,CAAC,OAAO,UAAU;AAG/B,QAAI,YAAY,UAAU,IAAI,SAAY;AAC1C,UAAM,OAAO,CAAC,OAAe,QAAgB;AAC3C,cAAQ,KAAK,OAAO,EAAE,OAAO,OAAO,IAAI,GAAG,SAAS;AACpD,UAAI,MAAM,SAAS,EAAG,aAAY;AAAA,IACpC;AAEA,QAAI,MAAM,KAAM,MAAK,MAAM,MAAM,aAAa;AAC9C,SAAK,MAAM,QAAQ,KAAK,mBAAmB,GAAG,aAAa;AAC3D,UAAM,KAAK,QAAQ,CAAC,KAAK,aAAa;AACpC,WAAK,IAAI,KAAK,mBAAmB,GAAG,QAAQ;AAAA,IAC9C,CAAC;AAAA,EACH,CAAC;AACD,SAAO,QAAQ,MAAM;AACvB;","names":[]}
@@ -0,0 +1,141 @@
1
+ "use client";
2
+ import {
3
+ createTextIndexBuilder
4
+ } from "./chunk-5YE2MUS2.js";
5
+
6
+ // src/adapters/pptx/pptx-model.ts
7
+ var DRAWING_NS = "http://schemas.openxmlformats.org/drawingml/2006/main";
8
+ var PRESENTATION_NS = "http://schemas.openxmlformats.org/presentationml/2006/main";
9
+ function orderSlidePaths(paths) {
10
+ return paths.filter((path) => /^ppt\/slides\/slide\d+\.xml$/.test(path)).sort((left, right) => slideNumber(left) - slideNumber(right));
11
+ }
12
+ function slideNumber(path) {
13
+ const match = /(\d+)\.xml$/.exec(path);
14
+ return match?.[1] ? Number(match[1]) : 0;
15
+ }
16
+ function textOf(node) {
17
+ const runs = node.getElementsByTagNameNS(DRAWING_NS, "t");
18
+ let text = "";
19
+ for (const run of Array.from(runs)) text += run.textContent ?? "";
20
+ return text.trim();
21
+ }
22
+ function linesOf(shape) {
23
+ const lines = [];
24
+ for (const paragraph of Array.from(shape.getElementsByTagNameNS(DRAWING_NS, "p"))) {
25
+ const text = textOf(paragraph);
26
+ if (!text) continue;
27
+ const properties = paragraph.getElementsByTagNameNS(DRAWING_NS, "pPr")[0];
28
+ const level = Number(properties?.getAttribute("lvl") ?? 0);
29
+ lines.push({ text, level: Number.isFinite(level) ? level : 0 });
30
+ }
31
+ return lines;
32
+ }
33
+ function placeholderType(shape) {
34
+ const placeholder = shape.getElementsByTagNameNS(PRESENTATION_NS, "ph")[0];
35
+ return placeholder?.getAttribute("type") ?? void 0;
36
+ }
37
+ function tableLines(frame) {
38
+ const lines = [];
39
+ for (const row of Array.from(frame.getElementsByTagNameNS(DRAWING_NS, "tr"))) {
40
+ const cells = Array.from(row.getElementsByTagNameNS(DRAWING_NS, "tc")).map(textOf);
41
+ const text = cells.join(" ").trim();
42
+ if (text) lines.push({ text, level: 0 });
43
+ }
44
+ return lines;
45
+ }
46
+ function parseSlide(document, index) {
47
+ const tree = document.getElementsByTagNameNS(PRESENTATION_NS, "spTree")[0];
48
+ const slide = { index, lines: [] };
49
+ if (!tree) return slide;
50
+ for (const node of Array.from(tree.children)) {
51
+ if (node.namespaceURI !== PRESENTATION_NS) continue;
52
+ if (node.localName === "sp") {
53
+ const type = placeholderType(node);
54
+ const lines = linesOf(node);
55
+ if ((type === "title" || type === "ctrTitle") && !slide.title) {
56
+ const title = lines.map((line) => line.text).join(" ");
57
+ if (title) slide.title = title;
58
+ continue;
59
+ }
60
+ slide.lines.push(...lines);
61
+ continue;
62
+ }
63
+ if (node.localName === "graphicFrame") slide.lines.push(...tableLines(node));
64
+ }
65
+ return slide;
66
+ }
67
+ function parseNotes(document) {
68
+ const tree = document.getElementsByTagNameNS(PRESENTATION_NS, "spTree")[0];
69
+ if (!tree) return void 0;
70
+ const lines = [];
71
+ for (const shape of Array.from(tree.getElementsByTagNameNS(PRESENTATION_NS, "sp"))) {
72
+ if (placeholderType(shape) !== "body") continue;
73
+ for (const line of linesOf(shape)) lines.push(line.text);
74
+ }
75
+ const notes = lines.join("\n").trim();
76
+ return notes || void 0;
77
+ }
78
+ var RELATIONSHIP_NS = "http://schemas.openxmlformats.org/package/2006/relationships";
79
+ function resolveRelative(fromPart, target) {
80
+ if (target.startsWith("/")) return target.slice(1);
81
+ const segments = fromPart.split("/").slice(0, -1);
82
+ for (const segment of target.split("/")) {
83
+ if (segment === "." || segment === "") continue;
84
+ if (segment === "..") segments.pop();
85
+ else segments.push(segment);
86
+ }
87
+ return segments.join("/");
88
+ }
89
+ function notesTarget(rels, slidePath) {
90
+ for (const relationship of Array.from(
91
+ rels.getElementsByTagNameNS(RELATIONSHIP_NS, "Relationship")
92
+ )) {
93
+ if (!relationship.getAttribute("Type")?.endsWith("/notesSlide")) continue;
94
+ const target = relationship.getAttribute("Target");
95
+ if (target) return resolveRelative(slidePath, target);
96
+ }
97
+ return void 0;
98
+ }
99
+ var PPTX_LINE_SEPARATOR = "\n";
100
+ var PPTX_SLIDE_SEPARATOR = "\n\n";
101
+ var PPTX_TITLE_LINE = -1;
102
+ var PPTX_NOTES_LINE = -2;
103
+ function slidesToTextWithMap(slides) {
104
+ const builder = createTextIndexBuilder({ separator: PPTX_LINE_SEPARATOR });
105
+ slides.forEach((slide, index) => {
106
+ let separator = index === 0 ? void 0 : PPTX_SLIDE_SEPARATOR;
107
+ const push = (chunk, line) => {
108
+ if (!chunk) return;
109
+ builder.push(chunk, { slide: slide.index, line }, separator);
110
+ separator = void 0;
111
+ };
112
+ push(slide.title, PPTX_TITLE_LINE);
113
+ slide.lines.forEach((line, lineIndex) => {
114
+ push(line.text, lineIndex);
115
+ });
116
+ push(slide.notes, PPTX_NOTES_LINE);
117
+ });
118
+ return builder.build();
119
+ }
120
+ function slidesToText(slides) {
121
+ return slidesToTextWithMap(slides).text;
122
+ }
123
+
124
+ export {
125
+ DRAWING_NS,
126
+ PRESENTATION_NS,
127
+ orderSlidePaths,
128
+ slideNumber,
129
+ parseSlide,
130
+ parseNotes,
131
+ RELATIONSHIP_NS,
132
+ resolveRelative,
133
+ notesTarget,
134
+ PPTX_LINE_SEPARATOR,
135
+ PPTX_SLIDE_SEPARATOR,
136
+ PPTX_TITLE_LINE,
137
+ PPTX_NOTES_LINE,
138
+ slidesToTextWithMap,
139
+ slidesToText
140
+ };
141
+ //# sourceMappingURL=chunk-US5KT3TR.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/adapters/pptx/pptx-model.ts"],"sourcesContent":["/**\n * PowerPoint XML → a slide outline.\n *\n * ## What a deck preview honestly is\n *\n * A `.pptx` slide is absolutely-positioned drawing: shapes at EMU coordinates,\n * theme-driven fills, transitions, embedded media. Reproducing that faithfully\n * is a rendering engine, not a preview — and a half-faithful reproduction is\n * worse than none, because it looks like the deck while quietly lying about it.\n *\n * So this reads the deck as an OUTLINE: per slide, the title, the text in\n * document order (with its indent level), and the speaker notes. That is the\n * content a reader is looking for when they open a deck in a file browser, it\n * renders in this system's typography, and it is honest about what it is.\n * Anything positional — layout, images, charts, transitions — is deliberately\n * absent; the toolbar's download is the answer for the real thing.\n *\n * Parsing goes through namespace URIs rather than `a:t` / `p:sp` tag names,\n * because the prefix is only a convention: a generator is free to bind the same\n * namespace to a different prefix, and tag-name matching would silently return\n * an empty deck.\n */\n\nimport { createTextIndexBuilder, type TextIndex } from \"../../core/text-index\";\n\n/** The DrawingML namespace — text, paragraphs, tables. */\nexport const DRAWING_NS = \"http://schemas.openxmlformats.org/drawingml/2006/main\";\n/** The PresentationML namespace — shapes, placeholders, the shape tree. */\nexport const PRESENTATION_NS = \"http://schemas.openxmlformats.org/presentationml/2006/main\";\n\n/** One line of slide text, with the indent level PowerPoint gave it. */\nexport interface PptxLine {\n text: string;\n /** Outline depth, `0` for a top-level bullet. */\n level: number;\n}\n\nexport interface PptxSlide {\n /** 1-based position in the deck. */\n index: number;\n /** The title placeholder's text, when the slide has one. */\n title?: string;\n /** Every other line, in document order. */\n lines: PptxLine[];\n /** The slide's speaker notes, when it has any. */\n notes?: string;\n}\n\n/**\n * Order slide parts the way the deck does.\n *\n * A zip lists its entries in whatever order they were written, and\n * `slide10.xml` sorts before `slide2.xml` as a string — so the number is pulled\n * out and compared as a number.\n */\nexport function orderSlidePaths(paths: string[]): string[] {\n return paths\n .filter((path) => /^ppt\\/slides\\/slide\\d+\\.xml$/.test(path))\n .sort((left, right) => slideNumber(left) - slideNumber(right));\n}\n\n/** The `N` in `…/slideN.xml`, or `0` when there is none. */\nexport function slideNumber(path: string): number {\n const match = /(\\d+)\\.xml$/.exec(path);\n return match?.[1] ? Number(match[1]) : 0;\n}\n\n/** All text under a node, in document order, as one string. */\nfunction textOf(node: Element): string {\n const runs = node.getElementsByTagNameNS(DRAWING_NS, \"t\");\n let text = \"\";\n for (const run of Array.from(runs)) text += run.textContent ?? \"\";\n return text.trim();\n}\n\n/** The paragraphs directly under a shape, as lines. Empty paragraphs are dropped. */\nfunction linesOf(shape: Element): PptxLine[] {\n const lines: PptxLine[] = [];\n for (const paragraph of Array.from(shape.getElementsByTagNameNS(DRAWING_NS, \"p\"))) {\n const text = textOf(paragraph);\n if (!text) continue;\n // `lvl` is absent for a top-level bullet, which is the common case.\n const properties = paragraph.getElementsByTagNameNS(DRAWING_NS, \"pPr\")[0];\n const level = Number(properties?.getAttribute(\"lvl\") ?? 0);\n lines.push({ text, level: Number.isFinite(level) ? level : 0 });\n }\n return lines;\n}\n\n/** The placeholder type a shape declares (`title`, `ctrTitle`, `body`, …), if any. */\nfunction placeholderType(shape: Element): string | undefined {\n const placeholder = shape.getElementsByTagNameNS(PRESENTATION_NS, \"ph\")[0];\n return placeholder?.getAttribute(\"type\") ?? undefined;\n}\n\n/** Table text, row by row, tab-separated — a table on a slide is usually the point of it. */\nfunction tableLines(frame: Element): PptxLine[] {\n const lines: PptxLine[] = [];\n for (const row of Array.from(frame.getElementsByTagNameNS(DRAWING_NS, \"tr\"))) {\n const cells = Array.from(row.getElementsByTagNameNS(DRAWING_NS, \"tc\")).map(textOf);\n const text = cells.join(\"\\t\").trim();\n if (text) lines.push({ text, level: 0 });\n }\n return lines;\n}\n\n/**\n * Parse one `ppt/slides/slideN.xml` document into a slide.\n *\n * The shape tree is walked in document order, so the outline reads in the order\n * the shapes were authored rather than in the order the file happens to store\n * them. A shape kind this does not name contributes nothing — the same\n * allowlist-by-parse the Word adapter uses.\n */\nexport function parseSlide(document: Document, index: number): PptxSlide {\n const tree = document.getElementsByTagNameNS(PRESENTATION_NS, \"spTree\")[0];\n const slide: PptxSlide = { index, lines: [] };\n if (!tree) return slide;\n\n for (const node of Array.from(tree.children)) {\n if (node.namespaceURI !== PRESENTATION_NS) continue;\n if (node.localName === \"sp\") {\n const type = placeholderType(node);\n const lines = linesOf(node);\n if ((type === \"title\" || type === \"ctrTitle\") && !slide.title) {\n // A title placeholder can hold several paragraphs; they are one heading.\n const title = lines.map((line) => line.text).join(\" \");\n if (title) slide.title = title;\n continue;\n }\n slide.lines.push(...lines);\n continue;\n }\n if (node.localName === \"graphicFrame\") slide.lines.push(...tableLines(node));\n }\n return slide;\n}\n\n/** Parse a `ppt/notesSlides/notesSlideN.xml` document into its notes text. */\nexport function parseNotes(document: Document): string | undefined {\n const tree = document.getElementsByTagNameNS(PRESENTATION_NS, \"spTree\")[0];\n if (!tree) return undefined;\n\n const lines: string[] = [];\n for (const shape of Array.from(tree.getElementsByTagNameNS(PRESENTATION_NS, \"sp\"))) {\n // The notes part repeats the slide's own body as a non-editable copy; only\n // the notes placeholder holds what the presenter actually wrote.\n if (placeholderType(shape) !== \"body\") continue;\n for (const line of linesOf(shape)) lines.push(line.text);\n }\n const notes = lines.join(\"\\n\").trim();\n return notes || undefined;\n}\n\n/** The OPC relationships namespace — how a part points at another part. */\nexport const RELATIONSHIP_NS = \"http://schemas.openxmlformats.org/package/2006/relationships\";\n\n/**\n * Resolve a relationship target (`../notesSlides/notesSlide1.xml`) against the\n * part that declared it (`ppt/slides/slide1.xml`).\n *\n * Zip entry names are plain strings, not URLs, so `..` has to be walked by hand.\n */\nexport function resolveRelative(fromPart: string, target: string): string {\n if (target.startsWith(\"/\")) return target.slice(1);\n const segments = fromPart.split(\"/\").slice(0, -1);\n for (const segment of target.split(\"/\")) {\n if (segment === \".\" || segment === \"\") continue;\n if (segment === \"..\") segments.pop();\n else segments.push(segment);\n }\n return segments.join(\"/\");\n}\n\n/**\n * The notes part a slide points at, if any.\n *\n * Read from the slide's own `_rels`, not by matching `slideN` to `notesSlideN`:\n * the numbers agree in decks PowerPoint wrote from scratch and drift in decks\n * that have had slides deleted, which would attach the wrong presenter notes to\n * the wrong slide — a quiet, plausible-looking error.\n */\nexport function notesTarget(rels: Document, slidePath: string): string | undefined {\n for (const relationship of Array.from(\n rels.getElementsByTagNameNS(RELATIONSHIP_NS, \"Relationship\"),\n )) {\n if (!relationship.getAttribute(\"Type\")?.endsWith(\"/notesSlide\")) continue;\n const target = relationship.getAttribute(\"Target\");\n if (target) return resolveRelative(slidePath, target);\n }\n return undefined;\n}\n\n/** Between two lines of the same slide. */\nexport const PPTX_LINE_SEPARATOR = \"\\n\";\n/** Between two slides. */\nexport const PPTX_SLIDE_SEPARATOR = \"\\n\\n\";\n\n/** The `line` of a slide's title. */\nexport const PPTX_TITLE_LINE = -1;\n/** The `line` of a slide's speaker notes. */\nexport const PPTX_NOTES_LINE = -2;\n\n/** Where a stretch of the projection came from in the outline. */\nexport interface PptxRef {\n /** 1-based slide position, the same number {@link PptxSlide.index} carries. */\n slide: number;\n /** Body-line index, or {@link PPTX_TITLE_LINE} / {@link PPTX_NOTES_LINE}. */\n line: number;\n}\n\n/**\n * Plain-text projection of a deck, plus the map back into the outline.\n *\n * A slide's title, its lines and its notes are each their own chunk, so a\n * citation resolves to the exact line the renderer draws rather than to \"slide\n * 4\". The blank line between slides is written before whichever chunk turns out\n * to be the next slide's first — a slide with no text at all contributes\n * nothing, instead of a run of empty lines that would shift every offset after\n * it.\n */\nexport function slidesToTextWithMap(slides: PptxSlide[]): TextIndex<PptxRef> {\n const builder = createTextIndexBuilder<PptxRef>({ separator: PPTX_LINE_SEPARATOR });\n slides.forEach((slide, index) => {\n let separator = index === 0 ? undefined : PPTX_SLIDE_SEPARATOR;\n const push = (chunk: string | undefined, line: number) => {\n if (!chunk) return;\n builder.push(chunk, { slide: slide.index, line }, separator);\n separator = undefined;\n };\n\n push(slide.title, PPTX_TITLE_LINE);\n slide.lines.forEach((line, lineIndex) => {\n push(line.text, lineIndex);\n });\n push(slide.notes, PPTX_NOTES_LINE);\n });\n return builder.build();\n}\n\n/**\n * Plain-text projection of a deck — powers search, copy and the raw view. A thin\n * wrapper so the projection has exactly one definition: it is the index's own\n * text, never a second assembly that could drift from it.\n */\nexport function slidesToText(slides: PptxSlide[]): string {\n return slidesToTextWithMap(slides).text;\n}\n"],"mappings":"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","names":[]}
@@ -1,7 +1,7 @@
1
1
  "use client";
2
2
  import {
3
3
  PROTOCOL_VERSION
4
- } from "./chunk-AORNMH77.js";
4
+ } from "./chunk-CIT2HHRI.js";
5
5
 
6
6
  // src/adapters/media/media-manifest.ts
7
7
  var mediaManifest = {
@@ -14,4 +14,4 @@ var mediaManifest = {
14
14
  export {
15
15
  mediaManifest
16
16
  };
17
- //# sourceMappingURL=chunk-HMG2ERXH.js.map
17
+ //# sourceMappingURL=chunk-YA72QE57.js.map
@@ -1,7 +1,7 @@
1
1
  "use client";
2
2
  import {
3
3
  PROTOCOL_VERSION
4
- } from "./chunk-AORNMH77.js";
4
+ } from "./chunk-CIT2HHRI.js";
5
5
 
6
6
  // src/adapters/markdown/markdown-manifest.ts
7
7
  var markdownManifest = {
@@ -19,4 +19,4 @@ var markdownManifest = {
19
19
  export {
20
20
  markdownManifest
21
21
  };
22
- //# sourceMappingURL=chunk-H3OAON3D.js.map
22
+ //# sourceMappingURL=chunk-YWHFMCS4.js.map
@@ -1,7 +1,7 @@
1
1
  "use client";
2
2
  import {
3
3
  PROTOCOL_VERSION
4
- } from "./chunk-AORNMH77.js";
4
+ } from "./chunk-CIT2HHRI.js";
5
5
 
6
6
  // src/adapters/text/text-manifest.ts
7
7
  var textManifest = {
@@ -18,4 +18,4 @@ var textManifest = {
18
18
  export {
19
19
  textManifest
20
20
  };
21
- //# sourceMappingURL=chunk-UJIJECEF.js.map
21
+ //# sourceMappingURL=chunk-ZAVXZRIO.js.map
@@ -0,0 +1,19 @@
1
+ "use client";
2
+ import {
3
+ GRID_CELL_SEPARATOR,
4
+ GRID_HEAD_ROW,
5
+ GRID_NAME_ROW,
6
+ GRID_ROW_SEPARATOR,
7
+ GRID_SHEET_SEPARATOR,
8
+ gridToText
9
+ } from "../chunk-TLCNGQKL.js";
10
+ import "../chunk-5YE2MUS2.js";
11
+ export {
12
+ GRID_CELL_SEPARATOR,
13
+ GRID_HEAD_ROW,
14
+ GRID_NAME_ROW,
15
+ GRID_ROW_SEPARATOR,
16
+ GRID_SHEET_SEPARATOR,
17
+ gridToText
18
+ };
19
+ //# sourceMappingURL=grid-text.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
@@ -0,0 +1,10 @@
1
+ "use client";
2
+ "use client";
3
+ import {
4
+ MarkedText
5
+ } from "../chunk-GNBD53AT.js";
6
+ import "../chunk-HOQZ7OPH.js";
7
+ export {
8
+ MarkedText
9
+ };
10
+ //# sourceMappingURL=marked-text.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
@@ -0,0 +1,14 @@
1
+ "use client";
2
+ "use client";
3
+ import {
4
+ SheetTable
5
+ } from "../chunk-AK7F3IZE.js";
6
+ import "../chunk-TLCNGQKL.js";
7
+ import "../chunk-GNBD53AT.js";
8
+ import "../chunk-5YE2MUS2.js";
9
+ import "../chunk-IB4N72FA.js";
10
+ import "../chunk-HOQZ7OPH.js";
11
+ export {
12
+ SheetTable
13
+ };
14
+ //# sourceMappingURL=sheet-table.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
@@ -0,0 +1,18 @@
1
+ "use client";
2
+ import {
3
+ ViewerError,
4
+ isAbort,
5
+ isModuleNotFound,
6
+ isViewerError,
7
+ parserMissingError,
8
+ toViewerError
9
+ } from "../chunk-JHFC2KBI.js";
10
+ export {
11
+ ViewerError,
12
+ isAbort,
13
+ isModuleNotFound,
14
+ isViewerError,
15
+ parserMissingError,
16
+ toViewerError
17
+ };
18
+ //# sourceMappingURL=errors.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
@@ -0,0 +1,10 @@
1
+ "use client";
2
+ import {
3
+ localizeRanges,
4
+ toMarkRanges
5
+ } from "../chunk-HOQZ7OPH.js";
6
+ export {
7
+ localizeRanges,
8
+ toMarkRanges
9
+ };
10
+ //# sourceMappingURL=highlight-marks.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
@@ -0,0 +1,10 @@
1
+ "use client";
2
+ import {
3
+ locateQuote,
4
+ resolveHighlights
5
+ } from "../chunk-FQHZEQUH.js";
6
+ export {
7
+ locateQuote,
8
+ resolveHighlights
9
+ };
10
+ //# sourceMappingURL=highlight-resolve.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
@@ -0,0 +1,12 @@
1
+ "use client";
2
+ import {
3
+ FIND_MATCH_LIMIT,
4
+ findMatchId,
5
+ isFindMatchId
6
+ } from "../chunk-CO2VJRSU.js";
7
+ export {
8
+ FIND_MATCH_LIMIT,
9
+ findMatchId,
10
+ isFindMatchId
11
+ };
12
+ //# sourceMappingURL=highlight.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
@@ -0,0 +1,12 @@
1
+ "use client";
2
+ import {
3
+ createRegistry,
4
+ scoreManifest
5
+ } from "../chunk-BWMXMHVZ.js";
6
+ import "../chunk-JHFC2KBI.js";
7
+ import "../chunk-CIT2HHRI.js";
8
+ export {
9
+ createRegistry,
10
+ scoreManifest
11
+ };
12
+ //# sourceMappingURL=registry.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
@@ -0,0 +1,10 @@
1
+ "use client";
2
+ import {
3
+ SCROLL_HOST_SELECTOR,
4
+ findScrollHost
5
+ } from "../chunk-7PGGKQVY.js";
6
+ export {
7
+ SCROLL_HOST_SELECTOR,
8
+ findScrollHost
9
+ };
10
+ //# sourceMappingURL=scroll-host.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
@@ -0,0 +1,14 @@
1
+ "use client";
2
+ import {
3
+ chunkOffset,
4
+ createTextIndexBuilder,
5
+ spanAt,
6
+ spansForRange
7
+ } from "../chunk-5YE2MUS2.js";
8
+ export {
9
+ chunkOffset,
10
+ createTextIndexBuilder,
11
+ spanAt,
12
+ spansForRange
13
+ };
14
+ //# sourceMappingURL=text-index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
@@ -0,0 +1,8 @@
1
+ "use client";
2
+ import {
3
+ PROTOCOL_VERSION
4
+ } from "../chunk-CIT2HHRI.js";
5
+ export {
6
+ PROTOCOL_VERSION
7
+ };
8
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
@@ -0,0 +1,11 @@
1
+ "use client";
2
+ "use client";
3
+ import {
4
+ ACTIVE_HIGHLIGHT_SELECTOR,
5
+ useScrollActiveHighlightIntoView
6
+ } from "../chunk-IB4N72FA.js";
7
+ export {
8
+ ACTIVE_HIGHLIGHT_SELECTOR,
9
+ useScrollActiveHighlightIntoView
10
+ };
11
+ //# sourceMappingURL=use-highlight-scroll.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}