@inkbar/svelte 0.0.0-stage → 0.1.1

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.
Files changed (99) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +154 -2
  3. package/dist/class-names.d.ts +3 -0
  4. package/dist/class-names.d.ts.map +1 -0
  5. package/dist/class-names.js +5 -0
  6. package/dist/components/InkAnnotation.svelte +73 -0
  7. package/dist/components/InkAnnotation.svelte.d.ts +13 -0
  8. package/dist/components/InkAnnotation.svelte.d.ts.map +1 -0
  9. package/dist/components/InkNotePopover.svelte +193 -0
  10. package/dist/components/InkNotePopover.svelte.d.ts +9 -0
  11. package/dist/components/InkNotePopover.svelte.d.ts.map +1 -0
  12. package/dist/components/InkPage.svelte +125 -0
  13. package/dist/components/InkPage.svelte.d.ts +22 -0
  14. package/dist/components/InkPage.svelte.d.ts.map +1 -0
  15. package/dist/components/InkPageCount.svelte +20 -0
  16. package/dist/components/InkPageCount.svelte.d.ts +6 -0
  17. package/dist/components/InkPageCount.svelte.d.ts.map +1 -0
  18. package/dist/components/InkPageInput.svelte +49 -0
  19. package/dist/components/InkPageInput.svelte.d.ts +6 -0
  20. package/dist/components/InkPageInput.svelte.d.ts.map +1 -0
  21. package/dist/components/InkRoot.svelte +358 -0
  22. package/dist/components/InkRoot.svelte.d.ts +51 -0
  23. package/dist/components/InkRoot.svelte.d.ts.map +1 -0
  24. package/dist/components/InkSuggestionCard.svelte +121 -0
  25. package/dist/components/InkSuggestionCard.svelte.d.ts +9 -0
  26. package/dist/components/InkSuggestionCard.svelte.d.ts.map +1 -0
  27. package/dist/components/InkToolButton.svelte +54 -0
  28. package/dist/components/InkToolButton.svelte.d.ts +17 -0
  29. package/dist/components/InkToolButton.svelte.d.ts.map +1 -0
  30. package/dist/components/InkToolbar.svelte +40 -0
  31. package/dist/components/InkToolbar.svelte.d.ts +13 -0
  32. package/dist/components/InkToolbar.svelte.d.ts.map +1 -0
  33. package/dist/components/InkViewport.svelte +306 -0
  34. package/dist/components/InkViewport.svelte.d.ts +27 -0
  35. package/dist/components/InkViewport.svelte.d.ts.map +1 -0
  36. package/dist/components/InkZoomIn.svelte +34 -0
  37. package/dist/components/InkZoomIn.svelte.d.ts +5 -0
  38. package/dist/components/InkZoomIn.svelte.d.ts.map +1 -0
  39. package/dist/components/InkZoomOut.svelte +34 -0
  40. package/dist/components/InkZoomOut.svelte.d.ts +5 -0
  41. package/dist/components/InkZoomOut.svelte.d.ts.map +1 -0
  42. package/dist/components/InkZoomSelect.svelte +56 -0
  43. package/dist/components/InkZoomSelect.svelte.d.ts +6 -0
  44. package/dist/components/InkZoomSelect.svelte.d.ts.map +1 -0
  45. package/dist/components/ThreadForm.svelte +42 -0
  46. package/dist/components/ThreadForm.svelte.d.ts +11 -0
  47. package/dist/components/ThreadForm.svelte.d.ts.map +1 -0
  48. package/dist/components/annotation-element.svelte.d.ts +9 -0
  49. package/dist/components/annotation-element.svelte.d.ts.map +1 -0
  50. package/dist/components/annotation-element.svelte.js +25 -0
  51. package/dist/components/child-snippet.d.ts +22 -0
  52. package/dist/components/child-snippet.d.ts.map +1 -0
  53. package/dist/components/child-snippet.js +14 -0
  54. package/dist/components/popover-focus.d.ts +33 -0
  55. package/dist/components/popover-focus.d.ts.map +1 -0
  56. package/dist/components/popover-focus.js +63 -0
  57. package/dist/components/portal.d.ts +26 -0
  58. package/dist/components/portal.d.ts.map +1 -0
  59. package/dist/components/portal.js +55 -0
  60. package/dist/components/zoom-button.d.ts +10 -0
  61. package/dist/components/zoom-button.d.ts.map +1 -0
  62. package/dist/components/zoom-button.js +1 -0
  63. package/dist/compositions/DefaultToolbar.svelte +90 -0
  64. package/dist/compositions/DefaultToolbar.svelte.d.ts +10 -0
  65. package/dist/compositions/DefaultToolbar.svelte.d.ts.map +1 -0
  66. package/dist/context.d.ts +57 -0
  67. package/dist/context.d.ts.map +1 -0
  68. package/dist/context.js +34 -0
  69. package/dist/hooks/useAnnotations.svelte.d.ts +1683 -0
  70. package/dist/hooks/useAnnotations.svelte.d.ts.map +1 -0
  71. package/dist/hooks/useAnnotations.svelte.js +33 -0
  72. package/dist/hooks/useDocument.svelte.d.ts +12 -0
  73. package/dist/hooks/useDocument.svelte.d.ts.map +1 -0
  74. package/dist/hooks/useDocument.svelte.js +28 -0
  75. package/dist/hooks/useEmbedded.svelte.d.ts +7 -0
  76. package/dist/hooks/useEmbedded.svelte.d.ts.map +1 -0
  77. package/dist/hooks/useEmbedded.svelte.js +17 -0
  78. package/dist/hooks/useHistory.svelte.d.ts +7 -0
  79. package/dist/hooks/useHistory.svelte.d.ts.map +1 -0
  80. package/dist/hooks/useHistory.svelte.js +8 -0
  81. package/dist/hooks/usePage.svelte.d.ts +10 -0
  82. package/dist/hooks/usePage.svelte.d.ts.map +1 -0
  83. package/dist/hooks/usePage.svelte.js +29 -0
  84. package/dist/hooks/useSelection.svelte.d.ts +6 -0
  85. package/dist/hooks/useSelection.svelte.d.ts.map +1 -0
  86. package/dist/hooks/useSelection.svelte.js +12 -0
  87. package/dist/hooks/useTool.svelte.d.ts +6 -0
  88. package/dist/hooks/useTool.svelte.d.ts.map +1 -0
  89. package/dist/hooks/useTool.svelte.js +10 -0
  90. package/dist/hooks/useZoom.svelte.d.ts +9 -0
  91. package/dist/hooks/useZoom.svelte.d.ts.map +1 -0
  92. package/dist/hooks/useZoom.svelte.js +18 -0
  93. package/dist/index.d.ts +45 -0
  94. package/dist/index.d.ts.map +1 -0
  95. package/dist/index.js +24 -0
  96. package/dist/use-readable.svelte.d.ts +13 -0
  97. package/dist/use-readable.svelte.d.ts.map +1 -0
  98. package/dist/use-readable.svelte.js +28 -0
  99. package/package.json +56 -3
