@embedpdf/plugin-selection 3.0.0-next.14 → 3.0.0-next.16

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 (41) hide show
  1. package/dist/contract/host.cjs +16 -2
  2. package/dist/contract/host.cjs.map +1 -0
  3. package/dist/contract/host.d.cts +3 -2
  4. package/dist/contract/host.d.ts +3 -2
  5. package/dist/contract/host.js +16 -2
  6. package/dist/contract/host.js.map +1 -0
  7. package/dist/contract-BUiw1d8F.cjs +36 -0
  8. package/dist/contract-BUiw1d8F.cjs.map +1 -0
  9. package/dist/contract-DBXA64ep.js +26 -0
  10. package/dist/contract-DBXA64ep.js.map +1 -0
  11. package/dist/contract-DLs4vXVt.d.cts +252 -0
  12. package/dist/contract-DLs4vXVt.d.ts +252 -0
  13. package/dist/contract.cjs +3 -6
  14. package/dist/contract.d.cts +2 -7
  15. package/dist/contract.d.ts +2 -7
  16. package/dist/contract.js +2 -7
  17. package/dist/host-contract-DO2aLU2e.d.ts +34 -0
  18. package/dist/host-contract-Ddo4PcJJ.d.cts +34 -0
  19. package/dist/index.cjs +777 -51
  20. package/dist/index.cjs.map +1 -1
  21. package/dist/index.d.cts +97 -29
  22. package/dist/index.d.ts +97 -29
  23. package/dist/index.js +772 -51
  24. package/dist/index.js.map +1 -1
  25. package/package.json +5 -9
  26. package/dist/contract.cjs.map +0 -1
  27. package/dist/contract.js.map +0 -1
  28. package/dist/internal.cjs +0 -13
  29. package/dist/internal.d.cts +0 -63
  30. package/dist/internal.d.ts +0 -63
  31. package/dist/internal.js +0 -4
  32. package/dist/reducer-BAOQ2CJ8.cjs +0 -732
  33. package/dist/reducer-BAOQ2CJ8.cjs.map +0 -1
  34. package/dist/reducer-DbKtk9Xl.js +0 -685
  35. package/dist/reducer-DbKtk9Xl.js.map +0 -1
  36. package/dist/types-BVUcOsCh.js +0 -14
  37. package/dist/types-BVUcOsCh.js.map +0 -1
  38. package/dist/types-CUrUVl6K.d.cts +0 -292
  39. package/dist/types-CUrUVl6K.d.ts +0 -292
  40. package/dist/types-D9kEBMFj.cjs +0 -18
  41. package/dist/types-D9kEBMFj.cjs.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"index.cjs","names":["SelectionToken","InteractionToken","FeedbackToken","initialSelectionState","selectionReducer","createSelectionCapability","createTextSelectHandler"],"sources":["../src/selection.plugin.ts","../src/handles.ts"],"sourcesContent":["import { definePlugin } from '@embedpdf/core';\nimport { FeedbackToken, InteractionToken } from '@embedpdf/plugin-interaction/contract';\nimport { createSelectionCapability } from './capability';\nimport { createTextSelectHandler } from './handler';\nimport { initialSelectionState, selectionReducer } from './reducer';\nimport { SelectionToken } from './types';\nimport type { SelectionAction, SelectionHostCapability, SelectionState } from './types';\n\n/**\n * Text selection — document-scoped, requires the interaction hub. In `init` it\n * registers ITS pointer handler with the hub; the hub owns the pointer stream\n * and arbitration. Works with `<Stage>` or a standalone `<PageView>` — selection\n * only needs the page coordinate context + the engine's text geometry.\n *\n * Registry/content invalidation is wired inside the capability itself\n * (revision watch + document event subscription), so headless/programmatic\n * use gets the same reconciliation as the full viewer.\n */\nexport const selectionPlugin = () =>\n definePlugin<SelectionState, SelectionAction, SelectionHostCapability>({\n id: 'selection',\n token: SelectionToken,\n scope: 'document',\n requires: [InteractionToken],\n // Platform feedback (haptics) is OPTIONAL: absent in headless setups, the\n // handler simply never buzzes.\n optional: [FeedbackToken],\n initialState: initialSelectionState,\n reduce: selectionReducer,\n capability: createSelectionCapability,\n init: (ctx) => {\n const interaction = ctx.get(InteractionToken);\n const selection = ctx.get(SelectionToken); // our own capability (built before init)\n const feedback = ctx.tryGet(FeedbackToken);\n interaction.registerHandler(\n createTextSelectHandler(selection, interaction, feedback ?? undefined),\n );\n },\n });\n","/**\n * Selection handles — the pure policy half (the `wheel.ts` convention).\n *\n * A handle IS the boundary glyph's leading (or trailing) edge: a segment\n * between two corners of the glyph's ORIENTED cell, with the grab head just\n * beyond the ascent at the start / past the baseline at the end. Bar length,\n * angle, and head all derive from that one segment, so rotated text and\n * rotated pages are carried by construction — the AABB never enters.\n *\n * Everything here is DOM-free and framework-free: geometry is pure given a\n * projector, and the drag session speaks the selection's own gesture verbs\n * (`beginAt`/`extendTo`/`end` — the same ones the pointer handler uses; a\n * handle drag IS a selection gesture, re-anchored). The framework adapters\n * keep only subscriptions and markup; the DOM listener mechanics live in\n * `@embedpdf/web`'s `attachSelectionHandle`.\n *\n * The view dependency is STRUCTURAL (satisfied by five lines over\n * `StageCapability`) so this plugin stays stage-free — selection also runs\n * in stage-less hosts (`PageView`) — and the math tests run against a fake.\n */\nimport { textQuadEdge } from '@embedpdf/core-geometry';\nimport type { Point, TextQuad } from '@embedpdf/core-geometry';\nimport type { PageObjectNumber } from '@embedpdf/core';\n\n/** What handle geometry & drags need from the hosting view. */\nexport interface SelectionHandleView {\n /** Page content point → overlay px. Must be POINT-exact (compose\n * `pageToWorld` with `toScreen`); an AABB projector loses orientation. */\n toOverlay(pon: PageObjectNumber, pt: Point): Point | null;\n /** Overlay px → the page under it, or null over a gap. */\n pageAt(overlay: Point): { pon: PageObjectNumber; point: Point } | null;\n /** Overlay px → a SPECIFIC page's content space, unclamped. */\n pointOnPage(pon: PageObjectNumber, overlay: Point): Point | null;\n}\n\n/** One selection boundary, as the handle needs it (a `SelectionEndpoint` slice). */\nexport interface SelectionHandleEndpoint {\n pon: PageObjectNumber;\n /** The boundary glyph's own oriented cell, page content space. */\n glyphQuad: TextQuad;\n /** Reading direction of its segment (+1 = the frame's +x) — decides which\n * side of the cell is the selection's leading edge. */\n advance: 1 | -1;\n}\n\n/** The gesture verbs a handle drag drives — `SelectionHostCapability` satisfies it. */\nexport interface SelectionHandleTarget {\n beginAt(pon: PageObjectNumber, point: Point): boolean;\n extendTo(pon: PageObjectNumber, point: Point): void;\n end(): void;\n}\n\n// The iOS caret-handle design, one source for every adapter: a thin BAR that\n// is the selection's edge, capped by a screen-constant circle; PAD is the\n// invisible finger padding around the visual.\nexport const HANDLE_HEAD = 12; // px — the circle\nexport const HANDLE_BAR = 2; // px — the caret bar\nexport const HANDLE_PAD = 14; // px — grab padding\n\n/** Tilts within ~0.05° of upright render untransformed (float-noise guard —\n * and the dominant case stays pixel-identical to an axis-aligned box). */\nconst HANDLE_ROT_EPSILON = 0.05;\n\nexport interface SelectionHandleGeom {\n /** The projected edge, ascent corner first, overlay px. */\n bar: { from: Point; to: Point };\n /** The glyph's ink height on screen — zoom scaling comes free. */\n length: number;\n /** Degrees clockwise; 0 when the text is upright on screen. */\n rotation: number;\n /** True within the epsilon of upright: render with NO transform. */\n upright: boolean;\n /** Centre of the grab head, overlay px. */\n head: Point;\n}\n\nconst midpoint = (a: Point, b: Point): Point => ({ x: (a.x + b.x) / 2, y: (a.y + b.y) / 2 });\n\n/**\n * The handle's geometry for one endpoint, in overlay space. Null when the\n * endpoint's page isn't laid out (or the cell is degenerate) — render nothing.\n */\nexport function selectionHandleGeom(\n view: SelectionHandleView,\n ep: SelectionHandleEndpoint,\n role: 'start' | 'end',\n): SelectionHandleGeom | null {\n // Which SIDE of the cell is this selection's edge is a reading-order\n // question (`advance`), never a geometric one.\n const leading = role === 'start' ? ep.advance > 0 : ep.advance < 0;\n const [upperPage, lowerPage] = textQuadEdge(ep.glyphQuad, leading ? 'start' : 'end');\n const upper = view.toOverlay(ep.pon, upperPage);\n const lower = view.toOverlay(ep.pon, lowerPage);\n if (!upper || !lower) return null;\n const dx = lower.x - upper.x;\n const dy = lower.y - upper.y;\n const length = Math.hypot(dx, dy);\n if (length === 0) return null;\n // 0° when the text is upright: the bar runs straight down the screen.\n const rotation = (Math.atan2(dy, dx) * 180) / Math.PI - 90;\n const upright = Math.abs(rotation) < HANDLE_ROT_EPSILON;\n const ux = dx / length;\n const uy = dy / length;\n const head =\n role === 'start'\n ? { x: upper.x - (ux * HANDLE_HEAD) / 2, y: upper.y - (uy * HANDLE_HEAD) / 2 }\n : { x: lower.x + (ux * HANDLE_HEAD) / 2, y: lower.y + (uy * HANDLE_HEAD) / 2 };\n return { bar: { from: upper, to: lower }, length, rotation, upright, head };\n}\n\nexport interface SelectionHandleDragSession {\n /** Feed the pointer's current overlay position. */\n move(overlay: Point): void;\n /** The pointer released (or was cancelled): settle the gesture. */\n end(): void;\n}\n\n/**\n * One armed handle drag. Dragging a handle extends from the OPPOSITE\n * endpoint: the first movement re-roots the selection gesture at that\n * endpoint's cell centre, and every position then extends toward the pointer\n * — snapping to glyphs and crossing pages exactly like a pointer drag,\n * firing the same change/commit signals. Over a gap the point projects onto\n * the last page hit (unclamped), so the selection keeps tracking instead of\n * freezing at a page edge. `end()` commits only if the drag actually\n * re-rooted; an untouched press settles nothing.\n */\nexport function createSelectionHandleDrag(\n selection: SelectionHandleTarget,\n view: SelectionHandleView,\n opposite: SelectionHandleEndpoint,\n draggedPon: PageObjectNumber,\n): SelectionHandleDragSession {\n // The fixed anchor: the opposite endpoint's cell CENTRE — orientation-free\n // (a parallelogram's diagonal midpoints coincide), so it lands inside the\n // glyph for rotated text too.\n const q = opposite.glyphQuad;\n const anchorPoint = midpoint(midpoint(q.upperStart, q.lowerEnd), midpoint(q.upperEnd, q.lowerStart));\n let begun = false;\n let lastPon = draggedPon;\n return {\n move: (overlay) => {\n if (!begun) {\n if (!selection.beginAt(opposite.pon, anchorPoint)) return;\n begun = true;\n }\n const hit = view.pageAt(overlay);\n if (hit) {\n lastPon = hit.pon;\n selection.extendTo(hit.pon, hit.point);\n } else {\n const p = view.pointOnPage(lastPon, overlay);\n if (p) selection.extendTo(lastPon, p);\n }\n },\n end: () => {\n if (begun) selection.end(); // settle → menu reappears, onCommit fires\n },\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AAkBA,MAAa,yBAAA,GAAA,eAAA,aAAA,CAC4D;CACrE,IAAI;CACJ,OAAOA,cAAAA;CACP,OAAO;CACP,UAAU,CAACC,sCAAAA,gBAAgB;CAG3B,UAAU,CAACC,sCAAAA,aAAa;CACxB,cAAcC,gBAAAA;CACd,QAAQC,gBAAAA;CACR,YAAYC,gBAAAA;CACZ,OAAO,QAAQ;EACb,MAAM,cAAc,IAAI,IAAIJ,sCAAAA,gBAAgB;EAC5C,MAAM,YAAY,IAAI,IAAID,cAAAA,cAAc;EACxC,MAAM,WAAW,IAAI,OAAOE,sCAAAA,aAAa;EACzC,YAAY,gBACVI,gBAAAA,wBAAwB,WAAW,aAAa,YAAY,KAAA,CAAS,CACvE;CACF;AACF,CAAC;;;;;;;;;;;;;;;;;;;;;;;ACiBH,MAAa,cAAc;AAC3B,MAAa,aAAa;AAC1B,MAAa,aAAa;;;AAI1B,MAAM,qBAAqB;AAe3B,MAAM,YAAY,GAAU,OAAqB;CAAE,IAAI,EAAE,IAAI,EAAE,KAAK;CAAG,IAAI,EAAE,IAAI,EAAE,KAAK;AAAE;;;;;AAM1F,SAAgB,oBACd,MACA,IACA,MAC4B;CAG5B,MAAM,UAAU,SAAS,UAAU,GAAG,UAAU,IAAI,GAAG,UAAU;CACjE,MAAM,CAAC,WAAW,cAAA,GAAA,wBAAA,aAAA,CAA0B,GAAG,WAAW,UAAU,UAAU,KAAK;CACnF,MAAM,QAAQ,KAAK,UAAU,GAAG,KAAK,SAAS;CAC9C,MAAM,QAAQ,KAAK,UAAU,GAAG,KAAK,SAAS;CAC9C,IAAI,CAAC,SAAS,CAAC,OAAO,OAAO;CAC7B,MAAM,KAAK,MAAM,IAAI,MAAM;CAC3B,MAAM,KAAK,MAAM,IAAI,MAAM;CAC3B,MAAM,SAAS,KAAK,MAAM,IAAI,EAAE;CAChC,IAAI,WAAW,GAAG,OAAO;CAEzB,MAAM,WAAY,KAAK,MAAM,IAAI,EAAE,IAAI,MAAO,KAAK,KAAK;CACxD,MAAM,UAAU,KAAK,IAAI,QAAQ,IAAI;CACrC,MAAM,KAAK,KAAK;CAChB,MAAM,KAAK,KAAK;CAChB,MAAM,OACJ,SAAS,UACL;EAAE,GAAG,MAAM,IAAK,KAAA,KAAoB;EAAG,GAAG,MAAM,IAAK,KAAA,KAAoB;CAAE,IAC3E;EAAE,GAAG,MAAM,IAAK,KAAA,KAAoB;EAAG,GAAG,MAAM,IAAK,KAAA,KAAoB;CAAE;CACjF,OAAO;EAAE,KAAK;GAAE,MAAM;GAAO,IAAI;EAAM;EAAG;EAAQ;EAAU;EAAS;CAAK;AAC5E;;;;;;;;;;;AAmBA,SAAgB,0BACd,WACA,MACA,UACA,YAC4B;CAI5B,MAAM,IAAI,SAAS;CACnB,MAAM,cAAc,SAAS,SAAS,EAAE,YAAY,EAAE,QAAQ,GAAG,SAAS,EAAE,UAAU,EAAE,UAAU,CAAC;CACnG,IAAI,QAAQ;CACZ,IAAI,UAAU;CACd,OAAO;EACL,OAAO,YAAY;GACjB,IAAI,CAAC,OAAO;IACV,IAAI,CAAC,UAAU,QAAQ,SAAS,KAAK,WAAW,GAAG;IACnD,QAAQ;GACV;GACA,MAAM,MAAM,KAAK,OAAO,OAAO;GAC/B,IAAI,KAAK;IACP,UAAU,IAAI;IACd,UAAU,SAAS,IAAI,KAAK,IAAI,KAAK;GACvC,OAAO;IACL,MAAM,IAAI,KAAK,YAAY,SAAS,OAAO;IAC3C,IAAI,GAAG,UAAU,SAAS,SAAS,CAAC;GACtC;EACF;EACA,WAAW;GACT,IAAI,OAAO,UAAU,IAAI;EAC3B;CACF;AACF"}
