@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.
- package/README.md +33 -11
- package/dist/components/InkColorMenu.svelte +242 -0
- package/dist/components/InkColorMenu.svelte.d.ts +33 -0
- package/dist/components/InkColorMenu.svelte.d.ts.map +1 -0
- package/dist/components/InkHighlightMenu.svelte +166 -0
- package/dist/components/InkHighlightMenu.svelte.d.ts +16 -0
- package/dist/components/InkHighlightMenu.svelte.d.ts.map +1 -0
- package/dist/components/InkLineStyleMenu.svelte +141 -0
- package/dist/components/InkLineStyleMenu.svelte.d.ts +15 -0
- package/dist/components/InkLineStyleMenu.svelte.d.ts.map +1 -0
- package/dist/components/InkRoot.svelte +77 -3
- package/dist/components/InkRoot.svelte.d.ts +20 -2
- package/dist/components/InkRoot.svelte.d.ts.map +1 -1
- package/dist/components/InkShapeMenu.svelte +157 -0
- package/dist/components/InkShapeMenu.svelte.d.ts +15 -0
- package/dist/components/InkShapeMenu.svelte.d.ts.map +1 -0
- package/dist/components/InkSignatureDialog.svelte +70 -0
- package/dist/components/InkSignatureDialog.svelte.d.ts +22 -0
- package/dist/components/InkSignatureDialog.svelte.d.ts.map +1 -0
- package/dist/components/InkSignatureMenu.svelte +263 -0
- package/dist/components/InkSignatureMenu.svelte.d.ts +18 -0
- package/dist/components/InkSignatureMenu.svelte.d.ts.map +1 -0
- package/dist/components/InkTextStyleMenu.svelte +225 -0
- package/dist/components/InkTextStyleMenu.svelte.d.ts +15 -0
- package/dist/components/InkTextStyleMenu.svelte.d.ts.map +1 -0
- package/dist/components/{InkNotePopover.svelte → InkThreadPopover.svelte} +38 -33
- package/dist/components/InkThreadPopover.svelte.d.ts +9 -0
- package/dist/components/InkThreadPopover.svelte.d.ts.map +1 -0
- package/dist/components/InkToolButton.svelte +23 -7
- package/dist/components/InkToolButton.svelte.d.ts.map +1 -1
- package/dist/components/InkViewport.svelte +7 -0
- package/dist/components/InkViewport.svelte.d.ts.map +1 -1
- package/dist/components/SignatureForm.svelte +219 -0
- package/dist/components/SignatureForm.svelte.d.ts +10 -0
- package/dist/components/SignatureForm.svelte.d.ts.map +1 -0
- package/dist/components/ThreadForm.svelte +4 -2
- package/dist/components/ThreadForm.svelte.d.ts +1 -0
- package/dist/components/ThreadForm.svelte.d.ts.map +1 -1
- package/dist/components/child-snippet.d.ts +8 -0
- package/dist/components/child-snippet.d.ts.map +1 -1
- package/dist/components/child-snippet.js +15 -0
- package/dist/context.d.ts +14 -2
- package/dist/context.d.ts.map +1 -1
- package/dist/hooks/useAnnotations.svelte.d.ts +1364 -146
- package/dist/hooks/useAnnotations.svelte.d.ts.map +1 -1
- package/dist/hooks/useToolOptions.svelte.d.ts +11 -0
- package/dist/hooks/useToolOptions.svelte.d.ts.map +1 -0
- package/dist/hooks/useToolOptions.svelte.js +19 -0
- package/dist/hooks/useToolStyle.svelte.d.ts +33 -0
- package/dist/hooks/useToolStyle.svelte.d.ts.map +1 -0
- package/dist/hooks/useToolStyle.svelte.js +33 -0
- package/dist/index.d.ts +20 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +10 -1
- package/package.json +5 -5
- package/dist/components/InkNotePopover.svelte.d.ts +0 -9
- 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 {
|
|
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 ?? '
|
|
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,
|
|
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,EACT,cAAc,EACf,MAAM,cAAc,CAAA;AACrB,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAA;AAC/D,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;;;OAGG;IACH,KAAK,CAAC,EAAE,IAAI,EAAE,GAAG,SAAS,CAAA;IAC1B;;;OAGG;IACH,KAAK,CAAC,EAAE,MAAM,EAAE,GAAG,SAAS,CAAA;IAC5B;;;;OAIG;IACH,UAAU,CAAC,EAAE,cAAc,GAAG,SAAS,CAAA;IACvC,2EAA2E;IAC3E,cAAc,CAAC,EAAE,MAAM,EAAE,GAAG,SAAS,CAAA;IACrC,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;AAqBH,OAAO,EAAkC,KAAK,UAAU,EAAE,MAAM,eAAe,CAAA;AAoW/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,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"}
|