@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.
- package/LICENSE +192 -0
- package/README.md +312 -2
- package/dist/actions/readers.svelte.d.ts +29 -0
- package/dist/actions/readers.svelte.js +55 -0
- package/dist/actions.d.ts +13 -0
- package/dist/actions.js +13 -0
- package/dist/anchored/Anchored.svelte +24 -0
- package/dist/anchored/Anchored.svelte.d.ts +4 -0
- package/dist/anchored/PlacedAnchored.svelte +97 -0
- package/dist/anchored/PlacedAnchored.svelte.d.ts +4 -0
- package/dist/anchored/context.d.ts +31 -0
- package/dist/anchored/context.js +36 -0
- package/dist/anchored/props.d.ts +29 -0
- package/dist/anchored/props.js +1 -0
- package/dist/anchored.d.ts +12 -0
- package/dist/anchored.js +2 -0
- package/dist/annotation/AnnotationChrome.svelte +173 -0
- package/dist/annotation/AnnotationChrome.svelte.d.ts +11 -0
- package/dist/annotation/AnnotationDraftMenu.svelte +27 -0
- package/dist/annotation/AnnotationDraftMenu.svelte.d.ts +4 -0
- package/dist/annotation/AnnotationFrame.svelte +46 -0
- package/dist/annotation/AnnotationFrame.svelte.d.ts +15 -0
- package/dist/annotation/AnnotationLayer.svelte +241 -0
- package/dist/annotation/AnnotationLayer.svelte.d.ts +4 -0
- package/dist/annotation/AnnotationLook.svelte +67 -0
- package/dist/annotation/AnnotationLook.svelte.d.ts +21 -0
- package/dist/annotation/AnnotationMenu.svelte +47 -0
- package/dist/annotation/AnnotationMenu.svelte.d.ts +4 -0
- package/dist/annotation/AnnotationRotationBadge.svelte +52 -0
- package/dist/annotation/AnnotationRotationBadge.svelte.d.ts +4 -0
- package/dist/annotation/AnnotationScene.svelte +27 -0
- package/dist/annotation/AnnotationScene.svelte.d.ts +7 -0
- package/dist/annotation/FreeTextBox.svelte +51 -0
- package/dist/annotation/FreeTextBox.svelte.d.ts +9 -0
- package/dist/annotation/NativeDrawing.svelte +61 -0
- package/dist/annotation/NativeDrawing.svelte.d.ts +10 -0
- package/dist/annotation/StampGhost.svelte +79 -0
- package/dist/annotation/StampGhost.svelte.d.ts +7 -0
- package/dist/annotation/chrome-paint.svelte.d.ts +30 -0
- package/dist/annotation/chrome-paint.svelte.js +27 -0
- package/dist/annotation/props.d.ts +181 -0
- package/dist/annotation/props.js +1 -0
- package/dist/annotation/readers.svelte.d.ts +59 -0
- package/dist/annotation/readers.svelte.js +136 -0
- package/dist/annotation/state.d.ts +18 -0
- package/dist/annotation/state.js +20 -0
- package/dist/annotation/text-box.svelte.d.ts +28 -0
- package/dist/annotation/text-box.svelte.js +87 -0
- package/dist/annotation.d.ts +32 -0
- package/dist/annotation.js +31 -0
- package/dist/attachment/readers.d.ts +23 -0
- package/dist/attachment/readers.js +21 -0
- package/dist/attachment.d.ts +13 -0
- package/dist/attachment.js +14 -0
- package/dist/commands/readers.svelte.d.ts +50 -0
- package/dist/commands/readers.svelte.js +83 -0
- package/dist/commands.d.ts +9 -0
- package/dist/commands.js +9 -0
- package/dist/form/ButtonControl.svelte +38 -0
- package/dist/form/ButtonControl.svelte.d.ts +11 -0
- package/dist/form/ComboControl.svelte +52 -0
- package/dist/form/ComboControl.svelte.d.ts +11 -0
- package/dist/form/FieldPictures.svelte +145 -0
- package/dist/form/FieldPictures.svelte.d.ts +3 -0
- package/dist/form/FormFocusRing.svelte +25 -0
- package/dist/form/FormFocusRing.svelte.d.ts +8 -0
- package/dist/form/FormLayer.svelte +98 -0
- package/dist/form/FormLayer.svelte.d.ts +3 -0
- package/dist/form/ListControl.svelte +49 -0
- package/dist/form/ListControl.svelte.d.ts +11 -0
- package/dist/form/NativeListBox.svelte +57 -0
- package/dist/form/NativeListBox.svelte.d.ts +4 -0
- package/dist/form/SignatureControl.svelte +53 -0
- package/dist/form/SignatureControl.svelte.d.ts +11 -0
- package/dist/form/TextControl.svelte +107 -0
- package/dist/form/TextControl.svelte.d.ts +11 -0
- package/dist/form/ToggleControl.svelte +50 -0
- package/dist/form/ToggleControl.svelte.d.ts +11 -0
- package/dist/form/WidgetBox.svelte +82 -0
- package/dist/form/WidgetBox.svelte.d.ts +15 -0
- package/dist/form/props.d.ts +21 -0
- package/dist/form/props.js +1 -0
- package/dist/form/readers.svelte.d.ts +46 -0
- package/dist/form/readers.svelte.js +52 -0
- package/dist/form/widget.d.ts +16 -0
- package/dist/form/widget.js +20 -0
- package/dist/form.d.ts +11 -0
- package/dist/form.js +12 -0
- package/dist/history/readers.d.ts +22 -0
- package/dist/history/readers.js +21 -0
- package/dist/history.d.ts +10 -0
- package/dist/history.js +11 -0
- package/dist/i18n/readers.svelte.d.ts +32 -0
- package/dist/i18n/readers.svelte.js +30 -0
- package/dist/i18n.d.ts +8 -0
- package/dist/i18n.js +9 -0
- package/dist/index.d.ts +30 -0
- package/dist/index.js +30 -0
- package/dist/interaction/PagePointerSource.svelte +37 -0
- package/dist/interaction/PagePointerSource.svelte.d.ts +3 -0
- package/dist/interaction/readers.svelte.d.ts +26 -0
- package/dist/interaction/readers.svelte.js +47 -0
- package/dist/interaction.d.ts +11 -0
- package/dist/interaction.js +13 -0
- package/dist/link/LinkLayer.svelte +164 -0
- package/dist/link/LinkLayer.svelte.d.ts +4 -0
- package/dist/link/props.d.ts +17 -0
- package/dist/link/props.js +1 -0
- package/dist/link/readers.svelte.d.ts +18 -0
- package/dist/link/readers.svelte.js +38 -0
- package/dist/link.d.ts +11 -0
- package/dist/link.js +11 -0
- package/dist/measurement/readers.d.ts +44 -0
- package/dist/measurement/readers.js +46 -0
- package/dist/measurement.d.ts +12 -0
- package/dist/measurement.js +13 -0
- package/dist/metadata/readers.d.ts +22 -0
- package/dist/metadata/readers.js +22 -0
- package/dist/metadata.d.ts +12 -0
- package/dist/metadata.js +13 -0
- package/dist/outline/readers.d.ts +26 -0
- package/dist/outline/readers.js +22 -0
- package/dist/outline.d.ts +12 -0
- package/dist/outline.js +13 -0
- package/dist/page-edit/readers.d.ts +17 -0
- package/dist/page-edit/readers.js +18 -0
- package/dist/page-edit.d.ts +12 -0
- package/dist/page-edit.js +13 -0
- package/dist/page-view/PageView.svelte +192 -0
- package/dist/page-view/PageView.svelte.d.ts +4 -0
- package/dist/page-view/props.d.ts +32 -0
- package/dist/page-view/props.js +1 -0
- package/dist/page-view.d.ts +8 -0
- package/dist/page-view.js +7 -0
- package/dist/redaction/readers.d.ts +37 -0
- package/dist/redaction/readers.js +38 -0
- package/dist/redaction.d.ts +12 -0
- package/dist/redaction.js +13 -0
- package/dist/render/RenderLayer.svelte +116 -0
- package/dist/render/RenderLayer.svelte.d.ts +4 -0
- package/dist/render/TileImage.svelte +65 -0
- package/dist/render/TileImage.svelte.d.ts +11 -0
- package/dist/render/TilePlane.svelte +88 -0
- package/dist/render/TilePlane.svelte.d.ts +7 -0
- package/dist/render/props.d.ts +16 -0
- package/dist/render/props.js +1 -0
- package/dist/render/readers.svelte.d.ts +9 -0
- package/dist/render/readers.svelte.js +13 -0
- package/dist/render.d.ts +10 -0
- package/dist/render.js +10 -0
- package/dist/runtime/DocumentGate.svelte +26 -0
- package/dist/runtime/DocumentGate.svelte.d.ts +4 -0
- package/dist/runtime/DocumentScope.svelte +14 -0
- package/dist/runtime/DocumentScope.svelte.d.ts +4 -0
- package/dist/runtime/KernelProvider.svelte +33 -0
- package/dist/runtime/KernelProvider.svelte.d.ts +4 -0
- package/dist/runtime/SvgShapes.svelte +30 -0
- package/dist/runtime/SvgShapes.svelte.d.ts +8 -0
- package/dist/runtime/Viewer.svelte +130 -0
- package/dist/runtime/Viewer.svelte.d.ts +4 -0
- package/dist/runtime/binding.svelte.d.ts +33 -0
- package/dist/runtime/binding.svelte.js +71 -0
- package/dist/runtime/capability-handle.d.ts +6 -0
- package/dist/runtime/capability-handle.js +71 -0
- package/dist/runtime/dev.d.ts +10 -0
- package/dist/runtime/dev.js +23 -0
- package/dist/runtime/documents.svelte.d.ts +34 -0
- package/dist/runtime/documents.svelte.js +49 -0
- package/dist/runtime/page-layers.svelte.d.ts +20 -0
- package/dist/runtime/page-layers.svelte.js +40 -0
- package/dist/runtime/page.d.ts +10 -0
- package/dist/runtime/page.js +23 -0
- package/dist/runtime/props.d.ts +63 -0
- package/dist/runtime/props.js +1 -0
- package/dist/runtime/readers.svelte.d.ts +47 -0
- package/dist/runtime/readers.svelte.js +122 -0
- package/dist/runtime/state.svelte.d.ts +35 -0
- package/dist/runtime/state.svelte.js +90 -0
- package/dist/runtime/theme.d.ts +12 -0
- package/dist/runtime/theme.js +15 -0
- package/dist/runtime/values.svelte.d.ts +28 -0
- package/dist/runtime/values.svelte.js +51 -0
- package/dist/runtime.d.ts +26 -0
- package/dist/runtime.js +25 -0
- package/dist/search/SearchLayer.svelte +94 -0
- package/dist/search/SearchLayer.svelte.d.ts +4 -0
- package/dist/search/props.d.ts +11 -0
- package/dist/search/props.js +1 -0
- package/dist/search/readers.svelte.d.ts +41 -0
- package/dist/search/readers.svelte.js +40 -0
- package/dist/search.d.ts +11 -0
- package/dist/search.js +11 -0
- package/dist/selection/SelectionClipboard.svelte +35 -0
- package/dist/selection/SelectionClipboard.svelte.d.ts +4 -0
- package/dist/selection/SelectionHandles.svelte +192 -0
- package/dist/selection/SelectionHandles.svelte.d.ts +4 -0
- package/dist/selection/SelectionLayer.svelte +67 -0
- package/dist/selection/SelectionLayer.svelte.d.ts +3 -0
- package/dist/selection/SelectionMenu.svelte +36 -0
- package/dist/selection/SelectionMenu.svelte.d.ts +4 -0
- package/dist/selection/props.d.ts +26 -0
- package/dist/selection/props.js +1 -0
- package/dist/selection/readers.svelte.d.ts +31 -0
- package/dist/selection/readers.svelte.js +26 -0
- package/dist/selection.d.ts +16 -0
- package/dist/selection.js +18 -0
- package/dist/shell/readers.svelte.d.ts +40 -0
- package/dist/shell/readers.svelte.js +40 -0
- package/dist/shell/surface-focus.svelte.d.ts +16 -0
- package/dist/shell/surface-focus.svelte.js +33 -0
- package/dist/shell.d.ts +12 -0
- package/dist/shell.js +11 -0
- package/dist/signature/readers.svelte.d.ts +43 -0
- package/dist/signature/readers.svelte.js +46 -0
- package/dist/signature.d.ts +15 -0
- package/dist/signature.js +16 -0
- package/dist/stage/PageSurface.svelte +126 -0
- package/dist/stage/PageSurface.svelte.d.ts +18 -0
- package/dist/stage/Scrollbar.svelte +148 -0
- package/dist/stage/Scrollbar.svelte.d.ts +4 -0
- package/dist/stage/Stage.svelte +181 -0
- package/dist/stage/Stage.svelte.d.ts +4 -0
- package/dist/stage/StageScope.svelte +14 -0
- package/dist/stage/StageScope.svelte.d.ts +9 -0
- package/dist/stage/props.d.ts +71 -0
- package/dist/stage/props.js +1 -0
- package/dist/stage/readers.svelte.d.ts +39 -0
- package/dist/stage/readers.svelte.js +84 -0
- package/dist/stage/stage-scope.d.ts +18 -0
- package/dist/stage/stage-scope.js +35 -0
- package/dist/stage/two-way.svelte.d.ts +13 -0
- package/dist/stage/two-way.svelte.js +26 -0
- package/dist/stage.d.ts +17 -0
- package/dist/stage.js +15 -0
- package/dist/stamp/readers.svelte.d.ts +50 -0
- package/dist/stamp/readers.svelte.js +76 -0
- package/dist/stamp.d.ts +16 -0
- package/dist/stamp.js +18 -0
- package/dist/toolbar/DefaultCollapsed.svelte +39 -0
- package/dist/toolbar/DefaultCollapsed.svelte.d.ts +9 -0
- package/dist/toolbar/DefaultCommand.svelte +33 -0
- package/dist/toolbar/DefaultCommand.svelte.d.ts +8 -0
- package/dist/toolbar/DefaultGroupTrigger.svelte +39 -0
- package/dist/toolbar/DefaultGroupTrigger.svelte.d.ts +9 -0
- package/dist/toolbar/DefaultOverflowMenu.svelte +106 -0
- package/dist/toolbar/DefaultOverflowMenu.svelte.d.ts +9 -0
- package/dist/toolbar/DefaultOverflowTrigger.svelte +25 -0
- package/dist/toolbar/DefaultOverflowTrigger.svelte.d.ts +7 -0
- package/dist/toolbar/Measured.svelte +46 -0
- package/dist/toolbar/Measured.svelte.d.ts +11 -0
- package/dist/toolbar/NativeSlotSocket.svelte +43 -0
- package/dist/toolbar/NativeSlotSocket.svelte.d.ts +11 -0
- package/dist/toolbar/Toolbar.svelte +314 -0
- package/dist/toolbar/Toolbar.svelte.d.ts +4 -0
- package/dist/toolbar/paint.d.ts +3 -0
- package/dist/toolbar/paint.js +9 -0
- package/dist/toolbar/props.d.ts +48 -0
- package/dist/toolbar/props.js +1 -0
- package/dist/toolbar/strip.svelte.d.ts +14 -0
- package/dist/toolbar/strip.svelte.js +35 -0
- package/dist/toolbar/views.d.ts +14 -0
- package/dist/toolbar/views.js +1 -0
- package/dist/toolbar.d.ts +16 -0
- package/dist/toolbar.js +12 -0
- package/dist/view-manager/readers.d.ts +19 -0
- package/dist/view-manager/readers.js +17 -0
- package/dist/view-manager.d.ts +11 -0
- package/dist/view-manager.js +12 -0
- 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,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
|
+
}
|
package/dist/search.d.ts
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
|
+
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,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,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,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,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;
|