@embedpdf/svelte 0.0.0-stage → 3.0.0-next.15
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 +55 -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 +27 -0
- package/dist/annotation/text-box.svelte.js +94 -0
- package/dist/annotation.d.ts +32 -0
- package/dist/annotation.js +31 -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 +28 -0
- package/dist/index.js +28 -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/page-edit/readers.d.ts +7 -0
- package/dist/page-edit/readers.js +11 -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 +124 -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 +195 -3
|
@@ -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;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { SelectionToken, selectionState } from '@embedpdf/plugin-selection';
|
|
2
|
+
import { useCapability, useCapabilityEvent } from '../runtime/readers.svelte.js';
|
|
3
|
+
import { settingsReader, stateReader } from '../runtime/state.svelte.js';
|
|
4
|
+
/**
|
|
5
|
+
* The public selection API (`select()`, `readText()`, `canCopy()`, …) for app chrome: toolbars,
|
|
6
|
+
* context menus, automation. A handle, always the capability of the document in scope.
|
|
7
|
+
*/
|
|
8
|
+
export function useSelection() {
|
|
9
|
+
return useCapability(SelectionToken);
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* The selection's state: whether anything is selected, whether the user is still selecting, the
|
|
13
|
+
* character range and the pages it's on (declared once in `selectionState`), as a reactive object
|
|
14
|
+
* (`state.hasSelection`). With a selector, the value it picks as `{ current }`. Empty without a
|
|
15
|
+
* document.
|
|
16
|
+
*/
|
|
17
|
+
export const useSelectionState = stateReader(selectionState);
|
|
18
|
+
/** The selection settings (`dragThreshold`, `color`, `handles`), with or without a document. */
|
|
19
|
+
export const useSelectionSettings = settingsReader(SelectionToken);
|
|
20
|
+
/**
|
|
21
|
+
* Subscribe to one selection event while the component lives:
|
|
22
|
+
* `useSelectionEvent((selection) => selection.onCommitted, handler)`.
|
|
23
|
+
*/
|
|
24
|
+
export function useSelectionEvent(select, handler) {
|
|
25
|
+
useCapabilityEvent(SelectionToken, select, handler);
|
|
26
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @embedpdf/svelte/selection — text selection.
|
|
3
|
+
*
|
|
4
|
+
* `<SelectionLayer>` paints the highlight on each page; `<SelectionMenu>` floats over a settled
|
|
5
|
+
* selection; `<SelectionHandles>` are the touch handles in a Stage's overlay;
|
|
6
|
+
* `<SelectionClipboard>` wires Ctrl+C / Cmd+C. The readers are the plugin's four:
|
|
7
|
+
* `useSelection()`, `useSelectionState()`, `useSelectionSettings()` and `useSelectionEvent()`.
|
|
8
|
+
*/
|
|
9
|
+
export * from '@embedpdf/plugin-selection';
|
|
10
|
+
export { copySelection } from '@embedpdf/web';
|
|
11
|
+
export { default as SelectionLayer } from './selection/SelectionLayer.svelte';
|
|
12
|
+
export { default as SelectionMenu } from './selection/SelectionMenu.svelte';
|
|
13
|
+
export { default as SelectionHandles } from './selection/SelectionHandles.svelte';
|
|
14
|
+
export { default as SelectionClipboard } from './selection/SelectionClipboard.svelte';
|
|
15
|
+
export type { SelectionClipboardProps, SelectionHandlesProps, SelectionMenuProps, } from './selection/props.js';
|
|
16
|
+
export { useSelection, useSelectionEvent, useSelectionSettings, useSelectionState, } from './selection/readers.svelte.js';
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @embedpdf/svelte/selection — text selection.
|
|
3
|
+
*
|
|
4
|
+
* `<SelectionLayer>` paints the highlight on each page; `<SelectionMenu>` floats over a settled
|
|
5
|
+
* selection; `<SelectionHandles>` are the touch handles in a Stage's overlay;
|
|
6
|
+
* `<SelectionClipboard>` wires Ctrl+C / Cmd+C. The readers are the plugin's four:
|
|
7
|
+
* `useSelection()`, `useSelectionState()`, `useSelectionSettings()` and `useSelectionEvent()`.
|
|
8
|
+
*/
|
|
9
|
+
// Registration travels with the UI.
|
|
10
|
+
export * from '@embedpdf/plugin-selection';
|
|
11
|
+
// The clipboard write lives in @embedpdf/web (the plugin has no DOM); it's here too, so app code
|
|
12
|
+
// has one import for the feature.
|
|
13
|
+
export { copySelection } from '@embedpdf/web';
|
|
14
|
+
export { default as SelectionLayer } from './selection/SelectionLayer.svelte';
|
|
15
|
+
export { default as SelectionMenu } from './selection/SelectionMenu.svelte';
|
|
16
|
+
export { default as SelectionHandles } from './selection/SelectionHandles.svelte';
|
|
17
|
+
export { default as SelectionClipboard } from './selection/SelectionClipboard.svelte';
|
|
18
|
+
export { useSelection, useSelectionEvent, useSelectionSettings, useSelectionState, } from './selection/readers.svelte.js';
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The shell plugin's readers. The app owns every surface's DOM; these bind its open and closed
|
|
3
|
+
* state to the kernel, so commands can drive it and an app can restore it.
|
|
4
|
+
*
|
|
5
|
+
* They work without a document: shell state is a document's, and chrome that uses it (panel
|
|
6
|
+
* buttons, menu anchors) stays mounted while none is open. With no document every surface reads
|
|
7
|
+
* as closed, no menu as open, and `useSurface`'s verbs do nothing; `useShell()` then reaches the
|
|
8
|
+
* stand-in, whose calls refuse with `not-ready`.
|
|
9
|
+
*/
|
|
10
|
+
import type { EventHook } from '@embedpdf/core';
|
|
11
|
+
import type { OpenSurfaceOptions, ShellCapability, SurfaceProps } from '@embedpdf/plugin-shell';
|
|
12
|
+
import { type MaybeGetter } from '../runtime/values.svelte.js';
|
|
13
|
+
/** The shell API: open and close panels, dialogs and menus. Without a document its calls refuse with `not-ready`. */
|
|
14
|
+
export declare function useShell(): ShellCapability;
|
|
15
|
+
/** Subscribe to one shell event while the component lives: `useShellEvent((shell) => shell.onSurfaceOpened, handler)`. */
|
|
16
|
+
export declare function useShellEvent<T>(select: (shell: ShellCapability) => EventHook<T>, handler: (event: T) => void): void;
|
|
17
|
+
/**
|
|
18
|
+
* Which panels, dialogs and menus are open (declared once in `shellState`), as a reactive object
|
|
19
|
+
* (`useShellState().openMenus`). Takes a selector. Without a document nothing is open.
|
|
20
|
+
*/
|
|
21
|
+
export declare const useShellState: import("../index.js").StateReader<{
|
|
22
|
+
openSurfaces: readonly import("@embedpdf/plugin-shell").SurfaceState[];
|
|
23
|
+
openMenus: readonly string[];
|
|
24
|
+
}>;
|
|
25
|
+
/** One surface: whether it's open and its props, read live, and the calls that change it. */
|
|
26
|
+
export interface SurfaceHandle {
|
|
27
|
+
readonly isOpen: boolean;
|
|
28
|
+
/** The props the surface was opened with, or updated to; empty when it has none. */
|
|
29
|
+
readonly props: SurfaceProps;
|
|
30
|
+
open(options?: OpenSurfaceOptions): void;
|
|
31
|
+
close(): void;
|
|
32
|
+
toggle(options?: OpenSurfaceOptions): void;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* One named surface (panel, dialog, overlay) of this component's document: `panel.isOpen` and
|
|
36
|
+
* `panel.props` update in a template, and `open`, `close` and `toggle` can be passed as handlers
|
|
37
|
+
* (`onclick={panel.close}`). Pass the id as a function to follow a prop. It reads as closed, and
|
|
38
|
+
* its calls do nothing, while no document is open.
|
|
39
|
+
*/
|
|
40
|
+
export declare function useSurface(id: MaybeGetter<string>): SurfaceHandle;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { ShellToken, shellState } from '@embedpdf/plugin-shell';
|
|
2
|
+
import { useCapability, useCapabilityEvent, useOptionalCapability, useOptionalSelector, } from '../runtime/readers.svelte.js';
|
|
3
|
+
import { stateReader } from '../runtime/state.svelte.js';
|
|
4
|
+
import { valueOf } from '../runtime/values.svelte.js';
|
|
5
|
+
/** The shell API: open and close panels, dialogs and menus. Without a document its calls refuse with `not-ready`. */
|
|
6
|
+
export function useShell() {
|
|
7
|
+
return useCapability(ShellToken);
|
|
8
|
+
}
|
|
9
|
+
/** Subscribe to one shell event while the component lives: `useShellEvent((shell) => shell.onSurfaceOpened, handler)`. */
|
|
10
|
+
export function useShellEvent(select, handler) {
|
|
11
|
+
useCapabilityEvent(ShellToken, select, handler);
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Which panels, dialogs and menus are open (declared once in `shellState`), as a reactive object
|
|
15
|
+
* (`useShellState().openMenus`). Takes a selector. Without a document nothing is open.
|
|
16
|
+
*/
|
|
17
|
+
export const useShellState = stateReader(shellState);
|
|
18
|
+
const NO_PROPS = Object.freeze({});
|
|
19
|
+
/**
|
|
20
|
+
* One named surface (panel, dialog, overlay) of this component's document: `panel.isOpen` and
|
|
21
|
+
* `panel.props` update in a template, and `open`, `close` and `toggle` can be passed as handlers
|
|
22
|
+
* (`onclick={panel.close}`). Pass the id as a function to follow a prop. It reads as closed, and
|
|
23
|
+
* its calls do nothing, while no document is open.
|
|
24
|
+
*/
|
|
25
|
+
export function useSurface(id) {
|
|
26
|
+
const shell = useOptionalCapability(ShellToken);
|
|
27
|
+
const isOpen = useOptionalSelector(ShellToken, (shell) => shell.isOpen(valueOf(id)), false);
|
|
28
|
+
const props = useOptionalSelector(ShellToken, (shell) => shell.getSurface(valueOf(id))?.props ?? NO_PROPS, NO_PROPS);
|
|
29
|
+
return {
|
|
30
|
+
get isOpen() {
|
|
31
|
+
return isOpen.current;
|
|
32
|
+
},
|
|
33
|
+
get props() {
|
|
34
|
+
return props.current;
|
|
35
|
+
},
|
|
36
|
+
open: (options) => shell.current?.open(valueOf(id), options),
|
|
37
|
+
close: () => shell.current?.close(valueOf(id)),
|
|
38
|
+
toggle: (options) => shell.current?.toggle(valueOf(id), options),
|
|
39
|
+
};
|
|
40
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { Attachment } from 'svelte/attachments';
|
|
2
|
+
import { type SurfaceFocusOptions } from '@embedpdf/web';
|
|
3
|
+
import { type MaybeGetter } from '../runtime/values.svelte.js';
|
|
4
|
+
export type { SurfaceFocusOptions };
|
|
5
|
+
/**
|
|
6
|
+
* An attachment that puts the cursor in its element when the surface `id` asks for it: on every
|
|
7
|
+
* `open(id, { focus: true })`, a command whose panel says `focus` among them, and when the
|
|
8
|
+
* element mounts while the request is up (the panel opened, or its code loaded, because of it).
|
|
9
|
+
* A plain open never moves it. `select` selects an input's text, so typing replaces it. It reads
|
|
10
|
+
* the surface of this component's document; pass the id as a function to follow a prop.
|
|
11
|
+
*
|
|
12
|
+
* ```svelte
|
|
13
|
+
* <input {@attach surfaceFocus('search', { select: true })} bind:value={query} />
|
|
14
|
+
* ```
|
|
15
|
+
*/
|
|
16
|
+
export declare function surfaceFocus(id: MaybeGetter<string>, options?: SurfaceFocusOptions): Attachment<HTMLElement>;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Putting the cursor in an element when its panel asks for it. `@embedpdf/web`'s surface focus
|
|
3
|
+
* follower owns the rule (a new request moves the cursor in, and so does mounting while one is
|
|
4
|
+
* up); this binds it to Svelte as an attachment.
|
|
5
|
+
*/
|
|
6
|
+
import { untrack } from 'svelte';
|
|
7
|
+
import { ShellToken } from '@embedpdf/plugin-shell';
|
|
8
|
+
import { createSurfaceFocusFollower } from '@embedpdf/web';
|
|
9
|
+
import { useOptionalCapability, useOptionalSelector } from '../runtime/readers.svelte.js';
|
|
10
|
+
import { valueOf } from '../runtime/values.svelte.js';
|
|
11
|
+
/**
|
|
12
|
+
* An attachment that puts the cursor in its element when the surface `id` asks for it: on every
|
|
13
|
+
* `open(id, { focus: true })`, a command whose panel says `focus` among them, and when the
|
|
14
|
+
* element mounts while the request is up (the panel opened, or its code loaded, because of it).
|
|
15
|
+
* A plain open never moves it. `select` selects an input's text, so typing replaces it. It reads
|
|
16
|
+
* the surface of this component's document; pass the id as a function to follow a prop.
|
|
17
|
+
*
|
|
18
|
+
* ```svelte
|
|
19
|
+
* <input {@attach surfaceFocus('search', { select: true })} bind:value={query} />
|
|
20
|
+
* ```
|
|
21
|
+
*/
|
|
22
|
+
export function surfaceFocus(id, options = {}) {
|
|
23
|
+
const shell = useOptionalCapability(ShellToken);
|
|
24
|
+
const request = useOptionalSelector(ShellToken, (current) => current.getSurface(valueOf(id))?.focusRequest ?? 0, 0);
|
|
25
|
+
return (element) => {
|
|
26
|
+
const follower = createSurfaceFocusFollower();
|
|
27
|
+
$effect(() => {
|
|
28
|
+
const current = shell.current;
|
|
29
|
+
const value = request.current;
|
|
30
|
+
untrack(() => follower.follow(element, current, value, options));
|
|
31
|
+
});
|
|
32
|
+
};
|
|
33
|
+
}
|
package/dist/shell.d.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @embedpdf/svelte/shell — panels, dialogs and menus.
|
|
3
|
+
*
|
|
4
|
+
* Your app draws every surface; the readers bind whether each one is open (and its props) to the
|
|
5
|
+
* kernel, so commands can open them and a layout can be saved and restored. `surfaceFocus` puts
|
|
6
|
+
* the cursor in a panel's element when the panel asks for it.
|
|
7
|
+
*/
|
|
8
|
+
export * from '@embedpdf/plugin-shell';
|
|
9
|
+
export { useShell, useShellEvent, useShellState, useSurface } from './shell/readers.svelte.js';
|
|
10
|
+
export type { SurfaceHandle } from './shell/readers.svelte.js';
|
|
11
|
+
export { surfaceFocus } from './shell/surface-focus.svelte.js';
|
|
12
|
+
export type { SurfaceFocusOptions } from './shell/surface-focus.svelte.js';
|
package/dist/shell.js
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @embedpdf/svelte/shell — panels, dialogs and menus.
|
|
3
|
+
*
|
|
4
|
+
* Your app draws every surface; the readers bind whether each one is open (and its props) to the
|
|
5
|
+
* kernel, so commands can open them and a layout can be saved and restored. `surfaceFocus` puts
|
|
6
|
+
* the cursor in a panel's element when the panel asks for it.
|
|
7
|
+
*/
|
|
8
|
+
// Registration travels with the UI.
|
|
9
|
+
export * from '@embedpdf/plugin-shell';
|
|
10
|
+
export { useShell, useShellEvent, useShellState, useSurface } from './shell/readers.svelte.js';
|
|
11
|
+
export { surfaceFocus } from './shell/surface-focus.svelte.js';
|