@@ -0,0 +1,17 @@
1
+ import type { ChildSnippet } from './child-snippet.js';
2
+ import type { ToolId } from '@inkbar/core/dom';
3
+ import type { Snippet } from 'svelte';
4
+ import type { HTMLButtonAttributes } from 'svelte/elements';
5
+ export interface InkToolButtonProps extends Omit<HTMLButtonAttributes, 'children'> {
6
+ tool: ToolId;
7
+ label?: string | undefined;
8
+ children?: Snippet<[{
9
+ active: boolean;
10
+ label: string;
11
+ }]> | undefined;
12
+ child?: ChildSnippet | undefined;
13
+ }
14
+ declare const InkToolButton: import("svelte").Component<InkToolButtonProps, {}, "">;
15
+ type InkToolButton = ReturnType<typeof InkToolButton>;
16
+ export default InkToolButton;
17
+ //# sourceMappingURL=InkToolButton.svelte.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"InkToolButton.svelte.d.ts","sourceRoot":"","sources":["../../src/lib/components/InkToolButton.svelte.ts"],"names":[],"mappings":"AAGE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAA;AACtD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAA;AAC9C,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAA;AACrC,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,iBAAiB,CAAA;AAE3D,MAAM,WAAW,kBAAmB,SAAQ,IAAI,CAAC,oBAAoB,EAAE,UAAU,CAAC;IAChF,IAAI,EAAE,MAAM,CAAA;IACZ,KAAK,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;IAC1B,QAAQ,CAAC,EAAE,OAAO,CAAC,CAAC;QAAE,MAAM,EAAE,OAAO,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC,GAAG,SAAS,CAAA;IACpE,KAAK,CAAC,EAAE,YAAY,GAAG,SAAS,CAAA;CACjC;AA4CH,QAAA,MAAM,aAAa,wDAAwC,CAAC;AAC5D,KAAK,aAAa,GAAG,UAAU,CAAC,OAAO,aAAa,CAAC,CAAC;AACtD,eAAe,aAAa,CAAC"}
@@ -0,0 +1,40 @@
1
+ <script module lang="ts">
2
+ import type { ChildSnippet } from './child-snippet.js'
3
+ import type { Snippet } from 'svelte'
4
+ import type { HTMLAttributes } from 'svelte/elements'
5
+
6
+ export interface InkToolbarProps extends Omit<
7
+ HTMLAttributes<HTMLDivElement>,
8
+ 'children'
9
+ > {
10
+ label?: string | undefined
11
+ children?: Snippet | undefined
12
+ /** Render your own toolbar element: spread `props` on it and put the controls inside. */
13
+ child?: ChildSnippet | undefined
14
+ }
15
+ </script>
16
+
17
+ <script lang="ts">
18
+ import { Toolbar } from 'bits-ui'
19
+
20
+ import { asBitsProps } from './child-snippet.js'
21
+
22
+ let {
23
+ label = 'Document tools',
24
+ class: className,
25
+ children,
26
+ child,
27
+ ...rest
28
+ }: InkToolbarProps = $props()
29
+ </script>
30
+
31
+ <Toolbar.Root
32
+ orientation="horizontal"
33
+ aria-label={label}
34
+ data-ink-toolbar=""
35
+ class={['ink-toolbar', className]}
36
+ {...child ? { child } : {}}
37
+ {...asBitsProps(rest)}
38
+ >
39
+ {@render children?.()}
40
+ </Toolbar.Root>
@@ -0,0 +1,13 @@
1
+ import type { ChildSnippet } from './child-snippet.js';
2
+ import type { Snippet } from 'svelte';
3
+ import type { HTMLAttributes } from 'svelte/elements';
4
+ export interface InkToolbarProps extends Omit<HTMLAttributes<HTMLDivElement>, 'children'> {
5
+ label?: string | undefined;
6
+ children?: Snippet | undefined;
7
+ /** Render your own toolbar element: spread `props` on it and put the controls inside. */
8
+ child?: ChildSnippet | undefined;
9
+ }
10
+ declare const InkToolbar: import("svelte").Component<InkToolbarProps, {}, "">;
11
+ type InkToolbar = ReturnType<typeof InkToolbar>;
12
+ export default InkToolbar;
13
+ //# sourceMappingURL=InkToolbar.svelte.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"InkToolbar.svelte.d.ts","sourceRoot":"","sources":["../../src/lib/components/InkToolbar.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,cAAc,EAAE,MAAM,iBAAiB,CAAA;AAErD,MAAM,WAAW,eAAgB,SAAQ,IAAI,CAC3C,cAAc,CAAC,cAAc,CAAC,EAC9B,UAAU,CACX;IACC,KAAK,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;IAC1B,QAAQ,CAAC,EAAE,OAAO,GAAG,SAAS,CAAA;IAC9B,yFAAyF;IACzF,KAAK,CAAC,EAAE,YAAY,GAAG,SAAS,CAAA;CACjC;AA6BH,QAAA,MAAM,UAAU,qDAAwC,CAAC;AACzD,KAAK,UAAU,GAAG,UAAU,CAAC,OAAO,UAAU,CAAC,CAAC;AAChD,eAAe,UAAU,CAAC"}
@@ -0,0 +1,306 @@
1
+ <script module lang="ts">
2
+ import type { InkError } from '@inkbar/core'
3
+ import type { AnnotationSlotProps, LayoutMode, ViewRotation } from '@inkbar/core/dom'
4
+ import type { Snippet } from 'svelte'
5
+ import type { HTMLAttributes } from 'svelte/elements'
6
+
7
+ export interface InkViewportProps extends Omit<
8
+ HTMLAttributes<HTMLDivElement>,
9
+ 'children'
10
+ > {
11
+ mode?: LayoutMode
12
+ rotation?: ViewRotation
13
+ /** Gap between pages, CSS px. */
14
+ gap?: number
15
+ /** Pages kept mounted either side of the visible ones. */
16
+ buffer?: number
17
+ loading?: Snippet | undefined
18
+ empty?: Snippet | undefined
19
+ error?: Snippet<[{ error: InkError }]> | undefined
20
+ annotation?: Snippet<[AnnotationSlotProps]> | undefined
21
+ /** Shown inside a page that failed to render. The rest of the document stays usable. */
22
+ pageError?: Snippet<[{ error: InkError; page: number }]> | undefined
23
+ }
24
+ </script>
25
+
26
+ <script lang="ts">
27
+ /*
28
+ * The scroll element is handed to core's `mountViewer`, which builds the viewport, the page
29
+ * layers, the tool controller and the keyboard shortcuts in it; Svelte never renders children
30
+ * into it. The engine reports each page host it mounts, and InkPage components rendered here
31
+ * (they render nothing visible in place) portal the page-error and annotation snippets into
32
+ * them.
33
+ *
34
+ * Two-way zoom and page: the viewport's own changes are reported through the context, and
35
+ * the host's props are applied only when they differ from the viewport's state, so the echo
36
+ * of a reported value does nothing.
37
+ */
38
+ import { mountViewer } from '@inkbar/core/dom'
39
+ import { untrack } from 'svelte'
40
+
41
+ import { useInkContext } from '../context.js'
42
+
43
+ import InkPage from './InkPage.svelte'
44
+
45
+ import type { InkDocument, InkStatus } from '@inkbar/core'
46
+ import type { PageMount, ViewerEngine, ZoomValue } from '@inkbar/core/dom'
47
+
48
+ let {
49
+ mode = 'continuous',
50
+ rotation = 0,
51
+ gap = 16,
52
+ buffer = 2,
53
+ loading,
54
+ empty,
55
+ error,
56
+ annotation,
57
+ pageError,
58
+ class: className,
59
+ ...rest
60
+ }: InkViewportProps = $props()
61
+
62
+ interface MountedPage {
63
+ /** The engine build the host belongs to; part of the InkPage key. */
64
+ build: number
65
+ page: number
66
+ host: HTMLElement
67
+ mount: PageMount
68
+ error: InkError | null
69
+ /** Whether the build that mounted the page renders the annotation snippet. */
70
+ custom: boolean
71
+ }
72
+
73
+ const ctx = useInkContext('InkViewport')
74
+ let scroll = $state<HTMLDivElement | null>(null)
75
+ /** Page hosts, mounts and errors the engine reported; InkPage renders portals for them. */
76
+ let pages = $state.raw<MountedPage[]>([])
77
+
78
+ let engine: ViewerEngine | null = null
79
+ /**
80
+ * Numbered before each build starts, so the pages a build mounts carry its own number. A
81
+ * rebuild gives each page a new host and mount, so pages from an earlier build must remount,
82
+ * not be reused.
83
+ */
84
+ let build = 0
85
+ /** The construction options the engine was last built with, so a rebuild runs only on a change. */
86
+ let builtWith: { gap: number; buffer: number; custom: boolean } | null = null
87
+ let stops: (() => void)[] = []
88
+ /** The zoom and page last reported to the host. */
89
+ let lastReportedZoom: ZoomValue | undefined
90
+ let lastReportedPage: number | undefined
91
+
92
+ // The engine's page callbacks fire synchronously inside the effects below (and on scroll).
93
+ // They read and write `pages`, so every call into the engine from an effect runs untracked:
94
+ // tracked, the effect would depend on the list it changes and run again.
95
+ function teardown(): void {
96
+ for (const stop of stops) stop()
97
+ stops = []
98
+ // Unmounts every page, which empties `pages` through `onPageUnmount`.
99
+ engine?.destroy()
100
+ engine = null
101
+ builtWith = null
102
+ ctx.setViewport(null, null)
103
+ if (pages.length > 0) pages = []
104
+ }
105
+
106
+ function setup(
107
+ doc: InkDocument | null,
108
+ state: InkStatus['state'],
109
+ el: HTMLDivElement | null
110
+ ): void {
111
+ lastReportedZoom = undefined
112
+ lastReportedPage = undefined
113
+ if (!doc || !el || state !== 'ready') return
114
+ let first = true
115
+ build += 1
116
+ const custom = !!annotation
117
+ builtWith = { gap, buffer, custom }
118
+ engine = mountViewer({
119
+ container: el,
120
+ document: doc,
121
+ zoom: ctx.zoomModel ?? 'fit-width',
122
+ ...(ctx.pageModel === undefined ? {} : { page: ctx.pageModel }),
123
+ mode,
124
+ rotation,
125
+ gap,
126
+ buffer,
127
+ tool: ctx.tool,
128
+ customAnnotations: custom,
129
+ onActivate: (id) => ctx.setActivated(id),
130
+ onBuild: (vp, tc) => {
131
+ ctx.setViewport(vp, tc)
132
+ if (first) {
133
+ // A new document starts from where the viewport opens (its zoom, on the first page:
134
+ // `createViewport` always starts at page 0); only later changes are reported, the
135
+ // restored page among them, since the engine has already scrolled to it.
136
+ first = false
137
+ lastReportedZoom = vp.zoom.get()
138
+ lastReportedPage = 0
139
+ }
140
+ for (const stop of stops) stop()
141
+ const reportZoom = (z: ZoomValue): void => {
142
+ if (z === lastReportedZoom) return
143
+ lastReportedZoom = z
144
+ ctx.reportZoom(z)
145
+ }
146
+ const reportPage = (p: number): void => {
147
+ if (p === lastReportedPage) return
148
+ lastReportedPage = p
149
+ ctx.reportPage(p)
150
+ }
151
+ stops = [vp.zoom.subscribe(reportZoom), vp.currentPage.subscribe(reportPage)]
152
+ // Whatever the build restored is reported once; nothing is when it matched.
153
+ reportZoom(vp.zoom.get())
154
+ reportPage(vp.currentPage.get())
155
+ },
156
+ onPageMount: (page, mount, host) => {
157
+ // `build` and `builtWith` belong to the build in progress: both are set before it starts.
158
+ const own = {
159
+ build,
160
+ page,
161
+ host,
162
+ mount,
163
+ error: null,
164
+ custom: builtWith?.custom ?? false,
165
+ }
166
+ pages = [...pages, own]
167
+ },
168
+ onPageUnmount: (page) => {
169
+ pages = pages.filter((p) => p.page !== page)
170
+ },
171
+ onPageError: (page, err) => {
172
+ pages = pages.map((p) => (p.page === page ? { ...p, error: err } : p))
173
+ },
174
+ })
175
+ }
176
+
177
+ // Build for a new document or status; tear down on the next change and on unmount. Only the
178
+ // values read before `untrack` are dependencies, each through a derived, which notifies only
179
+ // when the value changes: the status is a new object on every engine report.
180
+ const buildDocument = $derived(ctx.document)
181
+ const buildState = $derived(ctx.status.state)
182
+ $effect(() => {
183
+ const doc = buildDocument
184
+ const state = buildState
185
+ const el = scroll
186
+ untrack(() => setup(doc, state, el))
187
+ return () => untrack(teardown)
188
+ })
189
+
190
+ // Construction-only options rebuild the engine for the same document, keeping zoom and page.
191
+ // The deriveds notify only on a real change: a host that spreads its props invalidates `gap`
192
+ // and `buffer` without changing them. One effect for all three, so a gap and a snippet change
193
+ // in the same tick rebuild once. It skips its first run (and any run after a build that
194
+ // already used these values) by comparing with `builtWith`.
195
+ //
196
+ // A snippet toggle never puts a portal into an overlay built with the other setting. The
197
+ // template updates before this effect runs, so for a moment the old build's pages see the new
198
+ // `annotation` prop, but each InkPage reads targets only when the build that mounted it was
199
+ // custom (`p.custom`), and shows them only while the snippet is given. Then this effect swaps
200
+ // `pages` for the new build's pages. The rebuild keeps the scroll position.
201
+ const buildGap = $derived(gap)
202
+ const buildBuffer = $derived(buffer)
203
+ const buildCustom = $derived(!!annotation)
204
+ $effect(() => {
205
+ const next = { gap: buildGap, buffer: buildBuffer, custom: buildCustom }
206
+ untrack(() => {
207
+ const built = builtWith
208
+ if (!engine || !built) return
209
+ if (
210
+ built.gap === next.gap &&
211
+ built.buffer === next.buffer &&
212
+ built.custom === next.custom
213
+ )
214
+ return
215
+ build += 1
216
+ builtWith = next
217
+ engine.rebuild({
218
+ gap: next.gap,
219
+ buffer: next.buffer,
220
+ customAnnotations: next.custom,
221
+ })
222
+ })
223
+ })
224
+
225
+ // The setters below run untracked: core relays out inside them and the engine's page
226
+ // callbacks read and write `pages`. Tracked, each effect would depend on the list it changes
227
+ // and run again, without end when the host holds a zoom the viewport clamps.
228
+ $effect(() => {
229
+ const z = ctx.zoomModel
230
+ const vp = engine?.viewport
231
+ if (vp && z !== undefined && z !== vp.zoom.get()) untrack(() => vp.setZoom(z))
232
+ })
233
+ $effect(() => {
234
+ const p = ctx.pageModel
235
+ const vp = engine?.viewport
236
+ if (!vp || p === undefined || p === lastReportedPage) return
237
+ if (p !== vp.currentPage.get()) untrack(() => vp.scrollToPage(p))
238
+ })
239
+ $effect(() => {
240
+ const t = ctx.tool
241
+ const tc = engine?.tools
242
+ if (tc && tc.active.get() !== t) untrack(() => tc.set(t))
243
+ })
244
+ // The build applied both; these only act on a later change, not on mount.
245
+ $effect(() => {
246
+ const m = mode
247
+ const vp = engine?.viewport
248
+ if (vp && vp.mode.get() !== m) untrack(() => vp.setMode(m))
249
+ })
250
+ $effect(() => {
251
+ const r = rotation
252
+ const vp = engine?.viewport
253
+ if (vp && vp.rotation.get() !== r) untrack(() => vp.setRotation(r))
254
+ })
255
+
256
+ // Not `state`: next to the `$state` rune, Svelte reads that name as a store.
257
+ const viewState = $derived(buildState)
258
+ const err = $derived(ctx.status.state === 'error' ? ctx.status.error : null)
259
+ </script>
260
+
261
+ <div
262
+ {...rest}
263
+ class={['ink-viewport-root', className]}
264
+ data-ink-viewport-root=""
265
+ data-state={viewState}
266
+ >
267
+ {#if viewState === 'loading'}
268
+ <div class="ink-status" role="status" aria-live="polite">
269
+ {#if loading}{@render loading()}{:else}Loading document…{/if}
270
+ </div>
271
+ {:else if err}
272
+ <div class="ink-status ink-error" role="alert">
273
+ {#if error}
274
+ {@render error({ error: err })}
275
+ {:else}
276
+ <p class="ink-error-title">This document could not be opened.</p>
277
+ <!-- The message comes from the PDF engine and is not written for readers: it stays
278
+ off the page, on the code's tooltip. -->
279
+ <p><code class="ink-error-code" title={err.message}>{err.code}</code></p>
280
+ {/if}
281
+ </div>
282
+ {:else if viewState === 'idle'}
283
+ <div class="ink-status ink-empty">{@render empty?.()}</div>
284
+ {/if}
285
+ <!-- The scroll element takes focus so the keyboard can scroll, zoom and page through it. -->
286
+ <!-- svelte-ignore a11y_no_noninteractive_tabindex -->
287
+ <div
288
+ bind:this={scroll}
289
+ class="ink-scroll"
290
+ data-ink-scroll=""
291
+ tabindex="0"
292
+ role="document"
293
+ aria-label="Document"
294
+ ></div>
295
+ {#each pages as p (`${p.build}:${p.page}`)}
296
+ <InkPage
297
+ page={p.page}
298
+ host={p.host}
299
+ mount={p.mount}
300
+ error={p.error}
301
+ customAnnotations={p.custom}
302
+ {annotation}
303
+ {pageError}
304
+ />
305
+ {/each}
306
+ </div>
@@ -0,0 +1,27 @@
1
+ import type { InkError } from '@inkbar/core';
2
+ import type { AnnotationSlotProps, LayoutMode, ViewRotation } from '@inkbar/core/dom';
3
+ import type { Snippet } from 'svelte';
4
+ import type { HTMLAttributes } from 'svelte/elements';
5
+ export interface InkViewportProps extends Omit<HTMLAttributes<HTMLDivElement>, 'children'> {
6
+ mode?: LayoutMode;
7
+ rotation?: ViewRotation;
8
+ /** Gap between pages, CSS px. */
9
+ gap?: number;
10
+ /** Pages kept mounted either side of the visible ones. */
11
+ buffer?: number;
12
+ loading?: Snippet | undefined;
13
+ empty?: Snippet | undefined;
14
+ error?: Snippet<[{
15
+ error: InkError;
16
+ }]> | undefined;
17
+ annotation?: Snippet<[AnnotationSlotProps]> | undefined;
18
+ /** Shown inside a page that failed to render. The rest of the document stays usable. */
19
+ pageError?: Snippet<[{
20
+ error: InkError;
21
+ page: number;
22
+ }]> | undefined;
23
+ }
24
+ declare const InkViewport: import("svelte").Component<InkViewportProps, {}, "">;
25
+ type InkViewport = ReturnType<typeof InkViewport>;
26
+ export default InkViewport;
27
+ //# sourceMappingURL=InkViewport.svelte.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"InkViewport.svelte.d.ts","sourceRoot":"","sources":["../../src/lib/components/InkViewport.svelte.ts"],"names":[],"mappings":"AAGE,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAA;AAC5C,OAAO,KAAK,EAAE,mBAAmB,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAA;AACrF,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAA;AACrC,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAA;AAErD,MAAM,WAAW,gBAAiB,SAAQ,IAAI,CAC5C,cAAc,CAAC,cAAc,CAAC,EAC9B,UAAU,CACX;IACC,IAAI,CAAC,EAAE,UAAU,CAAA;IACjB,QAAQ,CAAC,EAAE,YAAY,CAAA;IACvB,iCAAiC;IACjC,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,0DAA0D;IAC1D,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,OAAO,CAAC,EAAE,OAAO,GAAG,SAAS,CAAA;IAC7B,KAAK,CAAC,EAAE,OAAO,GAAG,SAAS,CAAA;IAC3B,KAAK,CAAC,EAAE,OAAO,CAAC,CAAC;QAAE,KAAK,EAAE,QAAQ,CAAA;KAAE,CAAC,CAAC,GAAG,SAAS,CAAA;IAClD,UAAU,CAAC,EAAE,OAAO,CAAC,CAAC,mBAAmB,CAAC,CAAC,GAAG,SAAS,CAAA;IACvD,wFAAwF;IACxF,SAAS,CAAC,EAAE,OAAO,CAAC,CAAC;QAAE,KAAK,EAAE,QAAQ,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC,GAAG,SAAS,CAAA;CACrE;AAuRH,QAAA,MAAM,WAAW,sDAAwC,CAAC;AAC1D,KAAK,WAAW,GAAG,UAAU,CAAC,OAAO,WAAW,CAAC,CAAC;AAClD,eAAe,WAAW,CAAC"}
@@ -0,0 +1,34 @@
1
+ <script lang="ts">
2
+ import { Toolbar } from 'bits-ui'
3
+
4
+ import { useZoom } from '../hooks/useZoom.svelte.js'
5
+
6
+ import { asBitsProps } from './child-snippet.js'
7
+
8
+ import type { InkZoomButtonProps } from './zoom-button.js'
9
+
10
+ let {
11
+ class: className,
12
+ children,
13
+ child,
14
+ onclick,
15
+ ...rest
16
+ }: InkZoomButtonProps = $props()
17
+
18
+ const zoom = useZoom()
19
+ </script>
20
+
21
+ <Toolbar.Button
22
+ class={['ink-zoom-in', className]}
23
+ data-ink-zoom-in=""
24
+ aria-label="Zoom in"
25
+ title="Zoom in"
26
+ {...child ? { child } : {}}
27
+ {...asBitsProps(rest)}
28
+ onclick={(e) => {
29
+ onclick?.(e)
30
+ if (!e.defaultPrevented) zoom.zoomIn()
31
+ }}
32
+ >
33
+ {#if children}{@render children()}{:else}+{/if}
34
+ </Toolbar.Button>
@@ -0,0 +1,5 @@
1
+ import type { InkZoomButtonProps } from './zoom-button.js';
2
+ declare const InkZoomIn: import("svelte").Component<InkZoomButtonProps, {}, "">;
3
+ type InkZoomIn = ReturnType<typeof InkZoomIn>;
4
+ export default InkZoomIn;
5
+ //# sourceMappingURL=InkZoomIn.svelte.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"InkZoomIn.svelte.d.ts","sourceRoot":"","sources":["../../src/lib/components/InkZoomIn.svelte.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AA+B3D,QAAA,MAAM,SAAS,wDAAwC,CAAC;AACxD,KAAK,SAAS,GAAG,UAAU,CAAC,OAAO,SAAS,CAAC,CAAC;AAC9C,eAAe,SAAS,CAAC"}
@@ -0,0 +1,34 @@
1
+ <script lang="ts">
2
+ import { Toolbar } from 'bits-ui'
3
+
4
+ import { useZoom } from '../hooks/useZoom.svelte.js'
5
+
6
+ import { asBitsProps } from './child-snippet.js'
7
+
8
+ import type { InkZoomButtonProps } from './zoom-button.js'
9
+
10
+ let {
11
+ class: className,
12
+ children,
13
+ child,
14
+ onclick,
15
+ ...rest
16
+ }: InkZoomButtonProps = $props()
17
+
18
+ const zoom = useZoom()
19
+ </script>
20
+
21
+ <Toolbar.Button
22
+ class={['ink-zoom-out', className]}
23
+ data-ink-zoom-out=""
24
+ aria-label="Zoom out"
25
+ title="Zoom out"
26
+ {...child ? { child } : {}}
27
+ {...asBitsProps(rest)}
28
+ onclick={(e) => {
29
+ onclick?.(e)
30
+ if (!e.defaultPrevented) zoom.zoomOut()
31
+ }}
32
+ >
33
+ {#if children}{@render children()}{:else}−{/if}
34
+ </Toolbar.Button>
@@ -0,0 +1,5 @@
1
+ import type { InkZoomButtonProps } from './zoom-button.js';
2
+ declare const InkZoomOut: import("svelte").Component<InkZoomButtonProps, {}, "">;
3
+ type InkZoomOut = ReturnType<typeof InkZoomOut>;
4
+ export default InkZoomOut;
5
+ //# sourceMappingURL=InkZoomOut.svelte.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"InkZoomOut.svelte.d.ts","sourceRoot":"","sources":["../../src/lib/components/InkZoomOut.svelte.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AA+B3D,QAAA,MAAM,UAAU,wDAAwC,CAAC;AACzD,KAAK,UAAU,GAAG,UAAU,CAAC,OAAO,UAAU,CAAC,CAAC;AAChD,eAAe,UAAU,CAAC"}
@@ -0,0 +1,56 @@
1
+ <script module lang="ts">
2
+ import type { HTMLSelectAttributes } from 'svelte/elements'
3
+
4
+ export type InkZoomSelectProps = Omit<
5
+ HTMLSelectAttributes,
6
+ 'value' | 'onchange' | 'children'
7
+ >
8
+ </script>
9
+
10
+ <script lang="ts">
11
+ import { ZOOM_LEVELS, type ZoomValue } from '@inkbar/core/dom'
12
+
13
+ import { useZoom } from '../hooks/useZoom.svelte.js'
14
+
15
+ let { class: className, ...rest }: InkZoomSelectProps = $props()
16
+
17
+ const CUSTOM = 'custom:'
18
+ const level = (z: number) => ZOOM_LEVELS.find((l) => Math.abs(l - z) < 0.001)
19
+
20
+ const zoom = useZoom()
21
+ /** A numeric zoom that is not one of the levels (zoom in and out multiply). */
22
+ const custom = $derived(
23
+ typeof zoom.zoom === 'number' && level(zoom.zoom) === undefined ? zoom.zoom : null
24
+ )
25
+ const value = $derived(
26
+ typeof zoom.zoom !== 'number'
27
+ ? zoom.zoom
28
+ : custom === null
29
+ ? String(level(zoom.zoom))
30
+ : `${CUSTOM}${zoom.zoom}`
31
+ )
32
+ function onchange(e: Event & { currentTarget: HTMLSelectElement }): void {
33
+ const v = e.currentTarget.value
34
+ const next: ZoomValue =
35
+ v === 'fit-width' || v === 'fit-page' ? v : Number(v.replace(CUSTOM, ''))
36
+ zoom.setZoom(next)
37
+ }
38
+ </script>
39
+
40
+ <select
41
+ class={['ink-zoom-select', className]}
42
+ data-ink-zoom-select=""
43
+ aria-label="Zoom"
44
+ {value}
45
+ {onchange}
46
+ {...rest}
47
+ >
48
+ <option value="fit-width">Fit width</option>
49
+ <option value="fit-page">Fit page</option>
50
+ {#each ZOOM_LEVELS as l (l)}
51
+ <option value={String(l)}>{Math.round(l * 100)}%</option>
52
+ {/each}
53
+ {#if custom !== null}
54
+ <option value={`${CUSTOM}${custom}`}>{Math.round(zoom.scale * 100)}%</option>
55
+ {/if}
56
+ </select>
@@ -0,0 +1,6 @@
1
+ import type { HTMLSelectAttributes } from 'svelte/elements';
2
+ export type InkZoomSelectProps = Omit<HTMLSelectAttributes, 'value' | 'onchange' | 'children'>;
3
+ declare const InkZoomSelect: import("svelte").Component<InkZoomSelectProps, {}, "">;
4
+ type InkZoomSelect = ReturnType<typeof InkZoomSelect>;
5
+ export default InkZoomSelect;
6
+ //# sourceMappingURL=InkZoomSelect.svelte.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"InkZoomSelect.svelte.d.ts","sourceRoot":"","sources":["../../src/lib/components/InkZoomSelect.svelte.ts"],"names":[],"mappings":"AAGE,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,iBAAiB,CAAA;AAE3D,MAAM,MAAM,kBAAkB,GAAG,IAAI,CACnC,oBAAoB,EACpB,OAAO,GAAG,UAAU,GAAG,UAAU,CAClC,CACF;AAmDD,QAAA,MAAM,aAAa,wDAAwC,CAAC;AAC5D,KAAK,aAAa,GAAG,UAAU,CAAC,OAAO,aAAa,CAAC,CAAC;AACtD,eAAe,aAAa,CAAC"}
@@ -0,0 +1,42 @@
1
+ <script lang="ts">
2
+ /** The default form: the root note's own text while it is empty, replies after that. */
3
+ let {
4
+ editingRoot,
5
+ locked,
6
+ onSubmit,
7
+ onRemove,
8
+ onClose,
9
+ }: {
10
+ editingRoot: boolean
11
+ locked: boolean
12
+ onSubmit: (text: string) => void
13
+ onRemove: () => void
14
+ onClose: () => void
15
+ } = $props()
16
+
17
+ let draft = $state('')
18
+ function submit(e: SubmitEvent): void {
19
+ e.preventDefault()
20
+ const text = draft.trim()
21
+ if (!text) return
22
+ onSubmit(text)
23
+ draft = ''
24
+ }
25
+ </script>
26
+
27
+ <form class="ink-thread-reply" data-ink-thread-reply="" onsubmit={submit}>
28
+ <textarea
29
+ bind:value={draft}
30
+ aria-label={editingRoot ? 'Comment' : 'Reply'}
31
+ rows="2"
32
+ placeholder={editingRoot ? 'Comment' : 'Reply'}></textarea>
33
+ <div class="ink-thread-actions">
34
+ <button type="submit" disabled={!draft.trim()}
35
+ >{editingRoot ? 'Comment' : 'Reply'}</button
36
+ >
37
+ {#if !locked}
38
+ <button type="button" onclick={onRemove}>Delete</button>
39
+ {/if}
40
+ <button type="button" onclick={onClose}>Close</button>
41
+ </div>
42
+ </form>
@@ -0,0 +1,11 @@
1
+ type $$ComponentProps = {
2
+ editingRoot: boolean;
3
+ locked: boolean;
4
+ onSubmit: (text: string) => void;
5
+ onRemove: () => void;
6
+ onClose: () => void;
7
+ };
8
+ declare const ThreadForm: import("svelte").Component<$$ComponentProps, {}, "">;
9
+ type ThreadForm = ReturnType<typeof ThreadForm>;
10
+ export default ThreadForm;
11
+ //# sourceMappingURL=ThreadForm.svelte.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ThreadForm.svelte.d.ts","sourceRoot":"","sources":["../../src/lib/components/ThreadForm.svelte.ts"],"names":[],"mappings":"AAGC,KAAK,gBAAgB,GAAI;IACtB,WAAW,EAAE,OAAO,CAAA;IACpB,MAAM,EAAE,OAAO,CAAA;IACf,QAAQ,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAA;IAChC,QAAQ,EAAE,MAAM,IAAI,CAAA;IACpB,OAAO,EAAE,MAAM,IAAI,CAAA;CACpB,CAAC;AAkCJ,QAAA,MAAM,UAAU,sDAAwC,CAAC;AACzD,KAAK,UAAU,GAAG,UAAU,CAAC,OAAO,UAAU,CAAC,CAAC;AAChD,eAAe,UAAU,CAAC"}
@@ -0,0 +1,9 @@
1
+ /**
2
+ * The overlay element core draws for an annotation, for anchoring a popover to it. Core
3
+ * creates and removes these elements outside Svelte, so this follows the root's subtree while
4
+ * an id is wanted.
5
+ */
6
+ export declare function useAnnotationElement(root: () => HTMLElement | null, id: () => string | null): {
7
+ readonly current: HTMLElement | null;
8
+ };
9
+ //# sourceMappingURL=annotation-element.svelte.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"annotation-element.svelte.d.ts","sourceRoot":"","sources":["../../src/lib/components/annotation-element.svelte.ts"],"names":[],"mappings":"AAEA;;;;GAIG;AACH,wBAAgB,oBAAoB,CAClC,IAAI,EAAE,MAAM,WAAW,GAAG,IAAI,EAC9B,EAAE,EAAE,MAAM,MAAM,GAAG,IAAI,GACtB;IAAE,QAAQ,CAAC,OAAO,EAAE,WAAW,GAAG,IAAI,CAAA;CAAE,CAkB1C"}