@embedpdf/svelte 0.0.0-stage → 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 (269) hide show
  1. package/LICENSE +192 -0
  2. package/README.md +312 -2
  3. package/dist/actions/readers.svelte.d.ts +29 -0
  4. package/dist/actions/readers.svelte.js +55 -0
  5. package/dist/actions.d.ts +13 -0
  6. package/dist/actions.js +13 -0
  7. package/dist/anchored/Anchored.svelte +24 -0
  8. package/dist/anchored/Anchored.svelte.d.ts +4 -0
  9. package/dist/anchored/PlacedAnchored.svelte +97 -0
  10. package/dist/anchored/PlacedAnchored.svelte.d.ts +4 -0
  11. package/dist/anchored/context.d.ts +31 -0
  12. package/dist/anchored/context.js +36 -0
  13. package/dist/anchored/props.d.ts +29 -0
  14. package/dist/anchored/props.js +1 -0
  15. package/dist/anchored.d.ts +12 -0
  16. package/dist/anchored.js +2 -0
  17. package/dist/annotation/AnnotationChrome.svelte +173 -0
  18. package/dist/annotation/AnnotationChrome.svelte.d.ts +11 -0
  19. package/dist/annotation/AnnotationDraftMenu.svelte +27 -0
  20. package/dist/annotation/AnnotationDraftMenu.svelte.d.ts +4 -0
  21. package/dist/annotation/AnnotationFrame.svelte +46 -0
  22. package/dist/annotation/AnnotationFrame.svelte.d.ts +15 -0
  23. package/dist/annotation/AnnotationLayer.svelte +241 -0
  24. package/dist/annotation/AnnotationLayer.svelte.d.ts +4 -0
  25. package/dist/annotation/AnnotationLook.svelte +67 -0
  26. package/dist/annotation/AnnotationLook.svelte.d.ts +21 -0
  27. package/dist/annotation/AnnotationMenu.svelte +47 -0
  28. package/dist/annotation/AnnotationMenu.svelte.d.ts +4 -0
  29. package/dist/annotation/AnnotationRotationBadge.svelte +52 -0
  30. package/dist/annotation/AnnotationRotationBadge.svelte.d.ts +4 -0
  31. package/dist/annotation/AnnotationScene.svelte +27 -0
  32. package/dist/annotation/AnnotationScene.svelte.d.ts +7 -0
  33. package/dist/annotation/FreeTextBox.svelte +51 -0
  34. package/dist/annotation/FreeTextBox.svelte.d.ts +9 -0
  35. package/dist/annotation/NativeDrawing.svelte +61 -0
  36. package/dist/annotation/NativeDrawing.svelte.d.ts +10 -0
  37. package/dist/annotation/StampGhost.svelte +79 -0
  38. package/dist/annotation/StampGhost.svelte.d.ts +7 -0
  39. package/dist/annotation/chrome-paint.svelte.d.ts +30 -0
  40. package/dist/annotation/chrome-paint.svelte.js +27 -0
  41. package/dist/annotation/props.d.ts +181 -0
  42. package/dist/annotation/props.js +1 -0
  43. package/dist/annotation/readers.svelte.d.ts +59 -0
  44. package/dist/annotation/readers.svelte.js +136 -0
  45. package/dist/annotation/state.d.ts +18 -0
  46. package/dist/annotation/state.js +20 -0
  47. package/dist/annotation/text-box.svelte.d.ts +28 -0
  48. package/dist/annotation/text-box.svelte.js +87 -0
  49. package/dist/annotation.d.ts +32 -0
  50. package/dist/annotation.js +31 -0
  51. package/dist/attachment/readers.d.ts +23 -0
  52. package/dist/attachment/readers.js +21 -0
  53. package/dist/attachment.d.ts +13 -0
  54. package/dist/attachment.js +14 -0
  55. package/dist/commands/readers.svelte.d.ts +50 -0
  56. package/dist/commands/readers.svelte.js +83 -0
  57. package/dist/commands.d.ts +9 -0
  58. package/dist/commands.js +9 -0
  59. package/dist/form/ButtonControl.svelte +38 -0
  60. package/dist/form/ButtonControl.svelte.d.ts +11 -0
  61. package/dist/form/ComboControl.svelte +52 -0
  62. package/dist/form/ComboControl.svelte.d.ts +11 -0
  63. package/dist/form/FieldPictures.svelte +145 -0
  64. package/dist/form/FieldPictures.svelte.d.ts +3 -0
  65. package/dist/form/FormFocusRing.svelte +25 -0
  66. package/dist/form/FormFocusRing.svelte.d.ts +8 -0
  67. package/dist/form/FormLayer.svelte +98 -0
  68. package/dist/form/FormLayer.svelte.d.ts +3 -0
  69. package/dist/form/ListControl.svelte +49 -0
  70. package/dist/form/ListControl.svelte.d.ts +11 -0
  71. package/dist/form/NativeListBox.svelte +57 -0
  72. package/dist/form/NativeListBox.svelte.d.ts +4 -0
  73. package/dist/form/SignatureControl.svelte +53 -0
  74. package/dist/form/SignatureControl.svelte.d.ts +11 -0
  75. package/dist/form/TextControl.svelte +107 -0
  76. package/dist/form/TextControl.svelte.d.ts +11 -0
  77. package/dist/form/ToggleControl.svelte +50 -0
  78. package/dist/form/ToggleControl.svelte.d.ts +11 -0
  79. package/dist/form/WidgetBox.svelte +82 -0
  80. package/dist/form/WidgetBox.svelte.d.ts +15 -0
  81. package/dist/form/props.d.ts +21 -0
  82. package/dist/form/props.js +1 -0
  83. package/dist/form/readers.svelte.d.ts +46 -0
  84. package/dist/form/readers.svelte.js +52 -0
  85. package/dist/form/widget.d.ts +16 -0
  86. package/dist/form/widget.js +20 -0
  87. package/dist/form.d.ts +11 -0
  88. package/dist/form.js +12 -0
  89. package/dist/history/readers.d.ts +22 -0
  90. package/dist/history/readers.js +21 -0
  91. package/dist/history.d.ts +10 -0
  92. package/dist/history.js +11 -0
  93. package/dist/i18n/readers.svelte.d.ts +32 -0
  94. package/dist/i18n/readers.svelte.js +30 -0
  95. package/dist/i18n.d.ts +8 -0
  96. package/dist/i18n.js +9 -0
  97. package/dist/index.d.ts +30 -0
  98. package/dist/index.js +30 -0
  99. package/dist/interaction/PagePointerSource.svelte +37 -0
  100. package/dist/interaction/PagePointerSource.svelte.d.ts +3 -0
  101. package/dist/interaction/readers.svelte.d.ts +26 -0
  102. package/dist/interaction/readers.svelte.js +47 -0
  103. package/dist/interaction.d.ts +11 -0
  104. package/dist/interaction.js +13 -0
  105. package/dist/link/LinkLayer.svelte +164 -0
  106. package/dist/link/LinkLayer.svelte.d.ts +4 -0
  107. package/dist/link/props.d.ts +17 -0
  108. package/dist/link/props.js +1 -0
  109. package/dist/link/readers.svelte.d.ts +18 -0
  110. package/dist/link/readers.svelte.js +38 -0
  111. package/dist/link.d.ts +11 -0
  112. package/dist/link.js +11 -0
  113. package/dist/measurement/readers.d.ts +44 -0
  114. package/dist/measurement/readers.js +46 -0
  115. package/dist/measurement.d.ts +12 -0
  116. package/dist/measurement.js +13 -0
  117. package/dist/metadata/readers.d.ts +22 -0
  118. package/dist/metadata/readers.js +22 -0
  119. package/dist/metadata.d.ts +12 -0
  120. package/dist/metadata.js +13 -0
  121. package/dist/outline/readers.d.ts +26 -0
  122. package/dist/outline/readers.js +22 -0
  123. package/dist/outline.d.ts +12 -0
  124. package/dist/outline.js +13 -0
  125. package/dist/page-edit/readers.d.ts +17 -0
  126. package/dist/page-edit/readers.js +18 -0
  127. package/dist/page-edit.d.ts +12 -0
  128. package/dist/page-edit.js +13 -0
  129. package/dist/page-view/PageView.svelte +192 -0
  130. package/dist/page-view/PageView.svelte.d.ts +4 -0
  131. package/dist/page-view/props.d.ts +32 -0
  132. package/dist/page-view/props.js +1 -0
  133. package/dist/page-view.d.ts +8 -0
  134. package/dist/page-view.js +7 -0
  135. package/dist/redaction/readers.d.ts +37 -0
  136. package/dist/redaction/readers.js +38 -0
  137. package/dist/redaction.d.ts +12 -0
  138. package/dist/redaction.js +13 -0
  139. package/dist/render/RenderLayer.svelte +116 -0
  140. package/dist/render/RenderLayer.svelte.d.ts +4 -0
  141. package/dist/render/TileImage.svelte +65 -0
  142. package/dist/render/TileImage.svelte.d.ts +11 -0
  143. package/dist/render/TilePlane.svelte +88 -0
  144. package/dist/render/TilePlane.svelte.d.ts +7 -0
  145. package/dist/render/props.d.ts +16 -0
  146. package/dist/render/props.js +1 -0
  147. package/dist/render/readers.svelte.d.ts +9 -0
  148. package/dist/render/readers.svelte.js +13 -0
  149. package/dist/render.d.ts +10 -0
  150. package/dist/render.js +10 -0
  151. package/dist/runtime/DocumentGate.svelte +26 -0
  152. package/dist/runtime/DocumentGate.svelte.d.ts +4 -0
  153. package/dist/runtime/DocumentScope.svelte +14 -0
  154. package/dist/runtime/DocumentScope.svelte.d.ts +4 -0
  155. package/dist/runtime/KernelProvider.svelte +33 -0
  156. package/dist/runtime/KernelProvider.svelte.d.ts +4 -0
  157. package/dist/runtime/SvgShapes.svelte +30 -0
  158. package/dist/runtime/SvgShapes.svelte.d.ts +8 -0
  159. package/dist/runtime/Viewer.svelte +130 -0
  160. package/dist/runtime/Viewer.svelte.d.ts +4 -0
  161. package/dist/runtime/binding.svelte.d.ts +33 -0
  162. package/dist/runtime/binding.svelte.js +71 -0
  163. package/dist/runtime/capability-handle.d.ts +6 -0
  164. package/dist/runtime/capability-handle.js +71 -0
  165. package/dist/runtime/dev.d.ts +10 -0
  166. package/dist/runtime/dev.js +23 -0
  167. package/dist/runtime/documents.svelte.d.ts +34 -0
  168. package/dist/runtime/documents.svelte.js +49 -0
  169. package/dist/runtime/page-layers.svelte.d.ts +20 -0
  170. package/dist/runtime/page-layers.svelte.js +40 -0
  171. package/dist/runtime/page.d.ts +10 -0
  172. package/dist/runtime/page.js +23 -0
  173. package/dist/runtime/props.d.ts +63 -0
  174. package/dist/runtime/props.js +1 -0
  175. package/dist/runtime/readers.svelte.d.ts +47 -0
  176. package/dist/runtime/readers.svelte.js +122 -0
  177. package/dist/runtime/state.svelte.d.ts +35 -0
  178. package/dist/runtime/state.svelte.js +90 -0
  179. package/dist/runtime/theme.d.ts +12 -0
  180. package/dist/runtime/theme.js +15 -0
  181. package/dist/runtime/values.svelte.d.ts +28 -0
  182. package/dist/runtime/values.svelte.js +51 -0
  183. package/dist/runtime.d.ts +26 -0
  184. package/dist/runtime.js +25 -0
  185. package/dist/search/SearchLayer.svelte +94 -0
  186. package/dist/search/SearchLayer.svelte.d.ts +4 -0
  187. package/dist/search/props.d.ts +11 -0
  188. package/dist/search/props.js +1 -0
  189. package/dist/search/readers.svelte.d.ts +41 -0
  190. package/dist/search/readers.svelte.js +40 -0
  191. package/dist/search.d.ts +11 -0
  192. package/dist/search.js +11 -0
  193. package/dist/selection/SelectionClipboard.svelte +35 -0
  194. package/dist/selection/SelectionClipboard.svelte.d.ts +4 -0
  195. package/dist/selection/SelectionHandles.svelte +192 -0
  196. package/dist/selection/SelectionHandles.svelte.d.ts +4 -0
  197. package/dist/selection/SelectionLayer.svelte +67 -0
  198. package/dist/selection/SelectionLayer.svelte.d.ts +3 -0
  199. package/dist/selection/SelectionMenu.svelte +36 -0
  200. package/dist/selection/SelectionMenu.svelte.d.ts +4 -0
  201. package/dist/selection/props.d.ts +26 -0
  202. package/dist/selection/props.js +1 -0
  203. package/dist/selection/readers.svelte.d.ts +31 -0
  204. package/dist/selection/readers.svelte.js +26 -0
  205. package/dist/selection.d.ts +16 -0
  206. package/dist/selection.js +18 -0
  207. package/dist/shell/readers.svelte.d.ts +40 -0
  208. package/dist/shell/readers.svelte.js +40 -0
  209. package/dist/shell/surface-focus.svelte.d.ts +16 -0
  210. package/dist/shell/surface-focus.svelte.js +33 -0
  211. package/dist/shell.d.ts +12 -0
  212. package/dist/shell.js +11 -0
  213. package/dist/signature/readers.svelte.d.ts +43 -0
  214. package/dist/signature/readers.svelte.js +46 -0
  215. package/dist/signature.d.ts +15 -0
  216. package/dist/signature.js +16 -0
  217. package/dist/stage/PageSurface.svelte +126 -0
  218. package/dist/stage/PageSurface.svelte.d.ts +18 -0
  219. package/dist/stage/Scrollbar.svelte +148 -0
  220. package/dist/stage/Scrollbar.svelte.d.ts +4 -0
  221. package/dist/stage/Stage.svelte +181 -0
  222. package/dist/stage/Stage.svelte.d.ts +4 -0
  223. package/dist/stage/StageScope.svelte +14 -0
  224. package/dist/stage/StageScope.svelte.d.ts +9 -0
  225. package/dist/stage/props.d.ts +71 -0
  226. package/dist/stage/props.js +1 -0
  227. package/dist/stage/readers.svelte.d.ts +39 -0
  228. package/dist/stage/readers.svelte.js +84 -0
  229. package/dist/stage/stage-scope.d.ts +18 -0
  230. package/dist/stage/stage-scope.js +35 -0
  231. package/dist/stage/two-way.svelte.d.ts +13 -0
  232. package/dist/stage/two-way.svelte.js +26 -0
  233. package/dist/stage.d.ts +17 -0
  234. package/dist/stage.js +15 -0
  235. package/dist/stamp/readers.svelte.d.ts +50 -0
  236. package/dist/stamp/readers.svelte.js +76 -0
  237. package/dist/stamp.d.ts +16 -0
  238. package/dist/stamp.js +18 -0
  239. package/dist/toolbar/DefaultCollapsed.svelte +39 -0
  240. package/dist/toolbar/DefaultCollapsed.svelte.d.ts +9 -0
  241. package/dist/toolbar/DefaultCommand.svelte +33 -0
  242. package/dist/toolbar/DefaultCommand.svelte.d.ts +8 -0
  243. package/dist/toolbar/DefaultGroupTrigger.svelte +39 -0
  244. package/dist/toolbar/DefaultGroupTrigger.svelte.d.ts +9 -0
  245. package/dist/toolbar/DefaultOverflowMenu.svelte +106 -0
  246. package/dist/toolbar/DefaultOverflowMenu.svelte.d.ts +9 -0
  247. package/dist/toolbar/DefaultOverflowTrigger.svelte +25 -0
  248. package/dist/toolbar/DefaultOverflowTrigger.svelte.d.ts +7 -0
  249. package/dist/toolbar/Measured.svelte +46 -0
  250. package/dist/toolbar/Measured.svelte.d.ts +11 -0
  251. package/dist/toolbar/NativeSlotSocket.svelte +43 -0
  252. package/dist/toolbar/NativeSlotSocket.svelte.d.ts +11 -0
  253. package/dist/toolbar/Toolbar.svelte +314 -0
  254. package/dist/toolbar/Toolbar.svelte.d.ts +4 -0
  255. package/dist/toolbar/paint.d.ts +3 -0
  256. package/dist/toolbar/paint.js +9 -0
  257. package/dist/toolbar/props.d.ts +48 -0
  258. package/dist/toolbar/props.js +1 -0
  259. package/dist/toolbar/strip.svelte.d.ts +14 -0
  260. package/dist/toolbar/strip.svelte.js +35 -0
  261. package/dist/toolbar/views.d.ts +14 -0
  262. package/dist/toolbar/views.js +1 -0
  263. package/dist/toolbar.d.ts +16 -0
  264. package/dist/toolbar.js +12 -0
  265. package/dist/view-manager/readers.d.ts +19 -0
  266. package/dist/view-manager/readers.js +17 -0
  267. package/dist/view-manager.d.ts +11 -0
  268. package/dist/view-manager.js +12 -0
  269. package/package.json +207 -3
