@inkbar/svelte 0.10.0 → 0.12.0
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/README.md +63 -13
- package/dist/components/InkBack.svelte +40 -0
- package/dist/components/InkBack.svelte.d.ts +5 -0
- package/dist/components/InkBack.svelte.d.ts.map +1 -0
- package/dist/components/InkForward.svelte +40 -0
- package/dist/components/InkForward.svelte.d.ts +5 -0
- package/dist/components/InkForward.svelte.d.ts.map +1 -0
- package/dist/components/InkOutline.svelte +170 -0
- package/dist/components/InkOutline.svelte.d.ts +19 -0
- package/dist/components/InkOutline.svelte.d.ts.map +1 -0
- package/dist/components/InkRoot.svelte +50 -13
- package/dist/components/InkRoot.svelte.d.ts +19 -6
- package/dist/components/InkRoot.svelte.d.ts.map +1 -1
- package/dist/components/InkSidebar.svelte +109 -3
- package/dist/components/InkSidebar.svelte.d.ts +24 -1
- package/dist/components/InkSidebar.svelte.d.ts.map +1 -1
- package/dist/components/InkViewport.svelte +25 -5
- package/dist/components/InkViewport.svelte.d.ts +2 -0
- package/dist/components/InkViewport.svelte.d.ts.map +1 -1
- package/dist/context.d.ts +21 -5
- package/dist/context.d.ts.map +1 -1
- package/dist/context.js +12 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -0
- package/package.json +5 -5
package/README.md
CHANGED
|
@@ -30,7 +30,7 @@ Annotations are plain typed objects. Bind them with `bind:annotations` to keep t
|
|
|
30
30
|
pnpm add @inkbar/svelte @inkbar/core @inkbar/theme
|
|
31
31
|
```
|
|
32
32
|
|
|
33
|
-
Svelte 5.46.4 or later
|
|
33
|
+
`@inkbar/core` (installed above, at the same version) and Svelte 5.46.4 or later are peer dependencies. Add the Vite plugin from `@inkbar/core/vite` so PDF.js can load its worker and fonts; the [`@inkbar/core` README](https://github.com/inkbar/inkbar/tree/main/packages/core#serving-pdfjs-assets) covers it. The package ships `.svelte` sources, so your bundler compiles them for your target, including the server.
|
|
34
34
|
|
|
35
35
|
## Usage
|
|
36
36
|
|
|
@@ -64,7 +64,7 @@ Svelte 5.46.4 or later is a peer dependency. Add the Vite plugin from `@inkbar/c
|
|
|
64
64
|
const tools = [highlightTool(), redactAreaTool(), noteTool()]
|
|
65
65
|
</script>
|
|
66
66
|
|
|
67
|
-
<InkRoot src={{ url: signedUrl, key: documentId }} {assets} {tools} bind:annotations>
|
|
67
|
+
<InkRoot src={{ url: signedUrl, key: documentId }} {assets} {tools} bind:annotations history={true}>
|
|
68
68
|
<InkToolbar>
|
|
69
69
|
<InkToolButton tool="select-text" />
|
|
70
70
|
<InkToolButton tool="select" />
|
|
@@ -89,9 +89,9 @@ Svelte 5.46.4 or later is a peer dependency. Add the Vite plugin from `@inkbar/c
|
|
|
89
89
|
</InkRoot>
|
|
90
90
|
```
|
|
91
91
|
|
|
92
|
-
`select-text` and `select` are always there. Every other tool is opt-in: pass the ones you offer through `tools`, from the tool factories in `@inkbar/core/dom`, or `allTools()` for all of them. A button for a tool you did not pass renders disabled and hidden,
|
|
92
|
+
`select-text` and `select` are always there. Every other tool is opt-in: pass the ones you offer through `tools`, from the tool factories in `@inkbar/core/dom`, or `allTools()` for all of them. A button for a tool you did not pass renders empty, disabled and `aria-hidden`, the highlight, shape and signature menus render nothing, and each logs an `InkError('UnknownTool')` to the console once. If the active tool leaves `tools`, the viewer switches to `select-text` and reports it through `onToolChange`.
|
|
93
93
|
|
|
94
|
-
Each toolbar control is a component, so keep the ones you need and put them where you like. Buttons show icons from `@inkbar/icons`. Pass your own as `children` to replace one, and use `InkIcon` to put the same icons on your own buttons. For a button of your own, such as `<InkToolbarButton onclick={download}>Download</InkToolbarButton>`, use `InkToolbarButton`: it joins the toolbar's arrow-key focus and looks like the other buttons. `bind:annotations` makes your list the source of truth. Leave
|
|
94
|
+
Each toolbar control is a component, so keep the ones you need and put them where you like. Buttons show icons from `@inkbar/icons`. Pass your own as `children` to replace one, and use `InkIcon` to put the same icons on your own buttons. For a button of your own, such as `<InkToolbarButton onclick={download}>Download</InkToolbarButton>`, use `InkToolbarButton`: it joins the toolbar's arrow-key focus and looks like the other buttons. `bind:annotations` makes your list the source of truth, and `history={true}` keeps undo and redo working with it. Leave the binding off and Inkbar keeps the state itself, with undo and redo on by default.
|
|
95
95
|
|
|
96
96
|
## Controlled mode
|
|
97
97
|
|
|
@@ -149,9 +149,17 @@ Inside `InkRoot`, `useHistory()` returns the same object. Its `batch(fn)` runs `
|
|
|
149
149
|
|
|
150
150
|
`key` identifies the document. Reuse the same key when only the signed URL changes and the document is not reloaded.
|
|
151
151
|
|
|
152
|
+
`InkRoot` takes a few more options. `password` opens an encrypted PDF. `storage` sets what the viewer keeps between visits (see [Storage](#storage)). `defaults` sets annotation colours; without it the theme's CSS variables apply. `fonts` sets the families the text style menu offers. `decorateAnnotation` adds attributes, classes and a screen-reader description to each annotation. `bind:page` and `bind:zoom` (or `onPageChange` and `onZoomChange`) follow and drive the viewport, and `bind:tool` (or `onToolChange`) does the same for the active tool. `onError` hears failed opens, form script errors and refused signings. `onActivate` hears the id of each activated annotation. `onFieldsChange` hears form value changes.
|
|
153
|
+
|
|
154
|
+
## Viewport
|
|
155
|
+
|
|
156
|
+
`InkViewport` takes `mode`, `rotation`, `gap` (CSS pixels between pages, default 16), `buffer` (pages kept mounted either side of the visible ones, default 2) and `pinchZoom` (default `true`; `false` leaves pinches to the browser, which zooms the whole page). A change to `gap`, `buffer` or `pinchZoom` rebuilds the engine.
|
|
157
|
+
|
|
158
|
+
On a touch screen, two fingers pinch to zoom the document and scroll it. With `draw`, `sketch`, `shape`, `redact-area` and `freetext`, one finger draws; a finger tap draws nothing, and a pen that lands while a palm rests on the screen takes over. A shape, area redaction or text box gives one-finger scrolling back once it has made something.
|
|
159
|
+
|
|
152
160
|
## Forms
|
|
153
161
|
|
|
154
|
-
`InkViewport` fills the PDF's form fields and runs the form's scripts. `useForm()` returns the fields, their values, and `set` and `reset`. Use `bind:fields` to hold the values in your own state. The form never submits, opens a link, sends mail or prints: `
|
|
162
|
+
`InkViewport` fills the PDF's form fields and runs the form's scripts. `useForm()` returns the fields, their values, and `set` and `reset`. Use `bind:fields` to hold the values in your own state. The form never submits, opens a link, sends mail or prints: `onaction` hands each request to you; so does a link to a URL. `onformaction` still works until 0.13.0. `formHighlight={false}` turns off the field tint, and a change applies to the open document in place.
|
|
155
163
|
|
|
156
164
|
The [forms guide](https://inkbar.dev/docs/guides/forms/) covers controlled fields, form actions, scripts, signature fields and saving values as Gloss.
|
|
157
165
|
|
|
@@ -163,9 +171,26 @@ A root note with no text and no replies is removed when its popover closes, or w
|
|
|
163
171
|
|
|
164
172
|
The highlight tool leaves `contents` free for the comment. `doc.text(page)` returns the page's text items; match the markup's `quads` against them to get the marked text. `InkHighlightMenu` is a split button for the tool: it picks the markup kind and its colour.
|
|
165
173
|
|
|
174
|
+
## Style menus
|
|
175
|
+
|
|
176
|
+
`InkColorMenu` (`property="border"`, `"fill"` or `"text"`), `InkLineStyleMenu` (weight, dashes, arrowheads) and `InkTextStyleMenu` (font, size, colour, bold, italic, underline, alignment) edit the selection, or the active tool's style when nothing is selected. A menu disables when nothing supports its properties. `InkShapeMenu` is a split button for the `shape` tool and renders nothing unless `shapeTool` is in `tools`. `fonts` on `InkRoot` sets the families the text style menu offers.
|
|
177
|
+
|
|
166
178
|
## Signatures
|
|
167
179
|
|
|
168
|
-
`InkSignatureMenu` lists saved signatures, picks one for the `signature` tool, removes them, and opens `InkSignatureDialog` to draw, type or upload a new one. The menu needs `signatureTool()` in `tools`. The dialog and signature form fields work without it; the dialog then only saves the signature. These are marks on the page, not cryptographic signatures.
|
|
180
|
+
`InkSignatureMenu` lists saved signatures, picks one for the `signature` tool, removes them, and opens `InkSignatureDialog` to draw, type or upload a new one. The menu needs `signatureTool()` in `tools`. The dialog and signature form fields work without it; the dialog then only saves the signature. These are marks on the page, not cryptographic signatures. `InkRoot` keeps them in its `storage` under the key `signatures`, so without a store they last until reload. A `signatures` store of your own still works and wins over `storage` until 0.13.0, which removes it. `signatureFonts` lists the handwriting fonts the Type tab offers; load them with `@font-face`. A placed signature keeps its aspect ratio when resized.
|
|
181
|
+
|
|
182
|
+
## Storage
|
|
183
|
+
|
|
184
|
+
`InkRoot` keeps a few small things between visits: saved signatures, sidebar widths and plugin settings. Pass `storage` to choose where. `createLocalStorage()` from `@inkbar/core` keeps them in `localStorage`, and any `InkStorage` works, including an async one that talks to your server. The default is `createMemoryStorage()`, which forgets everything on reload. Inkbar never touches `localStorage`, cookies or the network unless the store you pass does. The root reads `storage` once, when it is created. See the [`@inkbar/core` README](https://github.com/inkbar/inkbar/tree/main/packages/core#storage) for the keys.
|
|
185
|
+
|
|
186
|
+
```svelte
|
|
187
|
+
<script lang="ts">
|
|
188
|
+
import { createLocalStorage } from '@inkbar/core'
|
|
189
|
+
const storage = createLocalStorage()
|
|
190
|
+
</script>
|
|
191
|
+
|
|
192
|
+
<InkRoot {src} {assets} {storage}>
|
|
193
|
+
```
|
|
169
194
|
|
|
170
195
|
## Plugins
|
|
171
196
|
|
|
@@ -187,7 +212,7 @@ Plugin stores undo with annotations. In controlled mode with `history`, each und
|
|
|
187
212
|
|
|
188
213
|
## Search
|
|
189
214
|
|
|
190
|
-
`InkFindBar` is the find bar and `InkSearchButton` opens it. Place the bar anywhere inside `InkRoot`, once `InkViewport` is there; Ctrl/Cmd+F opens it while it is mounted. `useSearch()` returns the search `state` and its actions (`open`, `setQuery`, `setMatchCase`, `next`, `previous`, `close`) for your own control. See the [`@inkbar/core` README](https://github.com/inkbar/inkbar/tree/main/packages/core#search) for the API.
|
|
215
|
+
`InkFindBar` is the find bar and `InkSearchButton` opens it. Place the bar anywhere inside `InkRoot`, once `InkViewport` is there; Ctrl/Cmd+F opens it while it is mounted. `useSearch()` returns the search `state` and its actions (`open(query?)`, `setQuery`, `setMatchCase`, `next`, `previous`, `select`, `close`) for your own control. See the [`@inkbar/core` README](https://github.com/inkbar/inkbar/tree/main/packages/core#search) for the API.
|
|
191
216
|
|
|
192
217
|
The bar handles Escape itself and stops it there, so an `onkeydown` you pass to `InkFindBar` never sees Escape.
|
|
193
218
|
|
|
@@ -196,6 +221,10 @@ The bar handles Escape itself and stops it there, so an `onkeydown` you pass to
|
|
|
196
221
|
`InkSidebar` holds `InkPanel`s and places nothing itself: put it beside `InkViewport` with your own flex or grid layout. Each panel has an `id`, a `label`, an optional `icon` (an `IconNode` from `@inkbar/icons`) and an optional `badge`, a number or short string hidden when empty or 0. Its children are the panel's content, your own component or one of Inkbar's.
|
|
197
222
|
|
|
198
223
|
```svelte
|
|
224
|
+
<script lang="ts">
|
|
225
|
+
import { Square, MessageSquare } from '@inkbar/icons'
|
|
226
|
+
</script>
|
|
227
|
+
|
|
199
228
|
<InkRoot {src} {assets}>
|
|
200
229
|
<div class="app">
|
|
201
230
|
<InkSidebar side="left" layout="rail" bind:open>
|
|
@@ -213,7 +242,13 @@ The bar handles Escape itself and stops it there, so an `onkeydown` you pass to
|
|
|
213
242
|
|
|
214
243
|
`layout` is `rail` (default: icon buttons beside the document, and activating the open panel's button closes it), `tabs` (labelled tabs, one panel open) or `stack` (a header per panel, any number expanded). `side` (`left` by default) decides which edge the rail and border sit on. `bind:open` is the open panel's id, or `null`, for rail and tabs, and the expanded ids for stack. Left out, the first panel starts open. When the open panel goes (an `{#if}`), rail and tabs open the first one left and stack drops it, and the bound `open` receives the new value. In the rail, the open panel shows its label as a title above its content. `visible` (default `true`) shows or hides the whole sidebar; it keeps the open panel, its content and its scroll for when it shows again. Your code decides when: `visible={hits > 0}` keeps a results sidebar out of the way until a search finds something.
|
|
215
244
|
|
|
216
|
-
Panels take their place in the order they appear in the markup, including one added later with `{#if}`. A closed panel stays mounted and hidden, so its content keeps its state and scroll position, across layout changes too. The keys and ARIA are core's: see the [`@inkbar/core` README](https://github.com/inkbar/inkbar/tree/main/packages/core#sidebars). The sidebar needs no document and works outside `InkRoot`. On the server it renders its frame with the open panel showing, and the
|
|
245
|
+
Panels take their place in the order they appear in the markup, including one added later with `{#if}`. A closed panel stays mounted and hidden, so its content keeps its state and scroll position, across layout changes too. The keys and ARIA are core's: see the [`@inkbar/core` README](https://github.com/inkbar/inkbar/tree/main/packages/core#sidebars). The sidebar needs no document and works outside `InkRoot`. On the server it renders its frame at its width with the open panel showing, the stack headers and the resize handle; rail and tab buttons follow once it has mounted.
|
|
246
|
+
|
|
247
|
+
Each sidebar has a resize handle on its inner edge. Drag it, or focus it and use the arrow keys (16px a step), Home and End. Enter or a double-click resets the width. The width starts at `defaultWidth` (320) and stays between `minWidth` (240) and `maxWidth` (480), and never past half of the sidebar's container. The sidebar sets `--ink-sidebar-width` inline, so a CSS override of it no longer applies. Pass `defaultWidth` instead. Inside `InkRoot` the sidebar saves its width in the root's `storage` under `sidebar.<name>.width`, where `name` defaults to the side; give two sidebars on one side different names. Pass `storage` to the sidebar to use another store, or to save its width outside `InkRoot`. The sidebar reads `storage` and `name` once, when it is created. To control the width, bind `width` to a number: the handle then only updates the binding, and the sidebar neither reads nor writes storage. Bind it to a value that starts `undefined` and you only receive the width; the sidebar keeps saving it.
|
|
248
|
+
|
|
249
|
+
```svelte
|
|
250
|
+
<InkSidebar name="tools" side="right" defaultWidth={360} bind:width={toolsWidth}>
|
|
251
|
+
```
|
|
217
252
|
|
|
218
253
|
## Thumbnails
|
|
219
254
|
|
|
@@ -231,6 +266,20 @@ Panels take their place in the order they appear in the markup, including one ad
|
|
|
231
266
|
|
|
232
267
|
An item's height is worked out from its page alone, so position what you add absolutely. The `item` snippet renders inside the item's `<button>`: keep it non-interactive, with no links, buttons or inputs. Both need `InkRoot` and render empty on the server.
|
|
233
268
|
|
|
269
|
+
## Outline and Back
|
|
270
|
+
|
|
271
|
+
`InkOutline` shows the document's outline as a tree. Clicking an item, or Enter on it, follows it: a destination scrolls there (without changing the zoom), a web address goes to the host as an `open-url` action. The chevron expands and collapses an item without following it. The item the reader has reached is marked `aria-current="location"` and kept in view. It fills its parent's height and scrolls, so put it in a panel:
|
|
272
|
+
|
|
273
|
+
```svelte
|
|
274
|
+
<InkPanel id="outline" label="Outline" icon={List}>
|
|
275
|
+
<InkOutline emptyText="No outline" />
|
|
276
|
+
</InkPanel>
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
`emptyText` is shown when the document has no outline. The `item` snippet, given the item and `{ current, expanded }`, replaces an item's title. The keys and ARIA are core's, as in the thumbnails. Needs `InkRoot`; the tree renders empty on the server.
|
|
280
|
+
|
|
281
|
+
`InkBack` and `InkForward` return to where the view was before a jump (a link, an outline item) and undo that. Each is disabled until there is somewhere to go, and takes a `child` snippet. Put them in an `InkToolbar`.
|
|
282
|
+
|
|
234
283
|
## Record lists
|
|
235
284
|
|
|
236
285
|
`InkRecordList` lists a plugin's records as cards, the same cards their marks open, with the same actions. Name the records `<plugin>.<name>`:
|
|
@@ -243,7 +292,7 @@ An item's height is worked out from its page alone, so position what you add abs
|
|
|
243
292
|
</InkPanel>
|
|
244
293
|
```
|
|
245
294
|
|
|
246
|
-
The list and the marks share one selection: choosing a card selects its mark and scrolls the viewer there, and activating a mark selects its card. Each action runs in one undo step. `card`, `where` and `sort` replace the marks' card, filter and reading order. A new function rebuilds the list, so define them once in the script, never inline in the markup. `bind:count` receives the number listed whenever it changes, ready for a panel badge. The `empty` snippet replaces "Nothing here yet.". To list something of your own, pass a `source` (a `RecordListSource`, as core's `recordsSource` and `searchSource` build) in place of `records`: it gets the same cards, keys and selection. With both, `source` wins.
|
|
295
|
+
The list and the marks share one selection: choosing a card selects its mark and scrolls the viewer there, and activating a mark selects its card. Each action runs in one undo step. `card`, `where` and `sort` replace the marks' card, filter and reading order. A new function rebuilds the list, so define them once in the script, never inline in the markup. They do not apply to a `source`, which carries its own. `bind:count` receives the number listed whenever it changes, ready for a panel badge. The `empty` snippet replaces "Nothing here yet.". To list something of your own, pass a `source` (a `RecordListSource`, as core's `recordsSource` and `searchSource` build) in place of `records`: it gets the same cards, keys and selection. With both, `source` wins.
|
|
247
296
|
|
|
248
297
|
`InkSearchResults` is the same list over the document search: one card per hit, titled with its page ("Page 2"), with its place on that page ("Match 3") and the text around it. Choosing a card makes its hit current, as the find bar's next and previous do. It has its own field, labelled "Find in document", above the cards and the find bar's match count in a footer below them; `field={false}` leaves the field out when a find bar is on the page. Field and find bar share the search state, so they never disagree.
|
|
249
298
|
|
|
@@ -251,11 +300,11 @@ To build your own, `useRecords<T>(name)` returns the live `records` and the `han
|
|
|
251
300
|
|
|
252
301
|
## Custom rendering
|
|
253
302
|
|
|
254
|
-
Slots are snippets. `InkViewport` takes `loading`, `empty`, `error`, `annotation` and `pageError`. `InkThreadPopover` takes `children` as a snippet that receives the popover's props. `InkToolButton` takes `children` as a snippet of `{ active, label }`, and `InkCommand` one of `{ label, busy }`. `InkThumbnails`
|
|
303
|
+
Slots are snippets. `InkViewport` takes `loading`, `empty`, `error`, `annotation` and `pageError`. `InkThreadPopover` takes `children` as a snippet that receives the popover's props. `InkToolButton` takes `children` as a snippet of `{ active, label }`, and `InkCommand` one of `{ label, busy }`. `InkThumbnails` and `InkOutline` take `item`, and `InkRecordList` takes `empty`. The props each receives are typed as `AnnotationSlotProps` and `ThreadPopoverSlotProps`, the same types the Vue and React packages export.
|
|
255
304
|
|
|
256
305
|
`InkDownloadButton` and `InkPrintButton` download and print the open PDF; `filename` names the download (the opened `File`'s name, else the URL's last segment, else `document.pdf`). Printing loads the file into a hidden frame and opens the browser's print dialog for it; on iOS the PDF opens in a new tab instead. Until saving into the PDF ships, both give the file as it was opened, without this session's annotations.
|
|
257
306
|
|
|
258
|
-
`InkRoot`, `InkToolbar`, `InkToolButton`, `InkCommand`, `InkUndo`, `InkRedo`, `InkDownloadButton`, `InkPrintButton`, `InkPrevPage`, `InkNextPage`, `InkZoomIn` and `InkZoomOut` take a `child` snippet to render your own element. Spread the `props` it gives you on that element. The snippet type is exported as `ChildSnippet`.
|
|
307
|
+
`InkRoot`, `InkToolbar`, `InkToolButton`, `InkToolbarButton`, `InkCommand`, `InkUndo`, `InkRedo`, `InkDownloadButton`, `InkPrintButton`, `InkSearchButton`, `InkPrevPage`, `InkNextPage`, `InkBack`, `InkForward`, `InkZoomIn` and `InkZoomOut` take a `child` snippet to render your own element. Spread the `props` it gives you on that element. The snippet type is exported as `ChildSnippet`.
|
|
259
308
|
|
|
260
309
|
```svelte
|
|
261
310
|
<InkToolbar>
|
|
@@ -277,11 +326,12 @@ The scroll area takes focus. From there:
|
|
|
277
326
|
|
|
278
327
|
On a focused annotation:
|
|
279
328
|
|
|
280
|
-
- `Enter` or `Space` selects and activates it. A note or text markup opens its thread; other types are only selected.
|
|
329
|
+
- `Enter` or `Space` selects and activates it. A note or text markup opens its thread; other types are only selected. `Enter` on an editable text box opens its editor instead.
|
|
281
330
|
- `ArrowUp` and `ArrowDown` move to the previous or next annotation on the page.
|
|
282
331
|
- `Shift` + arrow moves it by 1 pt, or by 10 pt with `Alt` as well.
|
|
283
332
|
- `Ctrl` or `Cmd` + `Shift` + arrow resizes an area redaction, text box or shape.
|
|
284
333
|
- `Delete` or `Backspace` removes it.
|
|
334
|
+
- `Escape` clears the selection.
|
|
285
335
|
|
|
286
336
|
The full table, with the zoom and page keys, is in the [`@inkbar/core` README](https://github.com/inkbar/inkbar/tree/main/packages/core#tools-and-keyboard).
|
|
287
337
|
|
|
@@ -291,7 +341,7 @@ Components carry state on data attributes, for example `[data-annotation][data-s
|
|
|
291
341
|
|
|
292
342
|
## Hooks
|
|
293
343
|
|
|
294
|
-
`useDocument`, `useAnnotations`, `useTool`, `useSelection`, `useZoom`, `usePage`, `useHistory`, `useEmbedded`, `useCommand`, `useRecords` and `useRecordSelection` read the state of the nearest `InkRoot`. Call them during component initialisation, as you would `getContext`. Each returns an object whose reactive members are getters: read `useZoom().zoom` in your template or an effect and it tracks; destructure it (`const { zoom } = useZoom()`) and you copy the value once. Keep the object and read through it.
|
|
344
|
+
`useDocument`, `useAnnotations`, `useTool`, `useSelection`, `useZoom`, `usePage`, `useHistory`, `useEmbedded`, `useCommand`, `useForm`, `useSearch`, `useToolStyle`, `useToolOptions`, `useRecords` and `useRecordSelection` read the state of the nearest `InkRoot`. Call them during component initialisation, as you would `getContext`. Each returns an object whose reactive members are getters: read `useZoom().zoom` in your template or an effect and it tracks; destructure it (`const { zoom } = useZoom()`) and you copy the value once. Keep the object and read through it. `useToolOptions('shape')` reads and sets a tool's options, such as the shape kind, which last as long as `InkRoot`. The tool style that `useToolStyle` reads and sets (colours, line style, text style) belongs to the document: opening another one starts again from the theme's defaults.
|
|
295
345
|
|
|
296
346
|
## Licence
|
|
297
347
|
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { ArrowLeft } from '@inkbar/icons'
|
|
3
|
+
import { Toolbar } from 'bits-ui'
|
|
4
|
+
|
|
5
|
+
import { useInkContext } from '../context.js'
|
|
6
|
+
import { useReadable } from '../use-readable.svelte.js'
|
|
7
|
+
|
|
8
|
+
import { asBitsProps } from './child-snippet.js'
|
|
9
|
+
import InkIcon from './InkIcon.svelte'
|
|
10
|
+
|
|
11
|
+
import type { InkPageButtonProps } from './page-button.js'
|
|
12
|
+
|
|
13
|
+
let {
|
|
14
|
+
class: className,
|
|
15
|
+
children,
|
|
16
|
+
child,
|
|
17
|
+
disabled,
|
|
18
|
+
onclick,
|
|
19
|
+
...rest
|
|
20
|
+
}: InkPageButtonProps = $props()
|
|
21
|
+
|
|
22
|
+
const ctx = useInkContext('InkBack')
|
|
23
|
+
const can = useReadable(() => ctx.navigation?.canGoBack, false)
|
|
24
|
+
</script>
|
|
25
|
+
|
|
26
|
+
<Toolbar.Button
|
|
27
|
+
class={['ink-back', className]}
|
|
28
|
+
data-ink-back=""
|
|
29
|
+
aria-label="Back"
|
|
30
|
+
title="Back"
|
|
31
|
+
disabled={!can.current || !!disabled}
|
|
32
|
+
{...child ? { child } : {}}
|
|
33
|
+
{...asBitsProps(rest)}
|
|
34
|
+
onclick={(e) => {
|
|
35
|
+
onclick?.(e)
|
|
36
|
+
if (!e.defaultPrevented) ctx.navigation?.back()
|
|
37
|
+
}}
|
|
38
|
+
>
|
|
39
|
+
{#if children}{@render children()}{:else}<InkIcon icon={ArrowLeft} />{/if}
|
|
40
|
+
</Toolbar.Button>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"InkBack.svelte.d.ts","sourceRoot":"","sources":["../../src/lib/components/InkBack.svelte.ts"],"names":[],"mappings":"AAYA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AAoC3D,QAAA,MAAM,OAAO,wDAAwC,CAAC;AACtD,KAAK,OAAO,GAAG,UAAU,CAAC,OAAO,OAAO,CAAC,CAAC;AAC1C,eAAe,OAAO,CAAC"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { ArrowRight } from '@inkbar/icons'
|
|
3
|
+
import { Toolbar } from 'bits-ui'
|
|
4
|
+
|
|
5
|
+
import { useInkContext } from '../context.js'
|
|
6
|
+
import { useReadable } from '../use-readable.svelte.js'
|
|
7
|
+
|
|
8
|
+
import { asBitsProps } from './child-snippet.js'
|
|
9
|
+
import InkIcon from './InkIcon.svelte'
|
|
10
|
+
|
|
11
|
+
import type { InkPageButtonProps } from './page-button.js'
|
|
12
|
+
|
|
13
|
+
let {
|
|
14
|
+
class: className,
|
|
15
|
+
children,
|
|
16
|
+
child,
|
|
17
|
+
disabled,
|
|
18
|
+
onclick,
|
|
19
|
+
...rest
|
|
20
|
+
}: InkPageButtonProps = $props()
|
|
21
|
+
|
|
22
|
+
const ctx = useInkContext('InkForward')
|
|
23
|
+
const can = useReadable(() => ctx.navigation?.canGoForward, false)
|
|
24
|
+
</script>
|
|
25
|
+
|
|
26
|
+
<Toolbar.Button
|
|
27
|
+
class={['ink-forward', className]}
|
|
28
|
+
data-ink-forward=""
|
|
29
|
+
aria-label="Forward"
|
|
30
|
+
title="Forward"
|
|
31
|
+
disabled={!can.current || !!disabled}
|
|
32
|
+
{...child ? { child } : {}}
|
|
33
|
+
{...asBitsProps(rest)}
|
|
34
|
+
onclick={(e) => {
|
|
35
|
+
onclick?.(e)
|
|
36
|
+
if (!e.defaultPrevented) ctx.navigation?.forward()
|
|
37
|
+
}}
|
|
38
|
+
>
|
|
39
|
+
{#if children}{@render children()}{:else}<InkIcon icon={ArrowRight} />{/if}
|
|
40
|
+
</Toolbar.Button>
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { InkPageButtonProps } from './page-button.js';
|
|
2
|
+
declare const InkForward: import("svelte").Component<InkPageButtonProps, {}, "">;
|
|
3
|
+
type InkForward = ReturnType<typeof InkForward>;
|
|
4
|
+
export default InkForward;
|
|
5
|
+
//# sourceMappingURL=InkForward.svelte.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"InkForward.svelte.d.ts","sourceRoot":"","sources":["../../src/lib/components/InkForward.svelte.ts"],"names":[],"mappings":"AAYA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AAoC3D,QAAA,MAAM,UAAU,wDAAwC,CAAC;AACzD,KAAK,UAAU,GAAG,UAAU,CAAC,OAAO,UAAU,CAAC,CAAC;AAChD,eAAe,UAAU,CAAC"}
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
<script module lang="ts">
|
|
2
|
+
import type { OutlineItem } from '@inkbar/core'
|
|
3
|
+
import type { Snippet } from 'svelte'
|
|
4
|
+
import type { HTMLAttributes } from 'svelte/elements'
|
|
5
|
+
|
|
6
|
+
export interface InkOutlineProps extends Omit<
|
|
7
|
+
HTMLAttributes<HTMLDivElement>,
|
|
8
|
+
'children'
|
|
9
|
+
> {
|
|
10
|
+
/** Shown when the document has no outline. Default: "This document has no outline". */
|
|
11
|
+
emptyText?: string | undefined
|
|
12
|
+
/**
|
|
13
|
+
* Replaces an item's title, given the item and whether it is the current one and whether it
|
|
14
|
+
* is expanded. Keep what it renders non-interactive.
|
|
15
|
+
*/
|
|
16
|
+
item?: Snippet<[OutlineItem, { current: boolean; expanded: boolean }]> | undefined
|
|
17
|
+
}
|
|
18
|
+
</script>
|
|
19
|
+
|
|
20
|
+
<script lang="ts">
|
|
21
|
+
/*
|
|
22
|
+
* The open document's outline as a tree: `createOutline`'s markup on core's outline controller,
|
|
23
|
+
* which holds what is expanded, the Tab stop, the keys, the current item and Back's history.
|
|
24
|
+
* Clicking an item, or Enter on it, follows it; its chevron expands and collapses it. It fills
|
|
25
|
+
* its parent's height and scrolls, and keeps the item the reader has reached in view. The
|
|
26
|
+
* `item` snippet replaces an item's title. Inside `InkRoot`; the tree is empty on the server.
|
|
27
|
+
*/
|
|
28
|
+
import {
|
|
29
|
+
createOutlineController,
|
|
30
|
+
outlineTreeAttrs,
|
|
31
|
+
type OutlineController,
|
|
32
|
+
} from '@inkbar/core/dom'
|
|
33
|
+
import { ChevronRight } from '@inkbar/icons'
|
|
34
|
+
import { untrack } from 'svelte'
|
|
35
|
+
|
|
36
|
+
import { useInkContext } from '../context.js'
|
|
37
|
+
import { toReadable } from '../to-readable.svelte.js'
|
|
38
|
+
|
|
39
|
+
import InkIcon from './InkIcon.svelte'
|
|
40
|
+
|
|
41
|
+
let {
|
|
42
|
+
emptyText = 'This document has no outline',
|
|
43
|
+
item: renderItem,
|
|
44
|
+
class: className,
|
|
45
|
+
...rest
|
|
46
|
+
}: InkOutlineProps = $props()
|
|
47
|
+
|
|
48
|
+
const ctx = useInkContext('InkOutline')
|
|
49
|
+
const deps = {
|
|
50
|
+
document: toReadable(() => ctx.document),
|
|
51
|
+
viewport: toReadable(() => ctx.viewport),
|
|
52
|
+
navigation: toReadable(() => ctx.navigation),
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
let root = $state<HTMLDivElement | null>(null)
|
|
56
|
+
let controller = $state.raw<OutlineController | null>(null)
|
|
57
|
+
// Until the controller is built after mount, and on the server: nothing listed.
|
|
58
|
+
let items = $state.raw<readonly OutlineItem[]>([])
|
|
59
|
+
let loading = $state.raw(false)
|
|
60
|
+
let expanded = $state.raw<ReadonlySet<string>>(new Set())
|
|
61
|
+
let current = $state.raw<string | null>(null)
|
|
62
|
+
let focused = $state.raw<string | null>(null)
|
|
63
|
+
|
|
64
|
+
// Built once the element exists, as the other controller-backed components do: it subscribes
|
|
65
|
+
// to the document, so nothing is created while rendering on the server.
|
|
66
|
+
$effect(() => {
|
|
67
|
+
const el = root
|
|
68
|
+
if (!el) return
|
|
69
|
+
return untrack(() => {
|
|
70
|
+
const c = createOutlineController(deps)
|
|
71
|
+
controller = c
|
|
72
|
+
items = c.items.get()
|
|
73
|
+
loading = c.loading.get()
|
|
74
|
+
expanded = c.expanded.get()
|
|
75
|
+
current = c.current.get()
|
|
76
|
+
focused = c.focused.get()
|
|
77
|
+
const stops = [
|
|
78
|
+
c.items.subscribe((v) => (items = v)),
|
|
79
|
+
c.loading.subscribe((v) => (loading = v)),
|
|
80
|
+
c.expanded.subscribe((v) => (expanded = v)),
|
|
81
|
+
c.current.subscribe((v) => (current = v)),
|
|
82
|
+
c.focused.subscribe((v) => (focused = v)),
|
|
83
|
+
c.bindScroll(el),
|
|
84
|
+
]
|
|
85
|
+
return () => {
|
|
86
|
+
for (const stop of stops) stop()
|
|
87
|
+
c.destroy()
|
|
88
|
+
controller = null
|
|
89
|
+
}
|
|
90
|
+
})
|
|
91
|
+
})
|
|
92
|
+
|
|
93
|
+
/** The item's attributes. Reading the stores here makes a render follow them. */
|
|
94
|
+
function attrs(id: string): Record<string, string | undefined> {
|
|
95
|
+
void [expanded, current, focused]
|
|
96
|
+
return controller?.itemAttrs(id) ?? {}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const level = (id: string): number => id.split('.').length - 1
|
|
100
|
+
</script>
|
|
101
|
+
|
|
102
|
+
{#snippet nodes(list: readonly OutlineItem[])}
|
|
103
|
+
{#each list as entry (entry.id)}
|
|
104
|
+
{@const c = controller!}
|
|
105
|
+
{@const open = expanded.has(entry.id)}
|
|
106
|
+
<li
|
|
107
|
+
class="ink-outline-item"
|
|
108
|
+
data-ink-outline-item={entry.id}
|
|
109
|
+
data-bold={entry.bold ? '' : undefined}
|
|
110
|
+
data-italic={entry.italic ? '' : undefined}
|
|
111
|
+
style:--ink-outline-level={level(entry.id)}
|
|
112
|
+
{...attrs(entry.id)}
|
|
113
|
+
>
|
|
114
|
+
<!-- The tree's keys come from the controller, so the row itself is not focusable. -->
|
|
115
|
+
<div
|
|
116
|
+
class="ink-outline-row"
|
|
117
|
+
{...c.rowAttrs(entry.id)}
|
|
118
|
+
onclick={() => c.activate(entry.id)}
|
|
119
|
+
>
|
|
120
|
+
{#if entry.children.length > 0}
|
|
121
|
+
<span
|
|
122
|
+
class="ink-outline-toggle"
|
|
123
|
+
aria-hidden="true"
|
|
124
|
+
onclick={(e) => {
|
|
125
|
+
// Expanding is not following: the row's own click is not reached.
|
|
126
|
+
e.stopPropagation()
|
|
127
|
+
c.toggle(entry.id)
|
|
128
|
+
}}
|
|
129
|
+
>
|
|
130
|
+
<InkIcon icon={ChevronRight} />
|
|
131
|
+
</span>
|
|
132
|
+
{:else}
|
|
133
|
+
<span class="ink-outline-spacer" aria-hidden="true"></span>
|
|
134
|
+
{/if}
|
|
135
|
+
<span class="ink-outline-title" {...c.titleAttrs(entry.id)}>
|
|
136
|
+
{#if renderItem}
|
|
137
|
+
{@render renderItem(entry, { current: current === entry.id, expanded: open })}
|
|
138
|
+
{:else}
|
|
139
|
+
{entry.title}
|
|
140
|
+
{/if}
|
|
141
|
+
</span>
|
|
142
|
+
</div>
|
|
143
|
+
{#if entry.children.length > 0}
|
|
144
|
+
<ul class="ink-outline-group" role="group" hidden={!open}>
|
|
145
|
+
{@render nodes(entry.children)}
|
|
146
|
+
</ul>
|
|
147
|
+
{/if}
|
|
148
|
+
</li>
|
|
149
|
+
{/each}
|
|
150
|
+
{/snippet}
|
|
151
|
+
|
|
152
|
+
<div
|
|
153
|
+
{...rest}
|
|
154
|
+
bind:this={root}
|
|
155
|
+
class={['ink-outline', className]}
|
|
156
|
+
data-ink-outline=""
|
|
157
|
+
aria-busy={loading ? 'true' : undefined}
|
|
158
|
+
>
|
|
159
|
+
<ul
|
|
160
|
+
class="ink-outline-tree"
|
|
161
|
+
{...outlineTreeAttrs()}
|
|
162
|
+
hidden={items.length === 0}
|
|
163
|
+
onkeydown={(e) => controller?.onKeydown(e)}
|
|
164
|
+
>
|
|
165
|
+
{#if controller}{@render nodes(items)}{/if}
|
|
166
|
+
</ul>
|
|
167
|
+
{#if controller && !loading && items.length === 0}
|
|
168
|
+
<p class="ink-outline-empty">{emptyText}</p>
|
|
169
|
+
{/if}
|
|
170
|
+
</div>
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { OutlineItem } from '@inkbar/core';
|
|
2
|
+
import type { Snippet } from 'svelte';
|
|
3
|
+
import type { HTMLAttributes } from 'svelte/elements';
|
|
4
|
+
export interface InkOutlineProps extends Omit<HTMLAttributes<HTMLDivElement>, 'children'> {
|
|
5
|
+
/** Shown when the document has no outline. Default: "This document has no outline". */
|
|
6
|
+
emptyText?: string | undefined;
|
|
7
|
+
/**
|
|
8
|
+
* Replaces an item's title, given the item and whether it is the current one and whether it
|
|
9
|
+
* is expanded. Keep what it renders non-interactive.
|
|
10
|
+
*/
|
|
11
|
+
item?: Snippet<[OutlineItem, {
|
|
12
|
+
current: boolean;
|
|
13
|
+
expanded: boolean;
|
|
14
|
+
}]> | undefined;
|
|
15
|
+
}
|
|
16
|
+
declare const InkOutline: import("svelte").Component<InkOutlineProps, {}, "">;
|
|
17
|
+
type InkOutline = ReturnType<typeof InkOutline>;
|
|
18
|
+
export default InkOutline;
|
|
19
|
+
//# sourceMappingURL=InkOutline.svelte.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"InkOutline.svelte.d.ts","sourceRoot":"","sources":["../../src/lib/components/InkOutline.svelte.ts"],"names":[],"mappings":"AAGE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,cAAc,CAAA;AAC/C,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAA;AACrC,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAA;AAErD,MAAM,WAAW,eAAgB,SAAQ,IAAI,CAC3C,cAAc,CAAC,cAAc,CAAC,EAC9B,UAAU,CACX;IACC,uFAAuF;IACvF,SAAS,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;IAC9B;;;OAGG;IACH,IAAI,CAAC,EAAE,OAAO,CAAC,CAAC,WAAW,EAAE;QAAE,OAAO,EAAE,OAAO,CAAC;QAAC,QAAQ,EAAE,OAAO,CAAA;KAAE,CAAC,CAAC,GAAG,SAAS,CAAA;CACnF;AA+IH,QAAA,MAAM,UAAU,qDAAwC,CAAC;AACzD,KAAK,UAAU,GAAG,UAAU,CAAC,OAAO,UAAU,CAAC,CAAC;AAChD,eAAe,UAAU,CAAC"}
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
DocumentSource,
|
|
8
8
|
FieldsChange,
|
|
9
9
|
FieldValues,
|
|
10
|
-
|
|
10
|
+
ViewerAction,
|
|
11
11
|
InkDocument,
|
|
12
12
|
InkError,
|
|
13
13
|
InkStatus,
|
|
@@ -52,8 +52,13 @@
|
|
|
52
52
|
* are controlled, after the bound value is written.
|
|
53
53
|
*/
|
|
54
54
|
onFieldsChange?: ((next: FieldValues, change: FieldsChange) => void) | undefined
|
|
55
|
-
/**
|
|
56
|
-
|
|
55
|
+
/**
|
|
56
|
+
* The viewer asked the host to submit a form, open a link, send mail, print or prompt.
|
|
57
|
+
* Inkbar does none of these.
|
|
58
|
+
*/
|
|
59
|
+
onaction?: ((action: ViewerAction) => void) | undefined
|
|
60
|
+
/** @deprecated Use `onaction`. Removed in 0.13.0. Ignored when `onaction` is given. */
|
|
61
|
+
onformaction?: ((action: ViewerAction) => void) | undefined
|
|
57
62
|
/** Tints fillable form fields. Default true. A change applies to the open document in place. */
|
|
58
63
|
formHighlight?: boolean | undefined
|
|
59
64
|
tool?: ToolId | undefined
|
|
@@ -87,9 +92,16 @@
|
|
|
87
92
|
*/
|
|
88
93
|
fonts?: string[] | undefined
|
|
89
94
|
/**
|
|
90
|
-
* Where
|
|
91
|
-
*
|
|
92
|
-
*
|
|
95
|
+
* Where the viewer keeps what lasts between visits: saved signatures, each `InkSidebar`'s
|
|
96
|
+
* width and the plugins' settings. Default: memory, gone on reload. `createLocalStorage`
|
|
97
|
+
* keeps them in the browser; a host can keep them anywhere. Read once, when the root is
|
|
98
|
+
* created.
|
|
99
|
+
*/
|
|
100
|
+
storage?: InkStorage | undefined
|
|
101
|
+
/**
|
|
102
|
+
* Where saved signatures live, in place of `storage`. A new store takes effect at once, for
|
|
103
|
+
* the menus and the `signature` tool alike.
|
|
104
|
+
* @deprecated Pass `storage`; signatures are kept in it under `signatures`. Removed in 0.13.0.
|
|
93
105
|
*/
|
|
94
106
|
signatures?: SignatureStore | undefined
|
|
95
107
|
/** The handwriting fonts a typed signature offers; the host loads them. */
|
|
@@ -121,9 +133,10 @@
|
|
|
121
133
|
|
|
122
134
|
<script lang="ts">
|
|
123
135
|
import {
|
|
124
|
-
|
|
136
|
+
createMemoryStorage,
|
|
125
137
|
createSignatureLibrary,
|
|
126
138
|
createSnapshotHistory,
|
|
139
|
+
createStorageSignatureStore,
|
|
127
140
|
InkError as InkErrorClass,
|
|
128
141
|
openDocument,
|
|
129
142
|
readonly,
|
|
@@ -153,8 +166,9 @@
|
|
|
153
166
|
import { registeredToolIds } from '../hooks/useRegisteredTool.svelte.js'
|
|
154
167
|
import { useReadable } from '../use-readable.svelte.js'
|
|
155
168
|
|
|
156
|
-
import type { PluginHistory } from '@inkbar/core'
|
|
169
|
+
import type { InkStorage, PluginHistory } from '@inkbar/core'
|
|
157
170
|
import type {
|
|
171
|
+
Navigation,
|
|
158
172
|
PluginHost,
|
|
159
173
|
SearchController,
|
|
160
174
|
ToolController,
|
|
@@ -171,6 +185,7 @@
|
|
|
171
185
|
onChange,
|
|
172
186
|
fields = $bindable(),
|
|
173
187
|
onFieldsChange,
|
|
188
|
+
onaction,
|
|
174
189
|
onformaction,
|
|
175
190
|
formHighlight,
|
|
176
191
|
tool = $bindable(),
|
|
@@ -179,6 +194,7 @@
|
|
|
179
194
|
plugins,
|
|
180
195
|
decorateAnnotation,
|
|
181
196
|
fonts,
|
|
197
|
+
storage: storageProp,
|
|
182
198
|
signatures: signatureStore,
|
|
183
199
|
signatureFonts,
|
|
184
200
|
page = $bindable(),
|
|
@@ -197,9 +213,11 @@
|
|
|
197
213
|
|
|
198
214
|
type TestHookElement = HTMLElement & { __inkDocument?: InkDocument | undefined }
|
|
199
215
|
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
)
|
|
216
|
+
// Read once, like `plugins`: the sidebars and the plugins load from it as they are created.
|
|
217
|
+
const storage = untrack(() => storageProp ?? createMemoryStorage())
|
|
218
|
+
const storageSignatures = createStorageSignatureStore(storage)
|
|
219
|
+
// The deprecated `signatures` wins when both are given.
|
|
220
|
+
const signatures = $derived(createSignatureLibrary(signatureStore ?? storageSignatures))
|
|
203
221
|
$effect(() => {
|
|
204
222
|
void signatures.refresh()
|
|
205
223
|
})
|
|
@@ -233,6 +251,7 @@
|
|
|
233
251
|
let activatedNow: string | null = null
|
|
234
252
|
let viewport = $state.raw<Viewport | null>(null)
|
|
235
253
|
let tools = $state.raw<ToolController | null>(null)
|
|
254
|
+
let navigation = $state.raw<Navigation | null>(null)
|
|
236
255
|
|
|
237
256
|
const documentStatus = useReadable<InkStatus>(() => document?.status, IDLE)
|
|
238
257
|
const status = $derived(override ?? documentStatus.current)
|
|
@@ -428,7 +447,16 @@
|
|
|
428
447
|
zoom = z
|
|
429
448
|
onZoomChange?.(z)
|
|
430
449
|
}
|
|
450
|
+
/**
|
|
451
|
+
* Whether the host has given `page` a value of its own, as React's `page` prop. Until then the
|
|
452
|
+
* page written back below is only a report, so the next document opens at its first page
|
|
453
|
+
* instead of the last one's. Not reactive: it flips only with a change to `page`, which
|
|
454
|
+
* `pageModel`'s readers already track.
|
|
455
|
+
*/
|
|
456
|
+
let hostPage = untrack(() => page) !== undefined
|
|
457
|
+
let reportedPage: number | undefined
|
|
431
458
|
function reportPage(p: number): void {
|
|
459
|
+
reportedPage = p
|
|
432
460
|
page = p
|
|
433
461
|
onPageChange?.(p)
|
|
434
462
|
}
|
|
@@ -529,6 +557,7 @@
|
|
|
529
557
|
decorate: () => decorateAnnotation,
|
|
530
558
|
viewport: () => viewport,
|
|
531
559
|
search: viewerSearchOf(controller),
|
|
560
|
+
storage,
|
|
532
561
|
})
|
|
533
562
|
)
|
|
534
563
|
search = controller
|
|
@@ -628,6 +657,12 @@
|
|
|
628
657
|
get viewport() {
|
|
629
658
|
return viewport
|
|
630
659
|
},
|
|
660
|
+
get navigation() {
|
|
661
|
+
return navigation
|
|
662
|
+
},
|
|
663
|
+
setNavigation(n) {
|
|
664
|
+
navigation = n
|
|
665
|
+
},
|
|
631
666
|
get tools() {
|
|
632
667
|
return tools
|
|
633
668
|
},
|
|
@@ -651,6 +686,7 @@
|
|
|
651
686
|
return fonts ?? FONT_FAMILIES
|
|
652
687
|
},
|
|
653
688
|
toolOptions,
|
|
689
|
+
storage,
|
|
654
690
|
get signatures() {
|
|
655
691
|
return signatures
|
|
656
692
|
},
|
|
@@ -664,7 +700,8 @@
|
|
|
664
700
|
return zoom
|
|
665
701
|
},
|
|
666
702
|
get pageModel() {
|
|
667
|
-
|
|
703
|
+
if (page !== undefined && page !== reportedPage) hostPage = true
|
|
704
|
+
return hostPage ? page : undefined
|
|
668
705
|
},
|
|
669
706
|
reportZoom,
|
|
670
707
|
reportPage,
|
|
@@ -683,7 +720,7 @@
|
|
|
683
720
|
get formHighlight() {
|
|
684
721
|
return formHighlight
|
|
685
722
|
},
|
|
686
|
-
|
|
723
|
+
reportAction: (a) => (onaction ?? onformaction)?.(a),
|
|
687
724
|
reportError: (e) => onError?.(e),
|
|
688
725
|
}
|
|
689
726
|
setInkContext(context, () => activatedNow)
|