@inkbar/svelte 0.2.0 → 0.4.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 (57) hide show
  1. package/README.md +33 -11
  2. package/dist/components/InkColorMenu.svelte +242 -0
  3. package/dist/components/InkColorMenu.svelte.d.ts +33 -0
  4. package/dist/components/InkColorMenu.svelte.d.ts.map +1 -0
  5. package/dist/components/InkHighlightMenu.svelte +166 -0
  6. package/dist/components/InkHighlightMenu.svelte.d.ts +16 -0
  7. package/dist/components/InkHighlightMenu.svelte.d.ts.map +1 -0
  8. package/dist/components/InkLineStyleMenu.svelte +141 -0
  9. package/dist/components/InkLineStyleMenu.svelte.d.ts +15 -0
  10. package/dist/components/InkLineStyleMenu.svelte.d.ts.map +1 -0
  11. package/dist/components/InkRoot.svelte +77 -3
  12. package/dist/components/InkRoot.svelte.d.ts +20 -2
  13. package/dist/components/InkRoot.svelte.d.ts.map +1 -1
  14. package/dist/components/InkShapeMenu.svelte +157 -0
  15. package/dist/components/InkShapeMenu.svelte.d.ts +15 -0
  16. package/dist/components/InkShapeMenu.svelte.d.ts.map +1 -0
  17. package/dist/components/InkSignatureDialog.svelte +70 -0
  18. package/dist/components/InkSignatureDialog.svelte.d.ts +22 -0
  19. package/dist/components/InkSignatureDialog.svelte.d.ts.map +1 -0
  20. package/dist/components/InkSignatureMenu.svelte +263 -0
  21. package/dist/components/InkSignatureMenu.svelte.d.ts +18 -0
  22. package/dist/components/InkSignatureMenu.svelte.d.ts.map +1 -0
  23. package/dist/components/InkTextStyleMenu.svelte +225 -0
  24. package/dist/components/InkTextStyleMenu.svelte.d.ts +15 -0
  25. package/dist/components/InkTextStyleMenu.svelte.d.ts.map +1 -0
  26. package/dist/components/{InkNotePopover.svelte → InkThreadPopover.svelte} +38 -33
  27. package/dist/components/InkThreadPopover.svelte.d.ts +9 -0
  28. package/dist/components/InkThreadPopover.svelte.d.ts.map +1 -0
  29. package/dist/components/InkToolButton.svelte +23 -7
  30. package/dist/components/InkToolButton.svelte.d.ts.map +1 -1
  31. package/dist/components/InkViewport.svelte +7 -0
  32. package/dist/components/InkViewport.svelte.d.ts.map +1 -1
  33. package/dist/components/SignatureForm.svelte +219 -0
  34. package/dist/components/SignatureForm.svelte.d.ts +10 -0
  35. package/dist/components/SignatureForm.svelte.d.ts.map +1 -0
  36. package/dist/components/ThreadForm.svelte +4 -2
  37. package/dist/components/ThreadForm.svelte.d.ts +1 -0
  38. package/dist/components/ThreadForm.svelte.d.ts.map +1 -1
  39. package/dist/components/child-snippet.d.ts +8 -0
  40. package/dist/components/child-snippet.d.ts.map +1 -1
  41. package/dist/components/child-snippet.js +15 -0
  42. package/dist/context.d.ts +14 -2
  43. package/dist/context.d.ts.map +1 -1
  44. package/dist/hooks/useAnnotations.svelte.d.ts +1364 -146
  45. package/dist/hooks/useAnnotations.svelte.d.ts.map +1 -1
  46. package/dist/hooks/useToolOptions.svelte.d.ts +11 -0
  47. package/dist/hooks/useToolOptions.svelte.d.ts.map +1 -0
  48. package/dist/hooks/useToolOptions.svelte.js +19 -0
  49. package/dist/hooks/useToolStyle.svelte.d.ts +33 -0
  50. package/dist/hooks/useToolStyle.svelte.d.ts.map +1 -0
  51. package/dist/hooks/useToolStyle.svelte.js +33 -0
  52. package/dist/index.d.ts +20 -3
  53. package/dist/index.d.ts.map +1 -1
  54. package/dist/index.js +10 -1
  55. package/package.json +5 -5
  56. package/dist/components/InkNotePopover.svelte.d.ts +0 -9
  57. package/dist/components/InkNotePopover.svelte.d.ts.map +0 -1
