@inkbar/svelte 0.8.0 → 0.10.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 +71 -6
- package/dist/components/InkDownloadButton.svelte +62 -0
- package/dist/components/InkDownloadButton.svelte.d.ts +14 -0
- package/dist/components/InkDownloadButton.svelte.d.ts.map +1 -0
- package/dist/components/InkFindBar.svelte +18 -15
- package/dist/components/InkFindBar.svelte.d.ts.map +1 -1
- package/dist/components/InkHighlightMenu.svelte +101 -93
- package/dist/components/InkHighlightMenu.svelte.d.ts.map +1 -1
- package/dist/components/InkPanel.svelte +73 -0
- package/dist/components/InkPanel.svelte.d.ts +18 -0
- package/dist/components/InkPanel.svelte.d.ts.map +1 -0
- package/dist/components/InkPrintButton.svelte +57 -0
- package/dist/components/InkPrintButton.svelte.d.ts +12 -0
- package/dist/components/InkPrintButton.svelte.d.ts.map +1 -0
- package/dist/components/InkRecordList.svelte +91 -0
- package/dist/components/InkRecordList.svelte.d.ts +72 -0
- package/dist/components/InkRecordList.svelte.d.ts.map +1 -0
- package/dist/components/InkRoot.svelte +53 -5
- package/dist/components/InkRoot.svelte.d.ts +7 -2
- package/dist/components/InkRoot.svelte.d.ts.map +1 -1
- package/dist/components/InkSearchResults.svelte +72 -0
- package/dist/components/InkSearchResults.svelte.d.ts +9 -0
- package/dist/components/InkSearchResults.svelte.d.ts.map +1 -0
- package/dist/components/InkShapeMenu.svelte +89 -84
- package/dist/components/InkShapeMenu.svelte.d.ts.map +1 -1
- package/dist/components/InkSidebar.svelte +179 -0
- package/dist/components/InkSidebar.svelte.d.ts +22 -0
- package/dist/components/InkSidebar.svelte.d.ts.map +1 -0
- package/dist/components/InkSignatureDialog.svelte +7 -3
- package/dist/components/InkSignatureDialog.svelte.d.ts +4 -3
- package/dist/components/InkSignatureDialog.svelte.d.ts.map +1 -1
- package/dist/components/InkSignatureMenu.svelte +85 -77
- package/dist/components/InkSignatureMenu.svelte.d.ts.map +1 -1
- package/dist/components/InkThumbnail.svelte +74 -0
- package/dist/components/InkThumbnail.svelte.d.ts +19 -0
- package/dist/components/InkThumbnail.svelte.d.ts.map +1 -0
- package/dist/components/InkThumbnails.svelte +146 -0
- package/dist/components/InkThumbnails.svelte.d.ts +24 -0
- package/dist/components/InkThumbnails.svelte.d.ts.map +1 -0
- package/dist/components/InkToolButton.svelte +23 -24
- package/dist/components/InkToolButton.svelte.d.ts.map +1 -1
- package/dist/components/InkToolbarButton.svelte +33 -0
- package/dist/components/InkToolbarButton.svelte.d.ts +12 -0
- package/dist/components/InkToolbarButton.svelte.d.ts.map +1 -0
- package/dist/components/InkViewport.svelte +4 -0
- package/dist/components/InkViewport.svelte.d.ts.map +1 -1
- package/dist/components/InkZoomSelect.svelte +1 -1
- package/dist/components/RecordCards.svelte +173 -0
- package/dist/components/RecordCards.svelte.d.ts +16 -0
- package/dist/components/RecordCards.svelte.d.ts.map +1 -0
- package/dist/components/SidebarTrigger.svelte +20 -0
- package/dist/components/SidebarTrigger.svelte.d.ts +9 -0
- package/dist/components/SidebarTrigger.svelte.d.ts.map +1 -0
- package/dist/components/SignatureForm.svelte +9 -3
- package/dist/components/SignatureForm.svelte.d.ts.map +1 -1
- package/dist/components/body-lock.d.ts +3 -0
- package/dist/components/body-lock.d.ts.map +1 -0
- package/dist/components/body-lock.js +43 -0
- package/dist/components/sidebar-context.d.ts +24 -0
- package/dist/components/sidebar-context.d.ts.map +1 -0
- package/dist/components/sidebar-context.js +13 -0
- package/dist/context.d.ts +6 -0
- package/dist/context.d.ts.map +1 -1
- package/dist/hooks/useAnnotations.svelte.d.ts +78 -0
- package/dist/hooks/useAnnotations.svelte.d.ts.map +1 -1
- package/dist/hooks/useRecordSelection.svelte.d.ts +11 -0
- package/dist/hooks/useRecordSelection.svelte.d.ts.map +1 -0
- package/dist/hooks/useRecordSelection.svelte.js +19 -0
- package/dist/hooks/useRecords.svelte.d.ts +14 -0
- package/dist/hooks/useRecords.svelte.d.ts.map +1 -0
- package/dist/hooks/useRecords.svelte.js +24 -0
- package/dist/hooks/useRegisteredTool.svelte.d.ts +24 -0
- package/dist/hooks/useRegisteredTool.svelte.d.ts.map +1 -0
- package/dist/hooks/useRegisteredTool.svelte.js +47 -0
- package/dist/hooks/useSearch.svelte.d.ts.map +1 -1
- package/dist/hooks/useSearch.svelte.js +1 -0
- package/dist/hooks/useToolOptions.svelte.d.ts +8 -0
- package/dist/hooks/useToolOptions.svelte.d.ts.map +1 -1
- package/dist/hooks/useToolOptions.svelte.js +12 -2
- package/dist/index.d.ts +20 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +11 -0
- package/dist/to-readable.svelte.d.ts +8 -0
- package/dist/to-readable.svelte.d.ts.map +1 -0
- package/dist/to-readable.svelte.js +15 -0
- package/package.json +5 -5
package/README.md
CHANGED
|
@@ -53,6 +53,7 @@ Svelte 5.46.4 or later is a peer dependency. Add the Vite plugin from `@inkbar/c
|
|
|
53
53
|
InkZoomIn,
|
|
54
54
|
InkThreadPopover,
|
|
55
55
|
} from '@inkbar/svelte'
|
|
56
|
+
import { highlightTool, noteTool, redactAreaTool } from '@inkbar/core/dom'
|
|
56
57
|
import type { Annotation } from '@inkbar/core'
|
|
57
58
|
import { assets } from 'virtual:inkbar-assets'
|
|
58
59
|
import '@inkbar/theme/theme.css'
|
|
@@ -60,9 +61,10 @@ Svelte 5.46.4 or later is a peer dependency. Add the Vite plugin from `@inkbar/c
|
|
|
60
61
|
let { signedUrl, documentId }: { signedUrl: string; documentId: string } = $props()
|
|
61
62
|
|
|
62
63
|
let annotations = $state.raw<Annotation[]>([])
|
|
64
|
+
const tools = [highlightTool(), redactAreaTool(), noteTool()]
|
|
63
65
|
</script>
|
|
64
66
|
|
|
65
|
-
<InkRoot src={{ url: signedUrl, key: documentId }} {assets} bind:annotations>
|
|
67
|
+
<InkRoot src={{ url: signedUrl, key: documentId }} {assets} {tools} bind:annotations>
|
|
66
68
|
<InkToolbar>
|
|
67
69
|
<InkToolButton tool="select-text" />
|
|
68
70
|
<InkToolButton tool="select" />
|
|
@@ -87,7 +89,9 @@ Svelte 5.46.4 or later is a peer dependency. Add the Vite plugin from `@inkbar/c
|
|
|
87
89
|
</InkRoot>
|
|
88
90
|
```
|
|
89
91
|
|
|
90
|
-
|
|
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, a tool menu renders 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
|
+
|
|
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 it off and Inkbar keeps the state itself, with undo and redo.
|
|
91
95
|
|
|
92
96
|
## Controlled mode
|
|
93
97
|
|
|
@@ -161,7 +165,7 @@ The highlight tool leaves `contents` free for the comment. `doc.text(page)` retu
|
|
|
161
165
|
|
|
162
166
|
## Signatures
|
|
163
167
|
|
|
164
|
-
`InkSignatureMenu` lists saved signatures, picks one for the `signature` tool, removes them, and opens `InkSignatureDialog` to draw, type or upload a new one. These are marks on the page, not cryptographic signatures. Pass `signatures` to `InkRoot` to keep them somewhere: `createLocalSignatureStore(key)` from `@inkbar/core` uses `localStorage`, and without it they last until reload. `signatureFonts` lists the handwriting fonts the Type tab offers; load them with `@font-face`. A placed signature keeps its aspect ratio when resized.
|
|
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. Pass `signatures` to `InkRoot` to keep them somewhere: `createLocalSignatureStore(key)` from `@inkbar/core` uses `localStorage`, and without it they last until reload. `signatureFonts` lists the handwriting fonts the Type tab offers; load them with `@font-face`. A placed signature keeps its aspect ratio when resized.
|
|
165
169
|
|
|
166
170
|
## Plugins
|
|
167
171
|
|
|
@@ -187,11 +191,71 @@ Plugin stores undo with annotations. In controlled mode with `history`, each und
|
|
|
187
191
|
|
|
188
192
|
The bar handles Escape itself and stops it there, so an `onkeydown` you pass to `InkFindBar` never sees Escape.
|
|
189
193
|
|
|
194
|
+
## Sidebars
|
|
195
|
+
|
|
196
|
+
`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
|
+
|
|
198
|
+
```svelte
|
|
199
|
+
<InkRoot {src} {assets}>
|
|
200
|
+
<div class="app">
|
|
201
|
+
<InkSidebar side="left" layout="rail" bind:open>
|
|
202
|
+
<InkPanel id="pages" label="Pages" icon={Square}>
|
|
203
|
+
<InkThumbnails />
|
|
204
|
+
</InkPanel>
|
|
205
|
+
<InkPanel id="notes" label="Notes" icon={MessageSquare} badge={noteCount}>
|
|
206
|
+
<MyNotes />
|
|
207
|
+
</InkPanel>
|
|
208
|
+
</InkSidebar>
|
|
209
|
+
<InkViewport />
|
|
210
|
+
</div>
|
|
211
|
+
</InkRoot>
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
`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
|
+
|
|
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 stack headers; rail and tab buttons follow once it has mounted.
|
|
217
|
+
|
|
218
|
+
## Thumbnails
|
|
219
|
+
|
|
220
|
+
`InkThumbnail` draws one page (0-based `page`) as a small image, with its annotations and plugin marks. Give it a `width` or a `height` and the other follows the page; with neither it is 120px wide. `annotations={false}` and `marks={false}` leave those out, and `label` replaces its accessible name ("Page 4" for page 3). It is an image with no focus or click behaviour, so wrap it in the control you need, a checkbox label in a dialog for instance.
|
|
221
|
+
|
|
222
|
+
`InkThumbnails` lists the pages as thumbnails, each a button that scrolls the viewport to its page, with the current page marked `aria-current="page"`. It fills its parent's height and scrolls. `pages` lists the pages to show, in order. The thumbnails fill the list's width less a 16px gutter each side and follow it as it resizes; `width` fixes them at that many pixels instead. `annotations` and `marks` pass to each thumbnail. Only the items near the visible part are mounted. The `item` snippet adds to each item after its page number:
|
|
223
|
+
|
|
224
|
+
```svelte
|
|
225
|
+
<InkThumbnails>
|
|
226
|
+
{#snippet item({ page, current })}
|
|
227
|
+
<span class="page-count">{counts[page]}</span>
|
|
228
|
+
{/snippet}
|
|
229
|
+
</InkThumbnails>
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
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
|
+
|
|
234
|
+
## Record lists
|
|
235
|
+
|
|
236
|
+
`InkRecordList` lists a plugin's records as cards, the same cards their marks open, with the same actions. Name the records `<plugin>.<name>`:
|
|
237
|
+
|
|
238
|
+
```svelte
|
|
239
|
+
<InkPanel id="suggestions" label="Suggestions" icon={MessageSquare} badge={count}>
|
|
240
|
+
<InkRecordList records="review.suggestions" bind:count>
|
|
241
|
+
{#snippet empty()}No suggestions left.{/snippet}
|
|
242
|
+
</InkRecordList>
|
|
243
|
+
</InkPanel>
|
|
244
|
+
```
|
|
245
|
+
|
|
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.
|
|
247
|
+
|
|
248
|
+
`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
|
+
|
|
250
|
+
To build your own, `useRecords<T>(name)` returns the live `records` and the `handle` to read and write them, each write one undo step, and `useRecordSelection(name)` returns `selected` and `select(id | null)`. Both need `InkRoot`. Plugins are set up once it has mounted, so until then `handle` is null, `records` is empty and nothing is selected. Both lists fill their parent's height and render empty on the server. The keys, focus and announcements are core's: see the [`@inkbar/core` README](https://github.com/inkbar/inkbar/tree/main/packages/core#record-lists).
|
|
251
|
+
|
|
190
252
|
## Custom rendering
|
|
191
253
|
|
|
192
|
-
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 }`. The props each receives are typed as `AnnotationSlotProps` and `ThreadPopoverSlotProps`, the same types the Vue and React packages export.
|
|
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` takes `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
|
+
|
|
256
|
+
`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.
|
|
193
257
|
|
|
194
|
-
`InkRoot`, `InkToolbar`, `InkToolButton`, `InkCommand`, `InkUndo`, `InkRedo`, `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`.
|
|
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`.
|
|
195
259
|
|
|
196
260
|
```svelte
|
|
197
261
|
<InkToolbar>
|
|
@@ -209,6 +273,7 @@ The scroll area takes focus. From there:
|
|
|
209
273
|
- With the `note` tool active, `Enter` places a note at the centre of the current page and opens it.
|
|
210
274
|
- With the `signature` tool active, `Enter` places the chosen signature at the centre of the current page.
|
|
211
275
|
- `Ctrl` or `Cmd` + `Z` undoes. `Ctrl` or `Cmd` + `Shift` + `Z`, or `Ctrl` + `Y`, redoes. `Ctrl` and `Cmd` both work on every platform. The keys do what `InkUndo` and `InkRedo` do, from anywhere in `InkRoot`, toolbars included, and act only while that button is enabled. In a text field or editable content the keys stay with the element, so a field keeps its own text undo. In a `<select>` they are left to the browser, which does nothing with them.
|
|
276
|
+
- `Ctrl` or `Cmd` + `P` prints the PDF and `Ctrl` or `Cmd` + `S` downloads it, as `InkPrintButton` and `InkDownloadButton` do, from anywhere in `InkRoot` once the document is ready. Outside `InkRoot` the browser keeps them.
|
|
212
277
|
|
|
213
278
|
On a focused annotation:
|
|
214
279
|
|
|
@@ -226,7 +291,7 @@ Components carry state on data attributes, for example `[data-annotation][data-s
|
|
|
226
291
|
|
|
227
292
|
## Hooks
|
|
228
293
|
|
|
229
|
-
`useDocument`, `useAnnotations`, `useTool`, `useSelection`, `useZoom`, `usePage`, `useHistory`, `useEmbedded` and `
|
|
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. Tool options, such as the shape kind, 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.
|
|
230
295
|
|
|
231
296
|
## Licence
|
|
232
297
|
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
<script lang="ts" module>
|
|
2
|
+
import type { ChildSnippet } from './child-snippet.js'
|
|
3
|
+
import type { Snippet } from 'svelte'
|
|
4
|
+
import type { HTMLButtonAttributes } from 'svelte/elements'
|
|
5
|
+
|
|
6
|
+
export interface InkDownloadButtonProps extends Omit<HTMLButtonAttributes, 'children'> {
|
|
7
|
+
children?: Snippet | undefined
|
|
8
|
+
/** Render your own button: spread `props` on it. */
|
|
9
|
+
child?: ChildSnippet | undefined
|
|
10
|
+
/** The name to save under. Default: the document's `fileName`. */
|
|
11
|
+
filename?: string | undefined
|
|
12
|
+
}
|
|
13
|
+
</script>
|
|
14
|
+
|
|
15
|
+
<script lang="ts">
|
|
16
|
+
/*
|
|
17
|
+
* Saves the open document's file through the browser's download, under its `fileName`
|
|
18
|
+
* or `filename`. Disabled until the document is ready. Today that is the file as it was opened,
|
|
19
|
+
* without this session's annotations.
|
|
20
|
+
*/
|
|
21
|
+
import { downloadDocument } from '@inkbar/core/dom'
|
|
22
|
+
import { Download } from '@inkbar/icons'
|
|
23
|
+
import { Toolbar } from 'bits-ui'
|
|
24
|
+
|
|
25
|
+
import { useInkContext } from '../context.js'
|
|
26
|
+
|
|
27
|
+
import { asBitsProps } from './child-snippet.js'
|
|
28
|
+
import InkIcon from './InkIcon.svelte'
|
|
29
|
+
|
|
30
|
+
let {
|
|
31
|
+
class: className,
|
|
32
|
+
children,
|
|
33
|
+
child,
|
|
34
|
+
disabled,
|
|
35
|
+
onclick,
|
|
36
|
+
filename,
|
|
37
|
+
...rest
|
|
38
|
+
}: InkDownloadButtonProps = $props()
|
|
39
|
+
|
|
40
|
+
const ctx = useInkContext('InkDownloadButton')
|
|
41
|
+
const ready = $derived(ctx.document !== null && ctx.status.state === 'ready')
|
|
42
|
+
</script>
|
|
43
|
+
|
|
44
|
+
<Toolbar.Button
|
|
45
|
+
class={['ink-download', className]}
|
|
46
|
+
data-ink-download=""
|
|
47
|
+
aria-label="Download"
|
|
48
|
+
title="Download"
|
|
49
|
+
disabled={!ready || !!disabled}
|
|
50
|
+
{...child ? { child } : {}}
|
|
51
|
+
{...asBitsProps(rest)}
|
|
52
|
+
onclick={(e) => {
|
|
53
|
+
onclick?.(e)
|
|
54
|
+
const doc = ctx.document
|
|
55
|
+
if (e.defaultPrevented || !doc) return
|
|
56
|
+
void downloadDocument(doc, filename === undefined ? {} : { filename }).catch(
|
|
57
|
+
() => undefined
|
|
58
|
+
)
|
|
59
|
+
}}
|
|
60
|
+
>
|
|
61
|
+
{#if children}{@render children()}{:else}<InkIcon icon={Download} />{/if}
|
|
62
|
+
</Toolbar.Button>
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { ChildSnippet } from './child-snippet.js';
|
|
2
|
+
import type { Snippet } from 'svelte';
|
|
3
|
+
import type { HTMLButtonAttributes } from 'svelte/elements';
|
|
4
|
+
export interface InkDownloadButtonProps extends Omit<HTMLButtonAttributes, 'children'> {
|
|
5
|
+
children?: Snippet | undefined;
|
|
6
|
+
/** Render your own button: spread `props` on it. */
|
|
7
|
+
child?: ChildSnippet | undefined;
|
|
8
|
+
/** The name to save under. Default: the document's `fileName`. */
|
|
9
|
+
filename?: string | undefined;
|
|
10
|
+
}
|
|
11
|
+
declare const InkDownloadButton: import("svelte").Component<InkDownloadButtonProps, {}, "">;
|
|
12
|
+
type InkDownloadButton = ReturnType<typeof InkDownloadButton>;
|
|
13
|
+
export default InkDownloadButton;
|
|
14
|
+
//# sourceMappingURL=InkDownloadButton.svelte.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"InkDownloadButton.svelte.d.ts","sourceRoot":"","sources":["../../src/lib/components/InkDownloadButton.svelte.ts"],"names":[],"mappings":"AAGE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAA;AACtD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAA;AACrC,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,iBAAiB,CAAA;AAE3D,MAAM,WAAW,sBAAuB,SAAQ,IAAI,CAAC,oBAAoB,EAAE,UAAU,CAAC;IACpF,QAAQ,CAAC,EAAE,OAAO,GAAG,SAAS,CAAA;IAC9B,oDAAoD;IACpD,KAAK,CAAC,EAAE,YAAY,GAAG,SAAS,CAAA;IAChC,kEAAkE;IAClE,QAAQ,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;CAC9B;AAyDH,QAAA,MAAM,iBAAiB,4DAAwC,CAAC;AAChE,KAAK,iBAAiB,GAAG,UAAU,CAAC,OAAO,iBAAiB,CAAC,CAAC;AAC9D,eAAe,iBAAiB,CAAC"}
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
* Closing returns focus to where it was before the bar opened, else to the document.
|
|
19
19
|
*/
|
|
20
20
|
import { findAnnouncer, findCountText } from '@inkbar/core/dom'
|
|
21
|
-
import { CaseSensitive, ChevronDown, ChevronUp, X } from '@inkbar/icons'
|
|
21
|
+
import { CaseSensitive, ChevronDown, ChevronUp, Search, X } from '@inkbar/icons'
|
|
22
22
|
import { untrack } from 'svelte'
|
|
23
23
|
|
|
24
24
|
import { useInkContext } from '../context.js'
|
|
@@ -149,20 +149,23 @@
|
|
|
149
149
|
hidden={!open}
|
|
150
150
|
{@attach into(target)}
|
|
151
151
|
>
|
|
152
|
-
<input
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
e
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
152
|
+
<div class="ink-input-group">
|
|
153
|
+
<span class="ink-input-accessory"><InkIcon icon={Search} /></span>
|
|
154
|
+
<input
|
|
155
|
+
bind:this={field}
|
|
156
|
+
type="text"
|
|
157
|
+
aria-label="Find in document"
|
|
158
|
+
autocomplete="off"
|
|
159
|
+
spellcheck={false}
|
|
160
|
+
oninput={(e) => search.setQuery(e.currentTarget.value)}
|
|
161
|
+
onkeydown={(e) => {
|
|
162
|
+
if (e.key !== 'Enter' || e.isComposing) return
|
|
163
|
+
e.preventDefault()
|
|
164
|
+
if (e.shiftKey) search.previous()
|
|
165
|
+
else search.next()
|
|
166
|
+
}}
|
|
167
|
+
/>
|
|
168
|
+
</div>
|
|
166
169
|
<span class="ink-find-count" data-ink-find-count=""
|
|
167
170
|
>{findCountText(search.state)}</span
|
|
168
171
|
>
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"InkFindBar.svelte.d.ts","sourceRoot":"","sources":["../../src/lib/components/InkFindBar.svelte.ts"],"names":[],"mappings":"AAGE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAA;AAErD,MAAM,MAAM,eAAe,GAAG,IAAI,CAAC,cAAc,CAAC,cAAc,CAAC,EAAE,UAAU,CAAC,CAC/E;
|
|
1
|
+
{"version":3,"file":"InkFindBar.svelte.d.ts","sourceRoot":"","sources":["../../src/lib/components/InkFindBar.svelte.ts"],"names":[],"mappings":"AAGE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAA;AAErD,MAAM,MAAM,eAAe,GAAG,IAAI,CAAC,cAAc,CAAC,cAAc,CAAC,EAAE,UAAU,CAAC,CAC/E;AAsLD,QAAA,MAAM,UAAU,qDAAwC,CAAC;AACzD,KAAK,UAAU,GAAG,UAAU,CAAC,OAAO,UAAU,CAAC,CAAC;AAChD,eAAe,UAAU,CAAC"}
|
|
@@ -30,8 +30,9 @@
|
|
|
30
30
|
import { Popover, Toolbar } from 'bits-ui'
|
|
31
31
|
|
|
32
32
|
import { useDocument } from '../hooks/useDocument.svelte.js'
|
|
33
|
+
import { useRegisteredTool } from '../hooks/useRegisteredTool.svelte.js'
|
|
33
34
|
import { useTool } from '../hooks/useTool.svelte.js'
|
|
34
|
-
import {
|
|
35
|
+
import { useToolOptionsWhile } from '../hooks/useToolOptions.svelte.js'
|
|
35
36
|
|
|
36
37
|
import InkColorMenu from './InkColorMenu.svelte'
|
|
37
38
|
import InkIcon from './InkIcon.svelte'
|
|
@@ -41,7 +42,12 @@
|
|
|
41
42
|
|
|
42
43
|
const current = useTool()
|
|
43
44
|
const inkDocument = useDocument()
|
|
44
|
-
|
|
45
|
+
// Nothing renders while no `highlight` tool is registered.
|
|
46
|
+
const registered = useRegisteredTool('highlight')
|
|
47
|
+
const highlight = useToolOptionsWhile<HighlightOptions>(
|
|
48
|
+
'highlight',
|
|
49
|
+
() => registered.current
|
|
50
|
+
)
|
|
45
51
|
let open = $state(false)
|
|
46
52
|
/** The split button: the panel lines up under it rather than the chevron. */
|
|
47
53
|
let group = $state<HTMLElement | null>(null)
|
|
@@ -72,97 +78,99 @@
|
|
|
72
78
|
}
|
|
73
79
|
</script>
|
|
74
80
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
<Toolbar.Button
|
|
84
|
-
class="ink-tool-button ink-highlight-button"
|
|
85
|
-
data-ink-tool-button=""
|
|
86
|
-
data-ink-highlight-button=""
|
|
87
|
-
data-tool="highlight"
|
|
88
|
-
data-kind={model.kind}
|
|
89
|
-
data-state={active ? 'on' : 'off'}
|
|
90
|
-
aria-pressed={active}
|
|
91
|
-
aria-label={model.label}
|
|
92
|
-
title={model.label}
|
|
93
|
-
onclick={() => current.setTool('highlight')}
|
|
81
|
+
{#if registered.current}
|
|
82
|
+
<div
|
|
83
|
+
bind:this={group}
|
|
84
|
+
role="group"
|
|
85
|
+
aria-label="Highlight"
|
|
86
|
+
class={['ink-split-button ink-highlight-menu', className]}
|
|
87
|
+
data-ink-highlight-menu=""
|
|
88
|
+
{...rest}
|
|
94
89
|
>
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
{
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
90
|
+
<Toolbar.Button
|
|
91
|
+
class="ink-tool-button ink-highlight-button"
|
|
92
|
+
data-ink-tool-button=""
|
|
93
|
+
data-ink-highlight-button=""
|
|
94
|
+
data-tool="highlight"
|
|
95
|
+
data-kind={model.kind}
|
|
96
|
+
data-state={active ? 'on' : 'off'}
|
|
97
|
+
aria-pressed={active}
|
|
98
|
+
aria-label={model.label}
|
|
99
|
+
title={model.label}
|
|
100
|
+
onclick={() => current.setTool('highlight')}
|
|
101
|
+
>
|
|
102
|
+
{#if children}{@render children()}{:else}<InkIcon
|
|
103
|
+
icon={MARKUP_ICONS[model.kind]}
|
|
104
|
+
/>{/if}
|
|
105
|
+
</Toolbar.Button>
|
|
106
|
+
<Popover.Root bind:open>
|
|
107
|
+
<Popover.Trigger>
|
|
108
|
+
{#snippet child({ props })}
|
|
109
|
+
<Toolbar.Button
|
|
110
|
+
{...props}
|
|
111
|
+
class="ink-menu-trigger ink-highlight-menu-trigger"
|
|
112
|
+
data-ink-highlight-menu-trigger=""
|
|
113
|
+
data-state={open ? 'open' : 'closed'}
|
|
114
|
+
aria-label="Choose highlight"
|
|
115
|
+
title="Choose highlight"
|
|
116
|
+
aria-haspopup="menu"
|
|
117
|
+
onkeydown={onTriggerKeydown}
|
|
118
|
+
>
|
|
119
|
+
<InkIcon icon={ChevronDown} />
|
|
120
|
+
</Toolbar.Button>
|
|
121
|
+
{/snippet}
|
|
122
|
+
</Popover.Trigger>
|
|
123
|
+
<Popover.Portal>
|
|
124
|
+
<Popover.Content
|
|
125
|
+
bind:ref={menu}
|
|
126
|
+
role="menu"
|
|
127
|
+
aria-label="Highlights"
|
|
128
|
+
class="ink-select-content ink-menu-content"
|
|
129
|
+
data-ink-select-content=""
|
|
130
|
+
data-ink-menu="highlight"
|
|
131
|
+
customAnchor={group}
|
|
132
|
+
side="bottom"
|
|
133
|
+
align="start"
|
|
134
|
+
sideOffset={4}
|
|
135
|
+
collisionPadding={8}
|
|
136
|
+
onOpenAutoFocus={(e) => openFocus.onOpenAutoFocus(e, menu)}
|
|
137
|
+
onfocusin={openFocus.onfocusin}
|
|
138
|
+
onkeydowncapture={(e: KeyboardEvent) => menu && handleMenuKeys(e, menu)}
|
|
111
139
|
>
|
|
112
|
-
<
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
+
<div role="group" aria-label="Kinds" class="ink-highlight-kinds">
|
|
141
|
+
{#each model.items as item (item.kind)}
|
|
142
|
+
<button
|
|
143
|
+
type="button"
|
|
144
|
+
role="menuitemradio"
|
|
145
|
+
class="ink-select-item ink-menu-item ink-highlight-item"
|
|
146
|
+
data-ink-select-item=""
|
|
147
|
+
tabindex={-1}
|
|
148
|
+
data-key={item.kind}
|
|
149
|
+
aria-checked={item.checked}
|
|
150
|
+
data-state={item.checked ? 'checked' : 'unchecked'}
|
|
151
|
+
onclick={() => choose(item.kind)}
|
|
152
|
+
>
|
|
153
|
+
<InkIcon icon={MARKUP_ICONS[item.kind]} size={16} />
|
|
154
|
+
{item.label}
|
|
155
|
+
</button>
|
|
156
|
+
{/each}
|
|
157
|
+
</div>
|
|
158
|
+
<div role="separator" class="ink-select-separator"></div>
|
|
159
|
+
<div role="group" aria-label="Colour" class="ink-highlight-colour">
|
|
160
|
+
<span class="ink-highlight-colour-label" aria-hidden="true">Colour</span>
|
|
161
|
+
<!-- Capture on the panel moves on from this item before its own ArrowDown opens it. -->
|
|
162
|
+
<InkColorMenu
|
|
163
|
+
property="border"
|
|
164
|
+
group="markup"
|
|
165
|
+
label="Highlight colour"
|
|
166
|
+
standalone
|
|
167
|
+
{fallback}
|
|
168
|
+
role="menuitem"
|
|
140
169
|
tabindex={-1}
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
</button>
|
|
149
|
-
{/each}
|
|
150
|
-
</div>
|
|
151
|
-
<div role="separator" class="ink-select-separator"></div>
|
|
152
|
-
<div role="group" aria-label="Colour" class="ink-highlight-colour">
|
|
153
|
-
<span class="ink-highlight-colour-label" aria-hidden="true">Colour</span>
|
|
154
|
-
<!-- Capture on the panel moves on from this item before its own ArrowDown opens it. -->
|
|
155
|
-
<InkColorMenu
|
|
156
|
-
property="border"
|
|
157
|
-
group="markup"
|
|
158
|
-
label="Highlight colour"
|
|
159
|
-
standalone
|
|
160
|
-
{fallback}
|
|
161
|
-
role="menuitem"
|
|
162
|
-
tabindex={-1}
|
|
163
|
-
/>
|
|
164
|
-
</div>
|
|
165
|
-
</Popover.Content>
|
|
166
|
-
</Popover.Portal>
|
|
167
|
-
</Popover.Root>
|
|
168
|
-
</div>
|
|
170
|
+
/>
|
|
171
|
+
</div>
|
|
172
|
+
</Popover.Content>
|
|
173
|
+
</Popover.Portal>
|
|
174
|
+
</Popover.Root>
|
|
175
|
+
</div>
|
|
176
|
+
{/if}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"InkHighlightMenu.svelte.d.ts","sourceRoot":"","sources":["../../src/lib/components/InkHighlightMenu.svelte.ts"],"names":[],"mappings":"AAGE,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAA;AACrC,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAA;AAErD;;;;;GAKG;AACH,MAAM,WAAW,qBAAsB,SAAQ,IAAI,CACjD,cAAc,CAAC,cAAc,CAAC,EAC9B,UAAU,CACX;IACC,uCAAuC;IACvC,QAAQ,CAAC,EAAE,OAAO,GAAG,SAAS,CAAA;CAC/B;
|
|
1
|
+
{"version":3,"file":"InkHighlightMenu.svelte.d.ts","sourceRoot":"","sources":["../../src/lib/components/InkHighlightMenu.svelte.ts"],"names":[],"mappings":"AAGE,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAA;AACrC,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAA;AAErD;;;;;GAKG;AACH,MAAM,WAAW,qBAAsB,SAAQ,IAAI,CACjD,cAAc,CAAC,cAAc,CAAC,EAC9B,UAAU,CACX;IACC,uCAAuC;IACvC,QAAQ,CAAC,EAAE,OAAO,GAAG,SAAS,CAAA;CAC/B;AAoHH,QAAA,MAAM,gBAAgB,2DAAwC,CAAC;AAC/D,KAAK,gBAAgB,GAAG,UAAU,CAAC,OAAO,gBAAgB,CAAC,CAAC;AAC5D,eAAe,gBAAgB,CAAC"}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
<script module lang="ts">
|
|
2
|
+
import type { IconNode } from '@inkbar/icons'
|
|
3
|
+
import type { Snippet } from 'svelte'
|
|
4
|
+
|
|
5
|
+
export interface InkPanelProps {
|
|
6
|
+
/** Names the panel in the sidebar's `open`. */
|
|
7
|
+
id: string
|
|
8
|
+
/** Names its trigger. */
|
|
9
|
+
label: string
|
|
10
|
+
/** An `@inkbar/icons` node, shown on the rail. */
|
|
11
|
+
icon?: IconNode | undefined
|
|
12
|
+
/** A count or short text beside it, hidden when empty or 0. */
|
|
13
|
+
badge?: string | number | undefined
|
|
14
|
+
/** The panel's content. */
|
|
15
|
+
children?: Snippet | undefined
|
|
16
|
+
}
|
|
17
|
+
</script>
|
|
18
|
+
|
|
19
|
+
<script lang="ts">
|
|
20
|
+
/*
|
|
21
|
+
* One panel of an `InkSidebar`, holding `children` as its content. Closed, the panel stays
|
|
22
|
+
* mounted and hidden. In a stack it renders its own header above its content.
|
|
23
|
+
*/
|
|
24
|
+
import { useSidebarContext } from './sidebar-context.js'
|
|
25
|
+
import SidebarTrigger from './SidebarTrigger.svelte'
|
|
26
|
+
|
|
27
|
+
import type { SidebarPanelInfo } from '@inkbar/core/dom'
|
|
28
|
+
|
|
29
|
+
let { id, label, icon, badge, children }: InkPanelProps = $props()
|
|
30
|
+
|
|
31
|
+
const ctx = useSidebarContext('InkPanel')
|
|
32
|
+
let wrapper = $state<HTMLDivElement | null>(null)
|
|
33
|
+
|
|
34
|
+
const info = (): SidebarPanelInfo => ({
|
|
35
|
+
id,
|
|
36
|
+
label,
|
|
37
|
+
...(icon !== undefined && { icon }),
|
|
38
|
+
...(badge !== undefined && { badge }),
|
|
39
|
+
})
|
|
40
|
+
// Registered now, so the server renders this panel's attributes; ordered once in the DOM.
|
|
41
|
+
const unregister = ctx.register({ info, element: () => wrapper })
|
|
42
|
+
$effect(() => {
|
|
43
|
+
ctx.arrange()
|
|
44
|
+
return unregister
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
const view = $derived.by(() => {
|
|
48
|
+
const s = ctx.state.current
|
|
49
|
+
return {
|
|
50
|
+
stack: s.layout === 'stack',
|
|
51
|
+
trigger: ctx.controller.triggerAttrs(id),
|
|
52
|
+
panel: ctx.controller.panelAttrs(id),
|
|
53
|
+
}
|
|
54
|
+
})
|
|
55
|
+
</script>
|
|
56
|
+
|
|
57
|
+
<!-- A section in a stack; in rail and tabs the theme leaves the layout to the panel. Kept in
|
|
58
|
+
every layout, so the content is never remounted. -->
|
|
59
|
+
<div
|
|
60
|
+
bind:this={wrapper}
|
|
61
|
+
class={['ink-sidebar-slot', view.stack && 'ink-sidebar-section']}
|
|
62
|
+
>
|
|
63
|
+
{#if view.stack}
|
|
64
|
+
<button class="ink-sidebar-header" {...view.trigger}>
|
|
65
|
+
<SidebarTrigger panel={info()} rail={false} />
|
|
66
|
+
</button>
|
|
67
|
+
{/if}
|
|
68
|
+
<div class="ink-sidebar-panel" {...view.panel}>
|
|
69
|
+
<!-- The rail button names the panel for assistive technology; this repeats it on screen. -->
|
|
70
|
+
<div class="ink-sidebar-panel-title" aria-hidden="true">{label}</div>
|
|
71
|
+
<div class="ink-sidebar-panel-body">{@render children?.()}</div>
|
|
72
|
+
</div>
|
|
73
|
+
</div>
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { IconNode } from '@inkbar/icons';
|
|
2
|
+
import type { Snippet } from 'svelte';
|
|
3
|
+
export interface InkPanelProps {
|
|
4
|
+
/** Names the panel in the sidebar's `open`. */
|
|
5
|
+
id: string;
|
|
6
|
+
/** Names its trigger. */
|
|
7
|
+
label: string;
|
|
8
|
+
/** An `@inkbar/icons` node, shown on the rail. */
|
|
9
|
+
icon?: IconNode | undefined;
|
|
10
|
+
/** A count or short text beside it, hidden when empty or 0. */
|
|
11
|
+
badge?: string | number | undefined;
|
|
12
|
+
/** The panel's content. */
|
|
13
|
+
children?: Snippet | undefined;
|
|
14
|
+
}
|
|
15
|
+
declare const InkPanel: import("svelte").Component<InkPanelProps, {}, "">;
|
|
16
|
+
type InkPanel = ReturnType<typeof InkPanel>;
|
|
17
|
+
export default InkPanel;
|
|
18
|
+
//# sourceMappingURL=InkPanel.svelte.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"InkPanel.svelte.d.ts","sourceRoot":"","sources":["../../src/lib/components/InkPanel.svelte.ts"],"names":[],"mappings":"AAGE,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAA;AAC7C,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAA;AAErC,MAAM,WAAW,aAAa;IAC5B,+CAA+C;IAC/C,EAAE,EAAE,MAAM,CAAA;IACV,yBAAyB;IACzB,KAAK,EAAE,MAAM,CAAA;IACb,kDAAkD;IAClD,IAAI,CAAC,EAAE,QAAQ,GAAG,SAAS,CAAA;IAC3B,+DAA+D;IAC/D,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAAA;IACnC,2BAA2B;IAC3B,QAAQ,CAAC,EAAE,OAAO,GAAG,SAAS,CAAA;CAC/B;AAiEH,QAAA,MAAM,QAAQ,mDAAwC,CAAC;AACvD,KAAK,QAAQ,GAAG,UAAU,CAAC,OAAO,QAAQ,CAAC,CAAC;AAC5C,eAAe,QAAQ,CAAC"}
|