@embedpdf/viewer-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 +77 -2
- package/dist/component.svelte +105 -0
- package/dist/component.svelte.d.ts +4 -0
- package/dist/config.d.ts +20 -0
- package/dist/config.js +37 -0
- package/dist/core.d.ts +33 -0
- package/dist/core.js +29 -0
- package/dist/hooks.d.ts +21 -0
- package/dist/hooks.js +27 -0
- package/dist/index.d.ts +28 -0
- package/dist/index.js +25 -0
- package/dist/parts.d.ts +15 -0
- package/dist/parts.js +12 -0
- package/dist/props.d.ts +45 -0
- package/dist/props.js +1 -0
- package/dist/slots/Panel.svelte +15 -0
- package/dist/slots/Panel.svelte.d.ts +4 -0
- package/dist/slots/Region.svelte +15 -0
- package/dist/slots/Region.svelte.d.ts +4 -0
- package/dist/slots/ToolbarItem.svelte +27 -0
- package/dist/slots/ToolbarItem.svelte.d.ts +4 -0
- package/dist/slots/item-context.svelte.d.ts +27 -0
- package/dist/slots/item-context.svelte.js +73 -0
- package/dist/slots/props.d.ts +22 -0
- package/dist/slots/props.js +1 -0
- package/dist/slots.d.ts +12 -0
- package/dist/slots.js +11 -0
- package/dist/viewer-components/Button.svelte +22 -0
- package/dist/viewer-components/Button.svelte.d.ts +4 -0
- package/dist/viewer-components/Menu.svelte +13 -0
- package/dist/viewer-components/Menu.svelte.d.ts +4 -0
- package/dist/viewer-components/MenuItem.svelte +15 -0
- package/dist/viewer-components/MenuItem.svelte.d.ts +4 -0
- package/dist/viewer-components/props.d.ts +31 -0
- package/dist/viewer-components/props.js +1 -0
- package/dist/viewer-components.d.ts +10 -0
- package/dist/viewer-components.js +9 -0
- package/package.json +67 -3
package/LICENSE
ADDED
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
Copyright 2026 CloudPDF LTD
|
|
2
|
+
|
|
3
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
you may not use files in this distribution except in compliance with the License.
|
|
5
|
+
You may obtain a copy of the License at
|
|
6
|
+
|
|
7
|
+
https://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
|
|
9
|
+
Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
See the License for the specific language governing permissions and
|
|
13
|
+
limitations under the License.
|
|
14
|
+
|
|
15
|
+
-----------------------------------------------------------------------
|
|
16
|
+
|
|
17
|
+
Apache License
|
|
18
|
+
Version 2.0, January 2004
|
|
19
|
+
http://www.apache.org/licenses/
|
|
20
|
+
|
|
21
|
+
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
|
22
|
+
|
|
23
|
+
1. Definitions.
|
|
24
|
+
|
|
25
|
+
"License" shall mean the terms and conditions for use, reproduction,
|
|
26
|
+
and distribution as defined by Sections 1 through 9 of this document.
|
|
27
|
+
|
|
28
|
+
"Licensor" shall mean the copyright owner or entity authorized by
|
|
29
|
+
the copyright owner that is granting the License.
|
|
30
|
+
|
|
31
|
+
"Legal Entity" shall mean the union of the acting entity and all
|
|
32
|
+
other entities that control, are controlled by, or are under common
|
|
33
|
+
control with that entity. For the purposes of this definition,
|
|
34
|
+
"control" means (i) the power, direct or indirect, to cause the
|
|
35
|
+
direction or management of such entity, whether by contract or
|
|
36
|
+
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
|
37
|
+
outstanding shares, or (iii) beneficial ownership of such entity.
|
|
38
|
+
|
|
39
|
+
"You" (or "Your") shall mean an individual or Legal Entity
|
|
40
|
+
exercising permissions granted by this License.
|
|
41
|
+
|
|
42
|
+
"Source" form shall mean the preferred form for making modifications,
|
|
43
|
+
including but not limited to software source code, documentation
|
|
44
|
+
source, and configuration files.
|
|
45
|
+
|
|
46
|
+
"Object" form shall mean any form resulting from mechanical
|
|
47
|
+
transformation or translation of a Source form, including but
|
|
48
|
+
not limited to compiled object code, generated documentation,
|
|
49
|
+
and conversions to other media types.
|
|
50
|
+
|
|
51
|
+
"Work" shall mean the work of authorship, whether in Source or
|
|
52
|
+
Object form, made available under the License, as indicated by a
|
|
53
|
+
copyright notice that is included in or attached to the work
|
|
54
|
+
(an example is provided in the Appendix below).
|
|
55
|
+
|
|
56
|
+
"Derivative Works" shall mean any work, whether in Source or Object
|
|
57
|
+
form, that is based on (or derived from) the Work and for which the
|
|
58
|
+
editorial revisions, annotations, elaborations, or other modifications
|
|
59
|
+
represent, as a whole, an original work of authorship. For the purposes
|
|
60
|
+
of this License, Derivative Works shall not include works that remain
|
|
61
|
+
separable from, or merely link (or bind by name) to the interfaces of,
|
|
62
|
+
the Work and Derivative Works thereof.
|
|
63
|
+
|
|
64
|
+
"Contribution" shall mean any work of authorship, including
|
|
65
|
+
the original version of the Work and any modifications or additions
|
|
66
|
+
to that Work or Derivative Works thereof, that is intentionally
|
|
67
|
+
submitted to Licensor for inclusion in the Work by the copyright owner
|
|
68
|
+
or by an individual or Legal Entity authorized to submit on behalf of
|
|
69
|
+
the copyright owner. For the purposes of this definition, "submitted"
|
|
70
|
+
means any form of electronic, verbal, or written communication sent
|
|
71
|
+
to the Licensor or its representatives, including but not limited to
|
|
72
|
+
communication on electronic mailing lists, source code control systems,
|
|
73
|
+
and issue tracking systems that are managed by, or on behalf of, the
|
|
74
|
+
Licensor for the purpose of discussing and improving the Work, but
|
|
75
|
+
excluding communication that is conspicuously marked or otherwise
|
|
76
|
+
designated in writing by the copyright owner as "Not a Contribution."
|
|
77
|
+
|
|
78
|
+
"Contributor" shall mean Licensor and any individual or Legal Entity
|
|
79
|
+
on behalf of whom a Contribution has been received by Licensor and
|
|
80
|
+
subsequently incorporated within the Work.
|
|
81
|
+
|
|
82
|
+
2. Grant of Copyright License. Subject to the terms and conditions of
|
|
83
|
+
this License, each Contributor hereby grants to You a perpetual,
|
|
84
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
85
|
+
copyright license to reproduce, prepare Derivative Works of,
|
|
86
|
+
publicly display, publicly perform, sublicense, and distribute the
|
|
87
|
+
Work and such Derivative Works in Source or Object form.
|
|
88
|
+
|
|
89
|
+
3. Grant of Patent License. Subject to the terms and conditions of
|
|
90
|
+
this License, each Contributor hereby grants to You a perpetual,
|
|
91
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
92
|
+
(except as stated in this section) patent license to make, have made,
|
|
93
|
+
use, offer to sell, sell, import, and otherwise transfer the Work,
|
|
94
|
+
where such license applies only to those patent claims licensable
|
|
95
|
+
by such Contributor that are necessarily infringed by their
|
|
96
|
+
Contribution(s) alone or by combination of their Contribution(s)
|
|
97
|
+
with the Work to which such Contribution(s) was submitted. If You
|
|
98
|
+
institute patent litigation against any entity (including a
|
|
99
|
+
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
|
100
|
+
or a Contribution incorporated within the Work constitutes direct
|
|
101
|
+
or contributory patent infringement, then any patent licenses
|
|
102
|
+
granted to You under this License for that Work shall terminate
|
|
103
|
+
as of the date such litigation is filed.
|
|
104
|
+
|
|
105
|
+
4. Redistribution. You may reproduce and distribute copies of the
|
|
106
|
+
Work or Derivative Works thereof in any medium, with or without
|
|
107
|
+
modifications, and in Source or Object form, provided that You
|
|
108
|
+
meet the following conditions:
|
|
109
|
+
|
|
110
|
+
(a) You must give any other recipients of the Work or
|
|
111
|
+
Derivative Works a copy of this License; and
|
|
112
|
+
|
|
113
|
+
(b) You must cause any modified files to carry prominent notices
|
|
114
|
+
stating that You changed the files; and
|
|
115
|
+
|
|
116
|
+
(c) You must retain, in the Source form of any Derivative Works
|
|
117
|
+
that You distribute, all copyright, patent, trademark, and
|
|
118
|
+
attribution notices from the Source form of the Work,
|
|
119
|
+
excluding those notices that do not pertain to any part of
|
|
120
|
+
the Derivative Works; and
|
|
121
|
+
|
|
122
|
+
(d) If the Work includes a "NOTICE" text file as part of its
|
|
123
|
+
distribution, then any Derivative Works that You distribute must
|
|
124
|
+
include a readable copy of the attribution notices contained
|
|
125
|
+
within such NOTICE file, excluding those notices that do not
|
|
126
|
+
pertain to any part of the Derivative Works, in at least one
|
|
127
|
+
of the following places: within a NOTICE text file distributed
|
|
128
|
+
as part of the Derivative Works; within the Source form or
|
|
129
|
+
documentation, if provided along with the Derivative Works; or,
|
|
130
|
+
within a display generated by the Derivative Works, if and
|
|
131
|
+
wherever such third-party notices normally appear. The contents
|
|
132
|
+
of the NOTICE file are for informational purposes only and
|
|
133
|
+
do not modify the License. You may add Your own attribution
|
|
134
|
+
notices within Derivative Works that You distribute, alongside
|
|
135
|
+
or as an addendum to the NOTICE text from the Work, provided
|
|
136
|
+
that such additional attribution notices cannot be construed
|
|
137
|
+
as modifying the License.
|
|
138
|
+
|
|
139
|
+
You may add Your own copyright statement to Your modifications and
|
|
140
|
+
may provide additional or different license terms and conditions
|
|
141
|
+
for use, reproduction, or distribution of Your modifications, or
|
|
142
|
+
for any such Derivative Works as a whole, provided Your use,
|
|
143
|
+
reproduction, and distribution of the Work otherwise complies with
|
|
144
|
+
the conditions stated in this License.
|
|
145
|
+
|
|
146
|
+
5. Submission of Contributions. Unless You explicitly state otherwise,
|
|
147
|
+
any Contribution intentionally submitted for inclusion in the Work
|
|
148
|
+
by You to the Licensor shall be under the terms and conditions of
|
|
149
|
+
this License, without any additional terms or conditions.
|
|
150
|
+
Notwithstanding the above, nothing herein shall supersede or modify
|
|
151
|
+
the terms of any separate license agreement you may have executed
|
|
152
|
+
with Licensor regarding such Contributions.
|
|
153
|
+
|
|
154
|
+
6. Trademarks. This License does not grant permission to use the trade
|
|
155
|
+
names, trademarks, service marks, or product names of the Licensor,
|
|
156
|
+
except as required for reasonable and customary use in describing the
|
|
157
|
+
origin of the Work and reproducing the content of the NOTICE file.
|
|
158
|
+
|
|
159
|
+
7. Disclaimer of Warranty. Unless required by applicable law or
|
|
160
|
+
agreed to in writing, Licensor provides the Work (and each
|
|
161
|
+
Contributor provides its Contributions) on an "AS IS" BASIS,
|
|
162
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
|
163
|
+
implied, including, without limitation, any warranties or conditions
|
|
164
|
+
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
|
165
|
+
PARTICULAR PURPOSE. You are solely responsible for determining the
|
|
166
|
+
appropriateness of using or redistributing the Work and assume any
|
|
167
|
+
risks associated with Your exercise of permissions under this License.
|
|
168
|
+
|
|
169
|
+
8. Limitation of Liability. In no event and under no legal theory,
|
|
170
|
+
whether in tort (including negligence), contract, or otherwise,
|
|
171
|
+
unless required by applicable law (such as deliberate and grossly
|
|
172
|
+
negligent acts) or agreed to in writing, shall any Contributor be
|
|
173
|
+
liable to You for damages, including any direct, indirect, special,
|
|
174
|
+
incidental, or consequential damages of any character arising as a
|
|
175
|
+
result of this License or out of the use or inability to use the
|
|
176
|
+
Work (including but not limited to damages for loss of goodwill,
|
|
177
|
+
work stoppage, computer failure or malfunction, or any and all
|
|
178
|
+
other commercial damages or losses), even if such Contributor
|
|
179
|
+
has been advised of the possibility of such damages.
|
|
180
|
+
|
|
181
|
+
9. Accepting Warranty or Additional Liability. While redistributing
|
|
182
|
+
the Work or Derivative Works thereof, You may choose to offer,
|
|
183
|
+
and charge a fee for, acceptance of support, warranty, indemnity,
|
|
184
|
+
or other liability obligations and/or rights consistent with this
|
|
185
|
+
License. However, in accepting such obligations, You may act only
|
|
186
|
+
on Your own behalf and on Your sole responsibility, not on behalf
|
|
187
|
+
of any other Contributor, and only if You agree to indemnify,
|
|
188
|
+
defend, and hold each Contributor harmless for any liability
|
|
189
|
+
incurred by, or claims asserted against, such Contributor by reason
|
|
190
|
+
of your accepting any such warranty or additional liability.
|
|
191
|
+
|
|
192
|
+
END OF TERMS AND CONDITIONS
|
package/README.md
CHANGED
|
@@ -1,3 +1,78 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @embedpdf/viewer-svelte
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
The full EmbedPDF viewer as a Svelte 5 component: a thin wrapper around the
|
|
4
|
+
`<embedpdf-viewer>` custom element from [`@embedpdf/viewer`](https://www.npmjs.com/package/@embedpdf/viewer).
|
|
5
|
+
|
|
6
|
+
```bash
|
|
7
|
+
npm install @embedpdf/viewer-svelte
|
|
8
|
+
```
|
|
9
|
+
|
|
10
|
+
```svelte
|
|
11
|
+
<script lang="ts">
|
|
12
|
+
import { PDFViewer } from '@embedpdf/viewer-svelte';
|
|
13
|
+
</script>
|
|
14
|
+
|
|
15
|
+
<PDFViewer src="/report.pdf" style="height: 100vh" />
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
No engine or wasm configuration is needed: the built-in local engine (PDFium
|
|
19
|
+
compiled to WebAssembly, running in a Web Worker) is the default, and your
|
|
20
|
+
bundler ships `embedpdf.wasm` inside your own build. Self-hosting, strict-CSP
|
|
21
|
+
worker delivery, and injecting a different engine implementation are all
|
|
22
|
+
available through the `engine` prop — see the docs.
|
|
23
|
+
|
|
24
|
+
## Your own components
|
|
25
|
+
|
|
26
|
+
`ToolbarItem`, `Panel` and `Region` put your component in the place the layout
|
|
27
|
+
makes for it, while it stays in **your** Svelte app — your context, your
|
|
28
|
+
state, your CSS:
|
|
29
|
+
|
|
30
|
+
```svelte
|
|
31
|
+
<script lang="ts">
|
|
32
|
+
import { PDFViewer, ToolbarItem, type Layout } from '@embedpdf/viewer-svelte';
|
|
33
|
+
import DocPicker from './DocPicker.svelte';
|
|
34
|
+
|
|
35
|
+
const layout = (layout: Layout) => layout.add({ custom: 'doc-picker' }, { to: 'main', section: 'start' });
|
|
36
|
+
</script>
|
|
37
|
+
|
|
38
|
+
<PDFViewer src="/report.pdf" {layout}>
|
|
39
|
+
<ToolbarItem id="doc-picker"><DocPicker /></ToolbarItem>
|
|
40
|
+
</PDFViewer>
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
`useToolbarItem()` tells it where it sits, and `Button`, `Menu` and `MenuItem`
|
|
44
|
+
are the viewer's own, for yours.
|
|
45
|
+
|
|
46
|
+
## Driving the viewer
|
|
47
|
+
|
|
48
|
+
```svelte
|
|
49
|
+
<PDFViewer src="/report.pdf" onready={(viewer) => viewer.commands.execute('zoom:in')} />
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
`onready` hands you the `viewer`: each plugin's API by its name
|
|
53
|
+
(`viewer.get('search')`, or its token), `viewer.documents`, `viewer.commands`
|
|
54
|
+
and the `watch` reactivity primitive. Inside the viewer, a component of yours
|
|
55
|
+
gets it with `usePDFViewer()`, and `useCommand(id)` gives it a command as a
|
|
56
|
+
button needs it (label, icon, shortcut, state and `run()`). The headless
|
|
57
|
+
functions of `@embedpdf/svelte` work there too. `ondocumentchange` and
|
|
58
|
+
`onpanelresize` say when the active document changes and when someone resizes
|
|
59
|
+
a panel. When the viewer unmounts, its `viewer` is done: its listeners stop and
|
|
60
|
+
its calls refuse.
|
|
61
|
+
|
|
62
|
+
`src`, `theme`, `locale`, `disable` and `readOnly` follow their props while the
|
|
63
|
+
viewer runs; every other prop is read once, so wrap the viewer in `{#key}` to
|
|
64
|
+
start over with a changed one.
|
|
65
|
+
|
|
66
|
+
## Server-side rendering
|
|
67
|
+
|
|
68
|
+
Safe to import and render on the server (SvelteKit included, no
|
|
69
|
+
`{#if browser}`): `<PDFViewer>` renders the empty `<embedpdf-viewer>` element,
|
|
70
|
+
and the viewer starts in the browser.
|
|
71
|
+
|
|
72
|
+
## Documentation
|
|
73
|
+
|
|
74
|
+
Full guides and API reference: https://www.embedpdf.com/docs
|
|
75
|
+
|
|
76
|
+
## License
|
|
77
|
+
|
|
78
|
+
Apache-2.0
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
<PDFViewer>: the Svelte face of the <embedpdf-viewer> custom element, and engine-blind: it
|
|
3
|
+
imports the viewer for types only, so it holds no runtime edge to any engine. Which engine ships
|
|
4
|
+
is decided by the entry that re-exports it: `./index.ts` imports `@embedpdf/viewer` (the built-in
|
|
5
|
+
local engine, registered as the default), `./core.ts` imports `@embedpdf/viewer/core` (no default
|
|
6
|
+
engine: you give one, and PDFium is not in the module graph). That one import is the whole
|
|
7
|
+
difference between the two doors, and it lives only in those two files.
|
|
8
|
+
|
|
9
|
+
It renders the element and gives it the config before the element's deferred first mount, so the
|
|
10
|
+
viewer starts once, with it. `src`, `theme`, `locale`, `disable` and `readOnly` follow their
|
|
11
|
+
props: the element changes them in place, comparing by value. Every other prop is read once: a
|
|
12
|
+
later change is ignored (development warns), and a `{#key}` block mounts a new viewer with it.
|
|
13
|
+
`onready`, `ondocumentchange` and `onpanelresize` call the latest prop.
|
|
14
|
+
|
|
15
|
+
Your components are its children, light DOM the browser shows in the viewer's slots while they
|
|
16
|
+
stay in your Svelte tree, with your context, state and CSS. They mount once the viewer is ready,
|
|
17
|
+
inside its kernel, so the headless functions of `@embedpdf/svelte` in them read this viewer, and
|
|
18
|
+
`usePDFViewer()` gives them its `viewer`. When the viewer goes they unmount with it, and mount
|
|
19
|
+
again inside the next one. On the server it renders the empty element, and the viewer starts in
|
|
20
|
+
the browser.
|
|
21
|
+
-->
|
|
22
|
+
<script lang="ts">
|
|
23
|
+
import { DEV } from 'esm-env';
|
|
24
|
+
import { untrack } from 'svelte';
|
|
25
|
+
// The headless runtime carries no engine: it gives the children the viewer's kernel.
|
|
26
|
+
import { KernelProvider, type Kernel } from '@embedpdf/svelte/runtime';
|
|
27
|
+
// `ElementConfig` is the kernel's config, the widest of all, with the engine seam `unknown`: the
|
|
28
|
+
// shared implementation sits above every door, and each entry narrows it by assignment.
|
|
29
|
+
import type { ElementConfig, Viewer } from '@embedpdf/viewer/core';
|
|
30
|
+
import { changedReadOnceKey, liveConfigOf, readOnceWarning } from './config.js';
|
|
31
|
+
import { setViewerContext } from './hooks.js';
|
|
32
|
+
import type { PDFViewerImplProps } from './props.js';
|
|
33
|
+
|
|
34
|
+
let {
|
|
35
|
+
class: className,
|
|
36
|
+
style,
|
|
37
|
+
children,
|
|
38
|
+
element = $bindable(null),
|
|
39
|
+
onready,
|
|
40
|
+
ondocumentchange,
|
|
41
|
+
onpanelresize,
|
|
42
|
+
...config
|
|
43
|
+
}: PDFViewerImplProps = $props();
|
|
44
|
+
|
|
45
|
+
// The viewer once it's ready: the children render inside its kernel. Before that the viewer has
|
|
46
|
+
// no slots to show them in.
|
|
47
|
+
let viewer = $state.raw<Viewer | null>(null);
|
|
48
|
+
setViewerContext(() => viewer);
|
|
49
|
+
|
|
50
|
+
/** Where the viewer keeps its kernel (`createViewer` in `@embedpdf/viewer-chrome`). */
|
|
51
|
+
const VIEWER_KERNEL = Symbol.for('@embedpdf/viewer/kernel');
|
|
52
|
+
const kernel = $derived(
|
|
53
|
+
viewer ? ((viewer as unknown as Partial<Record<symbol, Kernel>>)[VIEWER_KERNEL] ?? null) : null,
|
|
54
|
+
);
|
|
55
|
+
|
|
56
|
+
// The config the viewer starts with: the props as they are now, read once. The callbacks reach
|
|
57
|
+
// the element once too, as functions that read the prop when they're called, so an inline
|
|
58
|
+
// handler that changes is the one called.
|
|
59
|
+
const initial = untrack(() => ({ ...config }));
|
|
60
|
+
const started: ElementConfig = {
|
|
61
|
+
...initial,
|
|
62
|
+
onReady: (ready) => {
|
|
63
|
+
viewer = ready;
|
|
64
|
+
onready?.(ready);
|
|
65
|
+
},
|
|
66
|
+
onDocumentChange: (documentId) => ondocumentchange?.(documentId),
|
|
67
|
+
onPanelResize: (event) => onpanelresize?.(event),
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
// The element's attributes and properties. Svelte sets a property while it creates the element,
|
|
71
|
+
// before the element is on the page, so `config` is there for its first mount; the live keys are
|
|
72
|
+
// set again when they change, and the element compares them by value (an inline array is no
|
|
73
|
+
// change). On the server there are no properties: it renders the empty element.
|
|
74
|
+
const attributes = $derived({
|
|
75
|
+
class: className,
|
|
76
|
+
style,
|
|
77
|
+
...(typeof document === 'undefined' ? {} : { config: started, ...liveConfigOf(config) }),
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
// Every other prop is read once: say so, once, when one changes after the viewer started.
|
|
81
|
+
// A development build, as Svelte itself reads it.
|
|
82
|
+
if (DEV) {
|
|
83
|
+
let warned = false;
|
|
84
|
+
$effect(() => {
|
|
85
|
+
if (warned) return;
|
|
86
|
+
const key = changedReadOnceKey(initial, { ...config });
|
|
87
|
+
if (key === null) return;
|
|
88
|
+
warned = true;
|
|
89
|
+
console.warn(readOnceWarning(key));
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
</script>
|
|
93
|
+
|
|
94
|
+
<!-- The element waits for the config set here, also over the server's HTML before hydration. -->
|
|
95
|
+
<embedpdf-viewer bind:this={element} data-epdf-wrapped="" {...attributes}>
|
|
96
|
+
{#if viewer}
|
|
97
|
+
{#key viewer}
|
|
98
|
+
{#if kernel}
|
|
99
|
+
<KernelProvider {kernel}>{@render children?.()}</KernelProvider>
|
|
100
|
+
{:else}
|
|
101
|
+
{@render children?.()}
|
|
102
|
+
{/if}
|
|
103
|
+
{/key}
|
|
104
|
+
{/if}
|
|
105
|
+
</embedpdf-viewer>
|
package/dist/config.d.ts
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* How `<PDFViewer>` treats its config: the live keys it hands on to the running viewer, and the
|
|
3
|
+
* rest, read once, with a warning in development when one changes.
|
|
4
|
+
*/
|
|
5
|
+
import type { LiveConfigKey } from '@embedpdf/viewer/core';
|
|
6
|
+
/** The live keys of `config`, as the element's properties of the same names take them. */
|
|
7
|
+
export declare function liveConfigOf(config: Partial<Record<LiveConfigKey, unknown>>): {
|
|
8
|
+
src: unknown;
|
|
9
|
+
theme: unknown;
|
|
10
|
+
locale: unknown;
|
|
11
|
+
disable: unknown;
|
|
12
|
+
readOnly: unknown;
|
|
13
|
+
};
|
|
14
|
+
/**
|
|
15
|
+
* The first key, other than the live ones, whose value isn't the one the viewer started with, or
|
|
16
|
+
* null. By identity: Svelte keeps a prop's value until what it's made from changes.
|
|
17
|
+
*/
|
|
18
|
+
export declare function changedReadOnceKey(started: object, current: object): string | null;
|
|
19
|
+
/** What development says when a read-once key changes. */
|
|
20
|
+
export declare const readOnceWarning: (key: string) => string;
|
package/dist/config.js
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/** The keys the element follows while it runs: `<PDFViewer>` hands their changes on. */
|
|
2
|
+
const LIVE_KEYS = {
|
|
3
|
+
src: true,
|
|
4
|
+
theme: true,
|
|
5
|
+
locale: true,
|
|
6
|
+
disable: true,
|
|
7
|
+
readOnly: true,
|
|
8
|
+
};
|
|
9
|
+
/** The live keys of `config`, as the element's properties of the same names take them. */
|
|
10
|
+
export function liveConfigOf(config) {
|
|
11
|
+
return {
|
|
12
|
+
src: config.src,
|
|
13
|
+
theme: config.theme,
|
|
14
|
+
locale: config.locale,
|
|
15
|
+
disable: config.disable,
|
|
16
|
+
readOnly: config.readOnly,
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* The first key, other than the live ones, whose value isn't the one the viewer started with, or
|
|
21
|
+
* null. By identity: Svelte keeps a prop's value until what it's made from changes.
|
|
22
|
+
*/
|
|
23
|
+
export function changedReadOnceKey(started, current) {
|
|
24
|
+
const before = started;
|
|
25
|
+
const after = current;
|
|
26
|
+
for (const key of new Set([...Object.keys(before), ...Object.keys(after)])) {
|
|
27
|
+
if (key in LIVE_KEYS)
|
|
28
|
+
continue;
|
|
29
|
+
if (!Object.is(before[key], after[key]))
|
|
30
|
+
return key;
|
|
31
|
+
}
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
/** What development says when a read-once key changes. */
|
|
35
|
+
export const readOnceWarning = (key) => `[embedpdf] <PDFViewer>: \`${key}\` is read once, when the viewer starts, so the change is ` +
|
|
36
|
+
'ignored. Pass a stable value (module scope, or $state that keeps it), or put the viewer in a ' +
|
|
37
|
+
'{#key} block to mount a new one with it.';
|
package/dist/core.d.ts
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @embedpdf/viewer-svelte/core — the engine-agnostic Svelte door.
|
|
3
|
+
*
|
|
4
|
+
* ```svelte
|
|
5
|
+
* <script lang="ts">
|
|
6
|
+
* import { PDFViewer } from '@embedpdf/viewer-svelte/core';
|
|
7
|
+
* import { cloudEngine } from '@cloudpdf/engine';
|
|
8
|
+
* </script>
|
|
9
|
+
*
|
|
10
|
+
* <PDFViewer engine={() => cloudEngine({ baseUrl, token })} {documents} />
|
|
11
|
+
* ```
|
|
12
|
+
*
|
|
13
|
+
* The same `<PDFViewer>` as the main entry, but with no default engine: `engine` is required, and
|
|
14
|
+
* takes only a real implementation (an `Engine`, or a function the viewer owns the lifetime of).
|
|
15
|
+
* In exchange the local PDFium engine (wasm, worker source, main-thread recipe) never enters your
|
|
16
|
+
* module graph: nothing is stubbed or aliased away, it is not imported. Its options are absent
|
|
17
|
+
* from the type for the same reason: there is no built-in engine here for `{ assetsUrl }` to
|
|
18
|
+
* configure.
|
|
19
|
+
*
|
|
20
|
+
* App code that wants the batteries-included viewer imports `@embedpdf/viewer-svelte`; this entry
|
|
21
|
+
* is for builds that always give their own engine, and is what the CloudPDF Svelte wrapper is
|
|
22
|
+
* built on.
|
|
23
|
+
*/
|
|
24
|
+
import '@embedpdf/viewer/core';
|
|
25
|
+
import type { ViewerConfig } from '@embedpdf/viewer/core';
|
|
26
|
+
import type { Component } from 'svelte';
|
|
27
|
+
import { type PDFViewerPropsOf } from './parts.js';
|
|
28
|
+
export * from '@embedpdf/viewer/core';
|
|
29
|
+
export * from './parts.js';
|
|
30
|
+
/** Props on this door: `engine` is required, and only a real engine satisfies it. */
|
|
31
|
+
export type PDFViewerProps = PDFViewerPropsOf<ViewerConfig>;
|
|
32
|
+
/** The shared component, narrowed to this door's config: the wider props type assigns to it. */
|
|
33
|
+
export declare const PDFViewer: Component<PDFViewerProps, {}, 'element'>;
|
package/dist/core.js
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @embedpdf/viewer-svelte/core — the engine-agnostic Svelte door.
|
|
3
|
+
*
|
|
4
|
+
* ```svelte
|
|
5
|
+
* <script lang="ts">
|
|
6
|
+
* import { PDFViewer } from '@embedpdf/viewer-svelte/core';
|
|
7
|
+
* import { cloudEngine } from '@cloudpdf/engine';
|
|
8
|
+
* </script>
|
|
9
|
+
*
|
|
10
|
+
* <PDFViewer engine={() => cloudEngine({ baseUrl, token })} {documents} />
|
|
11
|
+
* ```
|
|
12
|
+
*
|
|
13
|
+
* The same `<PDFViewer>` as the main entry, but with no default engine: `engine` is required, and
|
|
14
|
+
* takes only a real implementation (an `Engine`, or a function the viewer owns the lifetime of).
|
|
15
|
+
* In exchange the local PDFium engine (wasm, worker source, main-thread recipe) never enters your
|
|
16
|
+
* module graph: nothing is stubbed or aliased away, it is not imported. Its options are absent
|
|
17
|
+
* from the type for the same reason: there is no built-in engine here for `{ assetsUrl }` to
|
|
18
|
+
* configure.
|
|
19
|
+
*
|
|
20
|
+
* App code that wants the batteries-included viewer imports `@embedpdf/viewer-svelte`; this entry
|
|
21
|
+
* is for builds that always give their own engine, and is what the CloudPDF Svelte wrapper is
|
|
22
|
+
* built on.
|
|
23
|
+
*/
|
|
24
|
+
import '@embedpdf/viewer/core';
|
|
25
|
+
import { PDFViewer as PDFViewerImpl } from './parts.js';
|
|
26
|
+
export * from '@embedpdf/viewer/core';
|
|
27
|
+
export * from './parts.js';
|
|
28
|
+
/** The shared component, narrowed to this door's config: the wider props type assigns to it. */
|
|
29
|
+
export const PDFViewer = PDFViewerImpl;
|
package/dist/hooks.d.ts
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The functions for components inside `<PDFViewer>`: the `viewer`, and a command as a button
|
|
3
|
+
* needs it. They work in the components you give the viewer, which mount once it's live, inside
|
|
4
|
+
* it. The headless functions of `@embedpdf/svelte` work there too, for any plugin's API and state.
|
|
5
|
+
*/
|
|
6
|
+
import type { Viewer } from '@embedpdf/viewer/core';
|
|
7
|
+
/**
|
|
8
|
+
* A command as a button needs it, for the active document, as `{ current }`: its `label`, `icon`
|
|
9
|
+
* and `shortcut`, whether it's `enabled`, `active` and `visible`, and `run()`. The headless
|
|
10
|
+
* function, which reads the kernel `<PDFViewer>` gives its children: an id the viewer doesn't
|
|
11
|
+
* have is neither visible nor enabled.
|
|
12
|
+
*/
|
|
13
|
+
export { useCommand } from '@embedpdf/svelte/commands';
|
|
14
|
+
/** For `<PDFViewer>`: give the components inside it the viewer `current()` says is live. */
|
|
15
|
+
export declare function setViewerContext(current: () => Viewer | null): void;
|
|
16
|
+
/**
|
|
17
|
+
* The `viewer`, in a component inside `<PDFViewer>`: `get(name)`, `documents`, `commands`,
|
|
18
|
+
* `watch` and `forDocument`. Your components mount once the viewer is live, so it is always
|
|
19
|
+
* there. Outside the viewer, keep the one `onready` gives you; called there, this throws.
|
|
20
|
+
*/
|
|
21
|
+
export declare function usePDFViewer(): Viewer;
|
package/dist/hooks.js
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { getContext, hasContext, setContext, untrack } from 'svelte';
|
|
2
|
+
/**
|
|
3
|
+
* A command as a button needs it, for the active document, as `{ current }`: its `label`, `icon`
|
|
4
|
+
* and `shortcut`, whether it's `enabled`, `active` and `visible`, and `run()`. The headless
|
|
5
|
+
* function, which reads the kernel `<PDFViewer>` gives its children: an id the viewer doesn't
|
|
6
|
+
* have is neither visible nor enabled.
|
|
7
|
+
*/
|
|
8
|
+
export { useCommand } from '@embedpdf/svelte/commands';
|
|
9
|
+
const VIEWER = Symbol('embedpdf.pdf-viewer');
|
|
10
|
+
/** For `<PDFViewer>`: give the components inside it the viewer `current()` says is live. */
|
|
11
|
+
export function setViewerContext(current) {
|
|
12
|
+
setContext(VIEWER, current);
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* The `viewer`, in a component inside `<PDFViewer>`: `get(name)`, `documents`, `commands`,
|
|
16
|
+
* `watch` and `forDocument`. Your components mount once the viewer is live, so it is always
|
|
17
|
+
* there. Outside the viewer, keep the one `onready` gives you; called there, this throws.
|
|
18
|
+
*/
|
|
19
|
+
export function usePDFViewer() {
|
|
20
|
+
// Untracked: the components inside mount again for a new viewer, so this one never changes.
|
|
21
|
+
const viewer = hasContext(VIEWER) ? untrack(getContext(VIEWER)) : null;
|
|
22
|
+
if (!viewer) {
|
|
23
|
+
throw new Error('[embedpdf] usePDFViewer() works in a component inside <PDFViewer>. Outside the viewer, ' +
|
|
24
|
+
'keep the viewer its onready gives you.');
|
|
25
|
+
}
|
|
26
|
+
return viewer;
|
|
27
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @embedpdf/viewer-svelte — the full viewer as a Svelte component, with the built-in local engine
|
|
3
|
+
* (PDFium wasm in a worker) as the default.
|
|
4
|
+
*
|
|
5
|
+
* ```svelte
|
|
6
|
+
* <script lang="ts">
|
|
7
|
+
* import { PDFViewer } from '@embedpdf/viewer-svelte';
|
|
8
|
+
* </script>
|
|
9
|
+
*
|
|
10
|
+
* <PDFViewer src="/report.pdf" style="height: 100vh" />
|
|
11
|
+
* ```
|
|
12
|
+
*
|
|
13
|
+
* Zero config: no `engine` prop, no bundler setup. This is the door app code should use. Builds
|
|
14
|
+
* that always give their own engine (a cloud deployment, a self-hosted `@cloudpdf/server`) import
|
|
15
|
+
* `@embedpdf/viewer-svelte/core` instead, which is the same component with PDFium absent.
|
|
16
|
+
*
|
|
17
|
+
* The component itself lives in `./component.svelte` and is engine-blind; the one import below is
|
|
18
|
+
* what makes this entry the local-engine door.
|
|
19
|
+
*/
|
|
20
|
+
import '@embedpdf/viewer';
|
|
21
|
+
import type { ViewerConfig } from '@embedpdf/viewer';
|
|
22
|
+
import type { Component } from 'svelte';
|
|
23
|
+
import { type PDFViewerPropsOf } from './parts.js';
|
|
24
|
+
export * from '@embedpdf/viewer';
|
|
25
|
+
export * from './parts.js';
|
|
26
|
+
export type PDFViewerProps = PDFViewerPropsOf<ViewerConfig>;
|
|
27
|
+
/** The shared component, narrowed to this door's config: the wider props type assigns to it. */
|
|
28
|
+
export declare const PDFViewer: Component<PDFViewerProps, {}, 'element'>;
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @embedpdf/viewer-svelte — the full viewer as a Svelte component, with the built-in local engine
|
|
3
|
+
* (PDFium wasm in a worker) as the default.
|
|
4
|
+
*
|
|
5
|
+
* ```svelte
|
|
6
|
+
* <script lang="ts">
|
|
7
|
+
* import { PDFViewer } from '@embedpdf/viewer-svelte';
|
|
8
|
+
* </script>
|
|
9
|
+
*
|
|
10
|
+
* <PDFViewer src="/report.pdf" style="height: 100vh" />
|
|
11
|
+
* ```
|
|
12
|
+
*
|
|
13
|
+
* Zero config: no `engine` prop, no bundler setup. This is the door app code should use. Builds
|
|
14
|
+
* that always give their own engine (a cloud deployment, a self-hosted `@cloudpdf/server`) import
|
|
15
|
+
* `@embedpdf/viewer-svelte/core` instead, which is the same component with PDFium absent.
|
|
16
|
+
*
|
|
17
|
+
* The component itself lives in `./component.svelte` and is engine-blind; the one import below is
|
|
18
|
+
* what makes this entry the local-engine door.
|
|
19
|
+
*/
|
|
20
|
+
import '@embedpdf/viewer';
|
|
21
|
+
import { PDFViewer as PDFViewerImpl } from './parts.js';
|
|
22
|
+
export * from '@embedpdf/viewer';
|
|
23
|
+
export * from './parts.js';
|
|
24
|
+
/** The shared component, narrowed to this door's config: the wider props type assigns to it. */
|
|
25
|
+
export const PDFViewer = PDFViewerImpl;
|
package/dist/parts.d.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @embedpdf/viewer-svelte/component — the wrapper's door-blind parts: `<PDFViewer>` at the widest
|
|
3
|
+
* config (the kernel's `ElementConfig`), and the components and hooks every door shares.
|
|
4
|
+
*
|
|
5
|
+
* A door imports its engine's door, re-exports these, and narrows `<PDFViewer>` to its own
|
|
6
|
+
* `ViewerConfig` by assignment: this package's two (`.` with the built-in engine, `./core` with
|
|
7
|
+
* none) and `@cloudpdf/viewer-svelte` with the cloud engine (docs/conventions/viewer-doors.md).
|
|
8
|
+
*/
|
|
9
|
+
export { default as PDFViewer } from './component.svelte';
|
|
10
|
+
export type { PDFViewerExtras, PDFViewerPropsOf } from './props.js';
|
|
11
|
+
export { useCommand, usePDFViewer } from './hooks.js';
|
|
12
|
+
export { Panel, Region, ToolbarItem, useToolbarItem } from './slots.js';
|
|
13
|
+
export type { PanelProps, RegionName, RegionProps, ToolbarItemProps } from './slots.js';
|
|
14
|
+
export { Button, Menu, MenuItem } from './viewer-components.js';
|
|
15
|
+
export type { ButtonProps, MenuItemProps, MenuProps } from './viewer-components.js';
|
package/dist/parts.js
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @embedpdf/viewer-svelte/component — the wrapper's door-blind parts: `<PDFViewer>` at the widest
|
|
3
|
+
* config (the kernel's `ElementConfig`), and the components and hooks every door shares.
|
|
4
|
+
*
|
|
5
|
+
* A door imports its engine's door, re-exports these, and narrows `<PDFViewer>` to its own
|
|
6
|
+
* `ViewerConfig` by assignment: this package's two (`.` with the built-in engine, `./core` with
|
|
7
|
+
* none) and `@cloudpdf/viewer-svelte` with the cloud engine (docs/conventions/viewer-doors.md).
|
|
8
|
+
*/
|
|
9
|
+
export { default as PDFViewer } from './component.svelte';
|
|
10
|
+
export { useCommand, usePDFViewer } from './hooks.js';
|
|
11
|
+
export { Panel, Region, ToolbarItem, useToolbarItem } from './slots.js';
|
|
12
|
+
export { Button, Menu, MenuItem } from './viewer-components.js';
|
package/dist/props.d.ts
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The props of `<PDFViewer>`: every key of the viewer's config, its callbacks under their Svelte
|
|
3
|
+
* names, and what places it on your page. Types only from the viewer, as in `./component.svelte`:
|
|
4
|
+
* this module is shared by both doors.
|
|
5
|
+
*/
|
|
6
|
+
import type { ElementConfig, EmbedPdfViewerElement, PanelResizeEvent, Viewer } from '@embedpdf/viewer/core';
|
|
7
|
+
import type { Snippet } from 'svelte';
|
|
8
|
+
/** The framework-side props: everything that is not viewer config. */
|
|
9
|
+
export interface PDFViewerExtras {
|
|
10
|
+
class?: string;
|
|
11
|
+
style?: string;
|
|
12
|
+
/** Your components, shown where their slot says: `<ToolbarItem>`, `<Panel>`, `<Region>`. */
|
|
13
|
+
children?: Snippet;
|
|
14
|
+
/** The `<embedpdf-viewer>` element, for its imperative surface: `bind:element`. */
|
|
15
|
+
element?: EmbedPdfViewerElement | null;
|
|
16
|
+
/**
|
|
17
|
+
* Called with the `viewer` once it's live, before any document has opened: capabilities
|
|
18
|
+
* resolve from then on, and document-scoped calls wait for their document.
|
|
19
|
+
*/
|
|
20
|
+
onready?: (viewer: Viewer) => void;
|
|
21
|
+
/**
|
|
22
|
+
* Called with the active document's id whenever it changes: the first document that opens, a
|
|
23
|
+
* switch to another tab, and `null` once the last one closes.
|
|
24
|
+
*/
|
|
25
|
+
ondocumentchange?: (documentId: string | null) => void;
|
|
26
|
+
/**
|
|
27
|
+
* Called when the user has resized a panel, with its id and new width in px: once a drag of its
|
|
28
|
+
* edge ends, and on each arrow key there.
|
|
29
|
+
*/
|
|
30
|
+
onpanelresize?: (event: PanelResizeEvent) => void;
|
|
31
|
+
}
|
|
32
|
+
/** The config's callbacks, which `<PDFViewer>` takes under their Svelte names. */
|
|
33
|
+
type ConfigCallback = 'onReady' | 'onDocumentChange' | 'onPanelResize';
|
|
34
|
+
/**
|
|
35
|
+
* The props of `<PDFViewer>` for a door whose config is `Config`: each key of the config is a prop,
|
|
36
|
+
* except its callbacks, which are `onready`, `ondocumentchange` and `onpanelresize` here, with
|
|
37
|
+
* `class`, `style`, `children` and `element` next to them.
|
|
38
|
+
*/
|
|
39
|
+
export type PDFViewerPropsOf<Config extends object> = Omit<Config, ConfigCallback> & PDFViewerExtras;
|
|
40
|
+
/**
|
|
41
|
+
* Props for the shared implementation. Each entry re-exports `<PDFViewer>` narrowed to its door's
|
|
42
|
+
* config: `PDFViewerProps` in `./index.ts` (engine optional) and `./core.ts` (engine required).
|
|
43
|
+
*/
|
|
44
|
+
export type PDFViewerImplProps = PDFViewerPropsOf<ElementConfig>;
|
|
45
|
+
export {};
|
package/dist/props.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
What goes inside one of your panels, the one `addPanel(id, …)` adds to the layout: the viewer
|
|
3
|
+
draws its frame, its header and the way it opens. It renders as a child of the <embedpdf-viewer>
|
|
4
|
+
element with the slot `panel:<id>`, in an element that adds no box.
|
|
5
|
+
-->
|
|
6
|
+
<script lang="ts">
|
|
7
|
+
import type { PanelProps } from './props.js';
|
|
8
|
+
|
|
9
|
+
let { id, children }: PanelProps = $props();
|
|
10
|
+
|
|
11
|
+
// Spread, `slot` is the element's attribute: written out, Svelte reads it as a slot of its own.
|
|
12
|
+
const slot = $derived({ slot: `panel:${id}` });
|
|
13
|
+
</script>
|
|
14
|
+
|
|
15
|
+
<div {...slot} style="display: contents">{@render children?.()}</div>
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
Your component in one of the viewer's regions: `header`, `footer`, `tabs` or `empty`. It renders
|
|
3
|
+
as a child of the <embedpdf-viewer> element with the region's name for its slot, in an element
|
|
4
|
+
that adds no box.
|
|
5
|
+
-->
|
|
6
|
+
<script lang="ts">
|
|
7
|
+
import type { RegionProps } from './props.js';
|
|
8
|
+
|
|
9
|
+
let { name, children }: RegionProps = $props();
|
|
10
|
+
|
|
11
|
+
// Spread, `slot` is the element's attribute: written out, Svelte reads it as a slot of its own.
|
|
12
|
+
const slot = $derived({ slot: name });
|
|
13
|
+
</script>
|
|
14
|
+
|
|
15
|
+
<div {...slot} style="display: contents">{@render children?.()}</div>
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
Your component in the toolbar, where the layout's `{ custom: id }` makes room for it. It renders
|
|
3
|
+
as a child of the <embedpdf-viewer> element with the slot `item:<id>`, in an element that adds
|
|
4
|
+
no box, so it stays in your Svelte tree and lays out in the bar. `useToolbarItem()` inside it
|
|
5
|
+
says where the item sits.
|
|
6
|
+
-->
|
|
7
|
+
<script lang="ts">
|
|
8
|
+
import { untrack } from 'svelte';
|
|
9
|
+
import { provideToolbarItem } from './item-context.svelte.js';
|
|
10
|
+
import type { ToolbarItemProps } from './props.js';
|
|
11
|
+
|
|
12
|
+
let { id, children }: ToolbarItemProps = $props();
|
|
13
|
+
|
|
14
|
+
const place = provideToolbarItem();
|
|
15
|
+
let element: HTMLDivElement | undefined = $state();
|
|
16
|
+
$effect(() => {
|
|
17
|
+
const target = element;
|
|
18
|
+
if (!target) return;
|
|
19
|
+
// Reading the attributes writes the context, which this effect must not depend on.
|
|
20
|
+
return untrack(() => place.follow(target));
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
// Spread, `slot` is the element's attribute: written out, Svelte reads it as a slot of its own.
|
|
24
|
+
const slot = $derived({ slot: `item:${id}` });
|
|
25
|
+
</script>
|
|
26
|
+
|
|
27
|
+
<div bind:this={element} {...slot} style="display: contents">{@render children?.()}</div>
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Where a toolbar item sits, as the viewer tells it. The viewer keeps that on the item's element as
|
|
3
|
+
* `data-orientation`, `data-edge`, `data-variant` and `data-size`; `ToolbarItem` reads them, live,
|
|
4
|
+
* into a reactive object for `useToolbarItem()`. A change reaches your component in the microtasks
|
|
5
|
+
* after the viewer writes it, before the browser lays the bar out, so the toolbar measures your
|
|
6
|
+
* component in the variant it picked.
|
|
7
|
+
*/
|
|
8
|
+
import type { ToolbarItemContext, ToolbarItemEdge, ViewerSize } from '@embedpdf/viewer/core';
|
|
9
|
+
/** An item's context as a reactive object: each field reads the latest the viewer wrote. */
|
|
10
|
+
export declare class ToolbarItemPlace implements ToolbarItemContext {
|
|
11
|
+
#private;
|
|
12
|
+
get orientation(): ToolbarItemContext['orientation'];
|
|
13
|
+
get edge(): ToolbarItemEdge;
|
|
14
|
+
get variant(): string | null;
|
|
15
|
+
get size(): ViewerSize;
|
|
16
|
+
/** Read what the viewer says on `element`, now and whenever it writes it. Returns the stop. */
|
|
17
|
+
follow(element: Element): () => void;
|
|
18
|
+
}
|
|
19
|
+
/** For `ToolbarItem`: the context of the item it makes, for the components inside it. */
|
|
20
|
+
export declare function provideToolbarItem(): ToolbarItemPlace;
|
|
21
|
+
/**
|
|
22
|
+
* Where the toolbar item this component is in sits, as a reactive object: which way its bar runs
|
|
23
|
+
* (`orientation`), the bar's place (`edge`), which of the item's `variants` the toolbar picked
|
|
24
|
+
* (`variant`), and the viewer's size (`size`). Read the fields where you use them
|
|
25
|
+
* (`item.orientation`), so they stay live. Works inside a `ToolbarItem`.
|
|
26
|
+
*/
|
|
27
|
+
export declare function useToolbarItem(): ToolbarItemContext;
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { getContext, hasContext, setContext } from 'svelte';
|
|
2
|
+
const EDGES = ['top', 'bottom', 'start', 'end', 'float', 'selection'];
|
|
3
|
+
const SIZES = ['compact', 'medium', 'expanded'];
|
|
4
|
+
/** Before the viewer says where the item is: the main bar's place. */
|
|
5
|
+
const AT_THE_TOP = {
|
|
6
|
+
orientation: 'horizontal',
|
|
7
|
+
edge: 'top',
|
|
8
|
+
variant: null,
|
|
9
|
+
size: 'expanded',
|
|
10
|
+
};
|
|
11
|
+
const ATTRIBUTES = ['data-orientation', 'data-edge', 'data-variant', 'data-size'];
|
|
12
|
+
/** Where the item whose element is `element` sits, as the viewer wrote it there. */
|
|
13
|
+
function toolbarItemContextOf(element) {
|
|
14
|
+
const edge = element.getAttribute('data-edge');
|
|
15
|
+
const size = element.getAttribute('data-size');
|
|
16
|
+
return {
|
|
17
|
+
orientation: element.getAttribute('data-orientation') === 'vertical' ? 'vertical' : 'horizontal',
|
|
18
|
+
edge: edge && EDGES.includes(edge) ? edge : AT_THE_TOP.edge,
|
|
19
|
+
variant: element.getAttribute('data-variant'),
|
|
20
|
+
size: size && SIZES.includes(size) ? size : AT_THE_TOP.size,
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
const sameContext = (left, right) => left.orientation === right.orientation &&
|
|
24
|
+
left.edge === right.edge &&
|
|
25
|
+
left.variant === right.variant &&
|
|
26
|
+
left.size === right.size;
|
|
27
|
+
/** An item's context as a reactive object: each field reads the latest the viewer wrote. */
|
|
28
|
+
export class ToolbarItemPlace {
|
|
29
|
+
#current = $state.raw(AT_THE_TOP);
|
|
30
|
+
get orientation() {
|
|
31
|
+
return this.#current.orientation;
|
|
32
|
+
}
|
|
33
|
+
get edge() {
|
|
34
|
+
return this.#current.edge;
|
|
35
|
+
}
|
|
36
|
+
get variant() {
|
|
37
|
+
return this.#current.variant;
|
|
38
|
+
}
|
|
39
|
+
get size() {
|
|
40
|
+
return this.#current.size;
|
|
41
|
+
}
|
|
42
|
+
/** Read what the viewer says on `element`, now and whenever it writes it. Returns the stop. */
|
|
43
|
+
follow(element) {
|
|
44
|
+
const read = () => {
|
|
45
|
+
const next = toolbarItemContextOf(element);
|
|
46
|
+
if (!sameContext(this.#current, next))
|
|
47
|
+
this.#current = next;
|
|
48
|
+
};
|
|
49
|
+
read();
|
|
50
|
+
const observer = new MutationObserver(read);
|
|
51
|
+
observer.observe(element, { attributes: true, attributeFilter: ATTRIBUTES });
|
|
52
|
+
return () => observer.disconnect();
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
const TOOLBAR_ITEM = Symbol('embedpdf.toolbar-item');
|
|
56
|
+
/** For `ToolbarItem`: the context of the item it makes, for the components inside it. */
|
|
57
|
+
export function provideToolbarItem() {
|
|
58
|
+
const place = new ToolbarItemPlace();
|
|
59
|
+
setContext(TOOLBAR_ITEM, place);
|
|
60
|
+
return place;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Where the toolbar item this component is in sits, as a reactive object: which way its bar runs
|
|
64
|
+
* (`orientation`), the bar's place (`edge`), which of the item's `variants` the toolbar picked
|
|
65
|
+
* (`variant`), and the viewer's size (`size`). Read the fields where you use them
|
|
66
|
+
* (`item.orientation`), so they stay live. Works inside a `ToolbarItem`.
|
|
67
|
+
*/
|
|
68
|
+
export function useToolbarItem() {
|
|
69
|
+
if (!hasContext(TOOLBAR_ITEM)) {
|
|
70
|
+
throw new Error('[embedpdf] useToolbarItem() works inside a <ToolbarItem>: it says where that item sits.');
|
|
71
|
+
}
|
|
72
|
+
return getContext(TOOLBAR_ITEM);
|
|
73
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/** The props of your own components' places in the viewer: `ToolbarItem`, `Panel`, `Region`. */
|
|
2
|
+
import type { Snippet } from 'svelte';
|
|
3
|
+
export interface ToolbarItemProps {
|
|
4
|
+
/** The item's id: `{ custom: id }` in the layout. */
|
|
5
|
+
id: string;
|
|
6
|
+
children?: Snippet;
|
|
7
|
+
}
|
|
8
|
+
export interface PanelProps {
|
|
9
|
+
/** The panel's id, as `addPanel(id, …)` names it in the layout. */
|
|
10
|
+
id: string;
|
|
11
|
+
children?: Snippet;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* A place around the viewer for your own components: `header` above everything, `footer` below
|
|
15
|
+
* everything, `tabs` in place of the document tabs, `empty` over the pages while no document is
|
|
16
|
+
* open.
|
|
17
|
+
*/
|
|
18
|
+
export type RegionName = 'header' | 'footer' | 'tabs' | 'empty';
|
|
19
|
+
export interface RegionProps {
|
|
20
|
+
name: RegionName;
|
|
21
|
+
children?: Snippet;
|
|
22
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/slots.d.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Your own components in the viewer: `ToolbarItem`, `Panel` and `Region` put your component in
|
|
3
|
+
* the place the viewer makes for it (`item:<id>`, `panel:<id>`, or a region's name). It renders as
|
|
4
|
+
* a child of the `<embedpdf-viewer>` element, so it stays in your Svelte tree, with your context,
|
|
5
|
+
* your state and your CSS; the browser shows it in that place. `useToolbarItem()` tells a
|
|
6
|
+
* component inside a `ToolbarItem` where it sits.
|
|
7
|
+
*/
|
|
8
|
+
export { default as ToolbarItem } from './slots/ToolbarItem.svelte';
|
|
9
|
+
export { default as Panel } from './slots/Panel.svelte';
|
|
10
|
+
export { default as Region } from './slots/Region.svelte';
|
|
11
|
+
export { useToolbarItem } from './slots/item-context.svelte.js';
|
|
12
|
+
export type { PanelProps, RegionName, RegionProps, ToolbarItemProps } from './slots/props.js';
|
package/dist/slots.js
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Your own components in the viewer: `ToolbarItem`, `Panel` and `Region` put your component in
|
|
3
|
+
* the place the viewer makes for it (`item:<id>`, `panel:<id>`, or a region's name). It renders as
|
|
4
|
+
* a child of the `<embedpdf-viewer>` element, so it stays in your Svelte tree, with your context,
|
|
5
|
+
* your state and your CSS; the browser shows it in that place. `useToolbarItem()` tells a
|
|
6
|
+
* component inside a `ToolbarItem` where it sits.
|
|
7
|
+
*/
|
|
8
|
+
export { default as ToolbarItem } from './slots/ToolbarItem.svelte';
|
|
9
|
+
export { default as Panel } from './slots/Panel.svelte';
|
|
10
|
+
export { default as Region } from './slots/Region.svelte';
|
|
11
|
+
export { useToolbarItem } from './slots/item-context.svelte.js';
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
A toolbar button, the viewer's own: `<epdf-button>`, which the viewer draws like the buttons in
|
|
3
|
+
its bars. `command` makes it that command's button; or give it an `icon`, a `label` and an
|
|
4
|
+
`onclick`. Use it inside the viewer: in a `ToolbarItem`, a `Panel` or a `Region`.
|
|
5
|
+
-->
|
|
6
|
+
<script lang="ts">
|
|
7
|
+
import type { ButtonProps } from './props.js';
|
|
8
|
+
|
|
9
|
+
let { command, icon, label, onclick, class: className, style }: ButtonProps = $props();
|
|
10
|
+
</script>
|
|
11
|
+
|
|
12
|
+
<!-- What's left out is null: the element writes its properties to attributes, `undefined` too.
|
|
13
|
+
It draws a real button in its own shadow root, which takes the keys and fires the click. -->
|
|
14
|
+
<!-- svelte-ignore a11y_click_events_have_key_events, a11y_no_static_element_interactions -->
|
|
15
|
+
<epdf-button
|
|
16
|
+
command={command ?? null}
|
|
17
|
+
icon={icon ?? null}
|
|
18
|
+
label={label ?? null}
|
|
19
|
+
{onclick}
|
|
20
|
+
class={className}
|
|
21
|
+
{style}
|
|
22
|
+
></epdf-button>
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
A button that opens a menu of `MenuItem`s, the viewer's own `<epdf-menu>`: it opens the way the
|
|
3
|
+
viewer's menus do, away from its bar, flipped when there's no room, and up from the bottom on a
|
|
4
|
+
phone.
|
|
5
|
+
-->
|
|
6
|
+
<script lang="ts">
|
|
7
|
+
import type { MenuProps } from './props.js';
|
|
8
|
+
|
|
9
|
+
let { icon, label, children }: MenuProps = $props();
|
|
10
|
+
</script>
|
|
11
|
+
|
|
12
|
+
<!-- What's left out is null: the element writes its properties to attributes, `undefined` too. -->
|
|
13
|
+
<epdf-menu icon={icon ?? null} {label}>{@render children?.()}</epdf-menu>
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
A row in a `Menu`, the viewer's own `<epdf-menu-item>`: its text is the row's. `command` makes it
|
|
3
|
+
that command's row; picking it runs the command, and calls `onselect` either way (the element's
|
|
4
|
+
`select` event).
|
|
5
|
+
-->
|
|
6
|
+
<script lang="ts">
|
|
7
|
+
import type { MenuItemProps } from './props.js';
|
|
8
|
+
|
|
9
|
+
let { command, icon, onselect, children }: MenuItemProps = $props();
|
|
10
|
+
</script>
|
|
11
|
+
|
|
12
|
+
<!-- What's left out is null: the element writes its properties to attributes, `undefined` too. -->
|
|
13
|
+
<epdf-menu-item command={command ?? null} icon={icon ?? null} {onselect}
|
|
14
|
+
>{@render children?.()}</epdf-menu-item
|
|
15
|
+
>
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/** The props of the viewer's own components for yours: `Button`, `Menu` and `MenuItem`. */
|
|
2
|
+
import type { Snippet } from 'svelte';
|
|
3
|
+
export interface ButtonProps {
|
|
4
|
+
/** Makes it this command's button: its icon, tooltip and state, and it runs it. */
|
|
5
|
+
command?: string;
|
|
6
|
+
/** An icon by name, the viewer's or yours, for a button of your own. */
|
|
7
|
+
icon?: string;
|
|
8
|
+
/** The name of a button of your own, for its tooltip and screen readers. */
|
|
9
|
+
label?: string;
|
|
10
|
+
onclick?: (event: MouseEvent) => void;
|
|
11
|
+
class?: string;
|
|
12
|
+
style?: string;
|
|
13
|
+
}
|
|
14
|
+
export interface MenuProps {
|
|
15
|
+
/** The icon of the button that opens it, by name. */
|
|
16
|
+
icon?: string;
|
|
17
|
+
/** Its name, for the button's tooltip and screen readers. */
|
|
18
|
+
label: string;
|
|
19
|
+
/** Its rows: `MenuItem`s. */
|
|
20
|
+
children?: Snippet;
|
|
21
|
+
}
|
|
22
|
+
export interface MenuItemProps {
|
|
23
|
+
/** Makes it this command's row: its icon, label and state, and picking it runs it. */
|
|
24
|
+
command?: string;
|
|
25
|
+
/** An icon by name, the viewer's or yours. */
|
|
26
|
+
icon?: string;
|
|
27
|
+
/** Called when the row is picked. */
|
|
28
|
+
onselect?: () => void;
|
|
29
|
+
/** Its text. */
|
|
30
|
+
children?: Snippet;
|
|
31
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The viewer's own components, for yours: `Button`, `Menu` and `MenuItem` render the viewer's
|
|
3
|
+
* `<epdf-button>`, `<epdf-menu>` and `<epdf-menu-item>` elements, which the viewer draws like its
|
|
4
|
+
* own toolbar buttons and menus. Use them inside the viewer: in a `ToolbarItem`, a `Panel` or a
|
|
5
|
+
* `Region`. The elements come with the viewer, so these import nothing from it.
|
|
6
|
+
*/
|
|
7
|
+
export { default as Button } from './viewer-components/Button.svelte';
|
|
8
|
+
export { default as Menu } from './viewer-components/Menu.svelte';
|
|
9
|
+
export { default as MenuItem } from './viewer-components/MenuItem.svelte';
|
|
10
|
+
export type { ButtonProps, MenuItemProps, MenuProps } from './viewer-components/props.js';
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The viewer's own components, for yours: `Button`, `Menu` and `MenuItem` render the viewer's
|
|
3
|
+
* `<epdf-button>`, `<epdf-menu>` and `<epdf-menu-item>` elements, which the viewer draws like its
|
|
4
|
+
* own toolbar buttons and menus. Use them inside the viewer: in a `ToolbarItem`, a `Panel` or a
|
|
5
|
+
* `Region`. The elements come with the viewer, so these import nothing from it.
|
|
6
|
+
*/
|
|
7
|
+
export { default as Button } from './viewer-components/Button.svelte';
|
|
8
|
+
export { default as Menu } from './viewer-components/Menu.svelte';
|
|
9
|
+
export { default as MenuItem } from './viewer-components/MenuItem.svelte';
|
package/package.json
CHANGED
|
@@ -1,6 +1,70 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@embedpdf/viewer-svelte",
|
|
3
|
-
"version": "
|
|
4
|
-
"
|
|
5
|
-
"description": "
|
|
3
|
+
"version": "3.0.0-next.16",
|
|
4
|
+
"private": false,
|
|
5
|
+
"description": "Svelte wrapper for the full viewer: the PDFViewer component around the embedpdf-viewer custom element. Your own components (ToolbarItem, Panel, Region) are light-DOM children the viewer shows in its slots while they stay in your Svelte tree (your context, your CSS). The viewer inside is the Preact-compiled artifact.",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"svelte": "./dist/index.js",
|
|
12
|
+
"default": "./dist/index.js"
|
|
13
|
+
},
|
|
14
|
+
"./core": {
|
|
15
|
+
"types": "./dist/core.d.ts",
|
|
16
|
+
"svelte": "./dist/core.js",
|
|
17
|
+
"default": "./dist/core.js"
|
|
18
|
+
},
|
|
19
|
+
"./component": {
|
|
20
|
+
"types": "./dist/parts.d.ts",
|
|
21
|
+
"svelte": "./dist/parts.js",
|
|
22
|
+
"default": "./dist/parts.js"
|
|
23
|
+
},
|
|
24
|
+
"./package.json": "./package.json"
|
|
25
|
+
},
|
|
26
|
+
"files": [
|
|
27
|
+
"dist"
|
|
28
|
+
],
|
|
29
|
+
"dependencies": {
|
|
30
|
+
"esm-env": "^1.2.2",
|
|
31
|
+
"@embedpdf/viewer": "3.0.0-next.16",
|
|
32
|
+
"@embedpdf/svelte": "3.0.0-next.16"
|
|
33
|
+
},
|
|
34
|
+
"peerDependencies": {
|
|
35
|
+
"svelte": "^5.29.0"
|
|
36
|
+
},
|
|
37
|
+
"devDependencies": {
|
|
38
|
+
"@sveltejs/package": "^2.5.0",
|
|
39
|
+
"@sveltejs/vite-plugin-svelte": "^6.0.0",
|
|
40
|
+
"@types/node": "^22.0.0",
|
|
41
|
+
"playwright-core": "^1.63.0",
|
|
42
|
+
"svelte": "^5.39.5",
|
|
43
|
+
"svelte-check": "^4.3.0",
|
|
44
|
+
"typescript": "~5.8.3",
|
|
45
|
+
"vite": "^6.3.5",
|
|
46
|
+
"vitest": "^2.1.9",
|
|
47
|
+
"@embedpdf/tooling-build": "0.0.0"
|
|
48
|
+
},
|
|
49
|
+
"license": "Apache-2.0",
|
|
50
|
+
"sideEffects": [
|
|
51
|
+
"./src/index.ts",
|
|
52
|
+
"./src/core.ts",
|
|
53
|
+
"./dist/index.js",
|
|
54
|
+
"./dist/core.js"
|
|
55
|
+
],
|
|
56
|
+
"publishConfig": {
|
|
57
|
+
"access": "public"
|
|
58
|
+
},
|
|
59
|
+
"repository": {
|
|
60
|
+
"type": "git",
|
|
61
|
+
"url": "git+https://github.com/embedpdf/embed-pdf-viewer.git",
|
|
62
|
+
"directory": "packages/viewer/svelte"
|
|
63
|
+
},
|
|
64
|
+
"scripts": {
|
|
65
|
+
"build": "svelte-package --input src --output dist && node ../../../tooling/build/src/fully-specify-imports.mjs && node ../../../tooling/build/src/check-framework-package.mjs",
|
|
66
|
+
"clean": "rimraf dist",
|
|
67
|
+
"typecheck": "svelte-check --tsconfig ./tsconfig.json --threshold error && svelte-check --tsconfig ./test/tsconfig.json --threshold error",
|
|
68
|
+
"test": "vitest run"
|
|
69
|
+
}
|
|
6
70
|
}
|