@inkbar/svelte 0.3.0 → 0.5.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.
Files changed (66) hide show
  1. package/README.md +17 -12
  2. package/dist/components/InkAnnotation.svelte +0 -12
  3. package/dist/components/InkAnnotation.svelte.d.ts.map +1 -1
  4. package/dist/components/InkColorMenu.svelte +247 -0
  5. package/dist/components/InkColorMenu.svelte.d.ts +35 -0
  6. package/dist/components/InkColorMenu.svelte.d.ts.map +1 -0
  7. package/dist/components/InkHighlightMenu.svelte +175 -0
  8. package/dist/components/InkHighlightMenu.svelte.d.ts +16 -0
  9. package/dist/components/InkHighlightMenu.svelte.d.ts.map +1 -0
  10. package/dist/components/InkLineStyleMenu.svelte +141 -0
  11. package/dist/components/InkLineStyleMenu.svelte.d.ts +15 -0
  12. package/dist/components/InkLineStyleMenu.svelte.d.ts.map +1 -0
  13. package/dist/components/InkRoot.svelte +90 -3
  14. package/dist/components/InkRoot.svelte.d.ts +27 -2
  15. package/dist/components/InkRoot.svelte.d.ts.map +1 -1
  16. package/dist/components/InkShapeMenu.svelte +161 -0
  17. package/dist/components/InkShapeMenu.svelte.d.ts +15 -0
  18. package/dist/components/InkShapeMenu.svelte.d.ts.map +1 -0
  19. package/dist/components/InkSignatureDialog.svelte +70 -0
  20. package/dist/components/InkSignatureDialog.svelte.d.ts +22 -0
  21. package/dist/components/InkSignatureDialog.svelte.d.ts.map +1 -0
  22. package/dist/components/InkSignatureMenu.svelte +267 -0
  23. package/dist/components/InkSignatureMenu.svelte.d.ts +18 -0
  24. package/dist/components/InkSignatureMenu.svelte.d.ts.map +1 -0
  25. package/dist/components/InkTextStyleMenu.svelte +225 -0
  26. package/dist/components/InkTextStyleMenu.svelte.d.ts +15 -0
  27. package/dist/components/InkTextStyleMenu.svelte.d.ts.map +1 -0
  28. package/dist/components/{InkNotePopover.svelte → InkThreadPopover.svelte} +44 -32
  29. package/dist/components/InkThreadPopover.svelte.d.ts +9 -0
  30. package/dist/components/InkThreadPopover.svelte.d.ts.map +1 -0
  31. package/dist/components/InkToolButton.svelte +23 -7
  32. package/dist/components/InkToolButton.svelte.d.ts.map +1 -1
  33. package/dist/components/InkViewport.svelte +26 -2
  34. package/dist/components/InkViewport.svelte.d.ts.map +1 -1
  35. package/dist/components/SignatureForm.svelte +219 -0
  36. package/dist/components/SignatureForm.svelte.d.ts +10 -0
  37. package/dist/components/SignatureForm.svelte.d.ts.map +1 -0
  38. package/dist/components/ThreadForm.svelte +4 -2
  39. package/dist/components/ThreadForm.svelte.d.ts +1 -0
  40. package/dist/components/ThreadForm.svelte.d.ts.map +1 -1
  41. package/dist/components/child-snippet.d.ts +8 -0
  42. package/dist/components/child-snippet.d.ts.map +1 -1
  43. package/dist/components/child-snippet.js +15 -0
  44. package/dist/components/popover-focus.d.ts +16 -2
  45. package/dist/components/popover-focus.d.ts.map +1 -1
  46. package/dist/components/popover-focus.js +24 -3
  47. package/dist/context.d.ts +21 -2
  48. package/dist/context.d.ts.map +1 -1
  49. package/dist/hooks/useAnnotations.svelte.d.ts +1333 -578
  50. package/dist/hooks/useAnnotations.svelte.d.ts.map +1 -1
  51. package/dist/hooks/useAnnotations.svelte.js +1 -7
  52. package/dist/hooks/useToolOptions.svelte.d.ts +11 -0
  53. package/dist/hooks/useToolOptions.svelte.d.ts.map +1 -0
  54. package/dist/hooks/useToolOptions.svelte.js +19 -0
  55. package/dist/hooks/useToolStyle.svelte.d.ts +33 -0
  56. package/dist/hooks/useToolStyle.svelte.d.ts.map +1 -0
  57. package/dist/hooks/useToolStyle.svelte.js +33 -0
  58. package/dist/index.d.ts +21 -6
  59. package/dist/index.d.ts.map +1 -1
  60. package/dist/index.js +10 -2
  61. package/package.json +5 -5
  62. package/dist/components/InkNotePopover.svelte.d.ts +0 -9
  63. package/dist/components/InkNotePopover.svelte.d.ts.map +0 -1
  64. package/dist/components/InkSuggestionCard.svelte +0 -121
  65. package/dist/components/InkSuggestionCard.svelte.d.ts +0 -9
  66. package/dist/components/InkSuggestionCard.svelte.d.ts.map +0 -1