@@ -0,0 +1,94 @@
1
+ <!--
2
+ The search matches on one page, the selection layer's twin: it reads the page's matches in page
3
+ space from the plugin and maps them through the page's transform, in the colors of the plugin's
4
+ `highlight` setting, which the `--epdf-search-*` CSS variables win over. It calls no engine:
5
+ searching is app chrome's, through `useSearch()`.
6
+ -->
7
+ <script lang="ts">
8
+ import type { SearchHit } from '@embedpdf/plugin-search';
9
+ import { createClickDetector, paint, searchHighlightsOf } from '@embedpdf/web';
10
+ import { usePage } from '../runtime/page.js';
11
+ import type { SearchLayerProps } from './props.js';
12
+ import { useSearchHits, useSearchSettings, useSearchState } from './readers.svelte.js';
13
+
14
+ let { onHitClick }: SearchLayerProps = $props();
15
+
16
+ const page = usePage();
17
+ // The plugin keeps a page's list until matches land on it, so this redraws only then.
18
+ const hits = useSearchHits(() => page.ref);
19
+ // The record form: `activeHit` passes on only when it is another match (compared by identity).
20
+ const search = useSearchState();
21
+ const highlight = useSearchSettings((settings) => settings.highlight);
22
+ // Tells a click on a match from a drag that starts there (and selects text).
23
+ const clicks = createClickDetector();
24
+
25
+ // Each color is its CSS variable first, then the setting: CSS wins.
26
+ const color = $derived(paint('search-highlight', highlight.current.color));
27
+ const activeColor = $derived(paint('search-highlight-active', highlight.current.activeColor));
28
+ const blendMode = $derived(paint('search-blend-mode', highlight.current.blendMode));
29
+ // One piece per line of each match: a rounded box when upright, its true quad when turned.
30
+ const pieces = $derived(
31
+ searchHighlightsOf(hits.current, {
32
+ active: search.activeHit,
33
+ page: page.transform,
34
+ color,
35
+ activeColor,
36
+ }),
37
+ );
38
+
39
+ // Nothing here stops the press, so it reaches the page as well: a drag that starts on a match
40
+ // selects text, and only a click counts.
41
+ const press = (event: PointerEvent) => clicks.press(event);
42
+ const click = (hit: SearchHit, event: MouseEvent) => {
43
+ if (clicks.isClick(event)) onHitClick?.(hit);
44
+ };
45
+ </script>
46
+
47
+ {#if hits.current.length > 0}
48
+ <div style="position: absolute; inset: 0; pointer-events: none">
49
+ <!-- Only a clickable match takes the pointer; a plain highlight stays inert. A match isn't a
50
+ control of its own: the keyboard moves between matches with the search's next and
51
+ previous. -->
52
+ {#each pieces as piece (piece.key)}
53
+ {#if piece.box}
54
+ <!-- An upright line is a rounded box. -->
55
+ <!-- svelte-ignore a11y_click_events_have_key_events, a11y_no_static_element_interactions -->
56
+ <div
57
+ style:position="absolute"
58
+ style:left="{piece.box.left}px"
59
+ style:top="{piece.box.top}px"
60
+ style:width="{piece.box.width}px"
61
+ style:height="{piece.box.height}px"
62
+ style:background-color={piece.fill}
63
+ style:mix-blend-mode={blendMode}
64
+ style:border-radius="2px"
65
+ style:pointer-events={onHitClick ? 'auto' : undefined}
66
+ style:cursor={onHitClick ? 'pointer' : undefined}
67
+ onpointerdown={onHitClick ? press : undefined}
68
+ onclick={onHitClick ? (event) => click(piece.hit, event) : undefined}
69
+ ></div>
70
+ {:else}
71
+ <!-- A turned line draws its true quad. -->
72
+ <svg
73
+ style:position="absolute"
74
+ style:inset="0"
75
+ style:width="100%"
76
+ style:height="100%"
77
+ style:overflow="visible"
78
+ style:mix-blend-mode={blendMode}
79
+ >
80
+ <!-- The fill is in `style`: an SVG attribute doesn't read var(). -->
81
+ <!-- svelte-ignore a11y_click_events_have_key_events, a11y_no_static_element_interactions -->
82
+ <polygon
83
+ points={piece.points}
84
+ style:fill={piece.fill}
85
+ style:pointer-events={onHitClick ? 'auto' : undefined}
86
+ style:cursor={onHitClick ? 'pointer' : undefined}
87
+ onpointerdown={onHitClick ? press : undefined}
88
+ onclick={onHitClick ? (event) => click(piece.hit, event) : undefined}
89
+ />
90
+ </svg>
91
+ {/if}
92
+ {/each}
93
+ </div>
94
+ {/if}
@@ -0,0 +1,4 @@
1
+ import type { SearchLayerProps } from './props.js';
2
+ declare const SearchLayer: import("svelte").Component<SearchLayerProps, {}, "">;
3
+ type SearchLayer = ReturnType<typeof SearchLayer>;
4
+ export default SearchLayer;
@@ -0,0 +1,11 @@
1
+ /** The props of `<SearchLayer>`. */
2
+ import type { SearchHit } from '@embedpdf/plugin-search';
3
+ export interface SearchLayerProps {
4
+ /**
5
+ * Make matches clickable: called with the match someone clicks, for example to make it the
6
+ * active one with `search.goToHit(hit)`. The press still reaches the page, so a drag that
7
+ * starts on a match selects text, and only a click calls this. Without it, matches are only
8
+ * paint and the pointer goes straight through.
9
+ */
10
+ onHitClick?: (hit: SearchHit) => void;
11
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,41 @@
1
+ /**
2
+ * The search plugin's readers: `useSearch()` (the API), `useSearchState()`,
3
+ * `useSearchSettings()`, `useSearchEvent()`, and `useSearchHits()` for a results list.
4
+ */
5
+ import type { EventHook, PageRef } from '@embedpdf/core';
6
+ import type { SearchCapability, SearchHit } from '@embedpdf/plugin-search';
7
+ import { type CurrentValue, type MaybeGetter } from '../runtime/values.svelte.js';
8
+ /**
9
+ * The search API (`search()`, `clear()`, `nextHit()`, `goToHit()`, …) for app chrome. A handle,
10
+ * always the capability of the document in scope.
11
+ */
12
+ export declare function useSearch(): SearchCapability;
13
+ /**
14
+ * The search's state: the query, status, count, active match, progress and error (declared once
15
+ * in `searchState`), as a reactive object (`state.hitCount`). With a selector, the value it picks
16
+ * as `{ current }`. Empty without a document.
17
+ */
18
+ export declare const useSearchState: import("../index.js").StateReader<{
19
+ query: import("@embedpdf/plugin-search").SearchQuery | null;
20
+ status: import("@embedpdf/plugin-search").SearchStatus;
21
+ hitCount: number;
22
+ activeHitIndex: number;
23
+ activeHit: SearchHit | null;
24
+ progress: import("@embedpdf/plugin-search").SearchProgress;
25
+ error: import("@embedpdf/core").PluginErrorInfo | null;
26
+ }>;
27
+ /** The search settings (`reveal`, `highlight`), with or without a document. */
28
+ export declare const useSearchSettings: import("../index.js").SettingsReader<import("@embedpdf/plugin-search").SearchSettings & object>;
29
+ /**
30
+ * Subscribe to one search event while the component lives:
31
+ * `useSearchEvent((search) => search.onCompleted, handler)`.
32
+ */
33
+ export declare function useSearchEvent<T>(select: (search: SearchCapability) => EventHook<T>, handler: (event: T) => void): void;
34
+ /**
35
+ * Every match found so far, or one page's (its ref or its index), as `{ current }`: for a results
36
+ * list or a count per thumbnail. A page that changes is passed as a function
37
+ * (`useSearchHits(() => page.ref)`). The plugin keeps each list's identity until matches land on
38
+ * it, so a list redraws only then. Empty without a document, and for a page that has just left
39
+ * the document.
40
+ */
41
+ export declare function useSearchHits(page?: MaybeGetter<PageRef | number | undefined>): CurrentValue<readonly SearchHit[]>;
@@ -0,0 +1,40 @@
1
+ import { SearchToken, searchState } from '@embedpdf/plugin-search';
2
+ import { useCapability, useCapabilityEvent, useOptionalSelector } from '../runtime/readers.svelte.js';
3
+ import { settingsReader, stateReader } from '../runtime/state.svelte.js';
4
+ import { valueOf } from '../runtime/values.svelte.js';
5
+ /**
6
+ * The search API (`search()`, `clear()`, `nextHit()`, `goToHit()`, …) for app chrome. A handle,
7
+ * always the capability of the document in scope.
8
+ */
9
+ export function useSearch() {
10
+ return useCapability(SearchToken);
11
+ }
12
+ /**
13
+ * The search's state: the query, status, count, active match, progress and error (declared once
14
+ * in `searchState`), as a reactive object (`state.hitCount`). With a selector, the value it picks
15
+ * as `{ current }`. Empty without a document.
16
+ */
17
+ export const useSearchState = stateReader(searchState);
18
+ /** The search settings (`reveal`, `highlight`), with or without a document. */
19
+ export const useSearchSettings = settingsReader(SearchToken);
20
+ /**
21
+ * Subscribe to one search event while the component lives:
22
+ * `useSearchEvent((search) => search.onCompleted, handler)`.
23
+ */
24
+ export function useSearchEvent(select, handler) {
25
+ useCapabilityEvent(SearchToken, select, handler);
26
+ }
27
+ const NO_HITS = Object.freeze([]);
28
+ /**
29
+ * Every match found so far, or one page's (its ref or its index), as `{ current }`: for a results
30
+ * list or a count per thumbnail. A page that changes is passed as a function
31
+ * (`useSearchHits(() => page.ref)`). The plugin keeps each list's identity until matches land on
32
+ * it, so a list redraws only then. Empty without a document, and for a page that has just left
33
+ * the document.
34
+ */
35
+ export function useSearchHits(page) {
36
+ return useOptionalSelector(SearchToken, (search) => {
37
+ const only = valueOf(page);
38
+ return search.listHits(only === undefined ? undefined : { page: only });
39
+ }, NO_HITS);
40
+ }
@@ -0,0 +1,11 @@
1
+ /**
2
+ * @embedpdf/svelte/search — full-text search.
3
+ *
4
+ * `<SearchLayer>` paints the matches on each page (and makes them clickable with `onHitClick`);
5
+ * the readers are the plugin's four, `useSearch()`, `useSearchState()`, `useSearchSettings()` and
6
+ * `useSearchEvent()`, and `useSearchHits()` for a results list.
7
+ */
8
+ export * from '@embedpdf/plugin-search';
9
+ export { default as SearchLayer } from './search/SearchLayer.svelte';
10
+ export type { SearchLayerProps } from './search/props.js';
11
+ export { useSearch, useSearchEvent, useSearchHits, useSearchSettings, useSearchState, } from './search/readers.svelte.js';
package/dist/search.js ADDED
@@ -0,0 +1,11 @@
1
+ /**
2
+ * @embedpdf/svelte/search — full-text search.
3
+ *
4
+ * `<SearchLayer>` paints the matches on each page (and makes them clickable with `onHitClick`);
5
+ * the readers are the plugin's four, `useSearch()`, `useSearchState()`, `useSearchSettings()` and
6
+ * `useSearchEvent()`, and `useSearchHits()` for a results list.
7
+ */
8
+ // Registration travels with the UI.
9
+ export * from '@embedpdf/plugin-search';
10
+ export { default as SearchLayer } from './search/SearchLayer.svelte';
11
+ export { useSearch, useSearchEvent, useSearchHits, useSearchSettings, useSearchState, } from './search/readers.svelte.js';
@@ -0,0 +1,35 @@
1
+ <!--
2
+ Copying selected text with the keyboard and the browser's Copy menu. Mount it once per viewer:
3
+ it fetches the selected text when the selection settles, answers the native `copy` event at
4
+ once, and falls back to the async Clipboard API for Ctrl+C / Cmd+C when the page has no DOM
5
+ selection. A selection the user may not copy isn't copied, and `onRefused` is called. It
6
+ draws nothing, and wires nothing until a document is ready. A Copy button calls
7
+ `copySelection(useSelection())` from its click instead.
8
+ -->
9
+ <script lang="ts">
10
+ import { untrack } from 'svelte';
11
+ import { SelectionToken } from '@embedpdf/plugin-selection';
12
+ import { wireSelectionClipboard } from '@embedpdf/web';
13
+ import { useOptionalCapability } from '../runtime/readers.svelte.js';
14
+ import { valueOf } from '../runtime/values.svelte.js';
15
+ import type { SelectionClipboardProps } from './props.js';
16
+
17
+ let { prefetch, onRefused, target }: SelectionClipboardProps = $props();
18
+
19
+ // The capability itself, not a handle: the clipboard wiring keeps it and subscribes to it.
20
+ const selection = useOptionalCapability(SelectionToken);
21
+
22
+ $effect(() => {
23
+ const current = selection.current;
24
+ const element = valueOf(target);
25
+ // Read at the moment of the refusal, so a new callback needs no new wiring.
26
+ const options = {
27
+ ...(prefetch === undefined ? {} : { prefetch }),
28
+ ...(element ? { target: element } : {}),
29
+ onRefused: () => untrack(() => onRefused)?.(),
30
+ };
31
+ // A target that hasn't mounted yet wires nothing; this runs again once it has.
32
+ if (!current || (target !== undefined && !element)) return;
33
+ return untrack(() => wireSelectionClipboard(current, options));
34
+ });
35
+ </script>
@@ -0,0 +1,4 @@
1
+ import type { SelectionClipboardProps } from './props.js';
2
+ declare const SelectionClipboard: import("svelte").Component<SelectionClipboardProps, {}, "">;
3
+ type SelectionClipboard = ReturnType<typeof SelectionClipboard>;
4
+ export default SelectionClipboard;
@@ -0,0 +1,192 @@
1
+ <!--
2
+ Draggable selection handles, the way phones draw them: a thin caret bar that is the selection's
3
+ own start or end edge (the boundary glyph's edge, so turned text and turned pages carry the
4
+ handle with them), capped by a circle that keeps its size on screen, above the first line at
5
+ the start and below the last line at the end.
6
+
7
+ On touch, where nothing can drag a caret, the handles grow or shrink a selection: a long press
8
+ selects a word, then each handle extends from the opposite end, snapping to glyphs and crossing
9
+ pages like a pointer drag. A handle drag is the same selection gesture, so the highlight, the
10
+ menu and the commit events behave the same.
11
+
12
+ Mount it in a `<Stage>`'s overlay, next to `<SelectionMenu>`. Outside a Stage it draws nothing
13
+ (a `<PageView>` has no camera to project the handles through). A press on a handle never pans
14
+ the Stage. Painted in the plugin's `handles` setting, which the `--epdf-text-selection-handle`
15
+ and `--epdf-text-selection-handle-shadow` CSS variables win over.
16
+
17
+ The policy is shared: the view over the Stage, the endpoints, the geometry and the armed drag
18
+ come from the selection plugin's `handles` module, the listener mechanics from
19
+ `@embedpdf/web`'s `attachSelectionHandle`. This file keeps the subscriptions and the markup.
20
+ -->
21
+ <script lang="ts">
22
+ import {
23
+ HANDLE_BAR,
24
+ HANDLE_HEAD,
25
+ HANDLE_PAD,
26
+ armSelectionHandle,
27
+ selectionHandleEndpointsOf,
28
+ selectionHandleGeom,
29
+ selectionHandleViewOf,
30
+ type SelectionHandleGeom,
31
+ } from '@embedpdf/plugin-selection';
32
+ import { SelectionToken } from '@embedpdf/plugin-selection/contract/host';
33
+ import { attachSelectionHandle, paint, sameSelectionEndpoints } from '@embedpdf/web';
34
+ import { useOptionalProjectorBinding } from '../anchored/context.js';
35
+ import { devWarn } from '../runtime/dev.js';
36
+ import { useViewerSettings } from '../runtime/documents.svelte.js';
37
+ import {
38
+ useCapability,
39
+ useKernelValue,
40
+ useOptionalCapability,
41
+ useOptionalSelector,
42
+ } from '../runtime/readers.svelte.js';
43
+ import { stageTokenOf } from '../stage/stage-scope.js';
44
+ import type { SelectionHandlesProps } from './props.js';
45
+ import { useSelectionSettings, useSelectionState } from './readers.svelte.js';
46
+
47
+ type Role = 'start' | 'end';
48
+
49
+ let { token }: SelectionHandlesProps = $props();
50
+
51
+ const host = useCapability(SelectionToken);
52
+ const stage = useOptionalCapability(stageTokenOf(() => token));
53
+
54
+ // Outside a Stage there is no camera to project through: say so instead of drawing nothing
55
+ // without a word.
56
+ const surface = useOptionalProjectorBinding();
57
+ if (!surface || surface().projector.space !== 'overlay') {
58
+ devWarn(
59
+ 'selection-handles-outside-stage',
60
+ '<SelectionHandles> draws nothing here: put it in the <Stage> overlay snippet ' +
61
+ '(a <PageView> has no camera to project the handles through).',
62
+ );
63
+ }
64
+
65
+ const selecting = useSelectionState((selectionState) => selectionState.isSelecting);
66
+ const visible = useOptionalSelector(SelectionToken, (lens) => lens.isHighlightVisible(), false);
67
+ const handles = useSelectionSettings((settings) => settings.handles);
68
+ const accent = useViewerSettings((settings) => settings.accent);
69
+ const endpoints = useOptionalSelector(
70
+ SelectionToken,
71
+ (lens) => selectionHandleEndpointsOf(lens.getSnapshot()),
72
+ null,
73
+ sameSelectionEndpoints,
74
+ );
75
+ // The handles are placed by projecting the endpoints through the camera, so they move whenever
76
+ // it does: the visible pages are the Stage's value for exactly that, the same one its pages
77
+ // move on, so handle and highlight move in one frame.
78
+ const camera = useKernelValue(() => stage.current?.listVisiblePages() ?? null);
79
+
80
+ const geometry = $derived.by((): Record<Role, SelectionHandleGeom | null> | null => {
81
+ void camera.current;
82
+ const lens = stage.current;
83
+ const ends = endpoints.current;
84
+ if (!lens || !ends) return null;
85
+ const view = selectionHandleViewOf(lens);
86
+ return {
87
+ start: selectionHandleGeom(view, ends.start, 'start'),
88
+ end: selectionHandleGeom(view, ends.end, 'end'),
89
+ };
90
+ });
91
+
92
+ // The handle being dragged: it keeps its handles shown while the selection is mid-gesture.
93
+ let dragging = $state<Role | null>(null);
94
+
95
+ /**
96
+ * Arm a drag at the press. It reads the stage and the endpoints as they are now, not as they
97
+ * were when the listener was attached.
98
+ */
99
+ function arm(role: Role) {
100
+ const lens = stage.current;
101
+ const ends = endpoints.current;
102
+ if (!lens || !ends) return null;
103
+ const armed = armSelectionHandle(host, selectionHandleViewOf(lens), ends, role);
104
+ if (!armed) return null;
105
+ dragging = role;
106
+ return {
107
+ // The point the user grabbed: the bar's midpoint.
108
+ base: armed.base,
109
+ session: {
110
+ move: armed.drag.move,
111
+ end: () => {
112
+ armed.drag.end(); // settles the selection: the menu shows again, onCommitted fires
113
+ dragging = null;
114
+ },
115
+ },
116
+ };
117
+ }
118
+
119
+ /** The handle's listeners, as an attachment: a native press listener, so the Stage never sees it. */
120
+ const bindHandle = (role: Role) => (element: HTMLElement) =>
121
+ attachSelectionHandle(element, { arm: () => arm(role) });
122
+
123
+ // Each is its CSS variable first, then the setting; an unset color is the accent.
124
+ const color = $derived(paint('text-selection-handle', handles.current.color ?? accent.current));
125
+ const shadow = $derived(paint('text-selection-handle-shadow', handles.current.shadow));
126
+
127
+ // Hidden while a pointer drag selects (like the menu), but a handle drag is itself a selection
128
+ // gesture, so it keeps its handles.
129
+ const shown = $derived(
130
+ stage.current !== null &&
131
+ endpoints.current !== null &&
132
+ visible.current &&
133
+ (!selecting.current || dragging !== null),
134
+ );
135
+ </script>
136
+
137
+ <!--
138
+ One handle, laid out upright in its own frame (a bar as long as the edge, the head above it at
139
+ the start or below it at the end), then turned onto the projected edge. The pivot is the bar's
140
+ ascent-side tip, the one point that must land exactly on the glyph's corner. Upright text
141
+ carries no transform.
142
+ -->
143
+ {#snippet handle(role: Role, at: SelectionHandleGeom)}
144
+ {@const barTop = HANDLE_PAD + (role === 'start' ? HANDLE_HEAD : 0)}
145
+ {@const pivotX = HANDLE_PAD + HANDLE_BAR / 2}
146
+ <div
147
+ {@attach bindHandle(role)}
148
+ style:position="absolute"
149
+ style:left="{at.bar.from.x - pivotX}px"
150
+ style:top="{at.bar.from.y - barTop}px"
151
+ style:width="{HANDLE_BAR + 2 * HANDLE_PAD}px"
152
+ style:height="{at.length + HANDLE_HEAD + 2 * HANDLE_PAD}px"
153
+ style:transform={at.upright ? undefined : `rotate(${at.rotation}deg)`}
154
+ style:transform-origin={at.upright ? undefined : `${pivotX}px ${barTop}px`}
155
+ style:touch-action="none"
156
+ style:cursor="grab"
157
+ style:pointer-events="auto"
158
+ >
159
+ <!-- The caret bar: the selection's own edge, as tall as the glyph's ink, at any tilt. -->
160
+ <div
161
+ style:position="absolute"
162
+ style:left="{HANDLE_PAD}px"
163
+ style:top="{barTop}px"
164
+ style:width="{HANDLE_BAR}px"
165
+ style:height="{at.length}px"
166
+ style:background={color}
167
+ style:border-radius="{HANDLE_BAR / 2}px"
168
+ ></div>
169
+ <!-- The head, against the bar: past the ascent at the start, past the baseline at the end,
170
+ in the text's frame. -->
171
+ <div
172
+ style:position="absolute"
173
+ style:left="{pivotX - HANDLE_HEAD / 2}px"
174
+ style:top="{role === 'start' ? HANDLE_PAD : HANDLE_PAD + at.length}px"
175
+ style:width="{HANDLE_HEAD}px"
176
+ style:height="{HANDLE_HEAD}px"
177
+ style:border-radius="50%"
178
+ style:background={color}
179
+ style:box-shadow={shadow}
180
+ ></div>
181
+ </div>
182
+ {/snippet}
183
+
184
+ {#if shown && geometry}
185
+ <!-- A handle whose page isn't laid out right now draws nothing. -->
186
+ {#if geometry.start}
187
+ {@render handle('start', geometry.start)}
188
+ {/if}
189
+ {#if geometry.end}
190
+ {@render handle('end', geometry.end)}
191
+ {/if}
192
+ {/if}
@@ -0,0 +1,4 @@
1
+ import type { SelectionHandlesProps } from './props.js';
2
+ declare const SelectionHandles: import("svelte").Component<SelectionHandlesProps, {}, "">;
3
+ type SelectionHandles = ReturnType<typeof SelectionHandles>;
4
+ export default SelectionHandles;
@@ -0,0 +1,67 @@
1
+ <!--
2
+ The highlight of the selected text on one page. It only paints: it warms the page's text
3
+ geometry when it mounts, reads the page-space highlight from the plugin and maps each piece
4
+ through the page's transform, in the plugin's `color` setting, which the
5
+ `--epdf-text-selection` CSS variable wins over. Pointer input is `<PagePointerSource>`'s.
6
+
7
+ It reads the host lens (`/contract/host`: geometry warming, the highlight handshake), which
8
+ exists for exactly this; app code gets the public lens from `useSelection()`.
9
+ -->
10
+ <script lang="ts">
11
+ import { SelectionToken } from '@embedpdf/plugin-selection/contract/host';
12
+ import type { SelectionSegment } from '@embedpdf/plugin-selection';
13
+ import { mixAccent, paint, quadInPixels, svgPoints } from '@embedpdf/web';
14
+ import { useViewerSettings } from '../runtime/documents.svelte.js';
15
+ import { usePage } from '../runtime/page.js';
16
+ import {
17
+ shallowArray,
18
+ useOptionalCapability,
19
+ useOptionalSelector,
20
+ } from '../runtime/readers.svelte.js';
21
+ import { useSelectionSettings } from './readers.svelte.js';
22
+
23
+ const NO_SEGMENTS: readonly SelectionSegment[] = Object.freeze([]);
24
+
25
+ const page = usePage();
26
+ const selection = useOptionalCapability(SelectionToken);
27
+ const segments = useOptionalSelector(
28
+ SelectionToken,
29
+ (lens) => lens.listSegments(page.ref),
30
+ NO_SEGMENTS,
31
+ shallowArray,
32
+ );
33
+ // Code that draws its own selection (a markup tool's preview) can take the highlight over;
34
+ // then this draws nothing, so the two never overlap.
35
+ const visible = useOptionalSelector(SelectionToken, (lens) => lens.isHighlightVisible(), false);
36
+ // Settings need no document; without the selection plugin this throws now, with the fix.
37
+ const color = useSelectionSettings((settings) => settings.color);
38
+ // An unset color follows the viewer's accent, behind the `--epdf-accent` variable.
39
+ const accent = useViewerSettings((settings) => settings.accent);
40
+
41
+ // Warm this page's text geometry as soon as it's on screen, so the first press can hit-test
42
+ // without waiting for the engine. Nothing warms without the `doc.text.select` permission.
43
+ $effect(() => {
44
+ const lens = selection.current;
45
+ const ref = page.ref;
46
+ if (!lens) return;
47
+ void lens.ensureLoaded(ref);
48
+ });
49
+
50
+ // Unset, the color is the accent at 35%, the accent variables included.
51
+ const fill = $derived(
52
+ paint('text-selection', color.current ?? mixAccent('text-selection', accent.current)),
53
+ );
54
+ </script>
55
+
56
+ {#if visible.current}
57
+ <svg
58
+ style="position: absolute; inset: 0; width: 100%; height: 100%; overflow: visible; pointer-events: none"
59
+ >
60
+ {#each segments.current as segment, index (index)}
61
+ <!-- Page space to the page layer's pixels, which turn with the page: mapping the four
62
+ corners is exact, for turned text too. The fill is in `style`: an SVG attribute
63
+ doesn't read var(). -->
64
+ <polygon points={svgPoints(quadInPixels(segment.quad, page.transform))} style:fill />
65
+ {/each}
66
+ </svg>
67
+ {/if}
@@ -0,0 +1,3 @@
1
+ declare const SelectionLayer: import("svelte").Component<Record<string, never>, {}, "">;
2
+ type SelectionLayer = ReturnType<typeof SelectionLayer>;
3
+ export default SelectionLayer;
@@ -0,0 +1,36 @@
1
+ <!--
2
+ A menu over the text selection, once the selection settles. One anchor for the whole
3
+ selection, even across pages: it sits on the page the gesture ended on. Hidden while the user
4
+ is still dragging, it appears when the pointer lifts; a selection made from code shows it at
5
+ once. It works in a `<Stage>`'s overlay and in a `<PageView>` alike: the surface gives the
6
+ projection.
7
+
8
+ For UI that follows the selection during the drag, use `<Anchored>` with
9
+ `useSelection().getAnchor()` yourself: this component is that, plus the hiding.
10
+ -->
11
+ <script lang="ts">
12
+ import { SelectionToken } from '@embedpdf/plugin-selection';
13
+ import { samePageBounds } from '@embedpdf/web';
14
+ import Anchored from '../anchored/Anchored.svelte';
15
+ import { useOptionalSelector } from '../runtime/readers.svelte.js';
16
+ import type { SelectionMenuProps } from './props.js';
17
+ import { useSelectionState } from './readers.svelte.js';
18
+
19
+ let { children, gap = 8, placement = 'top' }: SelectionMenuProps = $props();
20
+
21
+ const selecting = useSelectionState((selectionState) => selectionState.isSelecting);
22
+ // Every read gives a new anchor: compared by page and box, so the menu moves only when the
23
+ // selection did.
24
+ const anchor = useOptionalSelector(
25
+ SelectionToken,
26
+ (selection) => selection.getAnchor(),
27
+ null,
28
+ samePageBounds,
29
+ );
30
+ </script>
31
+
32
+ {#if !selecting.current && anchor.current}
33
+ <Anchored anchor={anchor.current} {placement} {gap}>
34
+ {@render children()}
35
+ </Anchored>
36
+ {/if}
@@ -0,0 +1,4 @@
1
+ import type { SelectionMenuProps } from './props.js';
2
+ declare const SelectionMenu: import("svelte").Component<SelectionMenuProps, {}, "">;
3
+ type SelectionMenu = ReturnType<typeof SelectionMenu>;
4
+ export default SelectionMenu;
@@ -0,0 +1,26 @@
1
+ /** The props of the selection components. */
2
+ import type { Snippet } from 'svelte';
3
+ import type { AnchoredPlacement, SelectionClipboardOptions } from '@embedpdf/web';
4
+ import type { MaybeGetter } from '../runtime/values.svelte.js';
5
+ import type { StageTokenProp } from '../stage/stage-scope.js';
6
+ export interface SelectionMenuProps {
7
+ /** The menu: buttons that call `useSelection()`, `copySelection()`, an annotation's markup. */
8
+ children: Snippet;
9
+ /** The gap in screen pixels between the selection and the menu. Default 8. */
10
+ gap?: number;
11
+ /** Where the menu sits next to the selection. Default `'top'`. */
12
+ placement?: AnchoredPlacement;
13
+ }
14
+ export interface SelectionHandlesProps {
15
+ /** The stage lens this overlay is on: the enclosing `<Stage>`'s by default. */
16
+ token?: StageTokenProp;
17
+ }
18
+ /** The props of `<SelectionClipboard>`. */
19
+ export interface SelectionClipboardProps extends Pick<SelectionClipboardOptions, 'prefetch' | 'onRefused'> {
20
+ /**
21
+ * Copy only while focus is inside this element, for a page with more than one viewer. Pass a
22
+ * function (`() => area`, with `bind:this={area}`): copying works once the element has mounted.
23
+ * Default: anywhere on the page.
24
+ */
25
+ target?: MaybeGetter<HTMLElement | null | undefined>;
26
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,31 @@
1
+ /**
2
+ * The selection plugin's readers: `useSelection()` (the API), `useSelectionState()`,
3
+ * `useSelectionSettings()` and `useSelectionEvent()`. App code gets the public lens; the layers
4
+ * in this folder read the host lens themselves.
5
+ */
6
+ import type { EventHook } from '@embedpdf/core';
7
+ import type { SelectionCapability } from '@embedpdf/plugin-selection';
8
+ /**
9
+ * The public selection API (`select()`, `readText()`, `canCopy()`, …) for app chrome: toolbars,
10
+ * context menus, automation. A handle, always the capability of the document in scope.
11
+ */
12
+ export declare function useSelection(): SelectionCapability;
13
+ /**
14
+ * The selection's state: whether anything is selected, whether the user is still selecting, the
15
+ * character range and the pages it's on (declared once in `selectionState`), as a reactive object
16
+ * (`state.hasSelection`). With a selector, the value it picks as `{ current }`. Empty without a
17
+ * document.
18
+ */
19
+ export declare const useSelectionState: import("../index.js").StateReader<{
20
+ hasSelection: boolean;
21
+ isSelecting: boolean;
22
+ range: import("@embedpdf/plugin-selection").TextRange | null;
23
+ pages: readonly import("@embedpdf/core").PageRef[];
24
+ }>;
25
+ /** The selection settings (`dragThreshold`, `color`, `handles`), with or without a document. */
26
+ export declare const useSelectionSettings: import("../index.js").SettingsReader<import("@embedpdf/plugin-selection").SelectionSettings & object>;
27
+ /**
28
+ * Subscribe to one selection event while the component lives:
29
+ * `useSelectionEvent((selection) => selection.onCommitted, handler)`.
30
+ */
31
+ export declare function useSelectionEvent<T>(select: (selection: SelectionCapability) => EventHook<T>, handler: (event: T) => void): void;