@@ -0,0 +1,141 @@
1
+ <script module lang="ts">
2
+ import type { Snippet } from 'svelte'
3
+ import type { HTMLButtonAttributes } from 'svelte/elements'
4
+
5
+ /**
6
+ * Line weight, solid or dashed, and arrowheads, for the selection or the active tool. Each
7
+ * section disables when nothing selected (or no active tool) supports its props. The
8
+ * trigger carries `data-mixed` when the selection disagrees on any of them; a section with
9
+ * no single value shows no checkmark.
10
+ */
11
+ export interface InkLineStyleMenuProps extends Omit<HTMLButtonAttributes, 'children'> {
12
+ children?: Snippet | undefined
13
+ }
14
+ </script>
15
+
16
+ <script lang="ts">
17
+ import {
18
+ focusInitialMenuItem,
19
+ handleMenuKeys,
20
+ lineStyleMenuModel,
21
+ menuItems,
22
+ } from '@inkbar/core/dom'
23
+ import { Check, Minus } from '@inkbar/icons'
24
+ import { Popover, Toolbar } from 'bits-ui'
25
+
26
+ import { useToolStyle } from '../hooks/useToolStyle.svelte.js'
27
+
28
+ import { asBitsProps, composeHandlers } from './child-snippet.js'
29
+ import InkIcon from './InkIcon.svelte'
30
+
31
+ let {
32
+ class: className,
33
+ children,
34
+ disabled,
35
+ onclick,
36
+ onkeydown,
37
+ ...rest
38
+ }: InkLineStyleMenuProps = $props()
39
+
40
+ const style = useToolStyle()
41
+ let open = $state(false)
42
+
43
+ const model = $derived(lineStyleMenuModel(style.current, style.supported))
44
+
45
+ let menu = $state<HTMLElement | null>(null)
46
+
47
+ // The menu closes when the control disables (the selection cleared while it was open).
48
+ $effect(() => {
49
+ if (!model.enabled) open = false
50
+ })
51
+
52
+ /** ArrowDown opens on the first item; a click opens on the checked one. */
53
+ let focusFirst = false
54
+ // Bits runs the open-focus callback more than once, so the flag outlives it; reset on close.
55
+ $effect(() => {
56
+ if (!open) focusFirst = false
57
+ })
58
+ function onTriggerKeydown(e: KeyboardEvent): void {
59
+ if (e.key !== 'ArrowDown' || e.defaultPrevented || open || !model.enabled) return
60
+ e.preventDefault()
61
+ focusFirst = true
62
+ open = true
63
+ }
64
+ </script>
65
+
66
+ <Popover.Root bind:open>
67
+ <Popover.Trigger>
68
+ {#snippet child({ props })}
69
+ <Toolbar.Button
70
+ {...props}
71
+ {...asBitsProps(rest)}
72
+ class={['ink-menu-trigger ink-line-style-menu', className]}
73
+ data-ink-line-style-menu=""
74
+ data-state={open ? 'open' : 'closed'}
75
+ data-mixed={model.mixed ? '' : undefined}
76
+ aria-label="Line style"
77
+ title="Line style"
78
+ aria-haspopup="menu"
79
+ onclick={composeHandlers(onclick, props.onclick)}
80
+ onkeydown={composeHandlers(onkeydown, props.onkeydown, onTriggerKeydown)}
81
+ disabled={!!disabled || !model.enabled}
82
+ >
83
+ {#if children}{@render children()}{:else}<InkIcon icon={Minus} />{/if}
84
+ </Toolbar.Button>
85
+ {/snippet}
86
+ </Popover.Trigger>
87
+ <Popover.Portal>
88
+ <Popover.Content
89
+ bind:ref={menu}
90
+ role="menu"
91
+ aria-label="Line style"
92
+ class="ink-select-content ink-menu-content"
93
+ data-ink-select-content=""
94
+ data-ink-menu="line-style"
95
+ side="bottom"
96
+ align="start"
97
+ sideOffset={4}
98
+ collisionPadding={8}
99
+ onOpenAutoFocus={(e) => {
100
+ e.preventDefault()
101
+ if (menu) {
102
+ if (focusFirst) menuItems(menu)[0]?.focus({ preventScroll: true })
103
+ else focusInitialMenuItem(menu)
104
+ }
105
+ }}
106
+ onkeydown={(e) => menu && handleMenuKeys(e, menu)}
107
+ >
108
+ {#each model.sections as section (section.label)}
109
+ <div
110
+ role="group"
111
+ class="ink-menu-group"
112
+ aria-label={section.label}
113
+ aria-disabled={section.disabled || undefined}
114
+ data-disabled={section.disabled ? '' : undefined}
115
+ >
116
+ <div class="ink-menu-label" aria-hidden="true">{section.label}</div>
117
+ {#each section.items as item (item.key)}
118
+ <button
119
+ type="button"
120
+ role="menuitemradio"
121
+ class="ink-select-item ink-menu-item"
122
+ data-ink-select-item=""
123
+ tabindex={-1}
124
+ aria-checked={item.checked}
125
+ disabled={section.disabled}
126
+ data-state={item.checked ? 'checked' : 'unchecked'}
127
+ onclick={() => style.set(item.patch)}
128
+ >
129
+ {item.label}
130
+ {#if item.checked}
131
+ <span class="ink-select-indicator"
132
+ ><InkIcon icon={Check} size={16} /></span
133
+ >
134
+ {/if}
135
+ </button>
136
+ {/each}
137
+ </div>
138
+ {/each}
139
+ </Popover.Content>
140
+ </Popover.Portal>
141
+ </Popover.Root>
@@ -0,0 +1,15 @@
1
+ import type { Snippet } from 'svelte';
2
+ import type { HTMLButtonAttributes } from 'svelte/elements';
3
+ /**
4
+ * Line weight, solid or dashed, and arrowheads, for the selection or the active tool. Each
5
+ * section disables when nothing selected (or no active tool) supports its props. The
6
+ * trigger carries `data-mixed` when the selection disagrees on any of them; a section with
7
+ * no single value shows no checkmark.
8
+ */
9
+ export interface InkLineStyleMenuProps extends Omit<HTMLButtonAttributes, 'children'> {
10
+ children?: Snippet | undefined;
11
+ }
12
+ declare const InkLineStyleMenu: import("svelte").Component<InkLineStyleMenuProps, {}, "">;
13
+ type InkLineStyleMenu = ReturnType<typeof InkLineStyleMenu>;
14
+ export default InkLineStyleMenu;
15
+ //# sourceMappingURL=InkLineStyleMenu.svelte.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"InkLineStyleMenu.svelte.d.ts","sourceRoot":"","sources":["../../src/lib/components/InkLineStyleMenu.svelte.ts"],"names":[],"mappings":"AAGE,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAA;AACrC,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,iBAAiB,CAAA;AAE3D;;;;;GAKG;AACH,MAAM,WAAW,qBAAsB,SAAQ,IAAI,CAAC,oBAAoB,EAAE,UAAU,CAAC;IACnF,QAAQ,CAAC,EAAE,OAAO,GAAG,SAAS,CAAA;CAC/B;AAmGH,QAAA,MAAM,gBAAgB,2DAAwC,CAAC;AAC/D,KAAK,gBAAgB,GAAG,UAAU,CAAC,OAAO,gBAAgB,CAAC,CAAC;AAC5D,eAAe,gBAAgB,CAAC"}
@@ -9,8 +9,9 @@
9
9
  InkError,
10
10
  InkStatus,
11
11
  PdfAssets,
12
+ SignatureStore,
12
13
  } from '@inkbar/core'
13
- import type { ToolId, ZoomValue } from '@inkbar/core/dom'
14
+ import type { Tool, ToolId, ZoomValue } from '@inkbar/core/dom'
14
15
  import type { Snippet } from 'svelte'
15
16
  import type { HTMLAttributes } from 'svelte/elements'
16
17
 
@@ -33,6 +34,24 @@
33
34
  onChange?: ((change: AnnotationChange) => void) | undefined
34
35
  tool?: ToolId | undefined
35
36
  onToolChange?: ((tool: ToolId) => void) | undefined
37
+ /**
38
+ * Host tools, added to the built-ins; one with a built-in's id replaces it. Read when a
39
+ * document opens.
40
+ */
41
+ tools?: Tool[] | undefined
42
+ /**
43
+ * The font families text boxes offer, replacing the PDF standard families
44
+ * (`FONT_FAMILIES`). Fonts beyond those are the PDF writer's to embed.
45
+ */
46
+ fonts?: string[] | undefined
47
+ /**
48
+ * Where saved signatures live. Default: memory, gone on reload.
49
+ * `createLocalSignatureStore` keeps them in the browser; a host can keep them anywhere.
50
+ * A new store takes effect at once, for the menus and the `signature` tool alike.
51
+ */
52
+ signatures?: SignatureStore | undefined
53
+ /** The handwriting fonts a typed signature offers; the host loads them. */
54
+ signatureFonts?: string[] | undefined
36
55
  page?: number | undefined
37
56
  onPageChange?: ((page: number) => void) | undefined
38
57
  zoom?: ZoomValue | undefined
@@ -55,11 +74,19 @@
55
74
 
56
75
  <script lang="ts">
57
76
  import {
77
+ createMemorySignatureStore,
78
+ createSignatureLibrary,
58
79
  createSnapshotHistory,
59
80
  InkError as InkErrorClass,
60
81
  openDocument,
61
82
  } from '@inkbar/core'
62
- import { cssVariableDefaults } from '@inkbar/core/dom'
83
+ import {
84
+ createToolOptions,
85
+ cssVariableDefaults,
86
+ FONT_FAMILIES,
87
+ SIGNATURE_FONTS,
88
+ trackToolStyle,
89
+ } from '@inkbar/core/dom'
63
90
  import { DEV } from 'esm-env'
64
91
  import { untrack } from 'svelte'
65
92
  import { createAttachmentKey } from 'svelte/attachments'
@@ -79,6 +106,10 @@
79
106
  onChange,
80
107
  tool = $bindable(),
81
108
  onToolChange,
109
+ tools: hostTools,
110
+ fonts,
111
+ signatures: signatureStore,
112
+ signatureFonts,
82
113
  page = $bindable(),
83
114
  onPageChange,
84
115
  zoom = $bindable(),
@@ -95,6 +126,15 @@
95
126
 
96
127
  type TestHookElement = HTMLElement & { __inkDocument?: InkDocument | undefined }
97
128
 
129
+ const signatures = $derived(
130
+ createSignatureLibrary(signatureStore ?? createMemorySignatureStore())
131
+ )
132
+ $effect(() => {
133
+ void signatures.refresh()
134
+ })
135
+ /** Tool options last as long as the root; the tool style is per document. */
136
+ const toolOptions = createToolOptions()
137
+
98
138
  const IDLE: InkStatus = { state: 'idle' }
99
139
  const NO_ANNOTATIONS: readonly Annotation[] = []
100
140
  const NO_IDS: readonly string[] = []
@@ -130,7 +170,7 @@
130
170
  () => document?.annotations.selection,
131
171
  NO_IDS
132
172
  )
133
- const currentTool = $derived<ToolId>(tool ?? 'view')
173
+ const currentTool = $derived<ToolId>(tool ?? 'select-text')
134
174
 
135
175
  // What the next open will do follows the prop; the open document's mode is fixed.
136
176
  const controlledProp = $derived(annotations !== undefined)
@@ -277,6 +317,24 @@
277
317
  },
278
318
  }
279
319
 
320
+ /** The document's tool style; it batches through `inkHistory`, whichever mode is on. */
321
+ const styleTracker = $derived.by(() => {
322
+ const doc = document
323
+ if (!doc) return null
324
+ return untrack(() =>
325
+ trackToolStyle({
326
+ store: doc.annotations,
327
+ history: { batch: (fn) => inkHistory.batch(fn) },
328
+ tools: hostTools,
329
+ tool: currentTool,
330
+ })
331
+ )
332
+ })
333
+ $effect(() => {
334
+ const id = currentTool
335
+ styleTracker?.setTool(id)
336
+ })
337
+
280
338
  const context: InkContext = {
281
339
  get assets() {
282
340
  return assets
@@ -303,6 +361,22 @@
303
361
  return currentTool
304
362
  },
305
363
  setTool,
364
+ get hostTools() {
365
+ return hostTools
366
+ },
367
+ get fonts() {
368
+ return fonts ?? FONT_FAMILIES
369
+ },
370
+ toolOptions,
371
+ get signatures() {
372
+ return signatures
373
+ },
374
+ get signatureFonts() {
375
+ return signatureFonts ?? SIGNATURE_FONTS
376
+ },
377
+ get toolStyle() {
378
+ return styleTracker?.toolStyle ?? null
379
+ },
306
380
  get zoomModel() {
307
381
  return zoom
308
382
  },
@@ -1,6 +1,6 @@
1
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';
2
+ import type { Annotation, AnnotationChange, AnnotationDefaults, DocumentSource, InkDocument, InkError, InkStatus, PdfAssets, SignatureStore } from '@inkbar/core';
3
+ import type { Tool, ToolId, ZoomValue } from '@inkbar/core/dom';
4
4
  import type { Snippet } from 'svelte';
5
5
  import type { HTMLAttributes } from 'svelte/elements';
6
6
  export interface InkRootProps extends Omit<HTMLAttributes<HTMLDivElement>, 'children' | 'onchange' | 'onerror'> {
@@ -19,6 +19,24 @@ export interface InkRootProps extends Omit<HTMLAttributes<HTMLDivElement>, 'chil
19
19
  onChange?: ((change: AnnotationChange) => void) | undefined;
20
20
  tool?: ToolId | undefined;
21
21
  onToolChange?: ((tool: ToolId) => void) | undefined;
22
+ /**
23
+ * Host tools, added to the built-ins; one with a built-in's id replaces it. Read when a
24
+ * document opens.
25
+ */
26
+ tools?: Tool[] | undefined;
27
+ /**
28
+ * The font families text boxes offer, replacing the PDF standard families
29
+ * (`FONT_FAMILIES`). Fonts beyond those are the PDF writer's to embed.
30
+ */
31
+ fonts?: string[] | undefined;
32
+ /**
33
+ * Where saved signatures live. Default: memory, gone on reload.
34
+ * `createLocalSignatureStore` keeps them in the browser; a host can keep them anywhere.
35
+ * A new store takes effect at once, for the menus and the `signature` tool alike.
36
+ */
37
+ signatures?: SignatureStore | undefined;
38
+ /** The handwriting fonts a typed signature offers; the host loads them. */
39
+ signatureFonts?: string[] | undefined;
22
40
  page?: number | undefined;
23
41
  onPageChange?: ((page: number) => void) | undefined;
24
42
  zoom?: ZoomValue | undefined;
@@ -1 +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"}
1
+ {"version":3,"file":"InkRoot.svelte.d.ts","sourceRoot":"","sources":["../../src/lib/components/InkRoot.svelte.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,157 @@
1
+ <script module lang="ts">
2
+ import type { Snippet } from 'svelte'
3
+ import type { HTMLAttributes } from 'svelte/elements'
4
+
5
+ /**
6
+ * A split button for the shape tool. The main part activates the tool with the current
7
+ * kind and draws that kind's icon; the chevron opens a two-column grid of kinds. Choosing
8
+ * a kind switches to the tool with it.
9
+ */
10
+ export interface InkShapeMenuProps extends Omit<
11
+ HTMLAttributes<HTMLDivElement>,
12
+ 'children'
13
+ > {
14
+ /** Replaces the main button's icon. */
15
+ children?: Snippet | undefined
16
+ }
17
+ </script>
18
+
19
+ <script lang="ts">
20
+ import {
21
+ focusInitialMenuItem,
22
+ handleMenuKeys,
23
+ menuItems,
24
+ SHAPE_COLUMNS,
25
+ SHAPE_ICONS,
26
+ SHAPE_KINDS,
27
+ shapeMenuModel,
28
+ type ShapeKind,
29
+ type ShapeOptions,
30
+ } from '@inkbar/core/dom'
31
+ import { ChevronDown } from '@inkbar/icons'
32
+ import { Popover, Toolbar } from 'bits-ui'
33
+
34
+ import { useTool } from '../hooks/useTool.svelte.js'
35
+ import { useToolOptions } from '../hooks/useToolOptions.svelte.js'
36
+
37
+ import InkIcon from './InkIcon.svelte'
38
+
39
+ let { class: className, children, ...rest }: InkShapeMenuProps = $props()
40
+
41
+ const current = useTool()
42
+ const shape = useToolOptions<ShapeOptions>('shape')
43
+ let open = $state(false)
44
+
45
+ const model = $derived(shapeMenuModel(shape.options, SHAPE_KINDS))
46
+ const active = $derived(current.tool === 'shape')
47
+
48
+ let menu = $state<HTMLElement | null>(null)
49
+
50
+ /** ArrowDown opens on the first item; a click opens on the checked one. */
51
+ let focusFirst = false
52
+ // Bits runs the open-focus callback more than once, so the flag outlives it; reset on close.
53
+ $effect(() => {
54
+ if (!open) focusFirst = false
55
+ })
56
+ function onTriggerKeydown(e: KeyboardEvent): void {
57
+ if (e.key !== 'ArrowDown' || e.defaultPrevented || open) return
58
+ e.preventDefault()
59
+ focusFirst = true
60
+ open = true
61
+ }
62
+
63
+ function choose(kind: ShapeKind): void {
64
+ shape.setOptions({ kind })
65
+ current.setTool('shape')
66
+ open = false
67
+ }
68
+ </script>
69
+
70
+ <div
71
+ role="group"
72
+ aria-label="Shape"
73
+ class={['ink-split-button ink-shape-menu', className]}
74
+ data-ink-shape-menu=""
75
+ {...rest}
76
+ >
77
+ <Toolbar.Button
78
+ class="ink-tool-button ink-shape-button"
79
+ data-ink-tool-button=""
80
+ data-ink-shape-button=""
81
+ data-tool="shape"
82
+ data-kind={model.kind}
83
+ data-state={active ? 'on' : 'off'}
84
+ aria-pressed={active}
85
+ aria-label={model.label}
86
+ title={model.label}
87
+ onclick={() => current.setTool('shape')}
88
+ >
89
+ {#if children}{@render children()}{:else}<InkIcon
90
+ icon={SHAPE_ICONS[model.kind]}
91
+ />{/if}
92
+ </Toolbar.Button>
93
+ <Popover.Root bind:open>
94
+ <Popover.Trigger>
95
+ {#snippet child({ props })}
96
+ <Toolbar.Button
97
+ {...props}
98
+ class="ink-menu-trigger ink-shape-menu-trigger"
99
+ data-ink-shape-menu-trigger=""
100
+ data-state={open ? 'open' : 'closed'}
101
+ aria-label="Choose shape"
102
+ title="Choose shape"
103
+ aria-haspopup="menu"
104
+ onkeydown={onTriggerKeydown}
105
+ >
106
+ <InkIcon icon={ChevronDown} />
107
+ </Toolbar.Button>
108
+ {/snippet}
109
+ </Popover.Trigger>
110
+ <Popover.Portal>
111
+ <Popover.Content
112
+ bind:ref={menu}
113
+ role="menu"
114
+ aria-label="Shapes"
115
+ class="ink-select-content ink-menu-content"
116
+ data-ink-select-content=""
117
+ data-ink-menu="shape"
118
+ side="bottom"
119
+ align="start"
120
+ sideOffset={4}
121
+ collisionPadding={8}
122
+ onOpenAutoFocus={(e) => {
123
+ e.preventDefault()
124
+ if (menu) {
125
+ if (focusFirst) menuItems(menu)[0]?.focus({ preventScroll: true })
126
+ else focusInitialMenuItem(menu)
127
+ }
128
+ }}
129
+ onkeydown={(e) => menu && handleMenuKeys(e, menu)}
130
+ >
131
+ <div
132
+ role="group"
133
+ aria-label="Shapes"
134
+ class="ink-shape-grid"
135
+ data-ink-menu-columns={SHAPE_COLUMNS}
136
+ >
137
+ {#each model.items as item (item.kind)}
138
+ <button
139
+ type="button"
140
+ role="menuitemradio"
141
+ class="ink-select-item ink-menu-item ink-shape-item"
142
+ data-ink-select-item=""
143
+ tabindex={-1}
144
+ data-key={item.kind}
145
+ aria-checked={item.checked}
146
+ data-state={item.checked ? 'checked' : 'unchecked'}
147
+ onclick={() => choose(item.kind)}
148
+ >
149
+ <InkIcon icon={SHAPE_ICONS[item.kind]} size={16} />
150
+ {item.label}
151
+ </button>
152
+ {/each}
153
+ </div>
154
+ </Popover.Content>
155
+ </Popover.Portal>
156
+ </Popover.Root>
157
+ </div>
@@ -0,0 +1,15 @@
1
+ import type { Snippet } from 'svelte';
2
+ import type { HTMLAttributes } from 'svelte/elements';
3
+ /**
4
+ * A split button for the shape tool. The main part activates the tool with the current
5
+ * kind and draws that kind's icon; the chevron opens a two-column grid of kinds. Choosing
6
+ * a kind switches to the tool with it.
7
+ */
8
+ export interface InkShapeMenuProps extends Omit<HTMLAttributes<HTMLDivElement>, 'children'> {
9
+ /** Replaces the main button's icon. */
10
+ children?: Snippet | undefined;
11
+ }
12
+ declare const InkShapeMenu: import("svelte").Component<InkShapeMenuProps, {}, "">;
13
+ type InkShapeMenu = ReturnType<typeof InkShapeMenu>;
14
+ export default InkShapeMenu;
15
+ //# sourceMappingURL=InkShapeMenu.svelte.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"InkShapeMenu.svelte.d.ts","sourceRoot":"","sources":["../../src/lib/components/InkShapeMenu.svelte.ts"],"names":[],"mappings":"AAGE,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAA;AACrC,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAA;AAErD;;;;GAIG;AACH,MAAM,WAAW,iBAAkB,SAAQ,IAAI,CAC7C,cAAc,CAAC,cAAc,CAAC,EAC9B,UAAU,CACX;IACC,uCAAuC;IACvC,QAAQ,CAAC,EAAE,OAAO,GAAG,SAAS,CAAA;CAC/B;AAoGH,QAAA,MAAM,YAAY,uDAAwC,CAAC;AAC3D,KAAK,YAAY,GAAG,UAAU,CAAC,OAAO,YAAY,CAAC,CAAC;AACpD,eAAe,YAAY,CAAC"}
@@ -0,0 +1,70 @@
1
+ <script module lang="ts">
2
+ import type { SignatureTemplate } from '@inkbar/core'
3
+
4
+ /**
5
+ * "Create signature": a modal with Draw, Type and Image tabs. Draw is a pad with the draw
6
+ * tool's renderer; Type sets a name in one of the host's handwriting fonts; Image takes a PNG
7
+ * or JPEG scan. Save stores the template through InkRoot's signature store and selects it
8
+ * for the `signature` tool. Escape, Cancel or a press outside closes it.
9
+ */
10
+ export interface InkSignatureDialogProps {
11
+ open?: boolean
12
+ onOpenChange?: ((open: boolean) => void) | undefined
13
+ /** The fonts the Type tab offers. Default: InkRoot's `signatureFonts`. */
14
+ fonts?: readonly string[] | undefined
15
+ /** Called with the new template once it is saved and selected. */
16
+ onSave?: ((template: SignatureTemplate) => void) | undefined
17
+ /** Where focus goes on close; by default, back to what had it before. */
18
+ onCloseAutoFocus?: ((event: Event) => void) | undefined
19
+ class?: string | undefined
20
+ }
21
+ </script>
22
+
23
+ <script lang="ts">
24
+ import { SIGNATURE_LABELS } from '@inkbar/core/dom'
25
+ import { Dialog } from 'bits-ui'
26
+
27
+ import SignatureForm from './SignatureForm.svelte'
28
+
29
+ let {
30
+ open = $bindable(false),
31
+ onOpenChange,
32
+ fonts,
33
+ onSave,
34
+ onCloseAutoFocus,
35
+ class: className,
36
+ }: InkSignatureDialogProps = $props()
37
+
38
+ let content = $state<HTMLElement | null>(null)
39
+
40
+ function setOpen(next: boolean): void {
41
+ open = next
42
+ onOpenChange?.(next)
43
+ }
44
+ </script>
45
+
46
+ <Dialog.Root bind:open={() => open, setOpen}>
47
+ <Dialog.Portal>
48
+ <Dialog.Overlay class="ink-dialog-overlay" data-ink-dialog-overlay="" />
49
+ <Dialog.Content
50
+ bind:ref={content}
51
+ class={['ink-dialog ink-signature-dialog', className]}
52
+ data-ink-signature-dialog=""
53
+ aria-modal="true"
54
+ onOpenAutoFocus={(e) => {
55
+ e.preventDefault()
56
+ content
57
+ ?.querySelector<HTMLElement>('[role="tab"][aria-selected="true"]')
58
+ ?.focus({ preventScroll: true })
59
+ }}
60
+ onCloseAutoFocus={(e) => onCloseAutoFocus?.(e)}
61
+ >
62
+ <Dialog.Title class="ink-dialog-title">{SIGNATURE_LABELS.dialog}</Dialog.Title>
63
+ <SignatureForm
64
+ {fonts}
65
+ onSaved={(t) => onSave?.(t)}
66
+ onClose={() => setOpen(false)}
67
+ />
68
+ </Dialog.Content>
69
+ </Dialog.Portal>
70
+ </Dialog.Root>
@@ -0,0 +1,22 @@
1
+ import type { SignatureTemplate } from '@inkbar/core';
2
+ /**
3
+ * "Create signature": a modal with Draw, Type and Image tabs. Draw is a pad with the draw
4
+ * tool's renderer; Type sets a name in one of the host's handwriting fonts; Image takes a PNG
5
+ * or JPEG scan. Save stores the template through InkRoot's signature store and selects it
6
+ * for the `signature` tool. Escape, Cancel or a press outside closes it.
7
+ */
8
+ export interface InkSignatureDialogProps {
9
+ open?: boolean;
10
+ onOpenChange?: ((open: boolean) => void) | undefined;
11
+ /** The fonts the Type tab offers. Default: InkRoot's `signatureFonts`. */
12
+ fonts?: readonly string[] | undefined;
13
+ /** Called with the new template once it is saved and selected. */
14
+ onSave?: ((template: SignatureTemplate) => void) | undefined;
15
+ /** Where focus goes on close; by default, back to what had it before. */
16
+ onCloseAutoFocus?: ((event: Event) => void) | undefined;
17
+ class?: string | undefined;
18
+ }
19
+ declare const InkSignatureDialog: import("svelte").Component<InkSignatureDialogProps, {}, "open">;
20
+ type InkSignatureDialog = ReturnType<typeof InkSignatureDialog>;
21
+ export default InkSignatureDialog;
22
+ //# sourceMappingURL=InkSignatureDialog.svelte.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"InkSignatureDialog.svelte.d.ts","sourceRoot":"","sources":["../../src/lib/components/InkSignatureDialog.svelte.ts"],"names":[],"mappings":"AAGE,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAA;AAErD;;;;;GAKG;AACH,MAAM,WAAW,uBAAuB;IACtC,IAAI,CAAC,EAAE,OAAO,CAAA;IACd,YAAY,CAAC,EAAE,CAAC,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAC,GAAG,SAAS,CAAA;IACpD,0EAA0E;IAC1E,KAAK,CAAC,EAAE,SAAS,MAAM,EAAE,GAAG,SAAS,CAAA;IACrC,kEAAkE;IAClE,MAAM,CAAC,EAAE,CAAC,CAAC,QAAQ,EAAE,iBAAiB,KAAK,IAAI,CAAC,GAAG,SAAS,CAAA;IAC5D,yEAAyE;IACzE,gBAAgB,CAAC,EAAE,CAAC,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC,GAAG,SAAS,CAAA;IACvD,KAAK,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;CAC3B;AAkDH,QAAA,MAAM,kBAAkB,iEAAwC,CAAC;AACjE,KAAK,kBAAkB,GAAG,UAAU,CAAC,OAAO,kBAAkB,CAAC,CAAC;AAChE,eAAe,kBAAkB,CAAC"}