@@ -1,24 +1,26 @@
1
1
  <script module lang="ts">
2
- import type { NotePopoverSlotProps } from '@inkbar/core/dom'
2
+ import type { ThreadPopoverSlotProps } from '@inkbar/core/dom'
3
3
  import type { Snippet } from 'svelte'
4
4
 
5
- export interface InkNotePopoverProps {
6
- children?: Snippet<[NotePopoverSlotProps]> | undefined
5
+ export interface InkThreadPopoverProps {
6
+ children?: Snippet<[ThreadPopoverSlotProps]> | undefined
7
7
  }
8
8
  </script>
9
9
 
10
10
  <script lang="ts">
11
11
  /*
12
- * The thread for the activated note, anchored to the note's overlay element. Activating a
13
- * reply opens the whole thread from its root. Closing (Close, Escape, a press outside) clears
14
- * `activated`.
12
+ * The thread for the activated note or text markup, anchored to the root's overlay
13
+ * element. Activating a reply opens the whole thread from its root. Closing (Close, Escape,
14
+ * a press outside) clears `activated`.
15
15
  *
16
- * A new note has no text. While its root is empty (and unlocked) the default form writes the
17
- * root's own `contents`, labelled "Comment", and the thread list stays hidden until there is
18
- * something in it; after that the form adds replies. Closing a root that is still empty and
19
- * has no replies removes it, so an abandoned note does not stay on the page.
16
+ * A new note, or a highlight, has no text. While its root is empty (and unlocked) the
17
+ * default form writes the root's own `contents`, labelled "Comment", and the thread list
18
+ * stays hidden until there is something in it; after that the form adds replies. Closing a
19
+ * note that is still empty and has no replies removes it, so an abandoned note does not stay
20
+ * on the page. Markup stays: the mark is the point.
20
21
  */
21
22
  import { isAbandonedNote, threadOf, threadRoot } from '@inkbar/core'
23
+ import { threadDeleteLabel, threadPopoverLabel } from '@inkbar/core/dom'
22
24
  import { Popover } from 'bits-ui'
23
25
  import { untrack } from 'svelte'
24
26
 
@@ -29,18 +31,18 @@
29
31
  import { keepOpenOnAnnotation, useFocusReturn } from './popover-focus.js'
30
32
  import ThreadForm from './ThreadForm.svelte'
31
33
 
32
- import type { Annotation, NoteAnnotation } from '@inkbar/core'
34
+ import type { Annotation, ThreadRoot } from '@inkbar/core'
33
35
 
34
- let { children }: InkNotePopoverProps = $props()
36
+ let { children }: InkThreadPopoverProps = $props()
35
37
 
36
- const ctx = useInkContext('InkNotePopover')
38
+ const ctx = useInkContext('InkThreadPopover')
37
39
  const store = useAnnotations()
38
40
 
39
- const note = $derived.by<NoteAnnotation | null>(() => {
41
+ const note = $derived.by<ThreadRoot | null>(() => {
40
42
  const a = ctx.activated
41
43
  ? store.annotations.find((x) => x.id === ctx.activated)
42
44
  : undefined
43
- return a?.type === 'text' ? threadRoot(store.annotations, a) : null
45
+ return a ? threadRoot(store.annotations, a) : null
44
46
  })
45
47
  const anchorRef = useAnnotationElement(
46
48
  () => ctx.rootEl,
@@ -51,11 +53,11 @@
51
53
  const open = $derived(note !== null && anchor !== null)
52
54
  /** The root, then every reply below it, depth first, each level in creation order. */
53
55
  const thread = $derived<Annotation[]>(note ? threadOf(store.annotations, note) : [])
54
- /** True while the default form writes the root note's own text rather than a reply. */
56
+ /** True while the default form writes the root's own text rather than a reply. */
55
57
  const editingRoot = $derived(note !== null && !note.locked && !note.contents?.trim())
56
58
 
57
59
  /**
58
- * Notes this popover wrote text or a reply for. In controlled mode the write is a proposal
60
+ * Roots this popover wrote text or a reply for. In controlled mode the write is a proposal
59
61
  * the host may not have applied yet, so the applied list still shows the note empty; these
60
62
  * are never dropped as abandoned. Bookkeeping only: nothing renders from it.
61
63
  */
@@ -67,7 +69,13 @@
67
69
 
68
70
  /** Removes an unlocked root note with no text and no replies. True when it did. */
69
71
  function dropIfEmpty(id: string): boolean {
70
- if (dropped.has(id) || written.has(id) || !isAbandonedNote(store.annotations, id))
72
+ const a = store.annotations.find((x) => x.id === id)
73
+ if (
74
+ !a ||
75
+ dropped.has(id) ||
76
+ written.has(id) ||
77
+ !isAbandonedNote(a, store.annotations)
78
+ )
71
79
  return false
72
80
  dropped.add(id)
73
81
  store.remove(id)
@@ -97,13 +105,9 @@
97
105
  inReplyTo: root.id,
98
106
  })
99
107
  }
100
- function isEmptyRoot(root: NoteAnnotation): boolean {
101
- return (
102
- !written.has(root.id) &&
103
- !root.locked &&
104
- !root.contents?.trim() &&
105
- thread.length === 1
106
- )
108
+ /** A note that closing now would remove. */
109
+ function isEmptyRoot(root: ThreadRoot): boolean {
110
+ return !written.has(root.id) && isAbandonedNote(root, store.annotations)
107
111
  }
108
112
  function close(): void {
109
113
  const root = note
@@ -118,12 +122,12 @@
118
122
  if (contents.trim()) written.add(note.id)
119
123
  store.update(note.id, { contents })
120
124
  }
121
- /** Locked notes cannot be removed: removing one does nothing and keeps the popover. */
125
+ /** Locked roots cannot be removed: removing one does nothing and keeps the popover. */
122
126
  function removeThread(): void {
123
127
  const root = note
124
128
  if (!root || root.locked) return
125
129
  focusReturn.beforeRemove(anchor)
126
- // Under a slow controlled host the note stays listed for a while; it is not asked for again.
130
+ // Under a slow controlled host the root stays listed for a while; it is not asked for again.
127
131
  dropped.add(root.id)
128
132
  store.remove(root.id)
129
133
  ctx.setActivated(null)
@@ -133,17 +137,24 @@
133
137
  }
134
138
  </script>
135
139
 
140
+ <svelte:document
141
+ onpointerdowncapture={(e) =>
142
+ open && focusReturn.onPressOutside(e, '[data-ink-thread-popover]', close)}
143
+ onpointerup={focusReturn.onPressEnd}
144
+ onpointercancel={focusReturn.onPressEnd}
145
+ />
146
+
136
147
  <Popover.Root bind:open={() => open, onOpenChange}>
137
148
  <Popover.Portal>
138
149
  {#if note && anchor}
139
150
  <Popover.Content
140
- class="ink-note-popover"
141
- data-ink-note-popover=""
151
+ class="ink-thread-popover"
152
+ data-ink-thread-popover=""
142
153
  role="dialog"
143
154
  side="right"
144
155
  sideOffset={8}
145
156
  collisionPadding={8}
146
- aria-label="Note thread"
157
+ aria-label={threadPopoverLabel(note)}
147
158
  customAnchor={anchor}
148
159
  onInteractOutside={keepOpenOnAnnotation}
149
160
  onOpenAutoFocus={focusReturn.onOpenAutoFocus}
@@ -151,7 +162,7 @@
151
162
  >
152
163
  {#if children}
153
164
  {@render children({
154
- note,
165
+ root: note,
155
166
  replies: thread.slice(1),
156
167
  reply,
157
168
  update: updateContents,
@@ -176,11 +187,12 @@
176
187
  {/each}
177
188
  </ol>
178
189
  {/if}
179
- <!-- Keyed by note: moving to another note starts with an empty draft at once. -->
190
+ <!-- Keyed by root: moving to another thread starts with an empty draft at once. -->
180
191
  {#key note.id}
181
192
  <ThreadForm
182
193
  {editingRoot}
183
194
  locked={!!note.locked}
195
+ deleteLabel={threadDeleteLabel(note)}
184
196
  onSubmit={(text) => (editingRoot ? updateContents(text) : reply(text))}
185
197
  onRemove={removeThread}
186
198
  onClose={close}
@@ -0,0 +1,9 @@
1
+ import type { ThreadPopoverSlotProps } from '@inkbar/core/dom';
2
+ import type { Snippet } from 'svelte';
3
+ export interface InkThreadPopoverProps {
4
+ children?: Snippet<[ThreadPopoverSlotProps]> | undefined;
5
+ }
6
+ declare const InkThreadPopover: import("svelte").Component<InkThreadPopoverProps, {}, "">;
7
+ type InkThreadPopover = ReturnType<typeof InkThreadPopover>;
8
+ export default InkThreadPopover;
9
+ //# sourceMappingURL=InkThreadPopover.svelte.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"InkThreadPopover.svelte.d.ts","sourceRoot":"","sources":["../../src/lib/components/InkThreadPopover.svelte.ts"],"names":[],"mappings":"AAGE,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,kBAAkB,CAAA;AAC9D,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAA;AAErC,MAAM,WAAW,qBAAqB;IACpC,QAAQ,CAAC,EAAE,OAAO,CAAC,CAAC,sBAAsB,CAAC,CAAC,GAAG,SAAS,CAAA;CACzD;AAgMH,QAAA,MAAM,gBAAgB,2DAAwC,CAAC;AAC/D,KAAK,gBAAgB,GAAG,UAAU,CAAC,OAAO,gBAAgB,CAAC,CAAC;AAC5D,eAAe,gBAAgB,CAAC"}
@@ -14,7 +14,17 @@
14
14
 
15
15
  <script lang="ts">
16
16
  import { TOOL_LABELS } from '@inkbar/core/dom'
17
- import { Hand, Highlighter, MessageSquare, MousePointer2, Square } from '@inkbar/icons'
17
+ import {
18
+ Highlighter,
19
+ MessageSquare,
20
+ PenLine,
21
+ PenTool,
22
+ Signature,
23
+ Square,
24
+ SquareDashedMousePointer,
25
+ TextCursor,
26
+ Type,
27
+ } from '@inkbar/icons'
18
28
  import { Toolbar } from 'bits-ui'
19
29
 
20
30
  import { useTool } from '../hooks/useTool.svelte.js'
@@ -24,12 +34,16 @@
24
34
 
25
35
  import type { IconNode } from '@inkbar/icons'
26
36
 
27
- const ICONS: Readonly<Record<ToolId, IconNode>> = {
28
- view: Hand,
29
- select: MousePointer2,
37
+ const ICONS: Readonly<Partial<Record<ToolId, IconNode>>> = {
38
+ 'select-text': TextCursor,
39
+ select: SquareDashedMousePointer,
30
40
  highlight: Highlighter,
31
41
  'redact-area': Square,
42
+ sketch: PenTool,
43
+ draw: PenLine,
32
44
  note: MessageSquare,
45
+ freetext: Type,
46
+ signature: Signature,
33
47
  }
34
48
 
35
49
  let {
@@ -44,7 +58,9 @@
44
58
 
45
59
  const current = useTool()
46
60
  const active = $derived(current.tool === tool)
47
- const name = $derived(label ?? TOOL_LABELS[tool])
61
+ // A host tool has no built-in name or icon: `label` and `children` supply them.
62
+ const name = $derived(label ?? TOOL_LABELS[tool] ?? tool)
63
+ const icon = $derived(ICONS[tool])
48
64
  </script>
49
65
 
50
66
  <Toolbar.Button
@@ -62,7 +78,7 @@
62
78
  if (!e.defaultPrevented) current.setTool(tool)
63
79
  }}
64
80
  >
65
- {#if children}{@render children({ active, label: name })}{:else}<InkIcon
66
- icon={ICONS[tool]}
81
+ {#if children}{@render children({ active, label: name })}{:else if icon}<InkIcon
82
+ {icon}
67
83
  />{/if}
68
84
  </Toolbar.Button>
@@ -1 +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;AA4DH,QAAA,MAAM,aAAa,wDAAwC,CAAC;AAC5D,KAAK,aAAa,GAAG,UAAU,CAAC,OAAO,aAAa,CAAC,CAAC;AACtD,eAAe,aAAa,CAAC"}
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;AA4EH,QAAA,MAAM,aAAa,wDAAwC,CAAC;AAC5D,KAAK,aAAa,GAAG,UAAU,CAAC,OAAO,aAAa,CAAC,CAAC;AACtD,eAAe,aAAa,CAAC"}
@@ -43,7 +43,7 @@
43
43
  import InkPage from './InkPage.svelte'
44
44
 
45
45
  import type { InkDocument, InkStatus } from '@inkbar/core'
46
- import type { PageMount, ViewerEngine, ZoomValue } from '@inkbar/core/dom'
46
+ import type { PageMount, ToolId, ViewerEngine, ZoomValue } from '@inkbar/core/dom'
47
47
 
48
48
  let {
49
49
  mode = 'continuous',
@@ -125,6 +125,15 @@
125
125
  gap,
126
126
  buffer,
127
127
  tool: ctx.tool,
128
+ ...(ctx.hostTools ? { tools: ctx.hostTools } : {}),
129
+ // Read through, so the effect below can re-apply a new function without a rebuild.
130
+ decorateAnnotation: (a) => ctx.decorateAnnotation?.(a),
131
+ ...(ctx.toolStyle ? { toolStyle: ctx.toolStyle } : {}),
132
+ toolHistory: { batch: (fn) => ctx.history.batch(fn) },
133
+ toolOptions: ctx.toolOptions,
134
+ fonts: [...ctx.fonts],
135
+ // Read through, so a new `signatures` store reaches the tool as well as the menus.
136
+ signatureLibrary: () => ctx.signatures,
128
137
  customAnnotations: custom,
129
138
  onActivate: (id) => ctx.setActivated(id),
130
139
  onBuild: (vp, tc) => {
@@ -148,7 +157,15 @@
148
157
  lastReportedPage = p
149
158
  ctx.reportPage(p)
150
159
  }
151
- stops = [vp.zoom.subscribe(reportZoom), vp.currentPage.subscribe(reportPage)]
160
+ // A tool that switches itself (a one-shot tool once used) is reported as a pick.
161
+ const followTool = (id: ToolId): void => {
162
+ if (id !== ctx.tool) ctx.setTool(id)
163
+ }
164
+ stops = [
165
+ vp.zoom.subscribe(reportZoom),
166
+ vp.currentPage.subscribe(reportPage),
167
+ tc.active.subscribe(followTool),
168
+ ]
152
169
  // Whatever the build restored is reported once; nothing is when it matched.
153
170
  reportZoom(vp.zoom.get())
154
171
  reportPage(vp.currentPage.get())
@@ -222,6 +239,13 @@
222
239
  })
223
240
  })
224
241
 
242
+ // A new decorateAnnotation re-applies to the mounted annotation elements at once.
243
+ const decorate = $derived(ctx.decorateAnnotation)
244
+ $effect(() => {
245
+ void decorate
246
+ untrack(() => engine?.redecorate())
247
+ })
248
+
225
249
  // The setters below run untracked: core relays out inside them and the engine's page
226
250
  // callbacks read and write `pages`. Tracked, each effect would depend on the list it changes
227
251
  // and run again, without end when the host holds a zoom the viewport clamps.
@@ -1 +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"}
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;AA+SH,QAAA,MAAM,WAAW,sDAAwC,CAAC;AAC1D,KAAK,WAAW,GAAG,UAAU,CAAC,OAAO,WAAW,CAAC,CAAC;AAClD,eAAe,WAAW,CAAC"}
@@ -0,0 +1,219 @@
1
+ <script lang="ts">
2
+ /*
3
+ * The body of `InkSignatureDialog`, mounted while it is open: one draft per opening. Draw,
4
+ * Type and Image tabs, then Cancel and Save.
5
+ */
6
+ import {
7
+ createSignatureDraft,
8
+ fontFamilyValue,
9
+ handleRadioKeys,
10
+ handleTabKeys,
11
+ SIGNATURE_LABELS,
12
+ SIGNATURE_TABS,
13
+ type SignatureTab,
14
+ } from '@inkbar/core/dom'
15
+ import { Dialog } from 'bits-ui'
16
+ import { untrack } from 'svelte'
17
+
18
+ import { useInkContext } from '../context.js'
19
+ import { useTool } from '../hooks/useTool.svelte.js'
20
+ import { useToolOptions } from '../hooks/useToolOptions.svelte.js'
21
+ import { useReadable } from '../use-readable.svelte.js'
22
+
23
+ import type { SignatureOptions, SignatureTemplate } from '@inkbar/core'
24
+
25
+ let {
26
+ fonts,
27
+ onSaved,
28
+ onClose,
29
+ }: {
30
+ fonts: readonly string[] | undefined
31
+ onSaved: (t: SignatureTemplate) => void
32
+ onClose: () => void
33
+ } = $props()
34
+
35
+ const ctx = useInkContext('InkSignatureDialog')
36
+ const current = useTool()
37
+ const signature = useToolOptions<SignatureOptions>('signature')
38
+ // One draft per opening: the fonts are read once.
39
+ const draft = untrack(() =>
40
+ createSignatureDraft({ fonts: fonts ?? ctx.signatureFonts })
41
+ )
42
+ const draftState = useReadable(() => draft.state, draft.state.get())
43
+ const s = $derived(draftState.current)
44
+ const uid = $props.id()
45
+ let padHost = $state<HTMLDivElement | null>(null)
46
+
47
+ $effect(() => {
48
+ if (!padHost) return
49
+ return draft.attachPad(padHost)
50
+ })
51
+ $effect(() => () => draft.destroy())
52
+
53
+ function save(): void {
54
+ draft.save(ctx.signatures).then(
55
+ (t) => {
56
+ if (!t) return
57
+ signature.setOptions({ templateId: t.id })
58
+ current.setTool('signature')
59
+ onSaved(t)
60
+ onClose()
61
+ },
62
+ () => {
63
+ // The host's store refused it: the dialog stays open to try again.
64
+ }
65
+ )
66
+ }
67
+
68
+ const tabId = (tab: SignatureTab) => `${uid}-tab-${tab}`
69
+ const panelId = (tab: SignatureTab) => `${uid}-panel-${tab}`
70
+ const shown = $derived(s.name.trim() || SIGNATURE_LABELS.namePlaceholder)
71
+ </script>
72
+
73
+ <div
74
+ role="tablist"
75
+ tabindex="-1"
76
+ aria-label={SIGNATURE_LABELS.tabs}
77
+ class="ink-tabs"
78
+ onkeydown={(e) => handleTabKeys(e, e.currentTarget)}
79
+ >
80
+ {#each SIGNATURE_TABS as tab (tab)}
81
+ <button
82
+ type="button"
83
+ role="tab"
84
+ id={tabId(tab)}
85
+ aria-controls={panelId(tab)}
86
+ aria-selected={s.tab === tab}
87
+ tabindex={s.tab === tab ? 0 : -1}
88
+ class="ink-tab"
89
+ data-key={tab}
90
+ data-state={s.tab === tab ? 'active' : 'inactive'}
91
+ onclick={() => draft.setTab(tab)}
92
+ >
93
+ {SIGNATURE_LABELS[tab]}
94
+ </button>
95
+ {/each}
96
+ </div>
97
+ <div
98
+ role="tabpanel"
99
+ id={panelId('draw')}
100
+ aria-labelledby={tabId('draw')}
101
+ class="ink-tab-panel"
102
+ data-key="draw"
103
+ hidden={s.tab !== 'draw'}
104
+ >
105
+ <div
106
+ bind:this={padHost}
107
+ class="ink-signature-pad-host"
108
+ data-ink-signature-pad-host=""
109
+ role="img"
110
+ aria-label={SIGNATURE_LABELS.pad}
111
+ ></div>
112
+ <div class="ink-dialog-row">
113
+ <button
114
+ type="button"
115
+ class="ink-button"
116
+ data-ink-signature-clear=""
117
+ disabled={s.padEmpty}
118
+ onclick={() => draft.clearPad()}
119
+ >
120
+ {SIGNATURE_LABELS.clear}
121
+ </button>
122
+ </div>
123
+ </div>
124
+ <div
125
+ role="tabpanel"
126
+ id={panelId('type')}
127
+ aria-labelledby={tabId('type')}
128
+ class="ink-tab-panel"
129
+ data-key="type"
130
+ hidden={s.tab !== 'type'}
131
+ >
132
+ <label class="ink-field">
133
+ <span>{SIGNATURE_LABELS.name}</span>
134
+ <input
135
+ type="text"
136
+ class="ink-input"
137
+ data-ink-signature-name=""
138
+ autocomplete="name"
139
+ placeholder={SIGNATURE_LABELS.namePlaceholder}
140
+ value={s.name}
141
+ oninput={(e) => draft.setName(e.currentTarget.value)}
142
+ />
143
+ </label>
144
+ <div
145
+ role="radiogroup"
146
+ tabindex="-1"
147
+ aria-label={SIGNATURE_LABELS.fonts}
148
+ class="ink-signature-fonts"
149
+ onkeydown={(e) => handleRadioKeys(e, e.currentTarget)}
150
+ >
151
+ {#each draft.fonts as font (font)}
152
+ <button
153
+ type="button"
154
+ role="radio"
155
+ aria-checked={s.font === font}
156
+ aria-label={font}
157
+ tabindex={s.font === font ? 0 : -1}
158
+ class="ink-signature-font"
159
+ data-key={font}
160
+ data-state={s.font === font ? 'checked' : 'unchecked'}
161
+ onclick={() => draft.setFont(font)}
162
+ >
163
+ <!-- The font is the option itself, so it is set here, not left to the theme. -->
164
+ <span class="ink-signature-font-sample" style:font-family={fontFamilyValue(font)}
165
+ >{shown}</span
166
+ >
167
+ </button>
168
+ {/each}
169
+ </div>
170
+ </div>
171
+ <div
172
+ role="tabpanel"
173
+ id={panelId('image')}
174
+ aria-labelledby={tabId('image')}
175
+ class="ink-tab-panel"
176
+ data-key="image"
177
+ hidden={s.tab !== 'image'}
178
+ >
179
+ <label class="ink-field">
180
+ <span>{SIGNATURE_LABELS.file}</span>
181
+ <input
182
+ type="file"
183
+ accept="image/png,image/jpeg"
184
+ class="ink-input"
185
+ data-ink-signature-file=""
186
+ onchange={(e) => void draft.chooseFile(e.currentTarget.files?.[0])}
187
+ />
188
+ </label>
189
+ {#if s.image?.kind === 'image'}
190
+ <img class="ink-signature-image" src={s.image.imagedata} alt="" />
191
+ {/if}
192
+ {#if s.imageError}
193
+ <p class="ink-field-error" role="alert">{s.imageError}</p>
194
+ {/if}
195
+ </div>
196
+ {#if s.saveError}
197
+ <p class="ink-field-error" role="alert" data-ink-signature-save-error="">
198
+ {s.saveError}
199
+ </p>
200
+ {/if}
201
+ <div class="ink-dialog-actions">
202
+ <Dialog.Close>
203
+ {#snippet child({ props })}
204
+ <button {...props} type="button" class="ink-button" data-ink-signature-cancel="">
205
+ {SIGNATURE_LABELS.cancel}
206
+ </button>
207
+ {/snippet}
208
+ </Dialog.Close>
209
+ <button
210
+ type="button"
211
+ class="ink-button"
212
+ data-variant="primary"
213
+ data-ink-signature-save=""
214
+ disabled={!s.canSave}
215
+ onclick={save}
216
+ >
217
+ {SIGNATURE_LABELS.save}
218
+ </button>
219
+ </div>
@@ -0,0 +1,10 @@
1
+ import type { SignatureTemplate } from '@inkbar/core';
2
+ type $$ComponentProps = {
3
+ fonts: readonly string[] | undefined;
4
+ onSaved: (t: SignatureTemplate) => void;
5
+ onClose: () => void;
6
+ };
7
+ declare const SignatureForm: import("svelte").Component<$$ComponentProps, {}, "">;
8
+ type SignatureForm = ReturnType<typeof SignatureForm>;
9
+ export default SignatureForm;
10
+ //# sourceMappingURL=SignatureForm.svelte.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SignatureForm.svelte.d.ts","sourceRoot":"","sources":["../../src/lib/components/SignatureForm.svelte.ts"],"names":[],"mappings":"AAwBA,OAAO,KAAK,EAAoB,iBAAiB,EAAE,MAAM,cAAc,CAAC;AAEvE,KAAK,gBAAgB,GAAI;IACtB,KAAK,EAAE,SAAS,MAAM,EAAE,GAAG,SAAS,CAAA;IACpC,OAAO,EAAE,CAAC,CAAC,EAAE,iBAAiB,KAAK,IAAI,CAAA;IACvC,OAAO,EAAE,MAAM,IAAI,CAAA;CACpB,CAAC;AAuHJ,QAAA,MAAM,aAAa,sDAAwC,CAAC;AAC5D,KAAK,aAAa,GAAG,UAAU,CAAC,OAAO,aAAa,CAAC,CAAC;AACtD,eAAe,aAAa,CAAC"}
@@ -1,14 +1,16 @@
1
1
  <script lang="ts">
2
- /** The default form: the root note's own text while it is empty, replies after that. */
2
+ /** The default form: the root's own text while it is empty, replies after that. */
3
3
  let {
4
4
  editingRoot,
5
5
  locked,
6
+ deleteLabel,
6
7
  onSubmit,
7
8
  onRemove,
8
9
  onClose,
9
10
  }: {
10
11
  editingRoot: boolean
11
12
  locked: boolean
13
+ deleteLabel: string
12
14
  onSubmit: (text: string) => void
13
15
  onRemove: () => void
14
16
  onClose: () => void
@@ -35,7 +37,7 @@
35
37
  >{editingRoot ? 'Comment' : 'Reply'}</button
36
38
  >
37
39
  {#if !locked}
38
- <button type="button" onclick={onRemove}>Delete</button>
40
+ <button type="button" onclick={onRemove}>{deleteLabel}</button>
39
41
  {/if}
40
42
  <button type="button" onclick={onClose}>Close</button>
41
43
  </div>
@@ -1,6 +1,7 @@
1
1
  type $$ComponentProps = {
2
2
  editingRoot: boolean;
3
3
  locked: boolean;
4
+ deleteLabel: string;
4
5
  onSubmit: (text: string) => void;
5
6
  onRemove: () => void;
6
7
  onClose: () => void;
@@ -1 +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"}
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,WAAW,EAAE,MAAM,CAAA;IACnB,QAAQ,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAA;IAChC,QAAQ,EAAE,MAAM,IAAI,CAAA;IACpB,OAAO,EAAE,MAAM,IAAI,CAAA;CACpB,CAAC;AAmCJ,QAAA,MAAM,UAAU,sDAAwC,CAAC;AACzD,KAAK,UAAU,GAAG,UAAU,CAAC,OAAO,UAAU,CAAC,CAAC;AAChD,eAAe,UAAU,CAAC"}
@@ -19,4 +19,12 @@ export type BitsProps<T> = {
19
19
  * `loop`) receives the explicit `null` or `undefined`. This is the one place the cast lives.
20
20
  */
21
21
  export declare function asBitsProps<T extends object>(rest: T): BitsProps<T>;
22
+ type Handler<E> = ((e: E) => unknown) | null | undefined;
23
+ /**
24
+ * One handler for an element both the host and the component listen on: the host's first,
25
+ * then each of `inner` in turn unless the host (or an earlier one) prevented the default.
26
+ * `inner` takes Bits UI's own handler from its `props`, typed `unknown` there.
27
+ */
28
+ export declare function composeHandlers<E extends Event>(host: Handler<E>, ...inner: unknown[]): (e: E) => void;
29
+ export {};
22
30
  //# sourceMappingURL=child-snippet.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"child-snippet.d.ts","sourceRoot":"","sources":["../../src/lib/components/child-snippet.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAA;AAErC,qEAAqE;AACrE,MAAM,MAAM,YAAY,GAAG,OAAO,CAAC,CAAC;IAAE,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CAAE,CAAC,CAAC,CAAA;AAExE,6FAA6F;AAC7F,MAAM,MAAM,SAAS,CAAC,CAAC,IAAI;KAAG,CAAC,IAAI,MAAM,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,IAAI,GAAG,SAAS,CAAC;CAAE,CAAA;AAE9E;;;;;;;;;;GAUG;AACH,wBAAgB,WAAW,CAAC,CAAC,SAAS,MAAM,EAAE,IAAI,EAAE,CAAC,GAAG,SAAS,CAAC,CAAC,CAAC,CAEnE"}
1
+ {"version":3,"file":"child-snippet.d.ts","sourceRoot":"","sources":["../../src/lib/components/child-snippet.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAA;AAErC,qEAAqE;AACrE,MAAM,MAAM,YAAY,GAAG,OAAO,CAAC,CAAC;IAAE,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CAAE,CAAC,CAAC,CAAA;AAExE,6FAA6F;AAC7F,MAAM,MAAM,SAAS,CAAC,CAAC,IAAI;KAAG,CAAC,IAAI,MAAM,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,IAAI,GAAG,SAAS,CAAC;CAAE,CAAA;AAE9E;;;;;;;;;;GAUG;AACH,wBAAgB,WAAW,CAAC,CAAC,SAAS,MAAM,EAAE,IAAI,EAAE,CAAC,GAAG,SAAS,CAAC,CAAC,CAAC,CAEnE;AAED,KAAK,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,KAAK,OAAO,CAAC,GAAG,IAAI,GAAG,SAAS,CAAA;AAExD;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,CAAC,SAAS,KAAK,EAC7C,IAAI,EAAE,OAAO,CAAC,CAAC,CAAC,EAChB,GAAG,KAAK,EAAE,OAAO,EAAE,GAClB,CAAC,CAAC,EAAE,CAAC,KAAK,IAAI,CAQhB"}
@@ -12,3 +12,18 @@
12
12
  export function asBitsProps(rest) {
13
13
  return rest;
14
14
  }
15
+ /**
16
+ * One handler for an element both the host and the component listen on: the host's first,
17
+ * then each of `inner` in turn unless the host (or an earlier one) prevented the default.
18
+ * `inner` takes Bits UI's own handler from its `props`, typed `unknown` there.
19
+ */
20
+ export function composeHandlers(host, ...inner) {
21
+ return (e) => {
22
+ host?.(e);
23
+ for (const fn of inner) {
24
+ if (e.defaultPrevented)
25
+ return;
26
+ fn?.(e);
27
+ }
28
+ };
29
+ }
@@ -5,6 +5,18 @@ export interface PopoverFocusReturn {
5
5
  beforeRemove: FocusReturn['beforeRemove'];
6
6
  /** Bits UI's `onOpenAutoFocus` handler. Leaves the default alone: focus moves into the popover. */
7
7
  onOpenAutoFocus: (event: Event) => void;
8
+ /**
9
+ * A document-level pointer press, captured. `inside` selects the popover's content. A press on
10
+ * the viewer's own scroll element, outside the popover, that will not activate an annotation
11
+ * closes it now, through `close`, before the press moves focus. Bits UI closes it a beat
12
+ * later, and its focus trap pulls focus back into the popover in between, which ends a text
13
+ * selection the press has just begun. Nothing is focused when it closes this way: the press
14
+ * places focus. Every other press (a toolbar, a host's menu portaled out of the popover) is
15
+ * left to Bits UI, whose layer stack knows what belongs to the popover.
16
+ */
17
+ onPressOutside: (event: PointerEvent, inside: string, close: () => void) => void;
18
+ /** A pointer release or cancel, captured. */
19
+ onPressEnd: () => void;
8
20
  /** Bits UI's `onCloseAutoFocus` handler. Prevents the default; the planner places focus. */
9
21
  onCloseAutoFocus: (event: Event, anchor: HTMLElement | null) => void;
10
22
  }
@@ -21,8 +33,10 @@ export declare function keepOpenOnAnnotation(event: PointerEvent): void;
21
33
  * close reaches the planner one microtask late, and only when no open was reported after it.
22
34
  *
23
35
  * This relies on Bits UI reporting a remount as a close and then an open within one synchronous
24
- * call (observed in bits-ui 2.19.5, `FocusScope.mount`). If that changes, the browser case 'a
25
- * card opened by selection alone takes focus without passing it through the annotation' fails.
36
+ * call (observed in bits-ui 2.19.5, `FocusScope.mount`). The unit case 'a close followed by an
37
+ * open in the same tick moves no focus and uses up the removal plan' pins the planner's side, and
38
+ * the browser case 'a popover opened without passing focus through the annotation takes focus'
39
+ * checks that an open moves focus straight into the popover.
26
40
  *
27
41
  * A close that is superseded this way moves no focus, but it still uses up whatever
28
42
  * `beforeRemove` planned for it, so the plan cannot leak into a later close. A close that is