@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.
- package/README.md +17 -12
- package/dist/components/InkAnnotation.svelte +0 -12
- package/dist/components/InkAnnotation.svelte.d.ts.map +1 -1
- package/dist/components/InkColorMenu.svelte +247 -0
- package/dist/components/InkColorMenu.svelte.d.ts +35 -0
- package/dist/components/InkColorMenu.svelte.d.ts.map +1 -0
- package/dist/components/InkHighlightMenu.svelte +175 -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 +90 -3
- package/dist/components/InkRoot.svelte.d.ts +27 -2
- package/dist/components/InkRoot.svelte.d.ts.map +1 -1
- package/dist/components/InkShapeMenu.svelte +161 -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 +267 -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} +44 -32
- 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 +26 -2
- 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/components/popover-focus.d.ts +16 -2
- package/dist/components/popover-focus.d.ts.map +1 -1
- package/dist/components/popover-focus.js +24 -3
- package/dist/context.d.ts +21 -2
- package/dist/context.d.ts.map +1 -1
- package/dist/hooks/useAnnotations.svelte.d.ts +1333 -578
- package/dist/hooks/useAnnotations.svelte.d.ts.map +1 -1
- package/dist/hooks/useAnnotations.svelte.js +1 -7
- 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 +21 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +10 -2
- package/package.json +5 -5
- package/dist/components/InkNotePopover.svelte.d.ts +0 -9
- package/dist/components/InkNotePopover.svelte.d.ts.map +0 -1
- package/dist/components/InkSuggestionCard.svelte +0 -121
- package/dist/components/InkSuggestionCard.svelte.d.ts +0 -9
- package/dist/components/InkSuggestionCard.svelte.d.ts.map +0 -1
|
@@ -0,0 +1,267 @@
|
|
|
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 signature tool. The main part activates the tool with the chosen
|
|
7
|
+
* signature, or opens the menu when none is chosen; the chevron opens the saved signatures
|
|
8
|
+
* as previews, each with a remove button, and "Create signature", which opens
|
|
9
|
+
* `InkSignatureDialog`. Choosing a signature switches to the tool with it.
|
|
10
|
+
*/
|
|
11
|
+
export interface InkSignatureMenuProps extends Omit<
|
|
12
|
+
HTMLAttributes<HTMLDivElement>,
|
|
13
|
+
'children'
|
|
14
|
+
> {
|
|
15
|
+
/** Replaces the main button's icon. */
|
|
16
|
+
children?: Snippet | undefined
|
|
17
|
+
/** The fonts the dialog's Type tab offers. Default: InkRoot's `signatureFonts`. */
|
|
18
|
+
fonts?: readonly string[] | undefined
|
|
19
|
+
}
|
|
20
|
+
</script>
|
|
21
|
+
|
|
22
|
+
<script lang="ts">
|
|
23
|
+
import {
|
|
24
|
+
focusInitialMenuItem,
|
|
25
|
+
handleMenuKeys,
|
|
26
|
+
menuItems,
|
|
27
|
+
SIGNATURE_LABELS,
|
|
28
|
+
signatureMenuModel,
|
|
29
|
+
signaturePreview,
|
|
30
|
+
} from '@inkbar/core/dom'
|
|
31
|
+
import { ChevronDown, Plus, Signature, Trash2 } from '@inkbar/icons'
|
|
32
|
+
import { Popover, Toolbar } from 'bits-ui'
|
|
33
|
+
import { tick, untrack } from 'svelte'
|
|
34
|
+
|
|
35
|
+
import { useInkContext } from '../context.js'
|
|
36
|
+
import { useTool } from '../hooks/useTool.svelte.js'
|
|
37
|
+
import { useToolOptions } from '../hooks/useToolOptions.svelte.js'
|
|
38
|
+
import { useReadable } from '../use-readable.svelte.js'
|
|
39
|
+
|
|
40
|
+
import InkIcon from './InkIcon.svelte'
|
|
41
|
+
import InkSignatureDialog from './InkSignatureDialog.svelte'
|
|
42
|
+
|
|
43
|
+
import type { SignatureOptions, SignatureTemplate } from '@inkbar/core'
|
|
44
|
+
|
|
45
|
+
let { class: className, children, fonts, ...rest }: InkSignatureMenuProps = $props()
|
|
46
|
+
|
|
47
|
+
const NO_TEMPLATES: readonly SignatureTemplate[] = []
|
|
48
|
+
|
|
49
|
+
const ctx = useInkContext('InkSignatureMenu')
|
|
50
|
+
const current = useTool()
|
|
51
|
+
const signature = useToolOptions<SignatureOptions>('signature')
|
|
52
|
+
const templates = useReadable(() => ctx.signatures.templates, NO_TEMPLATES)
|
|
53
|
+
const removeError = useReadable(() => ctx.signatures.removeError, undefined)
|
|
54
|
+
// An old removal error is not news when the menu opens again.
|
|
55
|
+
$effect(() => {
|
|
56
|
+
if (open) untrack(() => ctx.signatures.clearRemoveError())
|
|
57
|
+
})
|
|
58
|
+
const model = $derived(signatureMenuModel(templates.current, signature.options))
|
|
59
|
+
const active = $derived(current.tool === 'signature')
|
|
60
|
+
let open = $state(false)
|
|
61
|
+
/** The split button: the panel lines up under it rather than the chevron. */
|
|
62
|
+
let group = $state<HTMLElement | null>(null)
|
|
63
|
+
let creating = $state(false)
|
|
64
|
+
let menu = $state<HTMLElement | null>(null)
|
|
65
|
+
let main = $state<HTMLElement | null>(null)
|
|
66
|
+
|
|
67
|
+
/** ArrowDown opens on the first item; a click opens on the checked one. */
|
|
68
|
+
let focusFirst = false
|
|
69
|
+
// Bits runs the open-focus callback more than once, so the flag outlives it; reset on close.
|
|
70
|
+
$effect(() => {
|
|
71
|
+
if (!open) focusFirst = false
|
|
72
|
+
})
|
|
73
|
+
/** Set while the menu closes for the dialog, which takes focus instead of the trigger. */
|
|
74
|
+
let toDialog = false
|
|
75
|
+
|
|
76
|
+
function onTriggerKeydown(e: KeyboardEvent): void {
|
|
77
|
+
if (e.key !== 'ArrowDown' || e.defaultPrevented || open) return
|
|
78
|
+
e.preventDefault()
|
|
79
|
+
focusFirst = true
|
|
80
|
+
open = true
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function onMain(): void {
|
|
84
|
+
if (model.selected) current.setTool('signature')
|
|
85
|
+
else open = true
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function choose(id: string): void {
|
|
89
|
+
signature.setOptions({ templateId: id })
|
|
90
|
+
current.setTool('signature')
|
|
91
|
+
open = false
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/** Removes a template; focus moves to the signature now in its row, else "Create signature". */
|
|
95
|
+
function remove(id: string, row: number): void {
|
|
96
|
+
ctx.signatures.remove(id).then(
|
|
97
|
+
async () => {
|
|
98
|
+
await tick()
|
|
99
|
+
if (!menu) return
|
|
100
|
+
const rows = menu.querySelectorAll<HTMLElement>('.ink-signature-item')
|
|
101
|
+
const next =
|
|
102
|
+
rows[Math.min(row, rows.length - 1)] ??
|
|
103
|
+
menu.querySelector<HTMLElement>('[data-ink-signature-create]')
|
|
104
|
+
next?.focus({ preventScroll: true })
|
|
105
|
+
},
|
|
106
|
+
() => {
|
|
107
|
+
// The host's store refused: the signature stays.
|
|
108
|
+
}
|
|
109
|
+
)
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function create(): void {
|
|
113
|
+
toDialog = true
|
|
114
|
+
open = false
|
|
115
|
+
creating = true
|
|
116
|
+
}
|
|
117
|
+
</script>
|
|
118
|
+
|
|
119
|
+
<div
|
|
120
|
+
bind:this={group}
|
|
121
|
+
role="group"
|
|
122
|
+
aria-label={SIGNATURE_LABELS.sign}
|
|
123
|
+
class={['ink-split-button ink-signature-menu', className]}
|
|
124
|
+
data-ink-signature-menu=""
|
|
125
|
+
{...rest}
|
|
126
|
+
>
|
|
127
|
+
<Toolbar.Button
|
|
128
|
+
bind:ref={main}
|
|
129
|
+
class="ink-tool-button ink-signature-button"
|
|
130
|
+
data-ink-tool-button=""
|
|
131
|
+
data-ink-signature-button=""
|
|
132
|
+
data-tool="signature"
|
|
133
|
+
data-state={active ? 'on' : 'off'}
|
|
134
|
+
aria-pressed={active}
|
|
135
|
+
aria-label={SIGNATURE_LABELS.sign}
|
|
136
|
+
title={SIGNATURE_LABELS.sign}
|
|
137
|
+
onclick={onMain}
|
|
138
|
+
>
|
|
139
|
+
{#if children}{@render children()}{:else}<InkIcon icon={Signature} />{/if}
|
|
140
|
+
</Toolbar.Button>
|
|
141
|
+
<Popover.Root bind:open>
|
|
142
|
+
<Popover.Trigger>
|
|
143
|
+
{#snippet child({ props })}
|
|
144
|
+
<Toolbar.Button
|
|
145
|
+
{...props}
|
|
146
|
+
class="ink-menu-trigger ink-signature-menu-trigger"
|
|
147
|
+
data-ink-signature-menu-trigger=""
|
|
148
|
+
data-state={open ? 'open' : 'closed'}
|
|
149
|
+
aria-label={SIGNATURE_LABELS.trigger}
|
|
150
|
+
title={SIGNATURE_LABELS.trigger}
|
|
151
|
+
aria-haspopup="menu"
|
|
152
|
+
onkeydown={onTriggerKeydown}
|
|
153
|
+
>
|
|
154
|
+
<InkIcon icon={ChevronDown} />
|
|
155
|
+
</Toolbar.Button>
|
|
156
|
+
{/snippet}
|
|
157
|
+
</Popover.Trigger>
|
|
158
|
+
<Popover.Portal>
|
|
159
|
+
<Popover.Content
|
|
160
|
+
bind:ref={menu}
|
|
161
|
+
role="menu"
|
|
162
|
+
aria-label={SIGNATURE_LABELS.menu}
|
|
163
|
+
class="ink-select-content ink-menu-content"
|
|
164
|
+
data-ink-select-content=""
|
|
165
|
+
data-ink-menu="signature"
|
|
166
|
+
customAnchor={group}
|
|
167
|
+
side="bottom"
|
|
168
|
+
align="start"
|
|
169
|
+
sideOffset={4}
|
|
170
|
+
collisionPadding={8}
|
|
171
|
+
onOpenAutoFocus={(e) => {
|
|
172
|
+
e.preventDefault()
|
|
173
|
+
if (menu) {
|
|
174
|
+
if (focusFirst) menuItems(menu)[0]?.focus({ preventScroll: true })
|
|
175
|
+
else focusInitialMenuItem(menu)
|
|
176
|
+
}
|
|
177
|
+
}}
|
|
178
|
+
onCloseAutoFocus={(e) => {
|
|
179
|
+
if (toDialog) e.preventDefault()
|
|
180
|
+
toDialog = false
|
|
181
|
+
}}
|
|
182
|
+
onkeydown={(e) => menu && handleMenuKeys(e, menu)}
|
|
183
|
+
>
|
|
184
|
+
{#if model.items.length > 0}
|
|
185
|
+
<div
|
|
186
|
+
role="group"
|
|
187
|
+
aria-label={SIGNATURE_LABELS.saved}
|
|
188
|
+
class="ink-signature-list"
|
|
189
|
+
data-ink-menu-columns="2"
|
|
190
|
+
>
|
|
191
|
+
{#each model.items as item, row (item.id)}
|
|
192
|
+
{@const preview =
|
|
193
|
+
item.template.kind === 'ink' ? signaturePreview(item.template) : null}
|
|
194
|
+
<button
|
|
195
|
+
type="button"
|
|
196
|
+
role="menuitemradio"
|
|
197
|
+
aria-checked={item.checked}
|
|
198
|
+
aria-label={item.label}
|
|
199
|
+
tabindex={-1}
|
|
200
|
+
class="ink-select-item ink-menu-item ink-signature-item"
|
|
201
|
+
data-ink-select-item=""
|
|
202
|
+
data-key={item.id}
|
|
203
|
+
data-state={item.checked ? 'checked' : 'unchecked'}
|
|
204
|
+
onclick={() => choose(item.id)}
|
|
205
|
+
>
|
|
206
|
+
<span class="ink-signature-preview" data-kind={item.template.kind}>
|
|
207
|
+
{#if item.template.kind === 'image'}
|
|
208
|
+
<img src={item.template.imagedata} alt="" draggable="false" />
|
|
209
|
+
{:else if preview}
|
|
210
|
+
<svg viewBox={preview.viewBox} aria-hidden="true">
|
|
211
|
+
{#each preview.paths as d, i (i)}
|
|
212
|
+
<path {d} fill="currentColor" />
|
|
213
|
+
{/each}
|
|
214
|
+
</svg>
|
|
215
|
+
{/if}
|
|
216
|
+
</span>
|
|
217
|
+
</button>
|
|
218
|
+
<button
|
|
219
|
+
type="button"
|
|
220
|
+
role="menuitem"
|
|
221
|
+
aria-label={item.removeLabel}
|
|
222
|
+
title={item.removeLabel}
|
|
223
|
+
tabindex={-1}
|
|
224
|
+
class="ink-menu-item ink-signature-remove"
|
|
225
|
+
data-ink-signature-remove=""
|
|
226
|
+
data-key={item.id}
|
|
227
|
+
onclick={() => remove(item.id, row)}
|
|
228
|
+
>
|
|
229
|
+
<InkIcon icon={Trash2} size={16} />
|
|
230
|
+
</button>
|
|
231
|
+
{/each}
|
|
232
|
+
</div>
|
|
233
|
+
<div role="separator" class="ink-select-separator"></div>
|
|
234
|
+
{/if}
|
|
235
|
+
<button
|
|
236
|
+
type="button"
|
|
237
|
+
role="menuitem"
|
|
238
|
+
tabindex={-1}
|
|
239
|
+
class="ink-select-item ink-menu-item ink-signature-create"
|
|
240
|
+
data-ink-select-item=""
|
|
241
|
+
data-ink-signature-create=""
|
|
242
|
+
onclick={create}
|
|
243
|
+
>
|
|
244
|
+
<InkIcon icon={Plus} size={16} />
|
|
245
|
+
{SIGNATURE_LABELS.create}
|
|
246
|
+
</button>
|
|
247
|
+
{#if removeError.current}
|
|
248
|
+
<p
|
|
249
|
+
class="ink-field-error ink-signature-remove-error"
|
|
250
|
+
role="alert"
|
|
251
|
+
data-ink-signature-remove-error=""
|
|
252
|
+
>
|
|
253
|
+
{removeError.current}
|
|
254
|
+
</p>
|
|
255
|
+
{/if}
|
|
256
|
+
</Popover.Content>
|
|
257
|
+
</Popover.Portal>
|
|
258
|
+
</Popover.Root>
|
|
259
|
+
<InkSignatureDialog
|
|
260
|
+
bind:open={creating}
|
|
261
|
+
{fonts}
|
|
262
|
+
onCloseAutoFocus={(e) => {
|
|
263
|
+
e.preventDefault()
|
|
264
|
+
main?.focus({ preventScroll: true })
|
|
265
|
+
}}
|
|
266
|
+
/>
|
|
267
|
+
</div>
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
2
|
+
import type { HTMLAttributes } from 'svelte/elements';
|
|
3
|
+
/**
|
|
4
|
+
* A split button for the signature tool. The main part activates the tool with the chosen
|
|
5
|
+
* signature, or opens the menu when none is chosen; the chevron opens the saved signatures
|
|
6
|
+
* as previews, each with a remove button, and "Create signature", which opens
|
|
7
|
+
* `InkSignatureDialog`. Choosing a signature switches to the tool with it.
|
|
8
|
+
*/
|
|
9
|
+
export interface InkSignatureMenuProps extends Omit<HTMLAttributes<HTMLDivElement>, 'children'> {
|
|
10
|
+
/** Replaces the main button's icon. */
|
|
11
|
+
children?: Snippet | undefined;
|
|
12
|
+
/** The fonts the dialog's Type tab offers. Default: InkRoot's `signatureFonts`. */
|
|
13
|
+
fonts?: readonly string[] | undefined;
|
|
14
|
+
}
|
|
15
|
+
declare const InkSignatureMenu: import("svelte").Component<InkSignatureMenuProps, {}, "">;
|
|
16
|
+
type InkSignatureMenu = ReturnType<typeof InkSignatureMenu>;
|
|
17
|
+
export default InkSignatureMenu;
|
|
18
|
+
//# sourceMappingURL=InkSignatureMenu.svelte.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"InkSignatureMenu.svelte.d.ts","sourceRoot":"","sources":["../../src/lib/components/InkSignatureMenu.svelte.ts"],"names":[],"mappings":"AAGE,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAA;AACrC,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAA;AAErD;;;;;GAKG;AACH,MAAM,WAAW,qBAAsB,SAAQ,IAAI,CACjD,cAAc,CAAC,cAAc,CAAC,EAC9B,UAAU,CACX;IACC,uCAAuC;IACvC,QAAQ,CAAC,EAAE,OAAO,GAAG,SAAS,CAAA;IAC9B,mFAAmF;IACnF,KAAK,CAAC,EAAE,SAAS,MAAM,EAAE,GAAG,SAAS,CAAA;CACtC;AAyLH,QAAA,MAAM,gBAAgB,2DAAwC,CAAC;AAC/D,KAAK,gBAAgB,GAAG,UAAU,CAAC,OAAO,gBAAgB,CAAC,CAAC;AAC5D,eAAe,gBAAgB,CAAC"}
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
<script module lang="ts">
|
|
2
|
+
import type { Snippet } from 'svelte'
|
|
3
|
+
import type { HTMLButtonAttributes } from 'svelte/elements'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Font, size, text colour, bold, italic, underline and alignment for the selected text
|
|
7
|
+
* boxes or the text tool. A popover panel rather than a menu list: it holds a select, a
|
|
8
|
+
* number input with a stepper, toggle buttons and a radio group, each named and operable on
|
|
9
|
+
* its own. The trigger carries `data-mixed` when the selection disagrees on any of them.
|
|
10
|
+
*/
|
|
11
|
+
export interface InkTextStyleMenuProps extends Omit<HTMLButtonAttributes, 'children'> {
|
|
12
|
+
children?: Snippet | undefined
|
|
13
|
+
}
|
|
14
|
+
</script>
|
|
15
|
+
|
|
16
|
+
<script lang="ts">
|
|
17
|
+
import { handleRadioKeys, textStyleMenuModel } from '@inkbar/core/dom'
|
|
18
|
+
import {
|
|
19
|
+
ALargeSmall,
|
|
20
|
+
AlignCenter,
|
|
21
|
+
AlignJustify,
|
|
22
|
+
AlignLeft,
|
|
23
|
+
AlignRight,
|
|
24
|
+
Bold,
|
|
25
|
+
Italic,
|
|
26
|
+
Minus,
|
|
27
|
+
Plus,
|
|
28
|
+
Underline,
|
|
29
|
+
} from '@inkbar/icons'
|
|
30
|
+
import { Popover, Toolbar } from 'bits-ui'
|
|
31
|
+
|
|
32
|
+
import { useInkContext } from '../context.js'
|
|
33
|
+
import { useToolStyle } from '../hooks/useToolStyle.svelte.js'
|
|
34
|
+
|
|
35
|
+
import { asBitsProps, composeHandlers } from './child-snippet.js'
|
|
36
|
+
import InkColorMenu from './InkColorMenu.svelte'
|
|
37
|
+
import InkIcon from './InkIcon.svelte'
|
|
38
|
+
|
|
39
|
+
let {
|
|
40
|
+
class: className,
|
|
41
|
+
children,
|
|
42
|
+
disabled,
|
|
43
|
+
onclick,
|
|
44
|
+
onkeydown,
|
|
45
|
+
...rest
|
|
46
|
+
}: InkTextStyleMenuProps = $props()
|
|
47
|
+
|
|
48
|
+
const ctx = useInkContext('InkTextStyleMenu')
|
|
49
|
+
const style = useToolStyle()
|
|
50
|
+
let open = $state(false)
|
|
51
|
+
let trigger = $state<HTMLElement | null>(null)
|
|
52
|
+
/** The size as typed, until it is committed with Enter or blur. */
|
|
53
|
+
let draft = $state<string | null>(null)
|
|
54
|
+
|
|
55
|
+
const model = $derived(textStyleMenuModel(style.current, style.supported, ctx.fonts))
|
|
56
|
+
const sizeShown = $derived(
|
|
57
|
+
draft ?? (model.size.value === null ? '' : String(model.size.value))
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
const TOGGLE_ICONS = { bold: Bold, italic: Italic, underline: Underline }
|
|
61
|
+
const ALIGN_ICONS = {
|
|
62
|
+
left: AlignLeft,
|
|
63
|
+
center: AlignCenter,
|
|
64
|
+
right: AlignRight,
|
|
65
|
+
justify: AlignJustify,
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// The menu closes when the control disables (the selection cleared while it was open).
|
|
69
|
+
$effect(() => {
|
|
70
|
+
if (!model.enabled) open = false
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
function onTriggerKeydown(e: KeyboardEvent): void {
|
|
74
|
+
if (e.key !== 'ArrowDown' || e.defaultPrevented || open || !model.enabled) return
|
|
75
|
+
e.preventDefault()
|
|
76
|
+
open = true
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function commitSize(): void {
|
|
80
|
+
if (draft === null) return
|
|
81
|
+
if (draft.trim() !== '') {
|
|
82
|
+
const patch = model.size.commit(Number(draft))
|
|
83
|
+
if (patch) style.set(patch)
|
|
84
|
+
}
|
|
85
|
+
draft = null
|
|
86
|
+
}
|
|
87
|
+
function nudge(direction: 1 | -1): void {
|
|
88
|
+
const patch = model.size.nudge(direction)
|
|
89
|
+
if (patch) style.set(patch)
|
|
90
|
+
draft = null
|
|
91
|
+
}
|
|
92
|
+
</script>
|
|
93
|
+
|
|
94
|
+
<Popover.Root bind:open>
|
|
95
|
+
<Popover.Trigger>
|
|
96
|
+
{#snippet child({ props })}
|
|
97
|
+
<Toolbar.Button
|
|
98
|
+
bind:ref={trigger}
|
|
99
|
+
{...props}
|
|
100
|
+
{...asBitsProps(rest)}
|
|
101
|
+
class={['ink-menu-trigger ink-text-style-menu', className]}
|
|
102
|
+
data-ink-text-style-menu=""
|
|
103
|
+
data-state={open ? 'open' : 'closed'}
|
|
104
|
+
data-mixed={model.mixed ? '' : undefined}
|
|
105
|
+
aria-label={model.label}
|
|
106
|
+
title={model.label}
|
|
107
|
+
aria-haspopup="dialog"
|
|
108
|
+
onclick={composeHandlers(onclick, props.onclick)}
|
|
109
|
+
onkeydown={composeHandlers(onkeydown, props.onkeydown, onTriggerKeydown)}
|
|
110
|
+
disabled={!!disabled || !model.enabled}
|
|
111
|
+
>
|
|
112
|
+
{#if children}{@render children()}{:else}<InkIcon icon={ALargeSmall} />{/if}
|
|
113
|
+
</Toolbar.Button>
|
|
114
|
+
{/snippet}
|
|
115
|
+
</Popover.Trigger>
|
|
116
|
+
<Popover.Portal>
|
|
117
|
+
<Popover.Content
|
|
118
|
+
role="dialog"
|
|
119
|
+
aria-label={model.label}
|
|
120
|
+
class="ink-select-content ink-menu-content ink-text-style-content"
|
|
121
|
+
data-ink-select-content=""
|
|
122
|
+
data-ink-menu="text-style"
|
|
123
|
+
side="bottom"
|
|
124
|
+
align="start"
|
|
125
|
+
sideOffset={4}
|
|
126
|
+
collisionPadding={8}
|
|
127
|
+
onCloseAutoFocus={(e) => {
|
|
128
|
+
// Focus may be inside the nested colour menu when the panel closes.
|
|
129
|
+
e.preventDefault()
|
|
130
|
+
trigger?.focus()
|
|
131
|
+
}}
|
|
132
|
+
>
|
|
133
|
+
<select
|
|
134
|
+
class="ink-text-style-font"
|
|
135
|
+
aria-label={model.font.label}
|
|
136
|
+
value={model.font.value}
|
|
137
|
+
onchange={(e) => style.set(model.font.patch(e.currentTarget.value))}
|
|
138
|
+
>
|
|
139
|
+
{#if model.font.value === ''}
|
|
140
|
+
<option value="" disabled hidden>Mixed</option>
|
|
141
|
+
{/if}
|
|
142
|
+
{#each model.font.options as f (f)}
|
|
143
|
+
<option value={f}>{f}</option>
|
|
144
|
+
{/each}
|
|
145
|
+
</select>
|
|
146
|
+
<div class="ink-text-style-row">
|
|
147
|
+
<InkColorMenu property="textColor" standalone />
|
|
148
|
+
<div class="ink-text-style-size" role="group" aria-label="Size">
|
|
149
|
+
<button
|
|
150
|
+
type="button"
|
|
151
|
+
class="ink-text-style-button"
|
|
152
|
+
aria-label="Decrease font size"
|
|
153
|
+
onclick={() => nudge(-1)}
|
|
154
|
+
>
|
|
155
|
+
<InkIcon icon={Minus} size={16} />
|
|
156
|
+
</button>
|
|
157
|
+
<input
|
|
158
|
+
type="number"
|
|
159
|
+
class="ink-text-style-input"
|
|
160
|
+
aria-label={model.size.label}
|
|
161
|
+
min={model.size.min}
|
|
162
|
+
max={model.size.max}
|
|
163
|
+
step={model.size.step}
|
|
164
|
+
value={sizeShown}
|
|
165
|
+
oninput={(e) => (draft = e.currentTarget.value)}
|
|
166
|
+
onblur={commitSize}
|
|
167
|
+
onkeydown={(e) => {
|
|
168
|
+
if (e.key === 'Enter') {
|
|
169
|
+
e.preventDefault()
|
|
170
|
+
commitSize()
|
|
171
|
+
}
|
|
172
|
+
}}
|
|
173
|
+
/>
|
|
174
|
+
<button
|
|
175
|
+
type="button"
|
|
176
|
+
class="ink-text-style-button"
|
|
177
|
+
aria-label="Increase font size"
|
|
178
|
+
onclick={() => nudge(1)}
|
|
179
|
+
>
|
|
180
|
+
<InkIcon icon={Plus} size={16} />
|
|
181
|
+
</button>
|
|
182
|
+
</div>
|
|
183
|
+
</div>
|
|
184
|
+
<div class="ink-text-style-row" role="group" aria-label="Emphasis">
|
|
185
|
+
{#each model.toggles as t (t.key)}
|
|
186
|
+
<button
|
|
187
|
+
type="button"
|
|
188
|
+
class="ink-text-style-button"
|
|
189
|
+
aria-label={t.label}
|
|
190
|
+
title={t.label}
|
|
191
|
+
aria-pressed={t.pressed}
|
|
192
|
+
data-state={t.pressed === true ? 'on' : 'off'}
|
|
193
|
+
data-mixed={t.pressed === 'mixed' ? '' : undefined}
|
|
194
|
+
onclick={() => style.set(t.patch)}
|
|
195
|
+
>
|
|
196
|
+
<InkIcon icon={TOGGLE_ICONS[t.key]} size={16} />
|
|
197
|
+
</button>
|
|
198
|
+
{/each}
|
|
199
|
+
</div>
|
|
200
|
+
<div
|
|
201
|
+
class="ink-text-style-row"
|
|
202
|
+
role="radiogroup"
|
|
203
|
+
aria-label={model.align.label}
|
|
204
|
+
tabindex="-1"
|
|
205
|
+
onkeydown={(e) => handleRadioKeys(e, e.currentTarget)}
|
|
206
|
+
>
|
|
207
|
+
{#each model.align.choices as c (c.key)}
|
|
208
|
+
<button
|
|
209
|
+
type="button"
|
|
210
|
+
role="radio"
|
|
211
|
+
class="ink-text-style-button"
|
|
212
|
+
aria-label={c.label}
|
|
213
|
+
title={c.label}
|
|
214
|
+
aria-checked={c.checked}
|
|
215
|
+
tabindex={c.tabStop ? 0 : -1}
|
|
216
|
+
data-state={c.checked ? 'on' : 'off'}
|
|
217
|
+
onclick={() => style.set(c.patch)}
|
|
218
|
+
>
|
|
219
|
+
<InkIcon icon={ALIGN_ICONS[c.key]} size={16} />
|
|
220
|
+
</button>
|
|
221
|
+
{/each}
|
|
222
|
+
</div>
|
|
223
|
+
</Popover.Content>
|
|
224
|
+
</Popover.Portal>
|
|
225
|
+
</Popover.Root>
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
2
|
+
import type { HTMLButtonAttributes } from 'svelte/elements';
|
|
3
|
+
/**
|
|
4
|
+
* Font, size, text colour, bold, italic, underline and alignment for the selected text
|
|
5
|
+
* boxes or the text tool. A popover panel rather than a menu list: it holds a select, a
|
|
6
|
+
* number input with a stepper, toggle buttons and a radio group, each named and operable on
|
|
7
|
+
* its own. The trigger carries `data-mixed` when the selection disagrees on any of them.
|
|
8
|
+
*/
|
|
9
|
+
export interface InkTextStyleMenuProps extends Omit<HTMLButtonAttributes, 'children'> {
|
|
10
|
+
children?: Snippet | undefined;
|
|
11
|
+
}
|
|
12
|
+
declare const InkTextStyleMenu: import("svelte").Component<InkTextStyleMenuProps, {}, "">;
|
|
13
|
+
type InkTextStyleMenu = ReturnType<typeof InkTextStyleMenu>;
|
|
14
|
+
export default InkTextStyleMenu;
|
|
15
|
+
//# sourceMappingURL=InkTextStyleMenu.svelte.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"InkTextStyleMenu.svelte.d.ts","sourceRoot":"","sources":["../../src/lib/components/InkTextStyleMenu.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;AAyJH,QAAA,MAAM,gBAAgB,2DAAwC,CAAC;AAC/D,KAAK,gBAAgB,GAAG,UAAU,CAAC,OAAO,gBAAgB,CAAC,CAAC;AAC5D,eAAe,gBAAgB,CAAC"}
|