@embedpdf/react 3.0.0-next.1 → 3.0.0-next.11
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.
- package/dist/AnnotationListSnapshot-C1U3XUxP-6kGwNoxy.d.cts +546 -0
- package/dist/AnnotationListSnapshot-C1U3XUxP-6kGwNoxy.d.ts +546 -0
- package/dist/actions.cjs +42 -0
- package/dist/actions.cjs.map +1 -0
- package/dist/actions.d.cts +18 -0
- package/dist/actions.d.ts +18 -0
- package/dist/actions.js +34 -0
- package/dist/actions.js.map +1 -0
- package/dist/anchored.cjs +89 -0
- package/dist/anchored.cjs.map +1 -0
- package/dist/anchored.d.cts +45 -0
- package/dist/anchored.d.ts +45 -0
- package/dist/anchored.js +60 -0
- package/dist/anchored.js.map +1 -0
- package/dist/{annotation-B6jwfTCh.d.cts → annotation-BCED_p9H.d.cts} +56 -69
- package/dist/annotation-anchors-CetwM1g7.js +15 -0
- package/dist/annotation-anchors-CetwM1g7.js.map +1 -0
- package/dist/annotation-anchors-DuYRBJLl.cjs +26 -0
- package/dist/annotation-anchors-DuYRBJLl.cjs.map +1 -0
- package/dist/annotation-hooks-BB5ZhPpf.cjs +16 -0
- package/dist/annotation-hooks-BB5ZhPpf.cjs.map +1 -0
- package/dist/annotation-hooks-Df8EmzDO.js +11 -0
- package/dist/annotation-hooks-Df8EmzDO.js.map +1 -0
- package/dist/{annotation-nUMefyjw.d.ts → annotation-k38KDlIZ.d.ts} +57 -70
- package/dist/annotation-menu.cjs +37 -88
- package/dist/annotation-menu.cjs.map +1 -1
- package/dist/annotation-menu.d.cts +28 -2
- package/dist/annotation-menu.d.ts +28 -2
- package/dist/annotation-menu.js +28 -79
- package/dist/annotation-menu.js.map +1 -1
- package/dist/annotation.cjs +738 -16
- package/dist/annotation.cjs.map +1 -0
- package/dist/annotation.d.cts +2 -2
- package/dist/annotation.d.ts +2 -2
- package/dist/annotation.js +723 -3
- package/dist/annotation.js.map +1 -0
- package/dist/commands.js +1 -0
- package/dist/commands.js.map +1 -1
- package/dist/form-BwrLFpKN.js +805 -0
- package/dist/form-BwrLFpKN.js.map +1 -0
- package/dist/form-D-ZSfGmB.cjs +836 -0
- package/dist/form-D-ZSfGmB.cjs.map +1 -0
- package/dist/form.cjs +8 -551
- package/dist/form.d.cts +4 -21
- package/dist/form.d.ts +4 -21
- package/dist/form.js +3 -544
- package/dist/index.cjs +49 -16
- package/dist/index.d.cts +17 -10
- package/dist/index.d.ts +17 -10
- package/dist/index.js +14 -9
- package/dist/interaction.cjs +17 -4
- package/dist/interaction.cjs.map +1 -1
- package/dist/interaction.d.cts +2 -2
- package/dist/interaction.d.ts +2 -2
- package/dist/interaction.js +7 -5
- package/dist/interaction.js.map +1 -1
- package/dist/link.cjs +70 -7
- package/dist/link.cjs.map +1 -1
- package/dist/link.d.cts +12 -3
- package/dist/link.d.ts +12 -3
- package/dist/link.js +73 -10
- package/dist/link.js.map +1 -1
- package/dist/metadata.js +1 -0
- package/dist/metadata.js.map +1 -1
- package/dist/page-edit.js +1 -0
- package/dist/page-edit.js.map +1 -1
- package/dist/page-view.cjs +62 -38
- package/dist/page-view.cjs.map +1 -1
- package/dist/page-view.js +59 -37
- package/dist/page-view.js.map +1 -1
- package/dist/redaction.js +1 -0
- package/dist/redaction.js.map +1 -1
- package/dist/render.cjs +67 -50
- package/dist/render.cjs.map +1 -1
- package/dist/render.d.cts +6 -19
- package/dist/render.d.ts +6 -19
- package/dist/render.js +71 -52
- package/dist/render.js.map +1 -1
- package/dist/rolldown-runtime-BBjsoOtd.js +27 -0
- package/dist/{runtime-CMxIuF2O.d.cts → runtime-DE76hlnl.d.ts} +32 -11
- package/dist/{runtime-DV98q7I0.d.ts → runtime-umHA_xHt.d.cts} +32 -11
- package/dist/runtime.cjs +25 -5
- package/dist/runtime.cjs.map +1 -1
- package/dist/runtime.d.cts +2 -2
- package/dist/runtime.d.ts +2 -2
- package/dist/runtime.js +26 -6
- package/dist/runtime.js.map +1 -1
- package/dist/scrollbar.cjs +3 -3
- package/dist/scrollbar.cjs.map +1 -1
- package/dist/scrollbar.d.cts +1 -1
- package/dist/scrollbar.d.ts +1 -1
- package/dist/scrollbar.js +1 -1
- package/dist/scrollbar.js.map +1 -1
- package/dist/search.cjs +36 -19
- package/dist/search.cjs.map +1 -1
- package/dist/search.d.cts +1 -1
- package/dist/search.d.ts +1 -1
- package/dist/search.js +36 -19
- package/dist/search.js.map +1 -1
- package/dist/selection.cjs +217 -22
- package/dist/selection.cjs.map +1 -1
- package/dist/selection.d.cts +60 -2
- package/dist/selection.d.ts +60 -2
- package/dist/selection.js +214 -27
- package/dist/selection.js.map +1 -1
- package/dist/shell.js +1 -0
- package/dist/shell.js.map +1 -1
- package/dist/stage.cjs +49 -148
- package/dist/stage.cjs.map +1 -1
- package/dist/stage.d.cts +18 -5
- package/dist/stage.d.ts +18 -5
- package/dist/stage.js +51 -149
- package/dist/stage.js.map +1 -1
- package/dist/stamp.js +1 -0
- package/dist/stamp.js.map +1 -1
- package/dist/toolbar.cjs +4 -4
- package/dist/toolbar.cjs.map +1 -1
- package/dist/toolbar.d.cts +1 -1
- package/dist/toolbar.d.ts +1 -1
- package/dist/toolbar.js +2 -1
- package/dist/toolbar.js.map +1 -1
- package/dist/views.js +1 -0
- package/dist/views.js.map +1 -1
- package/package.json +33 -26
- package/dist/AnnotationListSnapshot-W0RCsyDE-dWna4TTJ.d.cts +0 -2593
- package/dist/AnnotationListSnapshot-W0RCsyDE-dWna4TTJ.d.ts +0 -2593
- package/dist/annotation-Ct6dFtzJ.cjs +0 -913
- package/dist/annotation-Ct6dFtzJ.cjs.map +0 -1
- package/dist/annotation-DjGWJljY.js +0 -829
- package/dist/annotation-DjGWJljY.js.map +0 -1
- package/dist/form.cjs.map +0 -1
- package/dist/form.js.map +0 -1
package/dist/scrollbar.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"scrollbar.cjs","names":["StageToken","useSelector","useCapability"],"sources":["../src/scrollbar.tsx"],"sourcesContent":["/**\n * Headless scrollbar for a Stage lens — a pure view of `stage.scrollMetrics()`.\n *\n * The Stage exposes the native scroller contract (scrollTop/scrollHeight/\n * clientHeight, in screen px — see plugin-stage's README); this component turns\n * it into a native-feeling bar: thumb drag with pointer capture and a preserved\n * grab point, track paging with press-and-hold repeat that stops at the\n * pointer, a minimum thumb length on long documents, and macOS-style overlay\n * auto-hide. On an UNBOUNDED stage the metrics already carry the Figma\n * semantics (the range is the union of content and view), so the same bar\n * shrinks toward the edge as you pan away and rides you back — the mapping is\n * FROZEN for the duration of a thumb drag so the thumb never chases itself\n * while the union re-collapses.\n *\n * Headless styling contract: geometry and behavior live here; looks live in\n * your CSS. Style via `className`/`thumbClassName` (or the style props) against\n * [data-embedpdf-scrollbar][data-axis=\"y\"][data-state=\"visible|hidden\"]\n * [data-embedpdf-scrollbar][data-dragging]\n * [data-embedpdf-scrollbar-thumb]\n * The defaults are deliberately minimal: a transparent track pinned to the\n * viewport edge and a `var(--epdf-scrollbar-thumb, …)` pill, so the bar works\n * unstyled and disappears into any theme. Build something else entirely\n * (minimap, progress ring) from `useScrollMetrics()` + `stage.scrollTo()`.\n */\n\nimport * as React from 'react';\nimport { useEffect, useRef, useState } from 'react';\nimport { StageToken } from '@embedpdf/plugin-stage';\nimport type { ScrollMetrics, StageCapability } from '@embedpdf/plugin-stage';\nimport type { CapabilityToken } from '@embedpdf/core';\nimport { useCapability, useSelector } from './runtime';\n\nexport type ScrollbarAxis = 'x' | 'y';\n\n/** Live scroll metrics for a stage lens (reference-stable; see\n * `StageCapability.scrollMetrics`). The raw material for custom scroll UI. */\nexport function useScrollMetrics(\n token: CapabilityToken<StageCapability> = StageToken,\n): ScrollMetrics {\n return useSelector(token, (c) => c.scrollMetrics());\n}\n\nexport interface ScrollbarProps {\n axis: ScrollbarAxis;\n /** The stage lens to scroll (default: the main StageToken). */\n token?: CapabilityToken<StageCapability>;\n /**\n * Overlay auto-hide: fade `autoHide` ms after the camera stops moving\n * (drags and hover pin it visible; a hidden bar ignores the pointer, like\n * macOS). `false` = always visible. Visibility is published as\n * `data-state=\"visible|hidden\"` — the default style fades opacity; replace\n * it in CSS for any other treatment. Default 1200.\n */\n autoHide?: number | false;\n /** Minimum thumb length in px — native bars never vanish on long documents.\n * Position stays proportional via the standard travel remap. Default 24. */\n minThumbSize?: number;\n /**\n * Pressing the track: 'page' steps by 90% of a viewport toward the pointer,\n * repeats while held, and stops when the thumb reaches the pointer (the\n * native default); 'jump' centers the thumb at the pointer and hands off to\n * a drag (the macOS option-click / Figma feel). Default 'page'.\n */\n trackPress?: 'page' | 'jump';\n /** Track class/style. The default style pins the bar to the right (y) or\n * bottom (x) edge of the nearest positioned ancestor — the Stage container\n * when rendered in its `overlay`; override to place it anywhere else. */\n className?: string;\n style?: React.CSSProperties;\n thumbClassName?: string;\n thumbStyle?: React.CSSProperties;\n}\n\nconst clamp01 = (v: number) => Math.min(1, Math.max(0, v));\n\n/** Best-effort pointer capture: an already-released pointer (pen/touch races,\n * synthetic events in tests) throws NotFoundError — the press must still work,\n * just without capture (moves keep arriving while the pointer stays on us). */\nconst capturePointer = (el: Element, pointerId: number) => {\n try {\n el.setPointerCapture(pointerId);\n } catch {\n /* uncaptured is fine */\n }\n};\n\n/** Thumb geometry from metrics + a measured track. One formula for render,\n * drag capture, and paging — they can never disagree. */\nconst geometry = (m: ScrollMetrics, vertical: boolean, trackPx: number, minThumbSize: number) => {\n const client = vertical ? m.clientHeight : m.clientWidth;\n const total = vertical ? m.scrollHeight : m.scrollWidth;\n const offset = vertical ? m.scrollTop : m.scrollLeft;\n const maxOffset = Math.max(0, total - client);\n const thumbLen = Math.min(\n trackPx,\n Math.max(minThumbSize, total > 0 ? (client / total) * trackPx : 0),\n );\n const travel = Math.max(0, trackPx - thumbLen);\n const thumbPos = maxOffset > 0 ? (offset / maxOffset) * travel : 0;\n return { client, offset, maxOffset, thumbLen, travel, thumbPos };\n};\n\nexport function Scrollbar({\n axis,\n token = StageToken,\n autoHide = 1200,\n minThumbSize = 24,\n trackPress = 'page',\n className,\n style,\n thumbClassName,\n thumbStyle,\n}: ScrollbarProps) {\n const stage = useCapability(token);\n const m = useScrollMetrics(token);\n const vertical = axis === 'y';\n const scrollable = vertical ? m.scrollableY : m.scrollableX;\n\n const trackRef = useRef<HTMLDivElement>(null);\n const [trackPx, setTrackPx] = useState(0);\n const [dragging, setDragging] = useState(false);\n const [hovered, setHovered] = useState(false);\n const [active, setActive] = useState(true); // camera moved recently\n\n const g = geometry(m, vertical, trackPx, minThumbSize);\n\n // ── overlay auto-hide: any metrics change re-arms the fade timer ──────────\n const hideAfter = autoHide === false ? 0 : autoHide;\n useEffect(() => {\n if (!hideAfter) return;\n setActive(true);\n const t = setTimeout(() => setActive(false), hideAfter);\n return () => clearTimeout(t);\n }, [m, hideAfter]);\n const shown = !hideAfter || active || hovered || dragging;\n\n // ── track measurement (px mapping needs real geometry) ────────────────────\n useEffect(() => {\n const el = trackRef.current;\n if (!el) return;\n const measure = () => setTrackPx(vertical ? el.clientHeight : el.clientWidth);\n const ro = new ResizeObserver(measure);\n ro.observe(el);\n measure();\n return () => ro.disconnect();\n }, [vertical, scrollable]);\n\n // ── interactions. Drag state is FROZEN at pointer-down (grab point, travel,\n // max offset) and applied as relative pans — absolute-feeling in bounded\n // mode (the range is static there) and stable in unbounded mode, where\n // the live union would otherwise shift under the pointer mid-drag. ──────\n const dragRef = useRef<{ grab: number; applied: number; max: number; travel: number } | null>(\n null,\n );\n const pageRef = useRef<{ dir: 1 | -1; timer: number } | null>(null);\n const lastPtr = useRef(0);\n\n const ptrPos = (e: React.PointerEvent) => {\n const r = trackRef.current!.getBoundingClientRect();\n return vertical ? e.clientY - r.top : e.clientX - r.left;\n };\n /** Fresh geometry for paging steps — read from the capability, never a stale render. */\n const liveGeometry = () => geometry(stage.scrollMetrics(), vertical, trackPx, minThumbSize);\n\n const stopPaging = () => {\n if (pageRef.current) {\n clearTimeout(pageRef.current.timer);\n clearInterval(pageRef.current.timer);\n pageRef.current = null;\n }\n };\n const endDrag = () => {\n dragRef.current = null;\n stopPaging();\n setDragging(false);\n };\n\n const beginDrag = (grab: number, applied: number) => {\n dragRef.current = { grab, applied, max: g.maxOffset, travel: g.travel };\n setDragging(true);\n };\n\n const onThumbDown = (e: React.PointerEvent) => {\n if (e.button !== 0) return;\n e.preventDefault();\n e.stopPropagation();\n capturePointer(e.target as Element, e.pointerId);\n beginDrag(ptrPos(e) - g.thumbPos, g.offset);\n };\n\n const onTrackDown = (e: React.PointerEvent) => {\n if (e.button !== 0 || e.target !== e.currentTarget) return; // thumb handles its own\n e.preventDefault();\n capturePointer(e.currentTarget as Element, e.pointerId);\n const p = (lastPtr.current = ptrPos(e));\n\n if (trackPress === 'jump') {\n // land the thumb centered at the pointer, then it's an ordinary drag\n const want = g.travel > 0 ? clamp01((p - g.thumbLen / 2) / g.travel) * g.maxOffset : 0;\n stage.scrollTo(vertical ? { top: want } : { left: want });\n beginDrag(g.thumbLen / 2, want);\n return;\n }\n\n // 'page': step toward the pointer, repeat while held, stop at the pointer\n const dir: 1 | -1 = p < g.thumbPos ? -1 : 1;\n const step = () => {\n const live = liveGeometry();\n const reached =\n dir === 1\n ? lastPtr.current <= live.thumbPos + live.thumbLen\n : lastPtr.current >= live.thumbPos;\n if (reached) return stopPaging();\n stage.scrollBy(\n vertical ? { top: dir * live.client * 0.9 } : { left: dir * live.client * 0.9 },\n );\n };\n step();\n // native cadence: a beat before the repeat kicks in, then a steady march\n const timer = window.setTimeout(() => {\n if (!pageRef.current) return;\n pageRef.current.timer = window.setInterval(step, 80);\n }, 350);\n pageRef.current = { dir, timer };\n };\n\n // Pointer capture retargets to the pressed element and bubbles here — one\n // move/up pair serves thumb drags, jump-drags, and paging alike.\n const onMove = (e: React.PointerEvent) => {\n const d = dragRef.current;\n if (d) {\n if (d.travel <= 0) return;\n const want = clamp01((ptrPos(e) - d.grab) / d.travel) * d.max;\n const delta = want - d.applied;\n if (delta) {\n stage.panBy(vertical ? 0 : -delta, vertical ? -delta : 0);\n d.applied = want;\n }\n } else if (pageRef.current) {\n lastPtr.current = ptrPos(e);\n }\n };\n\n useEffect(() => endDrag, []); // unmount: no orphaned repeat timers\n\n if (!scrollable) return null;\n\n const trackDefaults: React.CSSProperties = vertical\n ? { position: 'absolute', top: 0, right: 0, bottom: 0, width: 12 }\n : { position: 'absolute', left: 0, right: 0, bottom: 0, height: 12 };\n const thumbDefaults: React.CSSProperties = vertical\n ? { position: 'absolute', left: 2, right: 2, top: g.thumbPos, height: g.thumbLen }\n : { position: 'absolute', top: 2, bottom: 2, left: g.thumbPos, width: g.thumbLen };\n\n return (\n <div\n ref={trackRef}\n role=\"scrollbar\"\n aria-orientation={vertical ? 'vertical' : 'horizontal'}\n aria-valuemin={0}\n aria-valuemax={Math.round(g.maxOffset)}\n aria-valuenow={Math.round(g.offset)}\n data-embedpdf-scrollbar=\"\"\n data-axis={axis}\n data-state={shown ? 'visible' : 'hidden'}\n data-dragging={dragging ? '' : undefined}\n className={className}\n style={{\n ...trackDefaults,\n touchAction: 'none',\n userSelect: 'none',\n opacity: shown ? 1 : 0,\n transition: 'opacity 200ms',\n // a hidden overlay bar must not eat the clicks under it\n pointerEvents: shown ? 'auto' : 'none',\n ...style,\n }}\n onPointerDown={onTrackDown}\n onPointerMove={onMove}\n onPointerUp={endDrag}\n onPointerCancel={endDrag}\n onPointerEnter={() => setHovered(true)}\n onPointerLeave={() => setHovered(false)}\n >\n <div\n data-embedpdf-scrollbar-thumb=\"\"\n className={thumbClassName}\n style={{\n ...thumbDefaults,\n borderRadius: 6,\n background: 'var(--epdf-scrollbar-thumb, rgba(0, 0, 0, 0.4))',\n ...thumbStyle,\n }}\n onPointerDown={onThumbDown}\n />\n </div>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"scrollbar.cjs","names":["StageToken","useSelector","useCapability"],"sources":["../src/scrollbar.tsx"],"sourcesContent":["/**\n * Headless scrollbar for a Stage lens — a pure view of `stage.scrollMetrics()`.\n *\n * The Stage exposes the native scroller contract (scrollTop/scrollHeight/\n * clientHeight, in screen px — see plugin-stage's README); this component turns\n * it into a native-feeling bar: thumb drag with pointer capture and a preserved\n * grab point, track paging with press-and-hold repeat that stops at the\n * pointer, a minimum thumb length on long documents, and macOS-style overlay\n * auto-hide. On an UNBOUNDED stage the metrics already carry the Figma\n * semantics (the range is the union of content and view), so the same bar\n * shrinks toward the edge as you pan away and rides you back — the mapping is\n * FROZEN for the duration of a thumb drag so the thumb never chases itself\n * while the union re-collapses.\n *\n * Headless styling contract: geometry and behavior live here; looks live in\n * your CSS. Style via `className`/`thumbClassName` (or the style props) against\n * [data-embedpdf-scrollbar][data-axis=\"y\"][data-state=\"visible|hidden\"]\n * [data-embedpdf-scrollbar][data-dragging]\n * [data-embedpdf-scrollbar-thumb]\n * The defaults are deliberately minimal: a transparent track pinned to the\n * viewport edge and a `var(--epdf-scrollbar-thumb, …)` pill, so the bar works\n * unstyled and disappears into any theme. Build something else entirely\n * (minimap, progress ring) from `useScrollMetrics()` + `stage.scrollTo()`.\n */\n\nimport * as React from 'react';\nimport { useEffect, useRef, useState } from 'react';\nimport { StageToken } from '@embedpdf/plugin-stage/contract';\nimport type { ScrollMetrics, StageCapability } from '@embedpdf/plugin-stage/contract';\nimport type { CapabilityToken } from '@embedpdf/core';\nimport { useCapability, useSelector } from './runtime';\n\nexport type ScrollbarAxis = 'x' | 'y';\n\n/** Live scroll metrics for a stage lens (reference-stable; see\n * `StageCapability.scrollMetrics`). The raw material for custom scroll UI. */\nexport function useScrollMetrics(\n token: CapabilityToken<StageCapability> = StageToken,\n): ScrollMetrics {\n return useSelector(token, (c) => c.scrollMetrics());\n}\n\nexport interface ScrollbarProps {\n axis: ScrollbarAxis;\n /** The stage lens to scroll (default: the main StageToken). */\n token?: CapabilityToken<StageCapability>;\n /**\n * Overlay auto-hide: fade `autoHide` ms after the camera stops moving\n * (drags and hover pin it visible; a hidden bar ignores the pointer, like\n * macOS). `false` = always visible. Visibility is published as\n * `data-state=\"visible|hidden\"` — the default style fades opacity; replace\n * it in CSS for any other treatment. Default 1200.\n */\n autoHide?: number | false;\n /** Minimum thumb length in px — native bars never vanish on long documents.\n * Position stays proportional via the standard travel remap. Default 24. */\n minThumbSize?: number;\n /**\n * Pressing the track: 'page' steps by 90% of a viewport toward the pointer,\n * repeats while held, and stops when the thumb reaches the pointer (the\n * native default); 'jump' centers the thumb at the pointer and hands off to\n * a drag (the macOS option-click / Figma feel). Default 'page'.\n */\n trackPress?: 'page' | 'jump';\n /** Track class/style. The default style pins the bar to the right (y) or\n * bottom (x) edge of the nearest positioned ancestor — the Stage container\n * when rendered in its `overlay`; override to place it anywhere else. */\n className?: string;\n style?: React.CSSProperties;\n thumbClassName?: string;\n thumbStyle?: React.CSSProperties;\n}\n\nconst clamp01 = (v: number) => Math.min(1, Math.max(0, v));\n\n/** Best-effort pointer capture: an already-released pointer (pen/touch races,\n * synthetic events in tests) throws NotFoundError — the press must still work,\n * just without capture (moves keep arriving while the pointer stays on us). */\nconst capturePointer = (el: Element, pointerId: number) => {\n try {\n el.setPointerCapture(pointerId);\n } catch {\n /* uncaptured is fine */\n }\n};\n\n/** Thumb geometry from metrics + a measured track. One formula for render,\n * drag capture, and paging — they can never disagree. */\nconst geometry = (m: ScrollMetrics, vertical: boolean, trackPx: number, minThumbSize: number) => {\n const client = vertical ? m.clientHeight : m.clientWidth;\n const total = vertical ? m.scrollHeight : m.scrollWidth;\n const offset = vertical ? m.scrollTop : m.scrollLeft;\n const maxOffset = Math.max(0, total - client);\n const thumbLen = Math.min(\n trackPx,\n Math.max(minThumbSize, total > 0 ? (client / total) * trackPx : 0),\n );\n const travel = Math.max(0, trackPx - thumbLen);\n const thumbPos = maxOffset > 0 ? (offset / maxOffset) * travel : 0;\n return { client, offset, maxOffset, thumbLen, travel, thumbPos };\n};\n\nexport function Scrollbar({\n axis,\n token = StageToken,\n autoHide = 1200,\n minThumbSize = 24,\n trackPress = 'page',\n className,\n style,\n thumbClassName,\n thumbStyle,\n}: ScrollbarProps) {\n const stage = useCapability(token);\n const m = useScrollMetrics(token);\n const vertical = axis === 'y';\n const scrollable = vertical ? m.scrollableY : m.scrollableX;\n\n const trackRef = useRef<HTMLDivElement>(null);\n const [trackPx, setTrackPx] = useState(0);\n const [dragging, setDragging] = useState(false);\n const [hovered, setHovered] = useState(false);\n const [active, setActive] = useState(true); // camera moved recently\n\n const g = geometry(m, vertical, trackPx, minThumbSize);\n\n // ── overlay auto-hide: any metrics change re-arms the fade timer ──────────\n const hideAfter = autoHide === false ? 0 : autoHide;\n useEffect(() => {\n if (!hideAfter) return;\n setActive(true);\n const t = setTimeout(() => setActive(false), hideAfter);\n return () => clearTimeout(t);\n }, [m, hideAfter]);\n const shown = !hideAfter || active || hovered || dragging;\n\n // ── track measurement (px mapping needs real geometry) ────────────────────\n useEffect(() => {\n const el = trackRef.current;\n if (!el) return;\n const measure = () => setTrackPx(vertical ? el.clientHeight : el.clientWidth);\n const ro = new ResizeObserver(measure);\n ro.observe(el);\n measure();\n return () => ro.disconnect();\n }, [vertical, scrollable]);\n\n // ── interactions. Drag state is FROZEN at pointer-down (grab point, travel,\n // max offset) and applied as relative pans — absolute-feeling in bounded\n // mode (the range is static there) and stable in unbounded mode, where\n // the live union would otherwise shift under the pointer mid-drag. ──────\n const dragRef = useRef<{ grab: number; applied: number; max: number; travel: number } | null>(\n null,\n );\n const pageRef = useRef<{ dir: 1 | -1; timer: number } | null>(null);\n const lastPtr = useRef(0);\n\n const ptrPos = (e: React.PointerEvent) => {\n const r = trackRef.current!.getBoundingClientRect();\n return vertical ? e.clientY - r.top : e.clientX - r.left;\n };\n /** Fresh geometry for paging steps — read from the capability, never a stale render. */\n const liveGeometry = () => geometry(stage.scrollMetrics(), vertical, trackPx, minThumbSize);\n\n const stopPaging = () => {\n if (pageRef.current) {\n clearTimeout(pageRef.current.timer);\n clearInterval(pageRef.current.timer);\n pageRef.current = null;\n }\n };\n const endDrag = () => {\n dragRef.current = null;\n stopPaging();\n setDragging(false);\n };\n\n const beginDrag = (grab: number, applied: number) => {\n dragRef.current = { grab, applied, max: g.maxOffset, travel: g.travel };\n setDragging(true);\n };\n\n const onThumbDown = (e: React.PointerEvent) => {\n if (e.button !== 0) return;\n e.preventDefault();\n e.stopPropagation();\n capturePointer(e.target as Element, e.pointerId);\n beginDrag(ptrPos(e) - g.thumbPos, g.offset);\n };\n\n const onTrackDown = (e: React.PointerEvent) => {\n if (e.button !== 0 || e.target !== e.currentTarget) return; // thumb handles its own\n e.preventDefault();\n capturePointer(e.currentTarget as Element, e.pointerId);\n const p = (lastPtr.current = ptrPos(e));\n\n if (trackPress === 'jump') {\n // land the thumb centered at the pointer, then it's an ordinary drag\n const want = g.travel > 0 ? clamp01((p - g.thumbLen / 2) / g.travel) * g.maxOffset : 0;\n stage.scrollTo(vertical ? { top: want } : { left: want });\n beginDrag(g.thumbLen / 2, want);\n return;\n }\n\n // 'page': step toward the pointer, repeat while held, stop at the pointer\n const dir: 1 | -1 = p < g.thumbPos ? -1 : 1;\n const step = () => {\n const live = liveGeometry();\n const reached =\n dir === 1\n ? lastPtr.current <= live.thumbPos + live.thumbLen\n : lastPtr.current >= live.thumbPos;\n if (reached) return stopPaging();\n stage.scrollBy(\n vertical ? { top: dir * live.client * 0.9 } : { left: dir * live.client * 0.9 },\n );\n };\n step();\n // native cadence: a beat before the repeat kicks in, then a steady march\n const timer = window.setTimeout(() => {\n if (!pageRef.current) return;\n pageRef.current.timer = window.setInterval(step, 80);\n }, 350);\n pageRef.current = { dir, timer };\n };\n\n // Pointer capture retargets to the pressed element and bubbles here — one\n // move/up pair serves thumb drags, jump-drags, and paging alike.\n const onMove = (e: React.PointerEvent) => {\n const d = dragRef.current;\n if (d) {\n if (d.travel <= 0) return;\n const want = clamp01((ptrPos(e) - d.grab) / d.travel) * d.max;\n const delta = want - d.applied;\n if (delta) {\n stage.panBy(vertical ? 0 : -delta, vertical ? -delta : 0);\n d.applied = want;\n }\n } else if (pageRef.current) {\n lastPtr.current = ptrPos(e);\n }\n };\n\n useEffect(() => endDrag, []); // unmount: no orphaned repeat timers\n\n if (!scrollable) return null;\n\n const trackDefaults: React.CSSProperties = vertical\n ? { position: 'absolute', top: 0, right: 0, bottom: 0, width: 12 }\n : { position: 'absolute', left: 0, right: 0, bottom: 0, height: 12 };\n const thumbDefaults: React.CSSProperties = vertical\n ? { position: 'absolute', left: 2, right: 2, top: g.thumbPos, height: g.thumbLen }\n : { position: 'absolute', top: 2, bottom: 2, left: g.thumbPos, width: g.thumbLen };\n\n return (\n <div\n ref={trackRef}\n role=\"scrollbar\"\n aria-orientation={vertical ? 'vertical' : 'horizontal'}\n aria-valuemin={0}\n aria-valuemax={Math.round(g.maxOffset)}\n aria-valuenow={Math.round(g.offset)}\n data-embedpdf-scrollbar=\"\"\n data-axis={axis}\n data-state={shown ? 'visible' : 'hidden'}\n data-dragging={dragging ? '' : undefined}\n className={className}\n style={{\n ...trackDefaults,\n touchAction: 'none',\n userSelect: 'none',\n opacity: shown ? 1 : 0,\n transition: 'opacity 200ms',\n // a hidden overlay bar must not eat the clicks under it\n pointerEvents: shown ? 'auto' : 'none',\n ...style,\n }}\n onPointerDown={onTrackDown}\n onPointerMove={onMove}\n onPointerUp={endDrag}\n onPointerCancel={endDrag}\n onPointerEnter={() => setHovered(true)}\n onPointerLeave={() => setHovered(false)}\n >\n <div\n data-embedpdf-scrollbar-thumb=\"\"\n className={thumbClassName}\n style={{\n ...thumbDefaults,\n borderRadius: 6,\n background: 'var(--epdf-scrollbar-thumb, rgba(0, 0, 0, 0.4))',\n ...thumbStyle,\n }}\n onPointerDown={onThumbDown}\n />\n </div>\n );\n}\n"],"mappings":"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"}
|
package/dist/scrollbar.d.cts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
2
|
import { CapabilityToken } from "@embedpdf/core";
|
|
3
|
-
import { ScrollMetrics, StageCapability } from "@embedpdf/plugin-stage";
|
|
3
|
+
import { ScrollMetrics, StageCapability } from "@embedpdf/plugin-stage/contract";
|
|
4
4
|
//#region src/scrollbar.d.ts
|
|
5
5
|
type ScrollbarAxis = 'x' | 'y';
|
|
6
6
|
/** Live scroll metrics for a stage lens (reference-stable; see
|
package/dist/scrollbar.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { CapabilityToken } from "@embedpdf/core";
|
|
2
2
|
import * as React from "react";
|
|
3
|
-
import { ScrollMetrics, StageCapability } from "@embedpdf/plugin-stage";
|
|
3
|
+
import { ScrollMetrics, StageCapability } from "@embedpdf/plugin-stage/contract";
|
|
4
4
|
//#region src/scrollbar.d.ts
|
|
5
5
|
type ScrollbarAxis = 'x' | 'y';
|
|
6
6
|
/** Live scroll metrics for a stage lens (reference-stable; see
|
package/dist/scrollbar.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { useCapability, useSelector } from "./runtime.js";
|
|
2
2
|
import { jsx } from "react/jsx-runtime";
|
|
3
3
|
import { useEffect, useRef, useState } from "react";
|
|
4
|
-
import { StageToken } from "@embedpdf/plugin-stage";
|
|
4
|
+
import { StageToken } from "@embedpdf/plugin-stage/contract";
|
|
5
5
|
//#region src/scrollbar.tsx
|
|
6
6
|
/** Live scroll metrics for a stage lens (reference-stable; see
|
|
7
7
|
* `StageCapability.scrollMetrics`). The raw material for custom scroll UI. */
|
package/dist/scrollbar.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"scrollbar.js","names":[],"sources":["../src/scrollbar.tsx"],"sourcesContent":["/**\n * Headless scrollbar for a Stage lens — a pure view of `stage.scrollMetrics()`.\n *\n * The Stage exposes the native scroller contract (scrollTop/scrollHeight/\n * clientHeight, in screen px — see plugin-stage's README); this component turns\n * it into a native-feeling bar: thumb drag with pointer capture and a preserved\n * grab point, track paging with press-and-hold repeat that stops at the\n * pointer, a minimum thumb length on long documents, and macOS-style overlay\n * auto-hide. On an UNBOUNDED stage the metrics already carry the Figma\n * semantics (the range is the union of content and view), so the same bar\n * shrinks toward the edge as you pan away and rides you back — the mapping is\n * FROZEN for the duration of a thumb drag so the thumb never chases itself\n * while the union re-collapses.\n *\n * Headless styling contract: geometry and behavior live here; looks live in\n * your CSS. Style via `className`/`thumbClassName` (or the style props) against\n * [data-embedpdf-scrollbar][data-axis=\"y\"][data-state=\"visible|hidden\"]\n * [data-embedpdf-scrollbar][data-dragging]\n * [data-embedpdf-scrollbar-thumb]\n * The defaults are deliberately minimal: a transparent track pinned to the\n * viewport edge and a `var(--epdf-scrollbar-thumb, …)` pill, so the bar works\n * unstyled and disappears into any theme. Build something else entirely\n * (minimap, progress ring) from `useScrollMetrics()` + `stage.scrollTo()`.\n */\n\nimport * as React from 'react';\nimport { useEffect, useRef, useState } from 'react';\nimport { StageToken } from '@embedpdf/plugin-stage';\nimport type { ScrollMetrics, StageCapability } from '@embedpdf/plugin-stage';\nimport type { CapabilityToken } from '@embedpdf/core';\nimport { useCapability, useSelector } from './runtime';\n\nexport type ScrollbarAxis = 'x' | 'y';\n\n/** Live scroll metrics for a stage lens (reference-stable; see\n * `StageCapability.scrollMetrics`). The raw material for custom scroll UI. */\nexport function useScrollMetrics(\n token: CapabilityToken<StageCapability> = StageToken,\n): ScrollMetrics {\n return useSelector(token, (c) => c.scrollMetrics());\n}\n\nexport interface ScrollbarProps {\n axis: ScrollbarAxis;\n /** The stage lens to scroll (default: the main StageToken). */\n token?: CapabilityToken<StageCapability>;\n /**\n * Overlay auto-hide: fade `autoHide` ms after the camera stops moving\n * (drags and hover pin it visible; a hidden bar ignores the pointer, like\n * macOS). `false` = always visible. Visibility is published as\n * `data-state=\"visible|hidden\"` — the default style fades opacity; replace\n * it in CSS for any other treatment. Default 1200.\n */\n autoHide?: number | false;\n /** Minimum thumb length in px — native bars never vanish on long documents.\n * Position stays proportional via the standard travel remap. Default 24. */\n minThumbSize?: number;\n /**\n * Pressing the track: 'page' steps by 90% of a viewport toward the pointer,\n * repeats while held, and stops when the thumb reaches the pointer (the\n * native default); 'jump' centers the thumb at the pointer and hands off to\n * a drag (the macOS option-click / Figma feel). Default 'page'.\n */\n trackPress?: 'page' | 'jump';\n /** Track class/style. The default style pins the bar to the right (y) or\n * bottom (x) edge of the nearest positioned ancestor — the Stage container\n * when rendered in its `overlay`; override to place it anywhere else. */\n className?: string;\n style?: React.CSSProperties;\n thumbClassName?: string;\n thumbStyle?: React.CSSProperties;\n}\n\nconst clamp01 = (v: number) => Math.min(1, Math.max(0, v));\n\n/** Best-effort pointer capture: an already-released pointer (pen/touch races,\n * synthetic events in tests) throws NotFoundError — the press must still work,\n * just without capture (moves keep arriving while the pointer stays on us). */\nconst capturePointer = (el: Element, pointerId: number) => {\n try {\n el.setPointerCapture(pointerId);\n } catch {\n /* uncaptured is fine */\n }\n};\n\n/** Thumb geometry from metrics + a measured track. One formula for render,\n * drag capture, and paging — they can never disagree. */\nconst geometry = (m: ScrollMetrics, vertical: boolean, trackPx: number, minThumbSize: number) => {\n const client = vertical ? m.clientHeight : m.clientWidth;\n const total = vertical ? m.scrollHeight : m.scrollWidth;\n const offset = vertical ? m.scrollTop : m.scrollLeft;\n const maxOffset = Math.max(0, total - client);\n const thumbLen = Math.min(\n trackPx,\n Math.max(minThumbSize, total > 0 ? (client / total) * trackPx : 0),\n );\n const travel = Math.max(0, trackPx - thumbLen);\n const thumbPos = maxOffset > 0 ? (offset / maxOffset) * travel : 0;\n return { client, offset, maxOffset, thumbLen, travel, thumbPos };\n};\n\nexport function Scrollbar({\n axis,\n token = StageToken,\n autoHide = 1200,\n minThumbSize = 24,\n trackPress = 'page',\n className,\n style,\n thumbClassName,\n thumbStyle,\n}: ScrollbarProps) {\n const stage = useCapability(token);\n const m = useScrollMetrics(token);\n const vertical = axis === 'y';\n const scrollable = vertical ? m.scrollableY : m.scrollableX;\n\n const trackRef = useRef<HTMLDivElement>(null);\n const [trackPx, setTrackPx] = useState(0);\n const [dragging, setDragging] = useState(false);\n const [hovered, setHovered] = useState(false);\n const [active, setActive] = useState(true); // camera moved recently\n\n const g = geometry(m, vertical, trackPx, minThumbSize);\n\n // ── overlay auto-hide: any metrics change re-arms the fade timer ──────────\n const hideAfter = autoHide === false ? 0 : autoHide;\n useEffect(() => {\n if (!hideAfter) return;\n setActive(true);\n const t = setTimeout(() => setActive(false), hideAfter);\n return () => clearTimeout(t);\n }, [m, hideAfter]);\n const shown = !hideAfter || active || hovered || dragging;\n\n // ── track measurement (px mapping needs real geometry) ────────────────────\n useEffect(() => {\n const el = trackRef.current;\n if (!el) return;\n const measure = () => setTrackPx(vertical ? el.clientHeight : el.clientWidth);\n const ro = new ResizeObserver(measure);\n ro.observe(el);\n measure();\n return () => ro.disconnect();\n }, [vertical, scrollable]);\n\n // ── interactions. Drag state is FROZEN at pointer-down (grab point, travel,\n // max offset) and applied as relative pans — absolute-feeling in bounded\n // mode (the range is static there) and stable in unbounded mode, where\n // the live union would otherwise shift under the pointer mid-drag. ──────\n const dragRef = useRef<{ grab: number; applied: number; max: number; travel: number } | null>(\n null,\n );\n const pageRef = useRef<{ dir: 1 | -1; timer: number } | null>(null);\n const lastPtr = useRef(0);\n\n const ptrPos = (e: React.PointerEvent) => {\n const r = trackRef.current!.getBoundingClientRect();\n return vertical ? e.clientY - r.top : e.clientX - r.left;\n };\n /** Fresh geometry for paging steps — read from the capability, never a stale render. */\n const liveGeometry = () => geometry(stage.scrollMetrics(), vertical, trackPx, minThumbSize);\n\n const stopPaging = () => {\n if (pageRef.current) {\n clearTimeout(pageRef.current.timer);\n clearInterval(pageRef.current.timer);\n pageRef.current = null;\n }\n };\n const endDrag = () => {\n dragRef.current = null;\n stopPaging();\n setDragging(false);\n };\n\n const beginDrag = (grab: number, applied: number) => {\n dragRef.current = { grab, applied, max: g.maxOffset, travel: g.travel };\n setDragging(true);\n };\n\n const onThumbDown = (e: React.PointerEvent) => {\n if (e.button !== 0) return;\n e.preventDefault();\n e.stopPropagation();\n capturePointer(e.target as Element, e.pointerId);\n beginDrag(ptrPos(e) - g.thumbPos, g.offset);\n };\n\n const onTrackDown = (e: React.PointerEvent) => {\n if (e.button !== 0 || e.target !== e.currentTarget) return; // thumb handles its own\n e.preventDefault();\n capturePointer(e.currentTarget as Element, e.pointerId);\n const p = (lastPtr.current = ptrPos(e));\n\n if (trackPress === 'jump') {\n // land the thumb centered at the pointer, then it's an ordinary drag\n const want = g.travel > 0 ? clamp01((p - g.thumbLen / 2) / g.travel) * g.maxOffset : 0;\n stage.scrollTo(vertical ? { top: want } : { left: want });\n beginDrag(g.thumbLen / 2, want);\n return;\n }\n\n // 'page': step toward the pointer, repeat while held, stop at the pointer\n const dir: 1 | -1 = p < g.thumbPos ? -1 : 1;\n const step = () => {\n const live = liveGeometry();\n const reached =\n dir === 1\n ? lastPtr.current <= live.thumbPos + live.thumbLen\n : lastPtr.current >= live.thumbPos;\n if (reached) return stopPaging();\n stage.scrollBy(\n vertical ? { top: dir * live.client * 0.9 } : { left: dir * live.client * 0.9 },\n );\n };\n step();\n // native cadence: a beat before the repeat kicks in, then a steady march\n const timer = window.setTimeout(() => {\n if (!pageRef.current) return;\n pageRef.current.timer = window.setInterval(step, 80);\n }, 350);\n pageRef.current = { dir, timer };\n };\n\n // Pointer capture retargets to the pressed element and bubbles here — one\n // move/up pair serves thumb drags, jump-drags, and paging alike.\n const onMove = (e: React.PointerEvent) => {\n const d = dragRef.current;\n if (d) {\n if (d.travel <= 0) return;\n const want = clamp01((ptrPos(e) - d.grab) / d.travel) * d.max;\n const delta = want - d.applied;\n if (delta) {\n stage.panBy(vertical ? 0 : -delta, vertical ? -delta : 0);\n d.applied = want;\n }\n } else if (pageRef.current) {\n lastPtr.current = ptrPos(e);\n }\n };\n\n useEffect(() => endDrag, []); // unmount: no orphaned repeat timers\n\n if (!scrollable) return null;\n\n const trackDefaults: React.CSSProperties = vertical\n ? { position: 'absolute', top: 0, right: 0, bottom: 0, width: 12 }\n : { position: 'absolute', left: 0, right: 0, bottom: 0, height: 12 };\n const thumbDefaults: React.CSSProperties = vertical\n ? { position: 'absolute', left: 2, right: 2, top: g.thumbPos, height: g.thumbLen }\n : { position: 'absolute', top: 2, bottom: 2, left: g.thumbPos, width: g.thumbLen };\n\n return (\n <div\n ref={trackRef}\n role=\"scrollbar\"\n aria-orientation={vertical ? 'vertical' : 'horizontal'}\n aria-valuemin={0}\n aria-valuemax={Math.round(g.maxOffset)}\n aria-valuenow={Math.round(g.offset)}\n data-embedpdf-scrollbar=\"\"\n data-axis={axis}\n data-state={shown ? 'visible' : 'hidden'}\n data-dragging={dragging ? '' : undefined}\n className={className}\n style={{\n ...trackDefaults,\n touchAction: 'none',\n userSelect: 'none',\n opacity: shown ? 1 : 0,\n transition: 'opacity 200ms',\n // a hidden overlay bar must not eat the clicks under it\n pointerEvents: shown ? 'auto' : 'none',\n ...style,\n }}\n onPointerDown={onTrackDown}\n onPointerMove={onMove}\n onPointerUp={endDrag}\n onPointerCancel={endDrag}\n onPointerEnter={() => setHovered(true)}\n onPointerLeave={() => setHovered(false)}\n >\n <div\n data-embedpdf-scrollbar-thumb=\"\"\n className={thumbClassName}\n style={{\n ...thumbDefaults,\n borderRadius: 6,\n background: 'var(--epdf-scrollbar-thumb, rgba(0, 0, 0, 0.4))',\n ...thumbStyle,\n }}\n onPointerDown={onThumbDown}\n />\n </div>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"scrollbar.js","names":[],"sources":["../src/scrollbar.tsx"],"sourcesContent":["/**\n * Headless scrollbar for a Stage lens — a pure view of `stage.scrollMetrics()`.\n *\n * The Stage exposes the native scroller contract (scrollTop/scrollHeight/\n * clientHeight, in screen px — see plugin-stage's README); this component turns\n * it into a native-feeling bar: thumb drag with pointer capture and a preserved\n * grab point, track paging with press-and-hold repeat that stops at the\n * pointer, a minimum thumb length on long documents, and macOS-style overlay\n * auto-hide. On an UNBOUNDED stage the metrics already carry the Figma\n * semantics (the range is the union of content and view), so the same bar\n * shrinks toward the edge as you pan away and rides you back — the mapping is\n * FROZEN for the duration of a thumb drag so the thumb never chases itself\n * while the union re-collapses.\n *\n * Headless styling contract: geometry and behavior live here; looks live in\n * your CSS. Style via `className`/`thumbClassName` (or the style props) against\n * [data-embedpdf-scrollbar][data-axis=\"y\"][data-state=\"visible|hidden\"]\n * [data-embedpdf-scrollbar][data-dragging]\n * [data-embedpdf-scrollbar-thumb]\n * The defaults are deliberately minimal: a transparent track pinned to the\n * viewport edge and a `var(--epdf-scrollbar-thumb, …)` pill, so the bar works\n * unstyled and disappears into any theme. Build something else entirely\n * (minimap, progress ring) from `useScrollMetrics()` + `stage.scrollTo()`.\n */\n\nimport * as React from 'react';\nimport { useEffect, useRef, useState } from 'react';\nimport { StageToken } from '@embedpdf/plugin-stage/contract';\nimport type { ScrollMetrics, StageCapability } from '@embedpdf/plugin-stage/contract';\nimport type { CapabilityToken } from '@embedpdf/core';\nimport { useCapability, useSelector } from './runtime';\n\nexport type ScrollbarAxis = 'x' | 'y';\n\n/** Live scroll metrics for a stage lens (reference-stable; see\n * `StageCapability.scrollMetrics`). The raw material for custom scroll UI. */\nexport function useScrollMetrics(\n token: CapabilityToken<StageCapability> = StageToken,\n): ScrollMetrics {\n return useSelector(token, (c) => c.scrollMetrics());\n}\n\nexport interface ScrollbarProps {\n axis: ScrollbarAxis;\n /** The stage lens to scroll (default: the main StageToken). */\n token?: CapabilityToken<StageCapability>;\n /**\n * Overlay auto-hide: fade `autoHide` ms after the camera stops moving\n * (drags and hover pin it visible; a hidden bar ignores the pointer, like\n * macOS). `false` = always visible. Visibility is published as\n * `data-state=\"visible|hidden\"` — the default style fades opacity; replace\n * it in CSS for any other treatment. Default 1200.\n */\n autoHide?: number | false;\n /** Minimum thumb length in px — native bars never vanish on long documents.\n * Position stays proportional via the standard travel remap. Default 24. */\n minThumbSize?: number;\n /**\n * Pressing the track: 'page' steps by 90% of a viewport toward the pointer,\n * repeats while held, and stops when the thumb reaches the pointer (the\n * native default); 'jump' centers the thumb at the pointer and hands off to\n * a drag (the macOS option-click / Figma feel). Default 'page'.\n */\n trackPress?: 'page' | 'jump';\n /** Track class/style. The default style pins the bar to the right (y) or\n * bottom (x) edge of the nearest positioned ancestor — the Stage container\n * when rendered in its `overlay`; override to place it anywhere else. */\n className?: string;\n style?: React.CSSProperties;\n thumbClassName?: string;\n thumbStyle?: React.CSSProperties;\n}\n\nconst clamp01 = (v: number) => Math.min(1, Math.max(0, v));\n\n/** Best-effort pointer capture: an already-released pointer (pen/touch races,\n * synthetic events in tests) throws NotFoundError — the press must still work,\n * just without capture (moves keep arriving while the pointer stays on us). */\nconst capturePointer = (el: Element, pointerId: number) => {\n try {\n el.setPointerCapture(pointerId);\n } catch {\n /* uncaptured is fine */\n }\n};\n\n/** Thumb geometry from metrics + a measured track. One formula for render,\n * drag capture, and paging — they can never disagree. */\nconst geometry = (m: ScrollMetrics, vertical: boolean, trackPx: number, minThumbSize: number) => {\n const client = vertical ? m.clientHeight : m.clientWidth;\n const total = vertical ? m.scrollHeight : m.scrollWidth;\n const offset = vertical ? m.scrollTop : m.scrollLeft;\n const maxOffset = Math.max(0, total - client);\n const thumbLen = Math.min(\n trackPx,\n Math.max(minThumbSize, total > 0 ? (client / total) * trackPx : 0),\n );\n const travel = Math.max(0, trackPx - thumbLen);\n const thumbPos = maxOffset > 0 ? (offset / maxOffset) * travel : 0;\n return { client, offset, maxOffset, thumbLen, travel, thumbPos };\n};\n\nexport function Scrollbar({\n axis,\n token = StageToken,\n autoHide = 1200,\n minThumbSize = 24,\n trackPress = 'page',\n className,\n style,\n thumbClassName,\n thumbStyle,\n}: ScrollbarProps) {\n const stage = useCapability(token);\n const m = useScrollMetrics(token);\n const vertical = axis === 'y';\n const scrollable = vertical ? m.scrollableY : m.scrollableX;\n\n const trackRef = useRef<HTMLDivElement>(null);\n const [trackPx, setTrackPx] = useState(0);\n const [dragging, setDragging] = useState(false);\n const [hovered, setHovered] = useState(false);\n const [active, setActive] = useState(true); // camera moved recently\n\n const g = geometry(m, vertical, trackPx, minThumbSize);\n\n // ── overlay auto-hide: any metrics change re-arms the fade timer ──────────\n const hideAfter = autoHide === false ? 0 : autoHide;\n useEffect(() => {\n if (!hideAfter) return;\n setActive(true);\n const t = setTimeout(() => setActive(false), hideAfter);\n return () => clearTimeout(t);\n }, [m, hideAfter]);\n const shown = !hideAfter || active || hovered || dragging;\n\n // ── track measurement (px mapping needs real geometry) ────────────────────\n useEffect(() => {\n const el = trackRef.current;\n if (!el) return;\n const measure = () => setTrackPx(vertical ? el.clientHeight : el.clientWidth);\n const ro = new ResizeObserver(measure);\n ro.observe(el);\n measure();\n return () => ro.disconnect();\n }, [vertical, scrollable]);\n\n // ── interactions. Drag state is FROZEN at pointer-down (grab point, travel,\n // max offset) and applied as relative pans — absolute-feeling in bounded\n // mode (the range is static there) and stable in unbounded mode, where\n // the live union would otherwise shift under the pointer mid-drag. ──────\n const dragRef = useRef<{ grab: number; applied: number; max: number; travel: number } | null>(\n null,\n );\n const pageRef = useRef<{ dir: 1 | -1; timer: number } | null>(null);\n const lastPtr = useRef(0);\n\n const ptrPos = (e: React.PointerEvent) => {\n const r = trackRef.current!.getBoundingClientRect();\n return vertical ? e.clientY - r.top : e.clientX - r.left;\n };\n /** Fresh geometry for paging steps — read from the capability, never a stale render. */\n const liveGeometry = () => geometry(stage.scrollMetrics(), vertical, trackPx, minThumbSize);\n\n const stopPaging = () => {\n if (pageRef.current) {\n clearTimeout(pageRef.current.timer);\n clearInterval(pageRef.current.timer);\n pageRef.current = null;\n }\n };\n const endDrag = () => {\n dragRef.current = null;\n stopPaging();\n setDragging(false);\n };\n\n const beginDrag = (grab: number, applied: number) => {\n dragRef.current = { grab, applied, max: g.maxOffset, travel: g.travel };\n setDragging(true);\n };\n\n const onThumbDown = (e: React.PointerEvent) => {\n if (e.button !== 0) return;\n e.preventDefault();\n e.stopPropagation();\n capturePointer(e.target as Element, e.pointerId);\n beginDrag(ptrPos(e) - g.thumbPos, g.offset);\n };\n\n const onTrackDown = (e: React.PointerEvent) => {\n if (e.button !== 0 || e.target !== e.currentTarget) return; // thumb handles its own\n e.preventDefault();\n capturePointer(e.currentTarget as Element, e.pointerId);\n const p = (lastPtr.current = ptrPos(e));\n\n if (trackPress === 'jump') {\n // land the thumb centered at the pointer, then it's an ordinary drag\n const want = g.travel > 0 ? clamp01((p - g.thumbLen / 2) / g.travel) * g.maxOffset : 0;\n stage.scrollTo(vertical ? { top: want } : { left: want });\n beginDrag(g.thumbLen / 2, want);\n return;\n }\n\n // 'page': step toward the pointer, repeat while held, stop at the pointer\n const dir: 1 | -1 = p < g.thumbPos ? -1 : 1;\n const step = () => {\n const live = liveGeometry();\n const reached =\n dir === 1\n ? lastPtr.current <= live.thumbPos + live.thumbLen\n : lastPtr.current >= live.thumbPos;\n if (reached) return stopPaging();\n stage.scrollBy(\n vertical ? { top: dir * live.client * 0.9 } : { left: dir * live.client * 0.9 },\n );\n };\n step();\n // native cadence: a beat before the repeat kicks in, then a steady march\n const timer = window.setTimeout(() => {\n if (!pageRef.current) return;\n pageRef.current.timer = window.setInterval(step, 80);\n }, 350);\n pageRef.current = { dir, timer };\n };\n\n // Pointer capture retargets to the pressed element and bubbles here — one\n // move/up pair serves thumb drags, jump-drags, and paging alike.\n const onMove = (e: React.PointerEvent) => {\n const d = dragRef.current;\n if (d) {\n if (d.travel <= 0) return;\n const want = clamp01((ptrPos(e) - d.grab) / d.travel) * d.max;\n const delta = want - d.applied;\n if (delta) {\n stage.panBy(vertical ? 0 : -delta, vertical ? -delta : 0);\n d.applied = want;\n }\n } else if (pageRef.current) {\n lastPtr.current = ptrPos(e);\n }\n };\n\n useEffect(() => endDrag, []); // unmount: no orphaned repeat timers\n\n if (!scrollable) return null;\n\n const trackDefaults: React.CSSProperties = vertical\n ? { position: 'absolute', top: 0, right: 0, bottom: 0, width: 12 }\n : { position: 'absolute', left: 0, right: 0, bottom: 0, height: 12 };\n const thumbDefaults: React.CSSProperties = vertical\n ? { position: 'absolute', left: 2, right: 2, top: g.thumbPos, height: g.thumbLen }\n : { position: 'absolute', top: 2, bottom: 2, left: g.thumbPos, width: g.thumbLen };\n\n return (\n <div\n ref={trackRef}\n role=\"scrollbar\"\n aria-orientation={vertical ? 'vertical' : 'horizontal'}\n aria-valuemin={0}\n aria-valuemax={Math.round(g.maxOffset)}\n aria-valuenow={Math.round(g.offset)}\n data-embedpdf-scrollbar=\"\"\n data-axis={axis}\n data-state={shown ? 'visible' : 'hidden'}\n data-dragging={dragging ? '' : undefined}\n className={className}\n style={{\n ...trackDefaults,\n touchAction: 'none',\n userSelect: 'none',\n opacity: shown ? 1 : 0,\n transition: 'opacity 200ms',\n // a hidden overlay bar must not eat the clicks under it\n pointerEvents: shown ? 'auto' : 'none',\n ...style,\n }}\n onPointerDown={onTrackDown}\n onPointerMove={onMove}\n onPointerUp={endDrag}\n onPointerCancel={endDrag}\n onPointerEnter={() => setHovered(true)}\n onPointerLeave={() => setHovered(false)}\n >\n <div\n data-embedpdf-scrollbar-thumb=\"\"\n className={thumbClassName}\n style={{\n ...thumbDefaults,\n borderRadius: 6,\n background: 'var(--epdf-scrollbar-thumb, rgba(0, 0, 0, 0.4))',\n ...thumbStyle,\n }}\n onPointerDown={onThumbDown}\n />\n </div>\n );\n}\n"],"mappings":"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"}
|
package/dist/search.cjs
CHANGED
|
@@ -17,25 +17,42 @@ function SearchLayer({ color = "#ffd500", activeColor = "#ff9632", blendMode = "
|
|
|
17
17
|
inset: 0,
|
|
18
18
|
pointerEvents: "none"
|
|
19
19
|
},
|
|
20
|
-
children: hits.map((hit) => hit.
|
|
21
|
-
const
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
20
|
+
children: hits.map((hit) => hit.segments.map(({ quad: q }, i) => {
|
|
21
|
+
const fill = hit === active ? activeColor : color;
|
|
22
|
+
if (q.upperStart.y === q.upperEnd.y && q.lowerStart.y === q.lowerEnd.y && q.upperStart.x === q.lowerStart.x) {
|
|
23
|
+
const tl = page.transform.toPixels(q.upperStart);
|
|
24
|
+
const br = page.transform.toPixels(q.lowerEnd);
|
|
25
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { style: {
|
|
26
|
+
position: "absolute",
|
|
27
|
+
left: tl.x,
|
|
28
|
+
top: tl.y,
|
|
29
|
+
width: br.x - tl.x,
|
|
30
|
+
height: br.y - tl.y,
|
|
31
|
+
background: fill,
|
|
32
|
+
mixBlendMode: blendMode,
|
|
33
|
+
borderRadius: 2
|
|
34
|
+
} }, `${hit.charStart}:${i}`);
|
|
35
|
+
}
|
|
36
|
+
const ring = [
|
|
37
|
+
q.upperStart,
|
|
38
|
+
q.upperEnd,
|
|
39
|
+
q.lowerEnd,
|
|
40
|
+
q.lowerStart
|
|
41
|
+
].map((p) => page.transform.toPixels(p));
|
|
42
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
|
|
43
|
+
style: {
|
|
44
|
+
position: "absolute",
|
|
45
|
+
inset: 0,
|
|
46
|
+
width: "100%",
|
|
47
|
+
height: "100%",
|
|
48
|
+
overflow: "visible",
|
|
49
|
+
mixBlendMode: blendMode
|
|
50
|
+
},
|
|
51
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("polygon", {
|
|
52
|
+
points: ring.map((p) => `${p.x},${p.y}`).join(" "),
|
|
53
|
+
fill
|
|
54
|
+
})
|
|
55
|
+
}, `${hit.charStart}:${i}`);
|
|
39
56
|
}))
|
|
40
57
|
});
|
|
41
58
|
}
|
package/dist/search.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"search.cjs","names":["usePage","useSelector","SearchToken","shallowArray","useCapability"],"sources":["../src/search.tsx"],"sourcesContent":["/**\n * The React surface for @embedpdf/plugin-search.\n *\n * <SearchLayer> is a dumb renderer, the SelectionLayer's twin: it reads\n * the page's content-space hit rects from the capability and paints them\n * through PageContext.
|
|
1
|
+
{"version":3,"file":"search.cjs","names":["usePage","useSelector","SearchToken","shallowArray","useCapability"],"sources":["../src/search.tsx"],"sourcesContent":["/**\n * The React surface for @embedpdf/plugin-search.\n *\n * <SearchLayer> is a dumb renderer, the SelectionLayer's twin: it reads\n * the page's content-space hit rects from the capability and paints them\n * through PageContext.toPixels. The active hit gets its own colour.\n * No pointer handling, no engine calls — search is driven from app chrome\n * via useSearch().\n */\n\n// One-line-per-feature: registration travels with the UI.\nexport * from '@embedpdf/plugin-search';\nimport * as React from 'react';\nimport { SearchToken } from '@embedpdf/plugin-search';\nimport type { SearchHit } from '@embedpdf/plugin-search';\nimport { shallowArray, useCapability, usePage, useSelector } from './runtime';\n\nexport interface SearchLayerProps {\n /** Highlight colour for hits — SOLID (default: highlighter yellow). */\n color?: string;\n /** Highlight colour for the ACTIVE hit — SOLID (default: orange). */\n activeColor?: string;\n /**\n * How the highlight composites with the page. `'multiply'` (default) is\n * the real-highlighter look: text stays crisp black through the colour,\n * only the paper tints. Pass `'normal'` (with translucent colours) for\n * dark/scanned documents where multiply-on-dark would vanish.\n */\n blendMode?: React.CSSProperties['mixBlendMode'];\n}\n\nexport function SearchLayer({\n color = '#ffd500',\n activeColor = '#ff9632',\n blendMode = 'multiply',\n}: SearchLayerProps) {\n const page = usePage();\n const hits = useSelector(SearchToken, (c) => c.hitsForPage(page.pon), shallowArray);\n const active = useSelector(SearchToken, (c) => c.activeHit());\n\n if (hits.length === 0) return null;\n\n return (\n <div style={{ position: 'absolute', inset: 0, pointerEvents: 'none' }}>\n {hits.map((hit: SearchHit) =>\n hit.segments.map(({ quad: q }, i) => {\n const fill = hit === active ? activeColor : color;\n // Upright hits keep the classic rounded div (pixel-identical to the\n // pre-orientation layer); rotated hits draw their true oriented cell.\n const upright =\n q.upperStart.y === q.upperEnd.y &&\n q.lowerStart.y === q.lowerEnd.y &&\n q.upperStart.x === q.lowerStart.x;\n if (upright) {\n const tl = page.transform.toPixels(q.upperStart);\n const br = page.transform.toPixels(q.lowerEnd);\n return (\n <div\n key={`${hit.charStart}:${i}`}\n style={{\n position: 'absolute',\n left: tl.x,\n top: tl.y,\n width: br.x - tl.x,\n height: br.y - tl.y,\n background: fill,\n mixBlendMode: blendMode,\n borderRadius: 2,\n }}\n />\n );\n }\n const ring = [q.upperStart, q.upperEnd, q.lowerEnd, q.lowerStart].map((p) =>\n page.transform.toPixels(p),\n );\n return (\n <svg\n key={`${hit.charStart}:${i}`}\n style={{\n position: 'absolute',\n inset: 0,\n width: '100%',\n height: '100%',\n overflow: 'visible',\n mixBlendMode: blendMode,\n }}\n >\n <polygon points={ring.map((p) => `${p.x},${p.y}`).join(' ')} fill={fill} />\n </svg>\n );\n }),\n )}\n </div>\n );\n}\n\n/** The search capability (search/clear/next/prev/…) for app chrome. */\nexport function useSearch() {\n return useCapability(SearchToken);\n}\n\n/** Reactive search read-model for chrome: the query, status, counts, progress. */\nexport function useSearchState() {\n const query = useSelector(SearchToken, (c) => c.query());\n const status = useSelector(SearchToken, (c) => c.status());\n const hitCount = useSelector(SearchToken, (c) => c.hitCount());\n const activeIndex = useSelector(SearchToken, (c) => c.activeIndex());\n const progress = useSelector(\n SearchToken,\n (c) => c.progress(),\n (a, b) => a.scanned === b.scanned && a.total === b.total,\n );\n const error = useSelector(SearchToken, (c) => c.errorMessage());\n return { query, status, hitCount, activeIndex, progress, error };\n}\n"],"mappings":";;;;;;;;AA+BA,SAAgB,YAAY,EAC1B,QAAQ,WACR,cAAc,WACd,YAAY,cACO;CACnB,MAAM,OAAOA,gBAAAA,QAAQ;CACrB,MAAM,OAAOC,gBAAAA,YAAYC,wBAAAA,cAAc,MAAM,EAAE,YAAY,KAAK,GAAG,GAAGC,gBAAAA,YAAY;CAClF,MAAM,SAASF,gBAAAA,YAAYC,wBAAAA,cAAc,MAAM,EAAE,UAAU,CAAC;CAE5D,IAAI,KAAK,WAAW,GAAG,OAAO;CAE9B,OACE,iBAAA,GAAA,kBAAA,IAAA,CAAC,OAAD;EAAK,OAAO;GAAE,UAAU;GAAY,OAAO;GAAG,eAAe;EAAO;EACjE,UAAA,KAAK,KAAK,QACT,IAAI,SAAS,KAAK,EAAE,MAAM,KAAK,MAAM;GACnC,MAAM,OAAO,QAAQ,SAAS,cAAc;GAO5C,IAHE,EAAE,WAAW,MAAM,EAAE,SAAS,KAC9B,EAAE,WAAW,MAAM,EAAE,SAAS,KAC9B,EAAE,WAAW,MAAM,EAAE,WAAW,GACrB;IACX,MAAM,KAAK,KAAK,UAAU,SAAS,EAAE,UAAU;IAC/C,MAAM,KAAK,KAAK,UAAU,SAAS,EAAE,QAAQ;IAC7C,OACE,iBAAA,GAAA,kBAAA,IAAA,CAAC,OAAD,EAEE,OAAO;KACL,UAAU;KACV,MAAM,GAAG;KACT,KAAK,GAAG;KACR,OAAO,GAAG,IAAI,GAAG;KACjB,QAAQ,GAAG,IAAI,GAAG;KAClB,YAAY;KACZ,cAAc;KACd,cAAc;IAChB,EACD,GAXM,GAAG,IAAI,UAAU,GAAG,GAW1B;GAEL;GACA,MAAM,OAAO;IAAC,EAAE;IAAY,EAAE;IAAU,EAAE;IAAU,EAAE;GAAU,CAAC,CAAC,KAAK,MACrE,KAAK,UAAU,SAAS,CAAC,CAC3B;GACA,OACE,iBAAA,GAAA,kBAAA,IAAA,CAAC,OAAD;IAEE,OAAO;KACL,UAAU;KACV,OAAO;KACP,OAAO;KACP,QAAQ;KACR,UAAU;KACV,cAAc;IAChB;IAEA,UAAA,iBAAA,GAAA,kBAAA,IAAA,CAAC,WAAD;KAAS,QAAQ,KAAK,KAAK,MAAM,GAAG,EAAE,EAAE,GAAG,EAAE,GAAG,CAAC,CAAC,KAAK,GAAG;KAAS;IAAO,CAAA;GACvE,GAXE,GAAG,IAAI,UAAU,GAAG,GAWtB;EAET,CAAC,CACH;CACG,CAAA;AAET;;AAGA,SAAgB,YAAY;CAC1B,OAAOE,gBAAAA,cAAcF,wBAAAA,WAAW;AAClC;;AAGA,SAAgB,iBAAiB;CAW/B,OAAO;EAAE,OAVKD,gBAAAA,YAAYC,wBAAAA,cAAc,MAAM,EAAE,MAAM,CAUzC;EAAG,QATDD,gBAAAA,YAAYC,wBAAAA,cAAc,MAAM,EAAE,OAAO,CASnC;EAAG,UARPD,gBAAAA,YAAYC,wBAAAA,cAAc,MAAM,EAAE,SAAS,CAQ7B;EAAG,aAPdD,gBAAAA,YAAYC,wBAAAA,cAAc,MAAM,EAAE,YAAY,CAOtB;EAAG,UAN9BD,gBAAAA,YACfC,wBAAAA,cACC,MAAM,EAAE,SAAS,IACjB,GAAG,MAAM,EAAE,YAAY,EAAE,WAAW,EAAE,UAAU,EAAE,KAGC;EAAG,OAD3CD,gBAAAA,YAAYC,wBAAAA,cAAc,MAAM,EAAE,aAAa,CACA;CAAE;AACjE"}
|
package/dist/search.d.cts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { n as SearchQuery } from "./AnnotationListSnapshot-C1U3XUxP-6kGwNoxy.cjs";
|
|
2
2
|
import * as React from "react";
|
|
3
3
|
export * from "@embedpdf/plugin-search";
|
|
4
4
|
//#region src/search.d.ts
|
package/dist/search.d.ts
CHANGED
package/dist/search.js
CHANGED
|
@@ -15,25 +15,42 @@ function SearchLayer({ color = "#ffd500", activeColor = "#ff9632", blendMode = "
|
|
|
15
15
|
inset: 0,
|
|
16
16
|
pointerEvents: "none"
|
|
17
17
|
},
|
|
18
|
-
children: hits.map((hit) => hit.
|
|
19
|
-
const
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
18
|
+
children: hits.map((hit) => hit.segments.map(({ quad: q }, i) => {
|
|
19
|
+
const fill = hit === active ? activeColor : color;
|
|
20
|
+
if (q.upperStart.y === q.upperEnd.y && q.lowerStart.y === q.lowerEnd.y && q.upperStart.x === q.lowerStart.x) {
|
|
21
|
+
const tl = page.transform.toPixels(q.upperStart);
|
|
22
|
+
const br = page.transform.toPixels(q.lowerEnd);
|
|
23
|
+
return /* @__PURE__ */ jsx("div", { style: {
|
|
24
|
+
position: "absolute",
|
|
25
|
+
left: tl.x,
|
|
26
|
+
top: tl.y,
|
|
27
|
+
width: br.x - tl.x,
|
|
28
|
+
height: br.y - tl.y,
|
|
29
|
+
background: fill,
|
|
30
|
+
mixBlendMode: blendMode,
|
|
31
|
+
borderRadius: 2
|
|
32
|
+
} }, `${hit.charStart}:${i}`);
|
|
33
|
+
}
|
|
34
|
+
const ring = [
|
|
35
|
+
q.upperStart,
|
|
36
|
+
q.upperEnd,
|
|
37
|
+
q.lowerEnd,
|
|
38
|
+
q.lowerStart
|
|
39
|
+
].map((p) => page.transform.toPixels(p));
|
|
40
|
+
return /* @__PURE__ */ jsx("svg", {
|
|
41
|
+
style: {
|
|
42
|
+
position: "absolute",
|
|
43
|
+
inset: 0,
|
|
44
|
+
width: "100%",
|
|
45
|
+
height: "100%",
|
|
46
|
+
overflow: "visible",
|
|
47
|
+
mixBlendMode: blendMode
|
|
48
|
+
},
|
|
49
|
+
children: /* @__PURE__ */ jsx("polygon", {
|
|
50
|
+
points: ring.map((p) => `${p.x},${p.y}`).join(" "),
|
|
51
|
+
fill
|
|
52
|
+
})
|
|
53
|
+
}, `${hit.charStart}:${i}`);
|
|
37
54
|
}))
|
|
38
55
|
});
|
|
39
56
|
}
|
package/dist/search.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"search.js","names":[],"sources":["../src/search.tsx"],"sourcesContent":["/**\n * The React surface for @embedpdf/plugin-search.\n *\n * <SearchLayer> is a dumb renderer, the SelectionLayer's twin: it reads\n * the page's content-space hit rects from the capability and paints them\n * through PageContext.
|
|
1
|
+
{"version":3,"file":"search.js","names":[],"sources":["../src/search.tsx"],"sourcesContent":["/**\n * The React surface for @embedpdf/plugin-search.\n *\n * <SearchLayer> is a dumb renderer, the SelectionLayer's twin: it reads\n * the page's content-space hit rects from the capability and paints them\n * through PageContext.toPixels. The active hit gets its own colour.\n * No pointer handling, no engine calls — search is driven from app chrome\n * via useSearch().\n */\n\n// One-line-per-feature: registration travels with the UI.\nexport * from '@embedpdf/plugin-search';\nimport * as React from 'react';\nimport { SearchToken } from '@embedpdf/plugin-search';\nimport type { SearchHit } from '@embedpdf/plugin-search';\nimport { shallowArray, useCapability, usePage, useSelector } from './runtime';\n\nexport interface SearchLayerProps {\n /** Highlight colour for hits — SOLID (default: highlighter yellow). */\n color?: string;\n /** Highlight colour for the ACTIVE hit — SOLID (default: orange). */\n activeColor?: string;\n /**\n * How the highlight composites with the page. `'multiply'` (default) is\n * the real-highlighter look: text stays crisp black through the colour,\n * only the paper tints. Pass `'normal'` (with translucent colours) for\n * dark/scanned documents where multiply-on-dark would vanish.\n */\n blendMode?: React.CSSProperties['mixBlendMode'];\n}\n\nexport function SearchLayer({\n color = '#ffd500',\n activeColor = '#ff9632',\n blendMode = 'multiply',\n}: SearchLayerProps) {\n const page = usePage();\n const hits = useSelector(SearchToken, (c) => c.hitsForPage(page.pon), shallowArray);\n const active = useSelector(SearchToken, (c) => c.activeHit());\n\n if (hits.length === 0) return null;\n\n return (\n <div style={{ position: 'absolute', inset: 0, pointerEvents: 'none' }}>\n {hits.map((hit: SearchHit) =>\n hit.segments.map(({ quad: q }, i) => {\n const fill = hit === active ? activeColor : color;\n // Upright hits keep the classic rounded div (pixel-identical to the\n // pre-orientation layer); rotated hits draw their true oriented cell.\n const upright =\n q.upperStart.y === q.upperEnd.y &&\n q.lowerStart.y === q.lowerEnd.y &&\n q.upperStart.x === q.lowerStart.x;\n if (upright) {\n const tl = page.transform.toPixels(q.upperStart);\n const br = page.transform.toPixels(q.lowerEnd);\n return (\n <div\n key={`${hit.charStart}:${i}`}\n style={{\n position: 'absolute',\n left: tl.x,\n top: tl.y,\n width: br.x - tl.x,\n height: br.y - tl.y,\n background: fill,\n mixBlendMode: blendMode,\n borderRadius: 2,\n }}\n />\n );\n }\n const ring = [q.upperStart, q.upperEnd, q.lowerEnd, q.lowerStart].map((p) =>\n page.transform.toPixels(p),\n );\n return (\n <svg\n key={`${hit.charStart}:${i}`}\n style={{\n position: 'absolute',\n inset: 0,\n width: '100%',\n height: '100%',\n overflow: 'visible',\n mixBlendMode: blendMode,\n }}\n >\n <polygon points={ring.map((p) => `${p.x},${p.y}`).join(' ')} fill={fill} />\n </svg>\n );\n }),\n )}\n </div>\n );\n}\n\n/** The search capability (search/clear/next/prev/…) for app chrome. */\nexport function useSearch() {\n return useCapability(SearchToken);\n}\n\n/** Reactive search read-model for chrome: the query, status, counts, progress. */\nexport function useSearchState() {\n const query = useSelector(SearchToken, (c) => c.query());\n const status = useSelector(SearchToken, (c) => c.status());\n const hitCount = useSelector(SearchToken, (c) => c.hitCount());\n const activeIndex = useSelector(SearchToken, (c) => c.activeIndex());\n const progress = useSelector(\n SearchToken,\n (c) => c.progress(),\n (a, b) => a.scanned === b.scanned && a.total === b.total,\n );\n const error = useSelector(SearchToken, (c) => c.errorMessage());\n return { query, status, hitCount, activeIndex, progress, error };\n}\n"],"mappings":";;;;;;AA+BA,SAAgB,YAAY,EAC1B,QAAQ,WACR,cAAc,WACd,YAAY,cACO;CACnB,MAAM,OAAO,QAAQ;CACrB,MAAM,OAAO,YAAY,cAAc,MAAM,EAAE,YAAY,KAAK,GAAG,GAAG,YAAY;CAClF,MAAM,SAAS,YAAY,cAAc,MAAM,EAAE,UAAU,CAAC;CAE5D,IAAI,KAAK,WAAW,GAAG,OAAO;CAE9B,OACE,oBAAC,OAAD;EAAK,OAAO;GAAE,UAAU;GAAY,OAAO;GAAG,eAAe;EAAO;EACjE,UAAA,KAAK,KAAK,QACT,IAAI,SAAS,KAAK,EAAE,MAAM,KAAK,MAAM;GACnC,MAAM,OAAO,QAAQ,SAAS,cAAc;GAO5C,IAHE,EAAE,WAAW,MAAM,EAAE,SAAS,KAC9B,EAAE,WAAW,MAAM,EAAE,SAAS,KAC9B,EAAE,WAAW,MAAM,EAAE,WAAW,GACrB;IACX,MAAM,KAAK,KAAK,UAAU,SAAS,EAAE,UAAU;IAC/C,MAAM,KAAK,KAAK,UAAU,SAAS,EAAE,QAAQ;IAC7C,OACE,oBAAC,OAAD,EAEE,OAAO;KACL,UAAU;KACV,MAAM,GAAG;KACT,KAAK,GAAG;KACR,OAAO,GAAG,IAAI,GAAG;KACjB,QAAQ,GAAG,IAAI,GAAG;KAClB,YAAY;KACZ,cAAc;KACd,cAAc;IAChB,EACD,GAXM,GAAG,IAAI,UAAU,GAAG,GAW1B;GAEL;GACA,MAAM,OAAO;IAAC,EAAE;IAAY,EAAE;IAAU,EAAE;IAAU,EAAE;GAAU,CAAC,CAAC,KAAK,MACrE,KAAK,UAAU,SAAS,CAAC,CAC3B;GACA,OACE,oBAAC,OAAD;IAEE,OAAO;KACL,UAAU;KACV,OAAO;KACP,OAAO;KACP,QAAQ;KACR,UAAU;KACV,cAAc;IAChB;IAEA,UAAA,oBAAC,WAAD;KAAS,QAAQ,KAAK,KAAK,MAAM,GAAG,EAAE,EAAE,GAAG,EAAE,GAAG,CAAC,CAAC,KAAK,GAAG;KAAS;IAAO,CAAA;GACvE,GAXE,GAAG,IAAI,UAAU,GAAG,GAWtB;EAET,CAAC,CACH;CACG,CAAA;AAET;;AAGA,SAAgB,YAAY;CAC1B,OAAO,cAAc,WAAW;AAClC;;AAGA,SAAgB,iBAAiB;CAW/B,OAAO;EAAE,OAVK,YAAY,cAAc,MAAM,EAAE,MAAM,CAUzC;EAAG,QATD,YAAY,cAAc,MAAM,EAAE,OAAO,CASnC;EAAG,UARP,YAAY,cAAc,MAAM,EAAE,SAAS,CAQ7B;EAAG,aAPd,YAAY,cAAc,MAAM,EAAE,YAAY,CAOtB;EAAG,UAN9B,YACf,cACC,MAAM,EAAE,SAAS,IACjB,GAAG,MAAM,EAAE,YAAY,EAAE,WAAW,EAAE,UAAU,EAAE,KAGC;EAAG,OAD3C,YAAY,cAAc,MAAM,EAAE,aAAa,CACA;CAAE;AACjE"}
|