1
+ {"version":3,"file":"index.cjs","names":["SELECTION_DEFAULTS","InteractionToken","FeedbackToken","PluginError","SelectionToken","InteractionToken","FeedbackToken","SELECTION_DEFAULTS","SelectionToken"],"sources":["../src/handler.ts","../src/connect.ts","../src/model.ts","../src/controller.ts","../src/selection.plugin.ts","../src/state.ts","../src/handles.ts"],"sourcesContent":["import type {\n InteractionHostCapability,\n InteractionHandler,\n PlatformFeedback,\n} from '@embedpdf/plugin-interaction/contract/host';\nimport type { PageRef } from '@embedpdf/engine-core/runtime';\nimport { SELECTION_DEFAULTS } from './contract';\nimport type { SelectionHostCapability } from './host-contract';\n\nconst CURSOR_TOKEN = 'selection-text';\n\nexport interface TextSelectHandlerOptions {\n /** Viewport px the pointer must move before a drag-select begins, read when the drag would start. */\n dragThreshold?: () => number;\n}\n\n/**\n * The text-selection pointer handler. It is live only under tools that enable the\n * `'text-select'` tag (the built-in `pointer` tool does; `pan` does not — so\n * switching to pan disables text selection with zero special-casing here).\n *\n * - single click → deselect; records an anchor but selects nothing until\n * the pointer moves past the drag threshold (Chrome/Acrobat feel)\n * - single-click + drag → caret selection from the anchor (extends across pages)\n * - double-click → select the word\n * - triple-click → select the visual line\n * - down off-text → deselect, and don't capture (so it can't block)\n * - hover → I-beam only when over text, else the pointer cursor\n */\nexport function createTextSelectHandler(\n selection: SelectionHostCapability,\n interaction: InteractionHostCapability,\n feedback?: PlatformFeedback,\n options: TextSelectHandlerOptions = {},\n): InteractionHandler {\n const dragThreshold = options.dragThreshold ?? (() => SELECTION_DEFAULTS.dragThreshold);\n // Per-gesture drag-threshold state (one active gesture at a time — the hub owner).\n let anchor: {\n page: PageRef;\n point: { x: number; y: number };\n viewportX: number;\n viewportY: number;\n } | null = null;\n let dragging = false;\n\n return {\n id: 'text-select',\n priority: 60,\n enabledFor: (tool) => tool.enables.has('text-select'),\n onDown: (sample) => {\n anchor = null;\n dragging = false;\n if (!sample.page) return false; // over a gap — let a lower handler (e.g. scroll) try\n if (!selection.canSelect()) return false; // nothing here can engage — stay out of the way\n const { ref: page, point } = sample.page;\n const clicks = sample.clickCount ?? 1;\n // The pointer is down: until it lifts, the gesture is active and\n // selection-scoped UI stays out of the way.\n selection.beginGesture();\n if (clicks >= 3) {\n selection.selectLineAt(page, point);\n return true;\n }\n if (clicks === 2) {\n const selected = selection.selectWordAt(page, point);\n // The platform's \"selection changed\" tick — only when a touch\n // long-press actually engaged a word (never on blank space, never for\n // a mouse double-click). The gesture marker is the honest signal; a\n // raw double-click sample never carries it.\n if (selected && sample.gesture === 'long-press') feedback?.selection();\n return true;\n }\n // Single click: clear immediately (clicking deselects), then record an\n // anchor only if over text, but begin no selection until the drag\n // threshold is met.\n selection.clear();\n if (!selection.isOverText(page, point)) {\n selection.endGesture(); // empty space → nothing to drive, don't capture\n return false;\n }\n anchor = { page, point, viewportX: sample.viewport.x, viewportY: sample.viewport.y };\n return true;\n },\n onMove: (sample) => {\n if (!sample.page || !anchor) return;\n if (!dragging) {\n const travelled = Math.hypot(\n sample.viewport.x - anchor.viewportX,\n sample.viewport.y - anchor.viewportY,\n );\n if (travelled < dragThreshold()) {\n return; // still a click, not a drag — select nothing yet\n }\n dragging = true;\n selection.beginGestureAt(anchor.page, anchor.point); // open the selection at the anchor\n }\n selection.extendTo(sample.page.ref, sample.page.point);\n },\n onUp: () => {\n anchor = null;\n dragging = false;\n selection.endGesture();\n },\n onCancel: () => {\n // Aborted (second finger → pinch, or a system cancel): drop the\n // in-flight selection instead of committing a sliver of it. endGesture()\n // after clear() settles the gesture with nothing to commit.\n anchor = null;\n dragging = false;\n selection.clear();\n selection.endGesture();\n },\n onHover: (sample) => {\n if (!sample.page) {\n interaction.claimCursor(CURSOR_TOKEN, null); // off the page → pointer\n return;\n }\n // Warm geometry on first hover; show the I-beam only when actually over text.\n void selection.ensureLoaded(sample.page.ref);\n const overText = selection.isOverText(sample.page.ref, sample.page.point);\n interaction.claimCursor(CURSOR_TOKEN, overText ? 'text' : null, 10);\n },\n };\n}\n","/** The selection plugin's wiring to its siblings, run from the controller's `connect`. */\nimport type { PluginContext } from '@embedpdf/core';\nimport { FeedbackToken, InteractionToken } from '@embedpdf/plugin-interaction/contract/host';\n\nimport type { SelectionSettings } from './contract';\nimport { createTextSelectHandler } from './handler';\nimport type { SelectionHostCapability } from './host-contract';\nimport type { SelectionState } from './model';\n\n/**\n * Register the text-select pointer handler with the interaction hub, which\n * owns the pointer stream and arbitration. Platform feedback (haptics) is\n * optional: without it (headless setups) the handler never buzzes. The\n * handler reads the drag threshold at each press, so a settings change applies\n * to the next gesture.\n */\nexport function connectSelection(\n ctx: PluginContext<SelectionState, SelectionSettings>,\n selection: SelectionHostCapability,\n): void {\n const interaction = ctx.get(InteractionToken);\n const feedback = ctx.tryGet(FeedbackToken) ?? undefined;\n const settings = ctx.settings();\n ctx.cleanup(\n interaction.registerHandler(\n createTextSelectHandler(selection, interaction, feedback, {\n dragThreshold: () => settings.get().dragThreshold,\n }),\n ),\n );\n}\n","/**\n * The selection's session state: the range, its derived page-space segments\n * and two facts. Every function below is a pure transition or projection;\n * the controller applies the transitions with `ctx.state.update`.\n */\nimport type { DocumentEvent, PageObjectNumber, PageRef } from '@embedpdf/core';\nimport type { Quad } from '@embedpdf/core-geometry';\nimport type { SelectionSegment } from './geometry';\n\n/** A glyph address: a page and a character index within that page. */\nexport interface GlyphPosition {\n readonly page: PageRef;\n readonly glyph: number;\n}\n\n/** Anchor = where the selection began, focus = the current end. Inclusive. */\nexport interface SelectionRange {\n readonly anchor: GlyphPosition;\n readonly focus: GlyphPosition;\n}\n\n/** Page object number → that page's segments, in page space. */\nexport type SegmentsByPage = Readonly<Record<PageObjectNumber, readonly SelectionSegment[]>>;\n\nexport interface SelectionState {\n readonly selection: SelectionRange | null;\n /** Per-line segments derived from the range, per page, in page space. */\n readonly segments: SegmentsByPage;\n /** A consumer owns the selection visual (a markup tool draws its own preview). */\n readonly highlightHidden: boolean;\n /** The user is still selecting: a gesture is in flight (pointer-down … pointer-up). */\n readonly selecting: boolean;\n}\n\nexport const initialSelectionState = (): SelectionState => ({\n selection: null,\n segments: {},\n highlightHidden: false,\n selecting: false,\n});\n\nconst samePosition = (left: GlyphPosition, right: GlyphPosition): boolean =>\n left.glyph === right.glyph && left.page.objectNumber === right.page.objectNumber;\n\nconst sameRange = (left: SelectionRange | null, right: SelectionRange | null): boolean =>\n left === right ||\n (left !== null &&\n right !== null &&\n samePosition(left.anchor, right.anchor) &&\n samePosition(left.focus, right.focus));\n\nconst sameQuad = (left: Quad, right: Quad): boolean =>\n (['upperLeft', 'upperRight', 'lowerLeft', 'lowerRight'] as const).every(\n (corner) => left[corner].x === right[corner].x && left[corner].y === right[corner].y,\n );\n\nconst sameSegments = (\n left: readonly SelectionSegment[],\n right: readonly SelectionSegment[],\n): boolean =>\n left === right ||\n (left.length === right.length &&\n left.every(\n (segment, index) =>\n segment.advance === right[index].advance && sameQuad(segment.quad, right[index].quad),\n ));\n\n/**\n * Replace the range and its segments. A page whose segments are unchanged\n * keeps its previous array, so per-page reads stay reference-stable, and a\n * recompute that changes nothing returns the same state.\n */\nexport function setSelection(\n state: SelectionState,\n selection: SelectionRange,\n segments: SegmentsByPage,\n): SelectionState {\n const pages = Object.keys(segments).map(Number);\n let segmentsChanged = pages.length !== Object.keys(state.segments).length;\n const merged: Record<PageObjectNumber, readonly SelectionSegment[]> = {};\n for (const pageObjectNumber of pages) {\n const previous = state.segments[pageObjectNumber];\n const next = segments[pageObjectNumber];\n if (previous && sameSegments(previous, next)) {\n merged[pageObjectNumber] = previous;\n } else {\n merged[pageObjectNumber] = next;\n segmentsChanged = true;\n }\n }\n const rangeChanged = !sameRange(state.selection, selection);\n if (!rangeChanged && !segmentsChanged) return state;\n return {\n ...state,\n selection: rangeChanged ? selection : state.selection,\n segments: segmentsChanged ? merged : state.segments,\n };\n}\n\nexport function clearSelection(state: SelectionState): SelectionState {\n return state.selection === null && Object.keys(state.segments).length === 0\n ? state\n : { ...state, selection: null, segments: {} };\n}\n\nexport const setHighlightHidden = (state: SelectionState, hidden: boolean): SelectionState =>\n state.highlightHidden === hidden ? state : { ...state, highlightHidden: hidden };\n\nexport const setSelecting = (state: SelectionState, selecting: boolean): SelectionState =>\n state.selecting === selecting ? state : { ...state, selecting };\n\n/**\n * The pages whose content (and so character space) a confirmed event\n * changed: applied redactions, flattened pages, and pages an annotation\n * flatten painted into (a free-text note becomes page text). Null for every\n * other event.\n */\nexport function contentChangedPagesOf(event: DocumentEvent): readonly PageRef[] | null {\n if (event.type === 'annotations.flattened') {\n return event.results.some((result) => result.status === 'applied') ? [event.page] : null;\n }\n if (event.type !== 'redaction.applied' && event.type !== 'pages.flattened') return null;\n const pages = event.results\n .filter((result) => result.status === 'applied')\n .map((result) => result.page);\n return pages.length > 0 ? pages : null;\n}\n","import {\n PluginError,\n memo,\n memoByKey,\n type PluginContext,\n type DocCapability,\n type OperationOptions,\n type PageObjectNumber,\n type PageRef,\n} from '@embedpdf/core';\nimport { boundsOfRects, quadBounds, type Point, type Rect } from '@embedpdf/core-geometry';\nimport {\n sliceText,\n toPageRef,\n type PageTextSnapshot,\n type TextLayout,\n} from '@embedpdf/engine-core/runtime';\nimport { connectSelection } from './connect';\nimport type {\n SelectionAnchor,\n SelectionChangedEvent,\n SelectionClearedEvent,\n SelectionCommittedEvent,\n SelectionEndpoint,\n SelectionRangeInput,\n SelectionSettings,\n SelectionSnapshot,\n TextRange,\n} from './contract';\nimport type { SelectionSegment } from './geometry';\nimport type { SelectionHostCapability } from './host-contract';\nimport {\n clearSelection,\n contentChangedPagesOf,\n setHighlightHidden,\n setSelecting,\n setSelection,\n type GlyphPosition,\n type SegmentsByPage,\n type SelectionRange,\n type SelectionState,\n} from './model';\n\nconst SELECT_SCOPE: DocCapability = 'doc.text.select';\nconst COPY_SCOPE: DocCapability = 'doc.text.copy';\nconst EMPTY_SEGMENTS: readonly SelectionSegment[] = Object.freeze([]);\nconst EMPTY_PAGES: readonly PageRef[] = Object.freeze([]);\nconst EMPTY_RECTS: readonly Rect[] = Object.freeze([]);\n/** How many page-text reads a `readText` keeps in flight at once. */\nconst TEXT_READ_CONCURRENCY = 8;\n/** An open-ended focus: settles to the page's real last character once its geometry loads. */\nconst OPEN_END = Number.MAX_SAFE_INTEGER;\n\n/** Everything the read model derives from one state object, built once per state. */\ninterface ReadModel {\n readonly snapshot: SelectionSnapshot;\n readonly pages: readonly PageRef[];\n readonly anchor: SelectionAnchor | null;\n}\n\n/**\n * The selection controller.\n *\n * State holds the range and its derived page-space segments. Engine data\n * lives outside state:\n *\n * - text geometry is a page mirror: PDF-space snapshots loaded on demand,\n * re-read when a confirmed event changes a page's content (redaction\n * apply, flatten) and dropped when a page is deleted. Page-space\n * geometry is derived from a snapshot and the page's current layout, so\n * a view rotation re-derives without asking the engine again.\n * - `textSnapshots` holds per-page text promises for `readText`, dropped\n * on the same content changes and when a page leaves the registry.\n *\n * `recompute` rebuilds the segments of every loaded page in the span and\n * warms the rest, which recompute when their geometry arrives. The change\n * and clear events are derived from state changes in one place.\n *\n * Selection is cross-page: glyphs are ordered globally by (page index,\n * glyph), so a drag from page 2 into page 4 selects the tail of 2, all of 3,\n * and the head of 4. Stored endpoints are clamped once their page's geometry\n * is known, which is how `selectAll`'s open end settles.\n *\n * The settings belong to the plugin registration; the pointer handler reads\n * the drag threshold from them, and the framework layers read the colors.\n */\nexport function createSelectionController(ctx: PluginContext<SelectionState, SelectionSettings>) {\n const settings = ctx.settings();\n const changed = ctx.events.source<SelectionChangedEvent>();\n const committed = ctx.events.source<SelectionCommittedEvent>();\n const cleared = ctx.events.source<SelectionClearedEvent>();\n const textSnapshots = new Map<PageObjectNumber, Promise<PageTextSnapshot>>();\n\n const state = () => ctx.state.get();\n const canSelect = (): boolean => ctx.allows(SELECT_SCOPE);\n const canCopy = (): boolean => ctx.allows(COPY_SCOPE);\n\n // ── page addressing (the registry is the truth for order and layout) ──\n const pageIndexOf = (page: PageRef): number => ctx.getPage(page)?.index ?? -1;\n const pageAtIndex = (index: number): PageRef | undefined => ctx.document()?.pages[index]?.ref;\n\n // ── text geometry ──\n const geometry = ctx.pageMirror<TextLayout>({\n name: 'geometry',\n load: (doc, page) => doc.page(page).text.layout(),\n affected: contentChangedPagesOf,\n changed: ({ cause }) => {\n // A page's geometry arrived: a boundary page's segments can fill in.\n if (cause !== 'load') return;\n const current = state().selection;\n if (current) recompute(current);\n },\n });\n\n /** A page's snapshot while it is loaded and current: a page whose re-read\n * failed has no geometry, exactly as {@link SelectionHostCapability.isLoaded} says. */\n const snapshotOf = (page: PageRef): TextLayout | undefined =>\n geometry.getStatus(page) === 'ready' ? geometry.get(page) : undefined;\n\n /** A page's text layout while it is loaded and current. */\n const geometryFor = snapshotOf;\n\n const glyphAt = (layout: TextLayout, point: Point): number | null => layout.charAt(point);\n\n /** Warm a page's geometry; the returned promise never rejects (see the host contract). */\n function ensureLoaded(page: PageRef): Promise<void> {\n // Authorized-only warming: without doc.text.select the read is guaranteed\n // to be refused, so it is not issued. The engine stays the security boundary.\n if (!canSelect() || !ctx.getPage(page)) return Promise.resolve();\n return geometry.ensureLoaded(page).catch(() => {\n /* refused or failed: nothing to paint, and a later call retries */\n });\n }\n\n // ── the range model ──\n function orderedEnds(selection: SelectionRange): {\n start: GlyphPosition;\n end: GlyphPosition;\n direction: 'forward' | 'backward';\n } {\n const anchorPageIndex = pageIndexOf(selection.anchor.page);\n const focusPageIndex = pageIndexOf(selection.focus.page);\n const anchorFirst =\n anchorPageIndex < focusPageIndex ||\n (anchorPageIndex === focusPageIndex && selection.anchor.glyph <= selection.focus.glyph);\n return anchorFirst\n ? { start: selection.anchor, end: selection.focus, direction: 'forward' }\n : { start: selection.focus, end: selection.anchor, direction: 'backward' };\n }\n\n /** Clamp a position into its page's real character range once geometry is known. */\n function clampPosition(position: GlyphPosition): GlyphPosition {\n const textLayout = geometryFor(position.page);\n if (!textLayout) return position;\n const max = Math.max(textLayout.charCount - 1, 0);\n const glyph = Math.max(0, Math.min(position.glyph, max));\n return glyph === position.glyph ? position : { page: position.page, glyph };\n }\n\n /** Rebuild merged line segments for every loaded page in the span; warm the\n * rest. Clears when an endpoint's page has left the registry: a range\n * across a structural edit is meaningless. */\n function recompute(selection: SelectionRange): void {\n const clamped: SelectionRange = {\n anchor: clampPosition(selection.anchor),\n focus: clampPosition(selection.focus),\n };\n const { start, end } = orderedEnds(clamped);\n const startPageIndex = pageIndexOf(start.page);\n const endPageIndex = pageIndexOf(end.page);\n if (startPageIndex < 0 || endPageIndex < 0) {\n ctx.state.update(clearSelection);\n return;\n }\n const segments: Record<PageObjectNumber, readonly SelectionSegment[]> = {};\n for (let i = startPageIndex; i <= endPageIndex; i++) {\n const page = pageAtIndex(i);\n if (!page) continue;\n const textLayout = geometryFor(page);\n if (!textLayout) {\n void ensureLoaded(page); // recomputes on arrival\n continue;\n }\n const from = i === startPageIndex ? start.glyph : 0;\n const to = i === endPageIndex ? end.glyph : textLayout.charCount - 1;\n segments[page.objectNumber] = textLayout.segments({ start: from, count: to - from + 1 });\n }\n ctx.state.update(setSelection, clamped, segments);\n }\n\n // ── the read model, built once per selection and registry revision ──\n // Only the range and the segments go in, so a change to the gesture fact or\n // the highlight's visibility keeps every read (the range, the page list) as it was.\n const readModel = memo(\n () => [state().selection, state().segments, ctx.document()?.revision ?? 0],\n (selection, segments): ReadModel => buildReadModel(selection, segments),\n );\n\n function buildReadModel(selection: SelectionRange | null, segments: SegmentsByPage): ReadModel {\n const pagesWithSegments = Object.keys(segments)\n .map((key) => toPageRef(Number(key)))\n .filter((page) => (segments[page.objectNumber]?.length ?? 0) > 0)\n .sort((left, right) => pageIndexOf(left) - pageIndexOf(right));\n const snapshotPages = pagesWithSegments.map((page) => ({\n page,\n segments: segments[page.objectNumber],\n }));\n const pages = pagesWithSegments.length ? pagesWithSegments : EMPTY_PAGES;\n if (!selection) {\n return {\n snapshot: {\n pages: snapshotPages,\n start: null,\n end: null,\n direction: 'forward',\n range: null,\n },\n pages,\n anchor: null,\n };\n }\n const { start, end, direction } = orderedEnds(selection);\n const snapshot: SelectionSnapshot = {\n pages: snapshotPages,\n start: endpointFor(segments, start, 'start'),\n end: endpointFor(segments, end, 'end'),\n direction,\n range: {\n start: { page: start.page, index: start.glyph },\n end: { page: end.page, index: end.glyph + 1 },\n },\n };\n // Prefer the gesture's end page; while its geometry is still loading,\n // fall back to the last page (document order) with materialized\n // segments, so the anchor never jumps backwards mid-drag.\n const endBounds = unionOf(segments[end.page.objectNumber]);\n let anchor: SelectionAnchor | null = endBounds ? { page: end.page, bounds: endBounds } : null;\n const last = pagesWithSegments[pagesWithSegments.length - 1];\n if (!anchor && last) {\n anchor = { page: last, bounds: unionOf(segments[last.objectNumber])! };\n }\n return { snapshot, pages, anchor };\n }\n\n function endpointFor(\n segmentsByPage: SegmentsByPage,\n position: GlyphPosition,\n which: 'start' | 'end',\n ): SelectionEndpoint | null {\n const segments = segmentsByPage[position.page.objectNumber] ?? EMPTY_SEGMENTS;\n if (!segments.length) return null;\n const segment = which === 'start' ? segments[0] : segments[segments.length - 1];\n // Anchor the endpoint to the boundary glyph's own oriented cell so caret\n // placement lands on the exact character edge; fall back to the segment\n // when the glyph is degenerate (e.g. a generated space).\n const textLayout = geometryFor(position.page);\n const cell = textLayout ? textLayout.charQuad(position.glyph) : null;\n if (textLayout && cell) {\n return {\n page: position.page,\n glyphQuad: cell,\n advance: segment.advance,\n rect: quadBounds(cell),\n };\n }\n return {\n page: position.page,\n glyphQuad: segment.quad,\n advance: segment.advance,\n rect: segment.rect,\n };\n }\n\n const unionOf = (segments: readonly SelectionSegment[] | undefined): Rect | null =>\n segments ? boundsOfRects(segments.map((segment) => segment.rect)) : null;\n\n /** A page's segment boxes, the same array until that page's segments change. */\n const rectsOf = memoByKey(\n (pageObjectNumber: PageObjectNumber) => [state().segments[pageObjectNumber]],\n (_pageObjectNumber, segments): readonly Rect[] =>\n (segments ?? EMPTY_SEGMENTS).map((segment) => segment.rect),\n );\n\n // ── events, derived from each state change ──\n ctx.state.onChange(({ previous, next }) => {\n if (previous.selection === next.selection && previous.segments === next.segments) return;\n const model = readModel();\n changed.emit({ range: model.snapshot.range, pages: model.pages });\n if (previous.selection !== null && next.selection === null) cleared.emit({});\n });\n\n // ── writes ──\n function select(input: SelectionRangeInput): void {\n ctx.assertAllowed(SELECT_SCOPE, 'selection.select');\n const range: TextRange =\n 'page' in input\n ? {\n start: { page: input.page, index: input.start },\n end: { page: input.page, index: input.start + input.count },\n }\n : input;\n ctx.assertPageRef(range.start.page);\n ctx.assertPageRef(range.end.page);\n const startPageIndex = pageIndexOf(range.start.page);\n let endPageIndex = pageIndexOf(range.end.page);\n let endCharIndex = range.end.index;\n if (\n endPageIndex < startPageIndex ||\n (endPageIndex === startPageIndex && endCharIndex <= range.start.index)\n ) {\n ctx.state.update(clearSelection); // an empty range is no selection\n return;\n }\n if (endCharIndex === 0) {\n // Half-open end exactly at a page boundary: the last included character\n // is the previous page's last one, which settles by clamping on load.\n endPageIndex -= 1;\n if (endPageIndex < startPageIndex) {\n ctx.state.update(clearSelection);\n return;\n }\n endCharIndex = OPEN_END;\n }\n const focusPage = pageAtIndex(endPageIndex);\n if (!focusPage) return;\n recompute({\n anchor: { page: range.start.page, glyph: Math.max(0, range.start.index) },\n focus: { page: focusPage, glyph: endCharIndex - 1 },\n });\n }\n\n function selectAll(): void {\n ctx.assertAllowed(SELECT_SCOPE, 'selection.selectAll');\n const pages = ctx.document()?.pages ?? [];\n if (pages.length === 0) return;\n recompute({\n anchor: { page: pages[0].ref, glyph: 0 },\n focus: { page: pages[pages.length - 1].ref, glyph: OPEN_END },\n });\n }\n\n function selectPage(target: PageRef | number): void {\n ctx.assertAllowed(SELECT_SCOPE, 'selection.selectPage');\n const page = ctx.pageOf(target).ref;\n recompute({ anchor: { page, glyph: 0 }, focus: { page, glyph: OPEN_END } });\n }\n\n /** Word / line around a point. Returns whether a span actually engaged\n * (geometry present and a glyph under the point): the fact haptics and\n * other success-gated feedback key on, so nothing buzzes over blank space. */\n function selectSpanAt(target: PageRef | number, point: Point, expand: 'word' | 'line'): boolean {\n ctx.assertAllowed(SELECT_SCOPE, `selection.select${expand === 'word' ? 'WordAt' : 'LineAt'}`);\n const page = ctx.pageOf(target).ref;\n const textLayout = geometryFor(page);\n if (!textLayout) return false;\n const glyph = glyphAt(textLayout, point);\n if (glyph == null) return false;\n const span = expand === 'word' ? textLayout.wordAt(glyph) : textLayout.lineAt(glyph);\n if (!span) return false;\n recompute({\n anchor: { page, glyph: span.start },\n focus: { page, glyph: span.start + span.count - 1 },\n });\n return true;\n }\n\n function extendTo(target: PageRef | number, point: Point): void {\n ctx.assertAllowed(SELECT_SCOPE, 'selection.extendTo');\n const page = ctx.pageOf(target).ref;\n const current = state().selection;\n if (!current) return;\n const textLayout = geometryFor(page);\n if (!textLayout) {\n void ensureLoaded(page); // extended onto a page not loaded yet: lands on arrival\n return;\n }\n const glyph = glyphAt(textLayout, point);\n if (glyph == null) return; // off-text: keep the last focus\n recompute({ anchor: current.anchor, focus: { page, glyph } });\n }\n\n // ── text extraction ──\n /** Per-page text snapshot, cached as a promise so concurrent readers share\n * one fetch. Rejections are evicted: a refused or failed read must not\n * poison the cache for a later authorized call. */\n function pageText(page: PageRef): Promise<PageTextSnapshot> {\n const key = page.objectNumber;\n const cached = textSnapshots.get(key);\n if (cached) return cached;\n const pending = Promise.resolve(ctx.doc.page(page).text.get());\n textSnapshots.set(key, pending);\n pending.catch(() => {\n if (textSnapshots.get(key) === pending) textSnapshots.delete(key);\n });\n return pending;\n }\n\n async function readText(options?: OperationOptions): Promise<string> {\n ctx.assertAllowed(COPY_SCOPE, 'selection.readText');\n const range = readModel().snapshot.range;\n return range ? readRange(range, options) : '';\n }\n\n async function readTextInRange(range: TextRange, options?: OperationOptions): Promise<string> {\n ctx.assertAllowed(COPY_SCOPE, 'selection.readTextInRange');\n return readRange(range, options);\n }\n\n /** A range's text, read a few pages at a time. Cancelling stops the wait at\n * once; the page reads it started finish into the cache, which other\n * readers share. */\n async function readRange(range: TextRange, options?: OperationOptions): Promise<string> {\n ctx.assertPageRef(range.start.page);\n ctx.assertPageRef(range.end.page);\n const startPageIndex = pageIndexOf(range.start.page);\n let endPageIndex = pageIndexOf(range.end.page);\n let endCharIndex = range.end.index;\n if (\n endPageIndex < startPageIndex ||\n (endPageIndex === startPageIndex && endCharIndex <= range.start.index)\n ) {\n return '';\n }\n if (endCharIndex === 0) {\n endPageIndex -= 1; // a range ending at a page boundary includes nothing of that page\n endCharIndex = OPEN_END;\n }\n // Per-page half-open character spans. Geometry is not needed: boundary\n // offsets come from the range, interior pages span their whole text\n // (`sliceText` clamps to the snapshot's charCount).\n const spans: Array<{ page: PageRef; from: number; to: number }> = [];\n for (let i = startPageIndex; i <= endPageIndex; i++) {\n const page = pageAtIndex(i);\n if (!page) continue;\n spans.push({\n page,\n from: i === startPageIndex ? Math.max(0, range.start.index) : 0,\n to: i === endPageIndex ? endCharIndex : OPEN_END,\n });\n }\n const signal = options?.signal;\n const parts: string[] = new Array(spans.length);\n for (let base = 0; base < spans.length; base += TEXT_READ_CONCURRENCY) {\n // A signal that has fired starts no more reads.\n if (signal?.aborted) {\n throw new PluginError('operation-cancelled', 'selection', 'reading the text was cancelled');\n }\n const batch = spans.slice(base, base + TEXT_READ_CONCURRENCY);\n const snapshots = await ctx.cancellable(\n signal,\n Promise.all(batch.map((span) => pageText(span.page))),\n );\n snapshots.forEach((snapshot, index) => {\n const { from, to } = batch[index];\n parts[base + index] = sliceText(snapshot, { start: from, count: to - from });\n });\n }\n return parts.join('\\n');\n }\n\n // ── invalidation ──\n /** Structural registry change (rotate/move/delete/insert): drop the text of\n * pages that left, then recompute. Rotation re-derives page-space geometry\n * through the layout key; a deleted endpoint page clears through\n * recompute's registry check. */\n function onPagesUpdated(): void {\n const alive = new Set((ctx.document()?.pages ?? []).map((info) => info.ref.objectNumber));\n for (const pageObjectNumber of [...textSnapshots.keys()]) {\n if (!alive.has(pageObjectNumber)) textSnapshots.delete(pageObjectNumber);\n }\n const current = state().selection;\n if (current) recompute(current);\n }\n\n /** Page content changed: the pages' text is stale, and a live range points\n * into a character space that no longer exists, so it clears. The\n * geometry mirror re-reads those pages itself. */\n function onContentChanged(pages: readonly PageRef[]): void {\n for (const page of pages) textSnapshots.delete(page.objectNumber);\n ctx.state.update(clearSelection);\n }\n\n const setGesture = (active: boolean): void => ctx.state.update(setSelecting, active);\n\n /** A page's segments, by its ref or its index: a page that isn't in the document has none. */\n const segmentsOn = (target: PageRef | number): readonly SelectionSegment[] => {\n const page = ctx.getPage(target);\n return page ? (state().segments[page.ref.objectNumber] ?? EMPTY_SEGMENTS) : EMPTY_SEGMENTS;\n };\n\n const api: SelectionHostCapability = {\n ...settings.api,\n\n // ── public lens ──\n canSelect,\n canCopy,\n select,\n selectAll,\n selectPage,\n selectWordAt: (page, point) => selectSpanAt(page, point, 'word'),\n selectLineAt: (page, point) => selectSpanAt(page, point, 'line'),\n extendTo,\n clear: () => ctx.state.update(clearSelection),\n hasSelection: () => state().selection != null,\n isSelecting: () => state().selecting,\n getSnapshot: () => readModel().snapshot,\n getRange: () => readModel().snapshot.range,\n listSelectedPages: () => readModel().pages,\n listSegments: segmentsOn,\n listRects: (target) => {\n const page = ctx.getPage(target);\n return page ? rectsOf(page.ref.objectNumber) : EMPTY_RECTS;\n },\n getAnchor: () => readModel().anchor,\n readText,\n readTextInRange,\n onChanged: changed.on,\n onCommitted: committed.on,\n onCleared: cleared.on,\n\n // ── host lens ──\n ensureLoaded,\n isLoaded: (page) => geometry.getStatus(page) === 'ready',\n isOverText: (page, point) => {\n const textLayout = geometryFor(page);\n return textLayout ? glyphAt(textLayout, point) != null : false;\n },\n beginGesture: () => setGesture(true),\n beginGestureAt: (page, point) => {\n if (!canSelect()) return false;\n const textLayout = geometryFor(page);\n if (!textLayout) return false;\n const glyph = glyphAt(textLayout, point);\n if (glyph == null) return false; // not near text: the caller lets the gesture go\n // The gesture opens before the selection changes, so listeners see a\n // coherent (gesture, segments) pair.\n setGesture(true);\n recompute({ anchor: { page, glyph }, focus: { page, glyph } });\n return true;\n },\n endGesture: () => {\n // Settle first, so commit listeners (menus, clipboard prefetch) observe\n // isSelecting() === false.\n setGesture(false);\n const range = readModel().snapshot.range;\n if (range) committed.emit({ range });\n },\n setHighlightVisible: (visible) => ctx.state.update(setHighlightHidden, !visible),\n isHighlightVisible: () => !state().highlightHidden,\n };\n\n return {\n api,\n connect() {\n // Registry changes (rotate/move/delete/insert) bump the document revision.\n ctx.watch(() => ctx.document()?.revision ?? 0, onPagesUpdated);\n ctx.listen(ctx.doc.events, (event) => {\n const pages = contentChangedPagesOf(event);\n if (pages) onContentChanged(pages);\n });\n connectSelection(ctx, api);\n },\n };\n}\n","import { definePlugin } from '@embedpdf/core';\nimport { FeedbackToken, InteractionToken } from '@embedpdf/plugin-interaction/contract/host';\nimport { SELECTION_DEFAULTS, type SelectionConfig } from './contract';\nimport { createSelectionController } from './controller';\nimport { SelectionToken } from './host-contract';\nimport { initialSelectionState } from './model';\n\n/**\n * Text selection: document-scoped, requires the interaction hub, whose\n * pointer stream drives the selection's own handler. Works with `<Stage>`\n * or a standalone `<PageView>`: selection only needs the page coordinate\n * context and the engine's text geometry. Platform feedback (haptics) is\n * optional. `config` is the settings the app registers, over\n * {@link SELECTION_DEFAULTS}.\n */\nexport const selectionPlugin = (config?: SelectionConfig) =>\n definePlugin({\n id: 'selection',\n token: SelectionToken,\n scope: 'document',\n requires: [InteractionToken],\n optional: [FeedbackToken],\n state: initialSelectionState,\n settings: { defaults: SELECTION_DEFAULTS, registered: config },\n create: createSelectionController,\n });\n","/**\n * The selection page's State table as code: what `useSelectionState()`\n * returns, and the same fields in every other framework. The geometry is not\n * here: a layer reads its page's segments with `listSegments(page)`, so a\n * toolbar that only shows whether something is selected doesn't re-render for\n * every step of a drag.\n */\nimport { defineState } from '@embedpdf/core';\n\nimport { SelectionToken } from './contract';\n\nexport const selectionState = defineState(SelectionToken, {\n read: (selection) => ({\n hasSelection: selection.hasSelection(),\n isSelecting: selection.isSelecting(),\n range: selection.getRange(),\n pages: selection.listSelectedPages(),\n }),\n empty: {\n hasSelection: false,\n isSelecting: false,\n range: null,\n pages: [],\n },\n});\n","/**\n * Selection handles — the pure policy half.\n *\n * A handle is the boundary glyph's leading (or trailing) edge: a segment\n * between two corners of the glyph's oriented cell, with the grab head just\n * beyond the ascent at the start / past the baseline at the end. Bar length,\n * angle, and head all derive from that one segment, so rotated text and\n * rotated pages are carried by construction — the AABB never enters.\n *\n * Everything here is DOM-free and framework-free: geometry is pure given a\n * projector, and the drag session speaks the selection's own gesture verbs\n * (`beginGestureAt`/`extendTo`/`endGesture` — the same ones the pointer handler uses; a\n * handle drag is a selection gesture, re-anchored). The framework adapters\n * keep only subscriptions and markup; the DOM listener mechanics live in\n * `@embedpdf/web`'s `attachSelectionHandle`.\n *\n * The view dependency is structural (`selectionHandleViewOf` builds it from\n * anything shaped like `StageCapability`) so this plugin stays stage-free —\n * selection also runs in stage-less hosts (`PageView`) — and the math tests\n * run against a fake.\n */\nimport { quadEdge } from '@embedpdf/core-geometry';\nimport type { Point, Quad } from '@embedpdf/core-geometry';\nimport type { PageRef } from '@embedpdf/engine-core/runtime';\nimport type { SelectionSnapshot } from './contract';\n\n/** What handle geometry & drags need from the hosting view. */\nexport interface SelectionHandleView {\n /** Page page point → overlay px. Must be point-exact (compose\n * `pageToWorld` with `toScreen`); an AABB projector loses orientation. */\n toOverlay(page: PageRef, point: Point): Point | null;\n /** Overlay px → the page under it, or null over a gap. */\n pageAt(overlay: Point): { ref: PageRef; point: Point } | null;\n /** Overlay px → a specific page's page space, unclamped. */\n pointOnPage(page: PageRef, overlay: Point): Point | null;\n}\n\n/** The Stage's projection, as the handles use it: `StageCapability` satisfies it. */\nexport interface SelectionHandleStage {\n pageToViewport(page: PageRef, point: Point): Point | null;\n getPageAt(point: Point): { ref: PageRef; point: Point } | null;\n viewportToPage(page: PageRef, point: Point): Point | null;\n}\n\n/**\n * The handles' view over a Stage: point-exact projection in, page lookup out.\n * (The Stage's box projector, `pageRectToViewport`, is for upright overlays\n * and would lose the orientation a handle needs.)\n */\nexport function selectionHandleViewOf(stage: SelectionHandleStage): SelectionHandleView {\n return {\n toOverlay: (page, point) => stage.pageToViewport(page, point),\n pageAt: (overlay) => stage.getPageAt(overlay),\n pointOnPage: (page, overlay) => stage.viewportToPage(page, overlay),\n };\n}\n\n/** One selection boundary, as the handle needs it (a `SelectionEndpoint` slice). */\nexport interface SelectionHandleEndpoint {\n page: PageRef;\n /** The boundary glyph's own oriented cell, page page space. */\n glyphQuad: Quad;\n /** Reading direction of its segment (+1 = the frame's +x) — decides which\n * side of the cell is the selection's leading edge. */\n advance: 1 | -1;\n}\n\n/** Both ends of a selection, as the handles need them. */\nexport interface SelectionHandleEndpoints {\n start: SelectionHandleEndpoint;\n end: SelectionHandleEndpoint;\n}\n\n/**\n * The two ends of the selection in `snapshot` (`getSnapshot()`), or null\n * while nothing is selected. A new object on every call: compare with\n * `sameSelectionEndpoints` from `@embedpdf/web`.\n */\nexport function selectionHandleEndpointsOf(\n snapshot: Pick<SelectionSnapshot, 'start' | 'end'>,\n): SelectionHandleEndpoints | null {\n const { start, end } = snapshot;\n if (!start || !end) return null;\n return {\n start: { page: start.page, glyphQuad: start.glyphQuad, advance: start.advance },\n end: { page: end.page, glyphQuad: end.glyphQuad, advance: end.advance },\n };\n}\n\n/** The gesture verbs a handle drag drives — `SelectionHostCapability` satisfies it. */\nexport interface SelectionHandleTarget {\n beginGestureAt(page: PageRef, point: Point): boolean;\n extendTo(page: PageRef, point: Point): void;\n endGesture(): void;\n}\n\n// The iOS caret-handle design, one source for every adapter: a thin bar that\n// is the selection's edge, capped by a screen-constant circle; pad is the\n// invisible finger padding around the visual.\nexport const HANDLE_HEAD = 12; // px — the circle\nexport const HANDLE_BAR = 2; // px — the caret bar\nexport const HANDLE_PAD = 14; // px — grab padding\n\n/** Tilts within ~0.05° of upright render untransformed (float-noise guard —\n * and the dominant case stays pixel-identical to an axis-aligned box). */\nconst HANDLE_ROT_EPSILON = 0.05;\n\nexport interface SelectionHandleGeom {\n /** The projected edge, ascent corner first, overlay px. */\n bar: { from: Point; to: Point };\n /** The glyph's ink height on screen — zoom scaling comes free. */\n length: number;\n /** Degrees clockwise; 0 when the text is upright on screen. */\n rotation: number;\n /** True within the epsilon of upright: render with no transform. */\n upright: boolean;\n /** Centre of the grab head, overlay px. */\n head: Point;\n}\n\nconst midpoint = (from: Point, to: Point): Point => ({\n x: (from.x + to.x) / 2,\n y: (from.y + to.y) / 2,\n});\n\n/**\n * The handle's geometry for one endpoint, in overlay space. Null when the\n * endpoint's page isn't laid out (or the cell is degenerate) — render nothing.\n */\nexport function selectionHandleGeom(\n view: SelectionHandleView,\n endpoint: SelectionHandleEndpoint,\n role: 'start' | 'end',\n): SelectionHandleGeom | null {\n // Which side of the cell is this selection's edge is a reading-order\n // question (`advance`), never a geometric one.\n const leading = role === 'start' ? endpoint.advance > 0 : endpoint.advance < 0;\n const [upperPage, lowerPage] = quadEdge(endpoint.glyphQuad, leading ? 'left' : 'right');\n const upper = view.toOverlay(endpoint.page, upperPage);\n const lower = view.toOverlay(endpoint.page, lowerPage);\n if (!upper || !lower) return null;\n const dx = lower.x - upper.x;\n const dy = lower.y - upper.y;\n const length = Math.hypot(dx, dy);\n if (length === 0) return null;\n // 0° when the text is upright: the bar runs straight down the screen.\n const rotation = (Math.atan2(dy, dx) * 180) / Math.PI - 90;\n const upright = Math.abs(rotation) < HANDLE_ROT_EPSILON;\n const ux = dx / length;\n const uy = dy / length;\n const head =\n role === 'start'\n ? { x: upper.x - (ux * HANDLE_HEAD) / 2, y: upper.y - (uy * HANDLE_HEAD) / 2 }\n : { x: lower.x + (ux * HANDLE_HEAD) / 2, y: lower.y + (uy * HANDLE_HEAD) / 2 };\n return { bar: { from: upper, to: lower }, length, rotation, upright, head };\n}\n\nexport interface SelectionHandleDragSession {\n /** Feed the pointer's current overlay position. */\n move(overlay: Point): void;\n /** The pointer released (or was cancelled): settle the gesture. */\n end(): void;\n}\n\n/**\n * One armed handle drag. Dragging a handle extends from the opposite\n * endpoint: the first movement re-roots the selection gesture at that\n * endpoint's cell centre, and every position then extends toward the pointer\n * — snapping to glyphs and crossing pages exactly like a pointer drag,\n * firing the same change/commit signals. Over a gap the point projects onto\n * the last page hit (unclamped), so the selection keeps tracking instead of\n * freezing at a page edge. `end()` commits only if the drag actually\n * re-rooted; an untouched press settles nothing.\n */\nexport function createSelectionHandleDrag(\n selection: SelectionHandleTarget,\n view: SelectionHandleView,\n opposite: SelectionHandleEndpoint,\n draggedPage: PageRef,\n): SelectionHandleDragSession {\n // The fixed anchor: the opposite endpoint's cell centre — orientation-free\n // (a parallelogram's diagonal midpoints coincide), so it lands inside the\n // glyph for rotated text too.\n const quad = opposite.glyphQuad;\n const anchorPoint = midpoint(\n midpoint(quad.upperLeft, quad.lowerRight),\n midpoint(quad.upperRight, quad.lowerLeft),\n );\n let begun = false;\n let lastPage = draggedPage;\n return {\n move: (overlay) => {\n if (!begun) {\n if (!selection.beginGestureAt(opposite.page, anchorPoint)) return;\n begun = true;\n }\n const hit = view.pageAt(overlay);\n if (hit) {\n lastPage = hit.ref;\n selection.extendTo(hit.ref, hit.point);\n } else {\n const point = view.pointOnPage(lastPage, overlay);\n if (point) selection.extendTo(lastPage, point);\n }\n },\n end: () => {\n if (begun) selection.endGesture(); // settle → menu reappears, onCommitted fires\n },\n };\n}\n\n/** A press on a handle, armed: the point it grabbed and the drag it starts. */\nexport interface ArmedSelectionHandle {\n /** The point the user grabbed: the bar's midpoint, overlay px. */\n base: Point;\n drag: SelectionHandleDragSession;\n}\n\n/**\n * Arm a drag of the `role` handle at a press, with the view and the endpoints\n * as they are at that moment: it extends the selection from the opposite end.\n * Null when the handle's page isn't laid out.\n */\nexport function armSelectionHandle(\n selection: SelectionHandleTarget,\n view: SelectionHandleView,\n endpoints: SelectionHandleEndpoints,\n role: 'start' | 'end',\n): ArmedSelectionHandle | null {\n const geometry = selectionHandleGeom(view, endpoints[role], role);\n if (!geometry) return null;\n const opposite = endpoints[role === 'start' ? 'end' : 'start'];\n return {\n base: midpoint(geometry.bar.from, geometry.bar.to),\n drag: createSelectionHandleDrag(selection, view, opposite, endpoints[role].page),\n };\n}\n"],"mappings":"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"}
package/dist/index.d.cts CHANGED
@@ -1,48 +1,104 @@
1
- import { a as SelectionHostCapability, c as SelectionRangeInput, f as TextPosition, h as SelectionSegment, i as SelectionEndpoint, l as SelectionSnapshot, n as SelectionAction, o as SelectionMenuAnchor, p as TextRange, r as SelectionCapability, u as SelectionState } from "./types-CUrUVl6K.cjs";
2
- import { SelectionToken } from "./contract.cjs";
3
- import { PageObjectNumber } from "@embedpdf/core";
4
- import { Point, TextQuad } from "@embedpdf/core-geometry";
1
+ import { a as SelectionClearedEvent, c as SelectionEndpoint, d as SelectionSnapshot, f as TextPosition, h as SelectionSegment, i as SelectionChangedEvent, l as SelectionRangeInput, m as SelectionToken, n as SelectionAnchor, o as SelectionCommittedEvent, p as TextRange, r as SelectionCapability, s as SelectionConfig, t as SELECTION_DEFAULTS, u as SelectionSettings } from "./contract-DLs4vXVt.cjs";
2
+ import { t as SelectionHostCapability } from "./host-contract-Ddo4PcJJ.cjs";
3
+ import { PageObjectNumber, PageRef } from "@embedpdf/core";
4
+ import { Point, Quad } from "@embedpdf/core-geometry";
5
+ import { PageRef as PageRef$1 } from "@embedpdf/engine-core/runtime";
6
+ //#region src/model.d.ts
7
+ /** A glyph address: a page and a character index within that page. */
8
+ interface GlyphPosition {
9
+ readonly page: PageRef;
10
+ readonly glyph: number;
11
+ }
12
+ /** Anchor = where the selection began, focus = the current end. Inclusive. */
13
+ interface SelectionRange {
14
+ readonly anchor: GlyphPosition;
15
+ readonly focus: GlyphPosition;
16
+ }
17
+ /** Page object number → that page's segments, in page space. */
18
+ type SegmentsByPage = Readonly<Record<PageObjectNumber, readonly SelectionSegment[]>>;
19
+ interface SelectionState {
20
+ readonly selection: SelectionRange | null;
21
+ /** Per-line segments derived from the range, per page, in page space. */
22
+ readonly segments: SegmentsByPage;
23
+ /** A consumer owns the selection visual (a markup tool draws its own preview). */
24
+ readonly highlightHidden: boolean;
25
+ /** The user is still selecting: a gesture is in flight (pointer-down … pointer-up). */
26
+ readonly selecting: boolean;
27
+ }
28
+ //#endregion
5
29
  //#region src/selection.plugin.d.ts
