@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,6 @@
1
+ import type { HTMLAttributes } from 'svelte/elements';
2
+ export type InkPageCountProps = Omit<HTMLAttributes<HTMLSpanElement>, 'children'>;
3
+ declare const InkPageCount: import("svelte").Component<InkPageCountProps, {}, "">;
4
+ type InkPageCount = ReturnType<typeof InkPageCount>;
5
+ export default InkPageCount;
6
+ //# sourceMappingURL=InkPageCount.svelte.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"InkPageCount.svelte.d.ts","sourceRoot":"","sources":["../../src/lib/components/InkPageCount.svelte.ts"],"names":[],"mappings":"AAGE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAA;AAErD,MAAM,MAAM,iBAAiB,GAAG,IAAI,CAAC,cAAc,CAAC,eAAe,CAAC,EAAE,UAAU,CAAC,CAClF;AAkBD,QAAA,MAAM,YAAY,uDAAwC,CAAC;AAC3D,KAAK,YAAY,GAAG,UAAU,CAAC,OAAO,YAAY,CAAC,CAAC;AACpD,eAAe,YAAY,CAAC"}
@@ -0,0 +1,49 @@
1
+ <script module lang="ts">
2
+ import type { HTMLInputAttributes } from 'svelte/elements'
3
+
4
+ export type InkPageInputProps = Omit<
5
+ HTMLInputAttributes,
6
+ 'value' | 'onchange' | 'oninput' | 'type' | 'min' | 'max' | 'children'
7
+ >
8
+ </script>
9
+
10
+ <script lang="ts">
11
+ import { usePage } from '../hooks/usePage.svelte.js'
12
+
13
+ let { class: className, onkeydown, ...rest }: InkPageInputProps = $props()
14
+
15
+ const page = usePage()
16
+ // Writable derived: follows the current page, and holds what the user types until then.
17
+ let draft = $derived(String(page.current + 1))
18
+
19
+ /** 1-based in the input, 0-based in the viewport. Out of range resets to the current page. */
20
+ function commit(value: string): void {
21
+ const n = Number(value)
22
+ if (Number.isInteger(n) && n >= 1 && n <= page.count) {
23
+ if (n - 1 !== page.current) page.goTo(n - 1)
24
+ } else draft = String(page.current + 1)
25
+ }
26
+ function onKeyDown(e: KeyboardEvent & { currentTarget: HTMLInputElement }): void {
27
+ onkeydown?.(e)
28
+ if (e.defaultPrevented || e.key !== 'Enter') return
29
+ e.preventDefault()
30
+ commit(draft)
31
+ }
32
+ </script>
33
+
34
+ <!-- `onchange` is the native change event: it fires on blur after typing, and on each step of
35
+ the spinner or the arrow keys. `oninput` keeps the draft for Enter. -->
36
+ <input
37
+ class={['ink-page-input', className]}
38
+ data-ink-page-input=""
39
+ type="number"
40
+ inputmode="numeric"
41
+ aria-label="Page"
42
+ min={1}
43
+ max={page.count}
44
+ value={draft}
45
+ oninput={(e) => (draft = e.currentTarget.value)}
46
+ onchange={(e) => commit(e.currentTarget.value)}
47
+ onkeydown={onKeyDown}
48
+ {...rest}
49
+ />
@@ -0,0 +1,6 @@
1
+ import type { HTMLInputAttributes } from 'svelte/elements';
2
+ export type InkPageInputProps = Omit<HTMLInputAttributes, 'value' | 'onchange' | 'oninput' | 'type' | 'min' | 'max' | 'children'>;
3
+ declare const InkPageInput: import("svelte").Component<InkPageInputProps, {}, "">;
4
+ type InkPageInput = ReturnType<typeof InkPageInput>;
5
+ export default InkPageInput;
6
+ //# sourceMappingURL=InkPageInput.svelte.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"InkPageInput.svelte.d.ts","sourceRoot":"","sources":["../../src/lib/components/InkPageInput.svelte.ts"],"names":[],"mappings":"AAGE,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,iBAAiB,CAAA;AAE1D,MAAM,MAAM,iBAAiB,GAAG,IAAI,CAClC,mBAAmB,EACnB,OAAO,GAAG,UAAU,GAAG,SAAS,GAAG,MAAM,GAAG,KAAK,GAAG,KAAK,GAAG,UAAU,CACvE,CACF;AAmCD,QAAA,MAAM,YAAY,uDAAwC,CAAC;AAC3D,KAAK,YAAY,GAAG,UAAU,CAAC,OAAO,YAAY,CAAC,CAAC;AACpD,eAAe,YAAY,CAAC"}
@@ -0,0 +1,358 @@
1
+ <script module lang="ts">
2
+ import type { ChildSnippet } from './child-snippet.js'
3
+ import type {
4
+ Annotation,
5
+ AnnotationChange,
6
+ AnnotationDefaults,
7
+ DocumentSource,
8
+ InkDocument,
9
+ InkError,
10
+ InkStatus,
11
+ PdfAssets,
12
+ } from '@inkbar/core'
13
+ import type { ToolId, ZoomValue } from '@inkbar/core/dom'
14
+ import type { Snippet } from 'svelte'
15
+ import type { HTMLAttributes } from 'svelte/elements'
16
+
17
+ export interface InkRootProps extends Omit<
18
+ HTMLAttributes<HTMLDivElement>,
19
+ 'children' | 'onchange' | 'onerror'
20
+ > {
21
+ src: DocumentSource | null
22
+ assets: PdfAssets
23
+ password?: string | undefined
24
+ /** Colour defaults. Without it, the theme's CSS variables on the root element apply. */
25
+ defaults?: AnnotationDefaults | undefined
26
+ /**
27
+ * Pass (with `bind:`) to put annotations under the host's control. The mode is fixed when a
28
+ * document opens: passing or dropping this prop later takes effect on the next open. A host
29
+ * that decides on each proposal uses a function binding.
30
+ */
31
+ annotations?: Annotation[] | undefined
32
+ onAnnotationsChange?: ((next: Annotation[]) => void) | undefined
33
+ onChange?: ((change: AnnotationChange) => void) | undefined
34
+ tool?: ToolId | undefined
35
+ onToolChange?: ((tool: ToolId) => void) | undefined
36
+ page?: number | undefined
37
+ onPageChange?: ((page: number) => void) | undefined
38
+ zoom?: ZoomValue | undefined
39
+ onZoomChange?: ((zoom: ZoomValue) => void) | undefined
40
+ /** Undo history. Defaults to on for uncontrolled, off for controlled. */
41
+ history?: boolean | undefined
42
+ onReady?: ((doc: InkDocument) => void) | undefined
43
+ onError?: ((error: InkError) => void) | undefined
44
+ onActivate?: ((id: string) => void) | undefined
45
+ /** The root element, for hosts that bind it. */
46
+ ref?: HTMLElement | null
47
+ children?: Snippet<[{ status: InkStatus; document: InkDocument | null }]>
48
+ /**
49
+ * Render your own root element: spread `props` on it and put your content inside. The
50
+ * context is still provided, so Inkbar components work inside it.
51
+ */
52
+ child?: ChildSnippet
53
+ }
54
+ </script>
55
+
56
+ <script lang="ts">
57
+ import {
58
+ createSnapshotHistory,
59
+ InkError as InkErrorClass,
60
+ openDocument,
61
+ } from '@inkbar/core'
62
+ import { cssVariableDefaults } from '@inkbar/core/dom'
63
+ import { DEV } from 'esm-env'
64
+ import { untrack } from 'svelte'
65
+ import { createAttachmentKey } from 'svelte/attachments'
66
+
67
+ import { setInkContext, type InkContext, type InkHistory } from '../context.js'
68
+ import { useReadable } from '../use-readable.svelte.js'
69
+
70
+ import type { ToolController, Viewport } from '@inkbar/core/dom'
71
+
72
+ let {
73
+ src,
74
+ assets,
75
+ password,
76
+ defaults,
77
+ annotations = $bindable(),
78
+ onAnnotationsChange,
79
+ onChange,
80
+ tool = $bindable(),
81
+ onToolChange,
82
+ page = $bindable(),
83
+ onPageChange,
84
+ zoom = $bindable(),
85
+ onZoomChange,
86
+ history: historyProp,
87
+ onReady,
88
+ onError,
89
+ onActivate,
90
+ ref = $bindable(null),
91
+ children,
92
+ child,
93
+ ...rest
94
+ }: InkRootProps = $props()
95
+
96
+ type TestHookElement = HTMLElement & { __inkDocument?: InkDocument | undefined }
97
+
98
+ const IDLE: InkStatus = { state: 'idle' }
99
+ const NO_ANNOTATIONS: readonly Annotation[] = []
100
+ const NO_IDS: readonly string[] = []
101
+
102
+ /** Signed URLs change; the key does not. Bytes and Blobs are their own identity. */
103
+ function identity(source: DocumentSource | null): unknown {
104
+ if (source === null) return null
105
+ if (typeof source === 'string') return source
106
+ if ('key' in source) return source.key
107
+ return source
108
+ }
109
+
110
+ function setTestHook(el: HTMLElement | null, doc: InkDocument | undefined): void {
111
+ if (el && DEV) (el as TestHookElement).__inkDocument = doc
112
+ }
113
+
114
+ let document = $state.raw<InkDocument | null>(null)
115
+ /** Overrides the document's status while opening, and when opening itself failed. */
116
+ let override = $state.raw<InkStatus | null>(null)
117
+ let activated = $state<string | null>(null)
118
+ /** The same value outside the reactive graph, for the popovers' close handlers. */
119
+ let activatedNow: string | null = null
120
+ let viewport = $state.raw<Viewport | null>(null)
121
+ let tools = $state.raw<ToolController | null>(null)
122
+
123
+ const documentStatus = useReadable<InkStatus>(() => document?.status, IDLE)
124
+ const status = $derived(override ?? documentStatus.current)
125
+ const annotationList = useReadable<readonly Annotation[]>(
126
+ () => document?.annotations.list,
127
+ NO_ANNOTATIONS
128
+ )
129
+ const selection = useReadable<readonly string[]>(
130
+ () => document?.annotations.selection,
131
+ NO_IDS
132
+ )
133
+ const currentTool = $derived<ToolId>(tool ?? 'view')
134
+
135
+ // What the next open will do follows the prop; the open document's mode is fixed.
136
+ const controlledProp = $derived(annotations !== undefined)
137
+ const controlled = $derived(
138
+ document ? document.annotations.mode === 'controlled' : controlledProp
139
+ )
140
+ const historyEnabled = $derived(controlled && historyProp === true)
141
+
142
+ const snapshotHistory = createSnapshotHistory({
143
+ apply: (list) => document?.annotations.replaceAll(list, 'history'),
144
+ enabled: () => historyEnabled,
145
+ })
146
+ $effect(() => {
147
+ snapshotHistory.observe(annotations)
148
+ })
149
+
150
+ // Open for a new source identity or password. The effect reads them through deriveds, which
151
+ // notify only when the value changes: a host that spreads its props, or passes a new URL under
152
+ // the same key, invalidates `src` without changing the identity. Everything else is read under
153
+ // `untrack`, so a changed `assets`, `defaults` or callback never reopens.
154
+ const sourceIdentity = $derived(identity(src))
155
+ const sourcePassword = $derived(password)
156
+ $effect(() => {
157
+ void sourceIdentity
158
+ const pw = sourcePassword
159
+ let cancelled = false
160
+ let opened: InkDocument | null = null
161
+ untrack(() => snapshotHistory.clear())
162
+ const source = untrack(() => src)
163
+ if (source === null) {
164
+ override = null
165
+ return
166
+ }
167
+ override = { state: 'loading' }
168
+ void (async () => {
169
+ try {
170
+ const doc = await openDocument(
171
+ source,
172
+ untrack(() => ({
173
+ assets,
174
+ password: pw,
175
+ defaults: defaults ?? (ref ? cssVariableDefaults(ref) : undefined),
176
+ // Controlled hosts get the snapshot history; core's own stack stays off.
177
+ history: controlledProp ? false : (historyProp ?? true),
178
+ annotations: controlledProp
179
+ ? {
180
+ mode: 'controlled' as const,
181
+ initial: annotations ?? [],
182
+ onChange: (next: Annotation[], change: AnnotationChange) => {
183
+ // A proposal made in the tick this root unmounts or reopens never reaches
184
+ // the host. Writing the bindable hands it to a bound host now; the effect
185
+ // below pushes the applied list to the store in Svelte's next effect flush.
186
+ if (cancelled) return
187
+ annotations = next
188
+ onAnnotationsChange?.(next)
189
+ onChange?.(change)
190
+ },
191
+ }
192
+ : { mode: 'uncontrolled' as const },
193
+ }))
194
+ )
195
+ if (cancelled) {
196
+ doc.destroy()
197
+ return
198
+ }
199
+ opened = doc
200
+ // The prop may have changed while the file loaded, when there was no store to apply it to.
201
+ const current = untrack(() => annotations)
202
+ if (doc.annotations.mode === 'controlled' && current !== undefined)
203
+ doc.annotations.setControlledValue(current)
204
+ document = doc
205
+ override = null
206
+ setTestHook(ref, doc)
207
+ const s = doc.status.get()
208
+ if (s.state === 'ready') onReady?.(doc)
209
+ else if (s.state === 'error') onError?.(s.error)
210
+ } catch (cause) {
211
+ if (cancelled) return
212
+ const error =
213
+ cause instanceof InkErrorClass
214
+ ? cause
215
+ : new InkErrorClass(
216
+ 'InvalidPdf',
217
+ cause instanceof Error ? cause.message : String(cause),
218
+ {},
219
+ cause
220
+ )
221
+ override = { state: 'error', error }
222
+ onError?.(error)
223
+ }
224
+ })()
225
+ return () => {
226
+ cancelled = true
227
+ opened?.destroy()
228
+ document = null
229
+ setTestHook(ref, undefined)
230
+ }
231
+ })
232
+
233
+ // The host's list reaches the store; the echo of a proposal it applied is the same list.
234
+ $effect(() => {
235
+ const list = annotations
236
+ const doc = document
237
+ if (list && doc?.annotations.mode === 'controlled')
238
+ untrack(() => doc.annotations.setControlledValue(list))
239
+ })
240
+
241
+ function setTool(id: ToolId): void {
242
+ tool = id
243
+ onToolChange?.(id)
244
+ }
245
+ function setActivated(id: string | null): void {
246
+ activated = id
247
+ activatedNow = id
248
+ if (id) onActivate?.(id)
249
+ }
250
+ function reportZoom(z: ZoomValue): void {
251
+ zoom = z
252
+ onZoomChange?.(z)
253
+ }
254
+ function reportPage(p: number): void {
255
+ page = p
256
+ onPageChange?.(p)
257
+ }
258
+
259
+ const snapCanUndo = useReadable(() => snapshotHistory.canUndo, false)
260
+ const snapCanRedo = useReadable(() => snapshotHistory.canRedo, false)
261
+ const coreCanUndo = useReadable(() => document?.history.canUndo, false)
262
+ const coreCanRedo = useReadable(() => document?.history.canRedo, false)
263
+ const inkHistory: InkHistory = {
264
+ get canUndo() {
265
+ return controlled ? historyEnabled && snapCanUndo.current : coreCanUndo.current
266
+ },
267
+ get canRedo() {
268
+ return controlled ? historyEnabled && snapCanRedo.current : coreCanRedo.current
269
+ },
270
+ undo: () => (controlled ? snapshotHistory.undo() : document?.history.undo()),
271
+ redo: () => (controlled ? snapshotHistory.redo() : document?.history.redo()),
272
+ clear: () => (controlled ? snapshotHistory.clear() : document?.history.clear()),
273
+ batch: (fn) => {
274
+ if (controlled) snapshotHistory.batch(fn)
275
+ else if (document) document.history.batch(fn)
276
+ else fn()
277
+ },
278
+ }
279
+
280
+ const context: InkContext = {
281
+ get assets() {
282
+ return assets
283
+ },
284
+ get document() {
285
+ return document
286
+ },
287
+ get status() {
288
+ return status
289
+ },
290
+ get annotations() {
291
+ return annotationList.current
292
+ },
293
+ get selection() {
294
+ return selection.current
295
+ },
296
+ get viewport() {
297
+ return viewport
298
+ },
299
+ get tools() {
300
+ return tools
301
+ },
302
+ get tool() {
303
+ return currentTool
304
+ },
305
+ setTool,
306
+ get zoomModel() {
307
+ return zoom
308
+ },
309
+ get pageModel() {
310
+ return page
311
+ },
312
+ reportZoom,
313
+ reportPage,
314
+ get activated() {
315
+ return activated
316
+ },
317
+ setActivated,
318
+ history: inkHistory,
319
+ get rootEl() {
320
+ return ref
321
+ },
322
+ setViewport(vp, tc) {
323
+ viewport = vp
324
+ tools = tc
325
+ },
326
+ }
327
+ setInkContext(context, () => activatedNow)
328
+
329
+ // Instance exports, for hosts that `bind:this`. Svelte exposes constants and functions, not
330
+ // getters, so the document and status are read through functions.
331
+ export const history = inkHistory
332
+ export function getDocument(): InkDocument | null {
333
+ return document
334
+ }
335
+ export function getStatus(): InkStatus {
336
+ return status
337
+ }
338
+
339
+ /** For the `child` snippet: captures the host's element the way `bind:this` would. */
340
+ const attachRoot = {
341
+ [createAttachmentKey()]: (node: HTMLElement) => {
342
+ ref = node
343
+ return () => {
344
+ ref = null
345
+ }
346
+ },
347
+ }
348
+ </script>
349
+
350
+ {#if child}
351
+ {@render child({
352
+ props: { ...rest, 'data-ink-root': '', 'data-state': status.state, ...attachRoot },
353
+ })}
354
+ {:else}
355
+ <div bind:this={ref} {...rest} data-ink-root="" data-state={status.state}>
356
+ {@render children?.({ status, document })}
357
+ </div>
358
+ {/if}
@@ -0,0 +1,51 @@
1
+ import type { ChildSnippet } from './child-snippet.js';
2
+ import type { Annotation, AnnotationChange, AnnotationDefaults, DocumentSource, InkDocument, InkError, InkStatus, PdfAssets } from '@inkbar/core';
3
+ import type { ToolId, ZoomValue } from '@inkbar/core/dom';
4
+ import type { Snippet } from 'svelte';
5
+ import type { HTMLAttributes } from 'svelte/elements';
6
+ export interface InkRootProps extends Omit<HTMLAttributes<HTMLDivElement>, 'children' | 'onchange' | 'onerror'> {
7
+ src: DocumentSource | null;
8
+ assets: PdfAssets;
9
+ password?: string | undefined;
10
+ /** Colour defaults. Without it, the theme's CSS variables on the root element apply. */
11
+ defaults?: AnnotationDefaults | undefined;
12
+ /**
13
+ * Pass (with `bind:`) to put annotations under the host's control. The mode is fixed when a
14
+ * document opens: passing or dropping this prop later takes effect on the next open. A host
15
+ * that decides on each proposal uses a function binding.
16
+ */
17
+ annotations?: Annotation[] | undefined;
18
+ onAnnotationsChange?: ((next: Annotation[]) => void) | undefined;
19
+ onChange?: ((change: AnnotationChange) => void) | undefined;
20
+ tool?: ToolId | undefined;
21
+ onToolChange?: ((tool: ToolId) => void) | undefined;
22
+ page?: number | undefined;
23
+ onPageChange?: ((page: number) => void) | undefined;
24
+ zoom?: ZoomValue | undefined;
25
+ onZoomChange?: ((zoom: ZoomValue) => void) | undefined;
26
+ /** Undo history. Defaults to on for uncontrolled, off for controlled. */
27
+ history?: boolean | undefined;
28
+ onReady?: ((doc: InkDocument) => void) | undefined;
29
+ onError?: ((error: InkError) => void) | undefined;
30
+ onActivate?: ((id: string) => void) | undefined;
31
+ /** The root element, for hosts that bind it. */
32
+ ref?: HTMLElement | null;
33
+ children?: Snippet<[{
34
+ status: InkStatus;
35
+ document: InkDocument | null;
36
+ }]>;
37
+ /**
38
+ * Render your own root element: spread `props` on it and put your content inside. The
39
+ * context is still provided, so Inkbar components work inside it.
40
+ */
41
+ child?: ChildSnippet;
42
+ }
43
+ import { type InkHistory } from '../context.js';
44
+ declare const InkRoot: import("svelte").Component<InkRootProps, {
45
+ history: InkHistory;
46
+ getDocument: () => InkDocument | null;
47
+ getStatus: () => InkStatus;
48
+ }, "page" | "annotations" | "tool" | "zoom" | "ref">;
49
+ type InkRoot = ReturnType<typeof InkRoot>;
50
+ export default InkRoot;
51
+ //# sourceMappingURL=InkRoot.svelte.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"InkRoot.svelte.d.ts","sourceRoot":"","sources":["../../src/lib/components/InkRoot.svelte.ts"],"names":[],"mappings":"AAGE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAA;AACtD,OAAO,KAAK,EACV,UAAU,EACV,gBAAgB,EAChB,kBAAkB,EAClB,cAAc,EACd,WAAW,EACX,QAAQ,EACR,SAAS,EACT,SAAS,EACV,MAAM,cAAc,CAAA;AACrB,OAAO,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAA;AACzD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAA;AACrC,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAA;AAErD,MAAM,WAAW,YAAa,SAAQ,IAAI,CACxC,cAAc,CAAC,cAAc,CAAC,EAC9B,UAAU,GAAG,UAAU,GAAG,SAAS,CACpC;IACC,GAAG,EAAE,cAAc,GAAG,IAAI,CAAA;IAC1B,MAAM,EAAE,SAAS,CAAA;IACjB,QAAQ,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;IAC7B,wFAAwF;IACxF,QAAQ,CAAC,EAAE,kBAAkB,GAAG,SAAS,CAAA;IACzC;;;;OAIG;IACH,WAAW,CAAC,EAAE,UAAU,EAAE,GAAG,SAAS,CAAA;IACtC,mBAAmB,CAAC,EAAE,CAAC,CAAC,IAAI,EAAE,UAAU,EAAE,KAAK,IAAI,CAAC,GAAG,SAAS,CAAA;IAChE,QAAQ,CAAC,EAAE,CAAC,CAAC,MAAM,EAAE,gBAAgB,KAAK,IAAI,CAAC,GAAG,SAAS,CAAA;IAC3D,IAAI,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;IACzB,YAAY,CAAC,EAAE,CAAC,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC,GAAG,SAAS,CAAA;IACnD,IAAI,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;IACzB,YAAY,CAAC,EAAE,CAAC,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC,GAAG,SAAS,CAAA;IACnD,IAAI,CAAC,EAAE,SAAS,GAAG,SAAS,CAAA;IAC5B,YAAY,CAAC,EAAE,CAAC,CAAC,IAAI,EAAE,SAAS,KAAK,IAAI,CAAC,GAAG,SAAS,CAAA;IACtD,yEAAyE;IACzE,OAAO,CAAC,EAAE,OAAO,GAAG,SAAS,CAAA;IAC7B,OAAO,CAAC,EAAE,CAAC,CAAC,GAAG,EAAE,WAAW,KAAK,IAAI,CAAC,GAAG,SAAS,CAAA;IAClD,OAAO,CAAC,EAAE,CAAC,CAAC,KAAK,EAAE,QAAQ,KAAK,IAAI,CAAC,GAAG,SAAS,CAAA;IACjD,UAAU,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAC,GAAG,SAAS,CAAA;IAC/C,gDAAgD;IAChD,GAAG,CAAC,EAAE,WAAW,GAAG,IAAI,CAAA;IACxB,QAAQ,CAAC,EAAE,OAAO,CAAC,CAAC;QAAE,MAAM,EAAE,SAAS,CAAC;QAAC,QAAQ,EAAE,WAAW,GAAG,IAAI,CAAA;KAAE,CAAC,CAAC,CAAA;IACzE;;;OAGG;IACH,KAAK,CAAC,EAAE,YAAY,CAAA;CACrB;AAaH,OAAO,EAAkC,KAAK,UAAU,EAAE,MAAM,eAAe,CAAA;AAqT/E,QAAA,MAAM,OAAO;;uBAhCc,WAAW,GAAG,IAAI;qBAGpB,SAAS;oDA6BmB,CAAC;AACtD,KAAK,OAAO,GAAG,UAAU,CAAC,OAAO,OAAO,CAAC,CAAC;AAC1C,eAAe,OAAO,CAAC"}
@@ -0,0 +1,121 @@
1
+ <script module lang="ts">
2
+ import type { SuggestionCardSlotProps } from '@inkbar/core/dom'
3
+ import type { Snippet } from 'svelte'
4
+
5
+ export interface InkSuggestionCardProps {
6
+ children?: Snippet<[SuggestionCardSlotProps]> | undefined
7
+ }
8
+ </script>
9
+
10
+ <script lang="ts">
11
+ /*
12
+ * Review card for a suggested redaction: opens when the activated annotation, or the only
13
+ * selected one, is a redaction with `origin: 'suggested'`. Locked suggestions cannot be
14
+ * dismissed, so they get no Dismiss button. A press on the overlay selects and activates, so a
15
+ * click opens it whatever the tool. Closing clears `activated` and the selection.
16
+ */
17
+ import { confidenceBucket } from '@inkbar/core'
18
+ import { Popover } from 'bits-ui'
19
+
20
+ import { useInkContext } from '../context.js'
21
+ import { useAnnotations } from '../hooks/useAnnotations.svelte.js'
22
+
23
+ import { useAnnotationElement } from './annotation-element.svelte.js'
24
+ import { keepOpenOnAnnotation, useFocusReturn } from './popover-focus.js'
25
+
26
+ import type { ConfidenceBucket, RedactAnnotation } from '@inkbar/core'
27
+
28
+ let { children }: InkSuggestionCardProps = $props()
29
+
30
+ const ctx = useInkContext('InkSuggestionCard')
31
+ const store = useAnnotations()
32
+
33
+ function suggestion(id: string | null | undefined): RedactAnnotation | null {
34
+ const a = id ? store.annotations.find((x) => x.id === id) : undefined
35
+ return a?.type === 'redact' && a.origin === 'suggested' ? a : null
36
+ }
37
+ // The activated annotation when it is a suggestion; otherwise the only selected one (a
38
+ // marquee selects without activating).
39
+ const target = $derived(
40
+ suggestion(ctx.activated) ??
41
+ (store.selection.length === 1 ? suggestion(store.selection[0]) : null)
42
+ )
43
+ const anchorRef = useAnnotationElement(
44
+ () => ctx.rootEl,
45
+ () => target?.id ?? null
46
+ )
47
+ const anchor = $derived(anchorRef.current)
48
+ const focusReturn = useFocusReturn(ctx)
49
+ const open = $derived(target !== null && anchor !== null)
50
+ const bucket = $derived<ConfidenceBucket | undefined>(
51
+ target?.suggestion ? confidenceBucket(target.suggestion.confidence) : undefined
52
+ )
53
+
54
+ function close(): void {
55
+ ctx.setActivated(null)
56
+ store.select([])
57
+ }
58
+ function accept(): void {
59
+ if (target) store.accept(target.id)
60
+ close()
61
+ }
62
+ /** Locked annotations cannot be removed: dismissing one does nothing and keeps the card. */
63
+ function dismiss(): void {
64
+ if (!target || target.locked) return
65
+ focusReturn.beforeRemove(anchor, '[data-origin="suggested"]')
66
+ store.dismiss(target.id)
67
+ close()
68
+ }
69
+ function onOpenChange(next: boolean): void {
70
+ if (!next) close()
71
+ }
72
+ </script>
73
+
74
+ <Popover.Root bind:open={() => open, onOpenChange}>
75
+ <Popover.Portal>
76
+ {#if target && anchor}
77
+ <Popover.Content
78
+ class="ink-suggestion-card"
79
+ data-ink-suggestion-card=""
80
+ role="dialog"
81
+ side="bottom"
82
+ sideOffset={6}
83
+ collisionPadding={8}
84
+ aria-label="Suggested redaction"
85
+ customAnchor={anchor}
86
+ onInteractOutside={keepOpenOnAnnotation}
87
+ onOpenAutoFocus={focusReturn.onOpenAutoFocus}
88
+ onCloseAutoFocus={(e) => focusReturn.onCloseAutoFocus(e, anchor)}
89
+ >
90
+ {#if children}
91
+ {@render children({
92
+ annotation: target,
93
+ suggestion: target.suggestion,
94
+ bucket,
95
+ accept,
96
+ dismiss,
97
+ close,
98
+ })}
99
+ {:else}
100
+ <p class="ink-suggestion-reason">
101
+ <strong>{target.suggestion?.reason ?? 'Suggested redaction'}</strong>
102
+ {#if bucket}
103
+ <span class="ink-suggestion-confidence" data-confidence={bucket}>
104
+ {bucket} confidence
105
+ </span>
106
+ {/if}
107
+ </p>
108
+ {#if target.suggestion?.explanation}
109
+ <p class="ink-suggestion-explanation">{target.suggestion.explanation}</p>
110
+ {/if}
111
+ <div class="ink-suggestion-actions">
112
+ <button type="button" onclick={accept}>Accept</button>
113
+ {#if !target.locked}
114
+ <button type="button" onclick={dismiss}>Dismiss</button>
115
+ {/if}
116
+ </div>
117
+ {/if}
118
+ </Popover.Content>
119
+ {/if}
120
+ </Popover.Portal>
121
+ </Popover.Root>
@@ -0,0 +1,9 @@
1
+ import type { SuggestionCardSlotProps } from '@inkbar/core/dom';
2
+ import type { Snippet } from 'svelte';
3
+ export interface InkSuggestionCardProps {
4
+ children?: Snippet<[SuggestionCardSlotProps]> | undefined;
5
+ }
6
+ declare const InkSuggestionCard: import("svelte").Component<InkSuggestionCardProps, {}, "">;
7
+ type InkSuggestionCard = ReturnType<typeof InkSuggestionCard>;
8
+ export default InkSuggestionCard;
9
+ //# sourceMappingURL=InkSuggestionCard.svelte.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"InkSuggestionCard.svelte.d.ts","sourceRoot":"","sources":["../../src/lib/components/InkSuggestionCard.svelte.ts"],"names":[],"mappings":"AAGE,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,kBAAkB,CAAA;AAC/D,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAA;AAErC,MAAM,WAAW,sBAAsB;IACrC,QAAQ,CAAC,EAAE,OAAO,CAAC,CAAC,uBAAuB,CAAC,CAAC,GAAG,SAAS,CAAA;CAC1D;AAwHH,QAAA,MAAM,iBAAiB,4DAAwC,CAAC;AAChE,KAAK,iBAAiB,GAAG,UAAU,CAAC,OAAO,iBAAiB,CAAC,CAAC;AAC9D,eAAe,iBAAiB,CAAC"}
@@ -0,0 +1,54 @@
1
+ <script module lang="ts">
2
+ import type { ChildSnippet } from './child-snippet.js'
3
+ import type { ToolId } from '@inkbar/core/dom'
4
+ import type { Snippet } from 'svelte'
5
+ import type { HTMLButtonAttributes } from 'svelte/elements'
6
+
7
+ export interface InkToolButtonProps extends Omit<HTMLButtonAttributes, 'children'> {
8
+ tool: ToolId
9
+ label?: string | undefined
10
+ children?: Snippet<[{ active: boolean; label: string }]> | undefined
11
+ child?: ChildSnippet | undefined
12
+ }
13
+ </script>
14
+
15
+ <script lang="ts">
16
+ import { TOOL_LABELS } from '@inkbar/core/dom'
17
+ import { Toolbar } from 'bits-ui'
18
+
19
+ import { useTool } from '../hooks/useTool.svelte.js'
20
+
21
+ import { asBitsProps } from './child-snippet.js'
22
+
23
+ let {
24
+ tool,
25
+ label,
26
+ class: className,
27
+ children,
28
+ child,
29
+ onclick,
30
+ ...rest
31
+ }: InkToolButtonProps = $props()
32
+
33
+ const current = useTool()
34
+ const active = $derived(current.tool === tool)
35
+ const name = $derived(label ?? TOOL_LABELS[tool])
36
+ </script>
37
+
38
+ <Toolbar.Button
39
+ class={['ink-tool-button', className]}
40
+ data-ink-tool-button=""
41
+ data-tool={tool}
42
+ data-state={active ? 'on' : 'off'}
43
+ aria-pressed={active}
44
+ aria-label={name}
45
+ title={name}
46
+ {...child ? { child } : {}}
47
+ {...asBitsProps(rest)}
48
+ onclick={(e) => {
49
+ onclick?.(e)
50
+ if (!e.defaultPrevented) current.setTool(tool)
51
+ }}
52
+ >
53
+ {#if children}{@render children({ active, label: name })}{:else}{name}{/if}
54
+ </Toolbar.Button>