6
30
  /**
7
- * Text selection — document-scoped, requires the interaction hub. In `init` it
8
- * registers ITS pointer handler with the hub; the hub owns the pointer stream
9
- * and arbitration. Works with `<Stage>` or a standalone `<PageView>` — selection
10
- * only needs the page coordinate context + the engine's text geometry.
11
- *
12
- * Registry/content invalidation is wired inside the capability itself
13
- * (revision watch + document event subscription), so headless/programmatic
14
- * use gets the same reconciliation as the full viewer.
31
+ * Text selection: document-scoped, requires the interaction hub, whose
32
+ * pointer stream drives the selection's own handler. Works with `<Stage>`
33
+ * or a standalone `<PageView>`: selection only needs the page coordinate
34
+ * context and the engine's text geometry. Platform feedback (haptics) is
35
+ * optional. `config` is the settings the app registers, over
36
+ * {@link SELECTION_DEFAULTS}.
15
37
  */
16
- declare const selectionPlugin: () => import("@embedpdf/core").PluginDef<SelectionState, SelectionAction, SelectionHostCapability>;
38
+ declare const selectionPlugin: (config?: SelectionConfig) => import("@embedpdf/core").PluginDef<SelectionState, SelectionHostCapability, SelectionSettings>;
39
+ //#endregion
40
+ //#region src/state.d.ts
41
+ declare const selectionState: import("@embedpdf/core").StateDeclaration<SelectionCapability, {
42
+ hasSelection: boolean;
43
+ isSelecting: boolean;
44
+ range: TextRange | null;
45
+ pages: readonly import("@embedpdf/core").PageRef[];
46
+ }>;
17
47
  //#endregion
18
48
  //#region src/handles.d.ts
19
49
  /** What handle geometry & drags need from the hosting view. */
20
50
  interface SelectionHandleView {
21
- /** Page content point → overlay px. Must be POINT-exact (compose
51
+ /** Page page point → overlay px. Must be point-exact (compose
22
52
  * `pageToWorld` with `toScreen`); an AABB projector loses orientation. */
23
- toOverlay(pon: PageObjectNumber, pt: Point): Point | null;
53
+ toOverlay(page: PageRef$1, point: Point): Point | null;
24
54
  /** Overlay px → the page under it, or null over a gap. */
25
55
  pageAt(overlay: Point): {
26
- pon: PageObjectNumber;
56
+ ref: PageRef$1;
27
57
  point: Point;
28
58
  } | null;
29
- /** Overlay px → a SPECIFIC page's content space, unclamped. */
30
- pointOnPage(pon: PageObjectNumber, overlay: Point): Point | null;
59
+ /** Overlay px → a specific page's page space, unclamped. */
60
+ pointOnPage(page: PageRef$1, overlay: Point): Point | null;
31
61
  }
62
+ /** The Stage's projection, as the handles use it: `StageCapability` satisfies it. */
63
+ interface SelectionHandleStage {
64
+ pageToViewport(page: PageRef$1, point: Point): Point | null;
65
+ getPageAt(point: Point): {
66
+ ref: PageRef$1;
67
+ point: Point;
68
+ } | null;
69
+ viewportToPage(page: PageRef$1, point: Point): Point | null;
70
+ }
71
+ /**
72
+ * The handles' view over a Stage: point-exact projection in, page lookup out.
73
+ * (The Stage's box projector, `pageRectToViewport`, is for upright overlays
74
+ * and would lose the orientation a handle needs.)
75
+ */
76
+ declare function selectionHandleViewOf(stage: SelectionHandleStage): SelectionHandleView;
32
77
  /** One selection boundary, as the handle needs it (a `SelectionEndpoint` slice). */
33
78
  interface SelectionHandleEndpoint {
34
- pon: PageObjectNumber;
35
- /** The boundary glyph's own oriented cell, page content space. */
36
- glyphQuad: TextQuad;
79
+ page: PageRef$1;
80
+ /** The boundary glyph's own oriented cell, page page space. */
81
+ glyphQuad: Quad;
37
82
  /** Reading direction of its segment (+1 = the frame's +x) — decides which
38
83
  * side of the cell is the selection's leading edge. */
39
84
  advance: 1 | -1;
40
85
  }
86
+ /** Both ends of a selection, as the handles need them. */
87
+ interface SelectionHandleEndpoints {
88
+ start: SelectionHandleEndpoint;
89
+ end: SelectionHandleEndpoint;
90
+ }
91
+ /**
92
+ * The two ends of the selection in `snapshot` (`getSnapshot()`), or null
93
+ * while nothing is selected. A new object on every call: compare with
94
+ * `sameSelectionEndpoints` from `@embedpdf/web`.
95
+ */
96
+ declare function selectionHandleEndpointsOf(snapshot: Pick<SelectionSnapshot, 'start' | 'end'>): SelectionHandleEndpoints | null;
41
97
  /** The gesture verbs a handle drag drives — `SelectionHostCapability` satisfies it. */
42
98
  interface SelectionHandleTarget {
43
- beginAt(pon: PageObjectNumber, point: Point): boolean;
44
- extendTo(pon: PageObjectNumber, point: Point): void;
45
- end(): void;
99
+ beginGestureAt(page: PageRef$1, point: Point): boolean;
100
+ extendTo(page: PageRef$1, point: Point): void;
101
+ endGesture(): void;
46
102
  }
47
103
  declare const HANDLE_HEAD = 12;
48
104
  declare const HANDLE_BAR = 2;
@@ -57,7 +113,7 @@ interface SelectionHandleGeom {
57
113
  length: number;
58
114
  /** Degrees clockwise; 0 when the text is upright on screen. */
59
115
  rotation: number;
60
- /** True within the epsilon of upright: render with NO transform. */
116
+ /** True within the epsilon of upright: render with no transform. */
61
117
  upright: boolean;
62
118
  /** Centre of the grab head, overlay px. */
63
119
  head: Point;
@@ -66,7 +122,7 @@ interface SelectionHandleGeom {
66
122
  * The handle's geometry for one endpoint, in overlay space. Null when the
67
123
  * endpoint's page isn't laid out (or the cell is degenerate) — render nothing.
68
124
  */
69
- declare function selectionHandleGeom(view: SelectionHandleView, ep: SelectionHandleEndpoint, role: 'start' | 'end'): SelectionHandleGeom | null;
125
+ declare function selectionHandleGeom(view: SelectionHandleView, endpoint: SelectionHandleEndpoint, role: 'start' | 'end'): SelectionHandleGeom | null;
70
126
  interface SelectionHandleDragSession {
71
127
  /** Feed the pointer's current overlay position. */
72
128
  move(overlay: Point): void;
@@ -74,7 +130,7 @@ interface SelectionHandleDragSession {
74
130
  end(): void;
75
131
  }
76
132
  /**
77
- * One armed handle drag. Dragging a handle extends from the OPPOSITE
133
+ * One armed handle drag. Dragging a handle extends from the opposite
78
134
  * endpoint: the first movement re-roots the selection gesture at that
79
135
  * endpoint's cell centre, and every position then extends toward the pointer
80
136
  * — snapping to glyphs and crossing pages exactly like a pointer drag,
@@ -83,7 +139,19 @@ interface SelectionHandleDragSession {
83
139
  * freezing at a page edge. `end()` commits only if the drag actually
84
140
  * re-rooted; an untouched press settles nothing.
85
141
  */
86
- declare function createSelectionHandleDrag(selection: SelectionHandleTarget, view: SelectionHandleView, opposite: SelectionHandleEndpoint, draggedPon: PageObjectNumber): SelectionHandleDragSession;
142
+ declare function createSelectionHandleDrag(selection: SelectionHandleTarget, view: SelectionHandleView, opposite: SelectionHandleEndpoint, draggedPage: PageRef$1): SelectionHandleDragSession;
143
+ /** A press on a handle, armed: the point it grabbed and the drag it starts. */
144
+ interface ArmedSelectionHandle {
145
+ /** The point the user grabbed: the bar's midpoint, overlay px. */
146
+ base: Point;
147
+ drag: SelectionHandleDragSession;
148
+ }
149
+ /**
150
+ * Arm a drag of the `role` handle at a press, with the view and the endpoints
151
+ * as they are at that moment: it extends the selection from the opposite end.
152
+ * Null when the handle's page isn't laid out.
153
+ */
154
+ declare function armSelectionHandle(selection: SelectionHandleTarget, view: SelectionHandleView, endpoints: SelectionHandleEndpoints, role: 'start' | 'end'): ArmedSelectionHandle | null;
87
155
  //#endregion
88
- export { HANDLE_BAR, HANDLE_HEAD, HANDLE_PAD, type SelectionCapability, type SelectionEndpoint, type SelectionHandleDragSession, type SelectionHandleEndpoint, type SelectionHandleGeom, type SelectionHandleTarget, type SelectionHandleView, type SelectionMenuAnchor, type SelectionRangeInput, type SelectionSegment, type SelectionSnapshot, SelectionToken, type TextPosition, type TextRange, createSelectionHandleDrag, selectionHandleGeom, selectionPlugin };
156
+ export { type ArmedSelectionHandle, HANDLE_BAR, HANDLE_HEAD, HANDLE_PAD, SELECTION_DEFAULTS, SelectionAnchor, SelectionCapability, SelectionChangedEvent, SelectionClearedEvent, SelectionCommittedEvent, SelectionConfig, SelectionEndpoint, type SelectionHandleDragSession, type SelectionHandleEndpoint, type SelectionHandleEndpoints, type SelectionHandleGeom, type SelectionHandleStage, type SelectionHandleTarget, type SelectionHandleView, SelectionRangeInput, type SelectionSegment, SelectionSettings, SelectionSnapshot, SelectionToken, TextPosition, TextRange, armSelectionHandle, createSelectionHandleDrag, selectionHandleEndpointsOf, selectionHandleGeom, selectionHandleViewOf, selectionPlugin, selectionState };
89
157
  //# sourceMappingURL=index.d.cts.map
package/dist/index.d.ts CHANGED
@@ -1,48 +1,104 @@
1
- import { a as SelectionHostCapability, c as SelectionRangeInput, f as TextPosition, h as SelectionSegment, i as SelectionEndpoint, l as SelectionSnapshot, n as SelectionAction, o as SelectionMenuAnchor, p as TextRange, r as SelectionCapability, u as SelectionState } from "./types-CUrUVl6K.js";
2
- import { SelectionToken } from "./contract.js";
3
- import { PageObjectNumber } from "@embedpdf/core";
4
- import { Point, TextQuad } from "@embedpdf/core-geometry";
1
+ import { a as SelectionClearedEvent, c as SelectionEndpoint, d as SelectionSnapshot, f as TextPosition, h as SelectionSegment, i as SelectionChangedEvent, l as SelectionRangeInput, m as SelectionToken, n as SelectionAnchor, o as SelectionCommittedEvent, p as TextRange, r as SelectionCapability, s as SelectionConfig, t as SELECTION_DEFAULTS, u as SelectionSettings } from "./contract-DLs4vXVt.js";
2
+ import { t as SelectionHostCapability } from "./host-contract-DO2aLU2e.js";
3
+ import { PageObjectNumber, PageRef } from "@embedpdf/core";
4
+ import { Point, Quad } from "@embedpdf/core-geometry";
5
+ import { PageRef as PageRef$1 } from "@embedpdf/engine-core/runtime";
6
+ //#region src/model.d.ts
7
+ /** A glyph address: a page and a character index within that page. */
8
+ interface GlyphPosition {
9
+ readonly page: PageRef;
10
+ readonly glyph: number;
11
+ }
12
+ /** Anchor = where the selection began, focus = the current end. Inclusive. */
13
+ interface SelectionRange {
14
+ readonly anchor: GlyphPosition;
15
+ readonly focus: GlyphPosition;
16
+ }
17
+ /** Page object number → that page's segments, in page space. */
18
+ type SegmentsByPage = Readonly<Record<PageObjectNumber, readonly SelectionSegment[]>>;
19
+ interface SelectionState {
20
+ readonly selection: SelectionRange | null;
21
+ /** Per-line segments derived from the range, per page, in page space. */
22
+ readonly segments: SegmentsByPage;
23
+ /** A consumer owns the selection visual (a markup tool draws its own preview). */
24
+ readonly highlightHidden: boolean;
25
+ /** The user is still selecting: a gesture is in flight (pointer-down … pointer-up). */
26
+ readonly selecting: boolean;
27
+ }
28
+ //#endregion
5
29
  //#region src/selection.plugin.d.ts
6
30
  /**
7
- * Text selection — document-scoped, requires the interaction hub. In `init` it
8
- * registers ITS pointer handler with the hub; the hub owns the pointer stream
9
- * and arbitration. Works with `<Stage>` or a standalone `<PageView>` — selection
10
- * only needs the page coordinate context + the engine's text geometry.
11
- *
12
- * Registry/content invalidation is wired inside the capability itself
13
- * (revision watch + document event subscription), so headless/programmatic
14
- * use gets the same reconciliation as the full viewer.
31
+ * Text selection: document-scoped, requires the interaction hub, whose
32
+ * pointer stream drives the selection's own handler. Works with `<Stage>`
33
+ * or a standalone `<PageView>`: selection only needs the page coordinate
34
+ * context and the engine's text geometry. Platform feedback (haptics) is
35
+ * optional. `config` is the settings the app registers, over
36
+ * {@link SELECTION_DEFAULTS}.
15
37
  */
16
- declare const selectionPlugin: () => import("@embedpdf/core").PluginDef<SelectionState, SelectionAction, SelectionHostCapability>;
38
+ declare const selectionPlugin: (config?: SelectionConfig) => import("@embedpdf/core").PluginDef<SelectionState, SelectionHostCapability, SelectionSettings>;
39
+ //#endregion
40
+ //#region src/state.d.ts
41
+ declare const selectionState: import("@embedpdf/core").StateDeclaration<SelectionCapability, {
42
+ hasSelection: boolean;
43
+ isSelecting: boolean;
44
+ range: TextRange | null;
45
+ pages: readonly import("@embedpdf/core").PageRef[];
46
+ }>;
17
47
  //#endregion
18
48
  //#region src/handles.d.ts
19
49
  /** What handle geometry & drags need from the hosting view. */
20
50
  interface SelectionHandleView {
21
- /** Page content point → overlay px. Must be POINT-exact (compose
51
+ /** Page page point → overlay px. Must be point-exact (compose
22
52
  * `pageToWorld` with `toScreen`); an AABB projector loses orientation. */
23
- toOverlay(pon: PageObjectNumber, pt: Point): Point | null;
53
+ toOverlay(page: PageRef$1, point: Point): Point | null;
24
54
  /** Overlay px → the page under it, or null over a gap. */
25
55
  pageAt(overlay: Point): {
26
- pon: PageObjectNumber;
56
+ ref: PageRef$1;
27
57
  point: Point;
28
58
  } | null;
29
- /** Overlay px → a SPECIFIC page's content space, unclamped. */
30
- pointOnPage(pon: PageObjectNumber, overlay: Point): Point | null;
59
+ /** Overlay px → a specific page's page space, unclamped. */
60
+ pointOnPage(page: PageRef$1, overlay: Point): Point | null;
31
61
  }
62
+ /** The Stage's projection, as the handles use it: `StageCapability` satisfies it. */
63
+ interface SelectionHandleStage {
64
+ pageToViewport(page: PageRef$1, point: Point): Point | null;
65
+ getPageAt(point: Point): {
66
+ ref: PageRef$1;
67
+ point: Point;
68
+ } | null;
69
+ viewportToPage(page: PageRef$1, point: Point): Point | null;
70
+ }
71
+ /**
72
+ * The handles' view over a Stage: point-exact projection in, page lookup out.
73
+ * (The Stage's box projector, `pageRectToViewport`, is for upright overlays
74
+ * and would lose the orientation a handle needs.)
75
+ */
76
+ declare function selectionHandleViewOf(stage: SelectionHandleStage): SelectionHandleView;
32
77
  /** One selection boundary, as the handle needs it (a `SelectionEndpoint` slice). */
33
78
  interface SelectionHandleEndpoint {
34
- pon: PageObjectNumber;
35
- /** The boundary glyph's own oriented cell, page content space. */
36
- glyphQuad: TextQuad;
79
+ page: PageRef$1;
80
+ /** The boundary glyph's own oriented cell, page page space. */
81
+ glyphQuad: Quad;
37
82
  /** Reading direction of its segment (+1 = the frame's +x) — decides which
38
83
  * side of the cell is the selection's leading edge. */
39
84
  advance: 1 | -1;
40
85
  }
86
+ /** Both ends of a selection, as the handles need them. */
87
+ interface SelectionHandleEndpoints {
88
+ start: SelectionHandleEndpoint;
89
+ end: SelectionHandleEndpoint;
90
+ }
91
+ /**
92
+ * The two ends of the selection in `snapshot` (`getSnapshot()`), or null
93
+ * while nothing is selected. A new object on every call: compare with
94
+ * `sameSelectionEndpoints` from `@embedpdf/web`.
95
+ */
96
+ declare function selectionHandleEndpointsOf(snapshot: Pick<SelectionSnapshot, 'start' | 'end'>): SelectionHandleEndpoints | null;
41
97
  /** The gesture verbs a handle drag drives — `SelectionHostCapability` satisfies it. */
42
98
  interface SelectionHandleTarget {
43
- beginAt(pon: PageObjectNumber, point: Point): boolean;
44
- extendTo(pon: PageObjectNumber, point: Point): void;
45
- end(): void;
99
+ beginGestureAt(page: PageRef$1, point: Point): boolean;
100
+ extendTo(page: PageRef$1, point: Point): void;
101
+ endGesture(): void;
46
102
  }
47
103
  declare const HANDLE_HEAD = 12;
48
104
  declare const HANDLE_BAR = 2;
@@ -57,7 +113,7 @@ interface SelectionHandleGeom {
57
113
  length: number;
58
114
  /** Degrees clockwise; 0 when the text is upright on screen. */
59
115
  rotation: number;
60
- /** True within the epsilon of upright: render with NO transform. */
116
+ /** True within the epsilon of upright: render with no transform. */
61
117
  upright: boolean;
62
118
  /** Centre of the grab head, overlay px. */
63
119
  head: Point;
@@ -66,7 +122,7 @@ interface SelectionHandleGeom {
66
122
  * The handle's geometry for one endpoint, in overlay space. Null when the
67
123
  * endpoint's page isn't laid out (or the cell is degenerate) — render nothing.
68
124
  */
69
- declare function selectionHandleGeom(view: SelectionHandleView, ep: SelectionHandleEndpoint, role: 'start' | 'end'): SelectionHandleGeom | null;
125
+ declare function selectionHandleGeom(view: SelectionHandleView, endpoint: SelectionHandleEndpoint, role: 'start' | 'end'): SelectionHandleGeom | null;
70
126
  interface SelectionHandleDragSession {
71
127
  /** Feed the pointer's current overlay position. */
72
128
  move(overlay: Point): void;
@@ -74,7 +130,7 @@ interface SelectionHandleDragSession {
74
130
  end(): void;
75
131
  }
76
132
  /**
77
- * One armed handle drag. Dragging a handle extends from the OPPOSITE
133
+ * One armed handle drag. Dragging a handle extends from the opposite
78
134
  * endpoint: the first movement re-roots the selection gesture at that
79
135
  * endpoint's cell centre, and every position then extends toward the pointer
80
136
  * — snapping to glyphs and crossing pages exactly like a pointer drag,
@@ -83,7 +139,19 @@ interface SelectionHandleDragSession {
83
139
  * freezing at a page edge. `end()` commits only if the drag actually
84
140
  * re-rooted; an untouched press settles nothing.
85
141
  */
86
- declare function createSelectionHandleDrag(selection: SelectionHandleTarget, view: SelectionHandleView, opposite: SelectionHandleEndpoint, draggedPon: PageObjectNumber): SelectionHandleDragSession;
142
+ declare function createSelectionHandleDrag(selection: SelectionHandleTarget, view: SelectionHandleView, opposite: SelectionHandleEndpoint, draggedPage: PageRef$1): SelectionHandleDragSession;
143
+ /** A press on a handle, armed: the point it grabbed and the drag it starts. */
144
+ interface ArmedSelectionHandle {
145
+ /** The point the user grabbed: the bar's midpoint, overlay px. */
146
+ base: Point;
147
+ drag: SelectionHandleDragSession;
148
+ }
149
+ /**
150
+ * Arm a drag of the `role` handle at a press, with the view and the endpoints
151
+ * as they are at that moment: it extends the selection from the opposite end.
152
+ * Null when the handle's page isn't laid out.
153
+ */
154
+ declare function armSelectionHandle(selection: SelectionHandleTarget, view: SelectionHandleView, endpoints: SelectionHandleEndpoints, role: 'start' | 'end'): ArmedSelectionHandle | null;
87
155
  //#endregion
88
- export { HANDLE_BAR, HANDLE_HEAD, HANDLE_PAD, type SelectionCapability, type SelectionEndpoint, type SelectionHandleDragSession, type SelectionHandleEndpoint, type SelectionHandleGeom, type SelectionHandleTarget, type SelectionHandleView, type SelectionMenuAnchor, type SelectionRangeInput, type SelectionSegment, type SelectionSnapshot, SelectionToken, type TextPosition, type TextRange, createSelectionHandleDrag, selectionHandleGeom, selectionPlugin };
156
+ export { type ArmedSelectionHandle, HANDLE_BAR, HANDLE_HEAD, HANDLE_PAD, SELECTION_DEFAULTS, SelectionAnchor, SelectionCapability, SelectionChangedEvent, SelectionClearedEvent, SelectionCommittedEvent, SelectionConfig, SelectionEndpoint, type SelectionHandleDragSession, type SelectionHandleEndpoint, type SelectionHandleEndpoints, type SelectionHandleGeom, type SelectionHandleStage, type SelectionHandleTarget, type SelectionHandleView, SelectionRangeInput, type SelectionSegment, SelectionSettings, SelectionSnapshot, SelectionToken, TextPosition, TextRange, armSelectionHandle, createSelectionHandleDrag, selectionHandleEndpointsOf, selectionHandleGeom, selectionHandleViewOf, selectionPlugin, selectionState };
89
157
  //# sourceMappingURL=index.d.ts.map