@inkbar/svelte 0.4.0 → 0.6.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 +23 -7
- package/dist/components/InkAnnotation.svelte +0 -12
- package/dist/components/InkAnnotation.svelte.d.ts.map +1 -1
- package/dist/components/InkColorMenu.svelte +13 -19
- package/dist/components/InkColorMenu.svelte.d.ts +2 -0
- package/dist/components/InkColorMenu.svelte.d.ts.map +1 -1
- package/dist/components/InkCommand.svelte +63 -0
- package/dist/components/InkCommand.svelte.d.ts +17 -0
- package/dist/components/InkCommand.svelte.d.ts.map +1 -0
- package/dist/components/InkHighlightMenu.svelte +15 -13
- package/dist/components/InkLineStyleMenu.svelte +7 -17
- package/dist/components/InkLineStyleMenu.svelte.d.ts.map +1 -1
- package/dist/components/InkRoot.svelte +111 -3
- package/dist/components/InkRoot.svelte.d.ts +14 -1
- package/dist/components/InkRoot.svelte.d.ts.map +1 -1
- package/dist/components/InkShapeMenu.svelte +10 -13
- package/dist/components/InkShapeMenu.svelte.d.ts.map +1 -1
- package/dist/components/InkSignatureMenu.svelte +10 -13
- package/dist/components/InkSignatureMenu.svelte.d.ts.map +1 -1
- package/dist/components/InkThreadPopover.svelte +7 -0
- package/dist/components/InkThreadPopover.svelte.d.ts.map +1 -1
- package/dist/components/InkToolButton.svelte +11 -3
- package/dist/components/InkToolButton.svelte.d.ts.map +1 -1
- package/dist/components/InkViewport.svelte +32 -2
- package/dist/components/InkViewport.svelte.d.ts.map +1 -1
- package/dist/components/menu-open-focus.d.ts +12 -0
- package/dist/components/menu-open-focus.d.ts.map +1 -0
- package/dist/components/menu-open-focus.js +35 -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 +22 -4
- package/dist/context.d.ts.map +1 -1
- package/dist/hooks/useAnnotations.svelte.d.ts +795 -1258
- package/dist/hooks/useAnnotations.svelte.d.ts.map +1 -1
- package/dist/hooks/useAnnotations.svelte.js +1 -7
- package/dist/hooks/useCommand.svelte.d.ts +23 -0
- package/dist/hooks/useCommand.svelte.d.ts.map +1 -0
- package/dist/hooks/useCommand.svelte.js +44 -0
- package/dist/index.d.ts +6 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/package.json +5 -5
- 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
package/README.md
CHANGED
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
|
|
21
21
|
---
|
|
22
22
|
|
|
23
|
-
Drop a PDF viewer into your Svelte app and let people highlight text, leave notes with replies and
|
|
23
|
+
Drop a PDF viewer into your Svelte app and let people highlight text, leave notes with replies and redact. `@inkbar/svelte` gives you components to build your own toolbars, a popover for comment threads, and hooks for anything else. `@inkbar/theme` styles them all.
|
|
24
24
|
|
|
25
25
|
Annotations are plain typed objects. Bind them with `bind:annotations` to keep them in your own state, or let Inkbar hold them with undo and redo.
|
|
26
26
|
|
|
@@ -52,7 +52,6 @@ Svelte 5.46.4 or later is a peer dependency. Add the Vite plugin from `@inkbar/c
|
|
|
52
52
|
InkZoomSelect,
|
|
53
53
|
InkZoomIn,
|
|
54
54
|
InkThreadPopover,
|
|
55
|
-
InkSuggestionCard,
|
|
56
55
|
} from '@inkbar/svelte'
|
|
57
56
|
import type { Annotation } from '@inkbar/core'
|
|
58
57
|
import { assets } from 'virtual:inkbar-assets'
|
|
@@ -85,7 +84,6 @@ Svelte 5.46.4 or later is a peer dependency. Add the Vite plugin from `@inkbar/c
|
|
|
85
84
|
<InkZoomIn />
|
|
86
85
|
</InkToolbar>
|
|
87
86
|
<InkThreadPopover />
|
|
88
|
-
<InkSuggestionCard />
|
|
89
87
|
</InkRoot>
|
|
90
88
|
```
|
|
91
89
|
|
|
@@ -159,11 +157,29 @@ The highlight tool leaves `contents` free for the comment. `doc.text(page)` retu
|
|
|
159
157
|
|
|
160
158
|
`InkSignatureMenu` lists saved signatures, picks one for the `signature` tool, removes them, and opens `InkSignatureDialog` to draw, type or upload a new one. These are marks on the page, not cryptographic signatures. Pass `signatures` to `InkRoot` to keep them somewhere: `createLocalSignatureStore(key)` from `@inkbar/core` uses `localStorage`, and without it they last until reload. `signatureFonts` lists the handwriting fonts the Type tab offers; load them with `@font-face`. A placed signature keeps its aspect ratio when resized.
|
|
161
159
|
|
|
160
|
+
## Plugins
|
|
161
|
+
|
|
162
|
+
A plugin from `definePlugin` (in `@inkbar/core/dom`) adds commands, tools, decorations, page layers and its own record stores. Pass plugins to `InkRoot` once: the prop is read when the root is created. Nothing a plugin registers shows until you place it:
|
|
163
|
+
|
|
164
|
+
```svelte
|
|
165
|
+
<InkRoot {src} {assets} plugins={[suggestions]}>
|
|
166
|
+
<InkToolbar>
|
|
167
|
+
<InkCommand id="sandbox.suggest" />
|
|
168
|
+
<InkToolButton tool="sandbox.stamp" />
|
|
169
|
+
</InkToolbar>
|
|
170
|
+
<InkViewport />
|
|
171
|
+
</InkRoot>
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
`InkCommand` is a button named by the command's label, disabled while the command is, and its status in a live region beside it. While the command runs, the button keeps focus and has `aria-busy` and `aria-disabled`, and pressing it does nothing. It shows the command's icon, or its label as text when the command has no icon. To build your own, `useCommand(id)` returns `run`, `enabled`, `busy`, `status`, `label` and `icon`. Plugins are set up once `InkRoot` has mounted in the browser, never on the server. Until then the command is disabled and has no label, and `InkCommand` keeps its button out of the accessibility tree (`aria-hidden`). Read `label` and `icon` off the handle, as you read `enabled`, so they fill in then. An id no plugin registered stays that way: `InkCommand` and `useCommand` log an `InkError('UnknownCommand')` to the console once, naming the id and the registered ones, and nothing throws. `InkToolButton` takes a plugin tool's own label and icon.
|
|
175
|
+
|
|
176
|
+
Plugin stores undo with annotations. In controlled mode with `history`, each undo step also snapshots the plugin stores, so accepting something and undoing it restores both.
|
|
177
|
+
|
|
162
178
|
## Custom rendering
|
|
163
179
|
|
|
164
|
-
Slots are snippets. `InkViewport` takes `loading`, `empty`, `error`, `annotation` and `pageError`. `InkThreadPopover`
|
|
180
|
+
Slots are snippets. `InkViewport` takes `loading`, `empty`, `error`, `annotation` and `pageError`. `InkThreadPopover` takes `children` as a snippet that receives the popover's props. `InkToolButton` takes `children` as a snippet of `{ active, label }`, and `InkCommand` one of `{ label, busy }`. The props each receives are typed as `AnnotationSlotProps` and `ThreadPopoverSlotProps`, the same types the Vue and React packages export.
|
|
165
181
|
|
|
166
|
-
`InkRoot`, `InkToolbar`, `InkToolButton`, `InkUndo`, `InkRedo`, `InkPrevPage`, `InkNextPage`, `InkZoomIn` and `InkZoomOut` take a `child` snippet to render your own element. Spread the `props` it gives you on that element. The snippet type is exported as `ChildSnippet`.
|
|
182
|
+
`InkRoot`, `InkToolbar`, `InkToolButton`, `InkCommand`, `InkUndo`, `InkRedo`, `InkPrevPage`, `InkNextPage`, `InkZoomIn` and `InkZoomOut` take a `child` snippet to render your own element. Spread the `props` it gives you on that element. The snippet type is exported as `ChildSnippet`.
|
|
167
183
|
|
|
168
184
|
```svelte
|
|
169
185
|
<InkToolbar>
|
|
@@ -183,7 +199,7 @@ The scroll area takes focus. From there:
|
|
|
183
199
|
|
|
184
200
|
On a focused annotation:
|
|
185
201
|
|
|
186
|
-
- `Enter` or `Space` selects and activates it. A note
|
|
202
|
+
- `Enter` or `Space` selects and activates it. A note or text markup opens its thread; other types are only selected.
|
|
187
203
|
- `ArrowUp` and `ArrowDown` move to the previous or next annotation on the page.
|
|
188
204
|
- `Shift` + arrow moves it by 1 pt, or by 10 pt with `Alt` as well.
|
|
189
205
|
- `Ctrl` or `Cmd` + `Shift` + arrow resizes an area redaction, text box or shape.
|
|
@@ -197,7 +213,7 @@ Components carry state on data attributes, for example `[data-annotation][data-s
|
|
|
197
213
|
|
|
198
214
|
## Hooks
|
|
199
215
|
|
|
200
|
-
`useDocument`, `useAnnotations`, `useTool`, `useSelection`, `useZoom`, `usePage`, `useHistory` and `
|
|
216
|
+
`useDocument`, `useAnnotations`, `useTool`, `useSelection`, `useZoom`, `usePage`, `useHistory`, `useEmbedded` and `useCommand` read the state of the nearest `InkRoot`. Call them during component initialisation, as you would `getContext`. Each returns an object whose reactive members are getters: read `useZoom().zoom` in your template or an effect and it tracks; destructure it (`const { zoom } = useZoom()`) and you copy the value once. Keep the object and read through it. Tool options, such as the shape kind, last as long as `InkRoot`. The tool style that `useToolStyle` reads and sets (colours, line style, text style) belongs to the document: opening another one starts again from the theme's defaults.
|
|
201
217
|
|
|
202
218
|
## Licence
|
|
203
219
|
|
|
@@ -17,8 +17,6 @@
|
|
|
17
17
|
* default visual for every annotation that is not custom, so this never draws a second one.
|
|
18
18
|
* InkPage portals it into the annotation's overlay element.
|
|
19
19
|
*/
|
|
20
|
-
import { acceptSuggestion } from '@inkbar/core'
|
|
21
|
-
|
|
22
20
|
import { useInkContext } from '../context.js'
|
|
23
21
|
|
|
24
22
|
import type { AnnotationState } from '@inkbar/core/dom'
|
|
@@ -48,9 +46,6 @@
|
|
|
48
46
|
return () => observer.disconnect()
|
|
49
47
|
})
|
|
50
48
|
|
|
51
|
-
const isSuggestion = $derived(
|
|
52
|
-
annotation.type === 'redact' && annotation.origin === 'suggested'
|
|
53
|
-
)
|
|
54
49
|
const locked = $derived(annotation.locked === true)
|
|
55
50
|
const selected = $derived(ctx.selection.includes(annotation.id))
|
|
56
51
|
const store = () => ctx.document?.annotations
|
|
@@ -60,13 +55,6 @@
|
|
|
60
55
|
locked,
|
|
61
56
|
select: () => store()?.select([annotation.id]),
|
|
62
57
|
remove: () => void store()?.remove(annotation.id),
|
|
63
|
-
accept: () => {
|
|
64
|
-
const s = store()
|
|
65
|
-
if (s && isSuggestion) acceptSuggestion(s, annotation.id)
|
|
66
|
-
},
|
|
67
|
-
dismiss: () => {
|
|
68
|
-
if (isSuggestion) store()?.remove(annotation.id)
|
|
69
|
-
},
|
|
70
58
|
})
|
|
71
59
|
</script>
|
|
72
60
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"InkAnnotation.svelte.d.ts","sourceRoot":"","sources":["../../src/lib/components/InkAnnotation.svelte.ts"],"names":[],"mappings":"AAGE,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,cAAc,CAAA;AAC9C,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,kBAAkB,CAAA;AAC3D,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAA;AAErC,MAAM,WAAW,kBAAkB;IACjC,UAAU,EAAE,UAAU,CAAA;IACtB,mFAAmF;IACnF,OAAO,CAAC,EAAE,WAAW,GAAG,SAAS,CAAA;IACjC,QAAQ,EAAE,OAAO,CAAC,CAAC,mBAAmB,CAAC,CAAC,CAAA;CACzC;
|
|
1
|
+
{"version":3,"file":"InkAnnotation.svelte.d.ts","sourceRoot":"","sources":["../../src/lib/components/InkAnnotation.svelte.ts"],"names":[],"mappings":"AAGE,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,cAAc,CAAA;AAC9C,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,kBAAkB,CAAA;AAC3D,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAA;AAErC,MAAM,WAAW,kBAAkB;IACjC,UAAU,EAAE,UAAU,CAAA;IACtB,mFAAmF;IACnF,OAAO,CAAC,EAAE,WAAW,GAAG,SAAS,CAAA;IACjC,QAAQ,EAAE,OAAO,CAAC,CAAC,mBAAmB,CAAC,CAAC,CAAA;CACzC;AA6DH,QAAA,MAAM,aAAa,wDAAwC,CAAC;AAC5D,KAAK,aAAa,GAAG,UAAU,CAAC,OAAO,aAAa,CAAC,CAAC;AACtD,eAAe,aAAa,CAAC"}
|
|
@@ -28,17 +28,13 @@
|
|
|
28
28
|
* tool is active, and recolours the selection only when it is all in the group.
|
|
29
29
|
*/
|
|
30
30
|
group?: StyleGroup | undefined
|
|
31
|
+
/** The colour new annotations get while the style leaves it unset; the swatch shows it. */
|
|
32
|
+
fallback?: string | undefined
|
|
31
33
|
}
|
|
32
34
|
</script>
|
|
33
35
|
|
|
34
36
|
<script lang="ts">
|
|
35
|
-
import {
|
|
36
|
-
COLOR_COLUMNS,
|
|
37
|
-
colorMenuModel,
|
|
38
|
-
focusInitialMenuItem,
|
|
39
|
-
handleMenuKeys,
|
|
40
|
-
menuItems,
|
|
41
|
-
} from '@inkbar/core/dom'
|
|
37
|
+
import { COLOR_COLUMNS, colorMenuModel, handleMenuKeys } from '@inkbar/core/dom'
|
|
42
38
|
import { Check } from '@inkbar/icons'
|
|
43
39
|
import { Popover, Toolbar } from 'bits-ui'
|
|
44
40
|
|
|
@@ -46,6 +42,7 @@
|
|
|
46
42
|
|
|
47
43
|
import { asBitsProps, composeHandlers } from './child-snippet.js'
|
|
48
44
|
import InkIcon from './InkIcon.svelte'
|
|
45
|
+
import { createMenuOpenFocus } from './menu-open-focus.js'
|
|
49
46
|
|
|
50
47
|
let {
|
|
51
48
|
property,
|
|
@@ -54,6 +51,7 @@
|
|
|
54
51
|
children,
|
|
55
52
|
standalone = false,
|
|
56
53
|
group,
|
|
54
|
+
fallback,
|
|
57
55
|
disabled,
|
|
58
56
|
onclick,
|
|
59
57
|
onkeydown,
|
|
@@ -63,7 +61,9 @@
|
|
|
63
61
|
const style = useToolStyle(() => group)
|
|
64
62
|
let open = $state(false)
|
|
65
63
|
|
|
66
|
-
const model = $derived(
|
|
64
|
+
const model = $derived(
|
|
65
|
+
colorMenuModel(property, style.current, style.supported, fallback)
|
|
66
|
+
)
|
|
67
67
|
const name = $derived(label ?? model.label)
|
|
68
68
|
const trigger = $derived(model.trigger)
|
|
69
69
|
|
|
@@ -77,15 +77,14 @@
|
|
|
77
77
|
})
|
|
78
78
|
|
|
79
79
|
/** ArrowDown opens on the first item; a click opens on the checked one. */
|
|
80
|
-
|
|
81
|
-
// Bits runs the open-focus callback more than once, so the flag outlives it; reset on close.
|
|
80
|
+
const openFocus = createMenuOpenFocus()
|
|
82
81
|
$effect(() => {
|
|
83
|
-
if (!open)
|
|
82
|
+
if (!open) openFocus.reset()
|
|
84
83
|
})
|
|
85
84
|
function onTriggerKeydown(e: KeyboardEvent): void {
|
|
86
85
|
if (e.key !== 'ArrowDown' || e.defaultPrevented || open || !model.enabled) return
|
|
87
86
|
e.preventDefault()
|
|
88
|
-
|
|
87
|
+
openFocus.first()
|
|
89
88
|
open = true
|
|
90
89
|
}
|
|
91
90
|
|
|
@@ -159,13 +158,8 @@
|
|
|
159
158
|
align="start"
|
|
160
159
|
sideOffset={4}
|
|
161
160
|
collisionPadding={8}
|
|
162
|
-
onOpenAutoFocus={(e) =>
|
|
163
|
-
|
|
164
|
-
if (menu) {
|
|
165
|
-
if (focusFirst) menuItems(menu)[0]?.focus({ preventScroll: true })
|
|
166
|
-
else focusInitialMenuItem(menu)
|
|
167
|
-
}
|
|
168
|
-
}}
|
|
161
|
+
onOpenAutoFocus={(e) => openFocus.onOpenAutoFocus(e, menu)}
|
|
162
|
+
onfocusin={openFocus.onfocusin}
|
|
169
163
|
onkeydown={(e) => {
|
|
170
164
|
// Nested in another panel, Escape closes this menu alone; Bits would otherwise pick
|
|
171
165
|
// the layer to close by registration order.
|
|
@@ -26,6 +26,8 @@ export interface InkColorMenuProps extends Omit<HTMLButtonAttributes, 'children'
|
|
|
26
26
|
* tool is active, and recolours the selection only when it is all in the group.
|
|
27
27
|
*/
|
|
28
28
|
group?: StyleGroup | undefined;
|
|
29
|
+
/** The colour new annotations get while the style leaves it unset; the swatch shows it. */
|
|
30
|
+
fallback?: string | undefined;
|
|
29
31
|
}
|
|
30
32
|
declare const InkColorMenu: import("svelte").Component<InkColorMenuProps, {}, "">;
|
|
31
33
|
type InkColorMenu = ReturnType<typeof InkColorMenu>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"InkColorMenu.svelte.d.ts","sourceRoot":"","sources":["../../src/lib/components/InkColorMenu.svelte.ts"],"names":[],"mappings":"AAGE,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,cAAc,CAAA;AAC9C,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAA;AACrD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAA;AACrC,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,iBAAiB,CAAA;AAE3D;;;;;;GAMG;AACH,MAAM,WAAW,iBAAkB,SAAQ,IAAI,CAAC,oBAAoB,EAAE,UAAU,CAAC;IAC/E,iCAAiC;IACjC,QAAQ,EAAE,aAAa,CAAA;IACvB,oFAAoF;IACpF,KAAK,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;IAC1B,2BAA2B;IAC3B,QAAQ,CAAC,EAAE,OAAO,GAAG,SAAS,CAAA;IAC9B;;;OAGG;IACH,UAAU,CAAC,EAAE,OAAO,GAAG,SAAS,CAAA;IAChC;;;OAGG;IACH,KAAK,CAAC,EAAE,UAAU,GAAG,SAAS,CAAA;
|
|
1
|
+
{"version":3,"file":"InkColorMenu.svelte.d.ts","sourceRoot":"","sources":["../../src/lib/components/InkColorMenu.svelte.ts"],"names":[],"mappings":"AAGE,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,cAAc,CAAA;AAC9C,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAA;AACrD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAA;AACrC,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,iBAAiB,CAAA;AAE3D;;;;;;GAMG;AACH,MAAM,WAAW,iBAAkB,SAAQ,IAAI,CAAC,oBAAoB,EAAE,UAAU,CAAC;IAC/E,iCAAiC;IACjC,QAAQ,EAAE,aAAa,CAAA;IACvB,oFAAoF;IACpF,KAAK,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;IAC1B,2BAA2B;IAC3B,QAAQ,CAAC,EAAE,OAAO,GAAG,SAAS,CAAA;IAC9B;;;OAGG;IACH,UAAU,CAAC,EAAE,OAAO,GAAG,SAAS,CAAA;IAChC;;;OAGG;IACH,KAAK,CAAC,EAAE,UAAU,GAAG,SAAS,CAAA;IAC9B,2FAA2F;IAC3F,QAAQ,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;CAC9B;AAkJH,QAAA,MAAM,YAAY,uDAAwC,CAAC;AAC3D,KAAK,YAAY,GAAG,UAAU,CAAC,OAAO,YAAY,CAAC,CAAC;AACpD,eAAe,YAAY,CAAC"}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
<script module lang="ts">
|
|
2
|
+
import type { ChildSnippet } from './child-snippet.js'
|
|
3
|
+
import type { Snippet } from 'svelte'
|
|
4
|
+
import type { HTMLButtonAttributes } from 'svelte/elements'
|
|
5
|
+
|
|
6
|
+
export interface InkCommandProps extends Omit<HTMLButtonAttributes, 'id' | 'children'> {
|
|
7
|
+
/** The command's namespaced id, such as `sandbox.suggest`. Read once. */
|
|
8
|
+
id: string
|
|
9
|
+
children?: Snippet<[{ label: string; busy: boolean }]> | undefined
|
|
10
|
+
/** Render your own button: spread `props` on it. */
|
|
11
|
+
child?: ChildSnippet | undefined
|
|
12
|
+
}
|
|
13
|
+
</script>
|
|
14
|
+
|
|
15
|
+
<script lang="ts">
|
|
16
|
+
/*
|
|
17
|
+
* A plugin command as a toolbar button with its status beside it: the markup of core's
|
|
18
|
+
* `createCommandButton`, so the theme styles both alike. The button is named by the command's
|
|
19
|
+
* label and disabled while the command is, except while it runs: then it keeps focus, with
|
|
20
|
+
* `aria-busy` and `aria-disabled`, and a press does nothing. The status is a live region. A command without an icon shows its label as the button's text. Until the plugins
|
|
21
|
+
* are set up (once InkRoot has mounted; never on the server) the button is disabled and
|
|
22
|
+
* `aria-hidden`. An id no plugin registered stays that way and logs
|
|
23
|
+
* `InkError('UnknownCommand')` to the console once.
|
|
24
|
+
*/
|
|
25
|
+
import { Toolbar } from 'bits-ui'
|
|
26
|
+
import { untrack } from 'svelte'
|
|
27
|
+
|
|
28
|
+
import { useCommand } from '../hooks/useCommand.svelte.js'
|
|
29
|
+
|
|
30
|
+
import { asBitsProps } from './child-snippet.js'
|
|
31
|
+
import InkIcon from './InkIcon.svelte'
|
|
32
|
+
|
|
33
|
+
let { id, children, child, disabled, onclick, ...rest }: InkCommandProps = $props()
|
|
34
|
+
|
|
35
|
+
// The id is read once, as `useCommand` does: a different command is a different component.
|
|
36
|
+
const commandId = untrack(() => id)
|
|
37
|
+
const command = useCommand(commandId)
|
|
38
|
+
</script>
|
|
39
|
+
|
|
40
|
+
<div class="ink-command">
|
|
41
|
+
<Toolbar.Button
|
|
42
|
+
type="button"
|
|
43
|
+
data-command={commandId}
|
|
44
|
+
aria-label={command.label || undefined}
|
|
45
|
+
title={command.label || undefined}
|
|
46
|
+
aria-hidden={command.label ? undefined : 'true'}
|
|
47
|
+
aria-busy={command.busy}
|
|
48
|
+
aria-disabled={command.busy ? 'true' : undefined}
|
|
49
|
+
disabled={(!command.enabled && !command.busy) || !!disabled}
|
|
50
|
+
{...child ? { child } : {}}
|
|
51
|
+
{...asBitsProps(rest)}
|
|
52
|
+
onclick={(e) => {
|
|
53
|
+
onclick?.(e)
|
|
54
|
+
if (!e.defaultPrevented) void command.run()
|
|
55
|
+
}}
|
|
56
|
+
>
|
|
57
|
+
{#if children}{@render children({
|
|
58
|
+
label: command.label,
|
|
59
|
+
busy: command.busy,
|
|
60
|
+
})}{:else if command.icon}<InkIcon icon={command.icon} />{:else}{command.label}{/if}
|
|
61
|
+
</Toolbar.Button>
|
|
62
|
+
<span class="ink-command-status" role="status">{command.status}</span>
|
|
63
|
+
</div>
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { ChildSnippet } from './child-snippet.js';
|
|
2
|
+
import type { Snippet } from 'svelte';
|
|
3
|
+
import type { HTMLButtonAttributes } from 'svelte/elements';
|
|
4
|
+
export interface InkCommandProps extends Omit<HTMLButtonAttributes, 'id' | 'children'> {
|
|
5
|
+
/** The command's namespaced id, such as `sandbox.suggest`. Read once. */
|
|
6
|
+
id: string;
|
|
7
|
+
children?: Snippet<[{
|
|
8
|
+
label: string;
|
|
9
|
+
busy: boolean;
|
|
10
|
+
}]> | undefined;
|
|
11
|
+
/** Render your own button: spread `props` on it. */
|
|
12
|
+
child?: ChildSnippet | undefined;
|
|
13
|
+
}
|
|
14
|
+
declare const InkCommand: import("svelte").Component<InkCommandProps, {}, "">;
|
|
15
|
+
type InkCommand = ReturnType<typeof InkCommand>;
|
|
16
|
+
export default InkCommand;
|
|
17
|
+
//# sourceMappingURL=InkCommand.svelte.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"InkCommand.svelte.d.ts","sourceRoot":"","sources":["../../src/lib/components/InkCommand.svelte.ts"],"names":[],"mappings":"AAGE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAA;AACtD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAA;AACrC,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,iBAAiB,CAAA;AAE3D,MAAM,WAAW,eAAgB,SAAQ,IAAI,CAAC,oBAAoB,EAAE,IAAI,GAAG,UAAU,CAAC;IACpF,yEAAyE;IACzE,EAAE,EAAE,MAAM,CAAA;IACV,QAAQ,CAAC,EAAE,OAAO,CAAC,CAAC;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,OAAO,CAAA;KAAE,CAAC,CAAC,GAAG,SAAS,CAAA;IAClE,oDAAoD;IACpD,KAAK,CAAC,EAAE,YAAY,GAAG,SAAS,CAAA;CACjC;AAsDH,QAAA,MAAM,UAAU,qDAAwC,CAAC;AACzD,KAAK,UAAU,GAAG,UAAU,CAAC,OAAO,UAAU,CAAC,CAAC;AAChD,eAAe,UAAU,CAAC"}
|
|
@@ -19,45 +19,49 @@
|
|
|
19
19
|
|
|
20
20
|
<script lang="ts">
|
|
21
21
|
import {
|
|
22
|
-
focusInitialMenuItem,
|
|
23
22
|
handleMenuKeys,
|
|
24
23
|
highlightMenuModel,
|
|
25
24
|
MARKUP_ICONS,
|
|
26
25
|
MARKUP_KINDS,
|
|
27
|
-
menuItems,
|
|
28
26
|
type HighlightOptions,
|
|
29
27
|
type MarkupKind,
|
|
30
28
|
} from '@inkbar/core/dom'
|
|
31
29
|
import { ChevronDown } from '@inkbar/icons'
|
|
32
30
|
import { Popover, Toolbar } from 'bits-ui'
|
|
33
31
|
|
|
32
|
+
import { useDocument } from '../hooks/useDocument.svelte.js'
|
|
34
33
|
import { useTool } from '../hooks/useTool.svelte.js'
|
|
35
34
|
import { useToolOptions } from '../hooks/useToolOptions.svelte.js'
|
|
36
35
|
|
|
37
36
|
import InkColorMenu from './InkColorMenu.svelte'
|
|
38
37
|
import InkIcon from './InkIcon.svelte'
|
|
38
|
+
import { createMenuOpenFocus } from './menu-open-focus.js'
|
|
39
39
|
|
|
40
40
|
let { class: className, children, ...rest }: InkHighlightMenuProps = $props()
|
|
41
41
|
|
|
42
42
|
const current = useTool()
|
|
43
|
+
const inkDocument = useDocument()
|
|
43
44
|
const highlight = useToolOptions<HighlightOptions>('highlight')
|
|
44
45
|
let open = $state(false)
|
|
46
|
+
/** The split button: the panel lines up under it rather than the chevron. */
|
|
47
|
+
let group = $state<HTMLElement | null>(null)
|
|
45
48
|
|
|
46
49
|
const model = $derived(highlightMenuModel(highlight.options, MARKUP_KINDS))
|
|
50
|
+
/** The colour the chosen kind gets while the markup style leaves it unset. */
|
|
51
|
+
const fallback = $derived(inkDocument.document?.annotations.defaults.color(model.kind))
|
|
47
52
|
const active = $derived(current.tool === 'highlight')
|
|
48
53
|
|
|
49
54
|
let menu = $state<HTMLElement | null>(null)
|
|
50
55
|
|
|
51
56
|
/** ArrowDown opens on the first item; a click opens on the checked one. */
|
|
52
|
-
|
|
53
|
-
// Bits runs the open-focus callback more than once, so the flag outlives it; reset on close.
|
|
57
|
+
const openFocus = createMenuOpenFocus()
|
|
54
58
|
$effect(() => {
|
|
55
|
-
if (!open)
|
|
59
|
+
if (!open) openFocus.reset()
|
|
56
60
|
})
|
|
57
61
|
function onTriggerKeydown(e: KeyboardEvent): void {
|
|
58
62
|
if (e.key !== 'ArrowDown' || e.defaultPrevented || open) return
|
|
59
63
|
e.preventDefault()
|
|
60
|
-
|
|
64
|
+
openFocus.first()
|
|
61
65
|
open = true
|
|
62
66
|
}
|
|
63
67
|
|
|
@@ -69,6 +73,7 @@
|
|
|
69
73
|
</script>
|
|
70
74
|
|
|
71
75
|
<div
|
|
76
|
+
bind:this={group}
|
|
72
77
|
role="group"
|
|
73
78
|
aria-label="Highlight"
|
|
74
79
|
class={['ink-split-button ink-highlight-menu', className]}
|
|
@@ -116,17 +121,13 @@
|
|
|
116
121
|
class="ink-select-content ink-menu-content"
|
|
117
122
|
data-ink-select-content=""
|
|
118
123
|
data-ink-menu="highlight"
|
|
124
|
+
customAnchor={group}
|
|
119
125
|
side="bottom"
|
|
120
126
|
align="start"
|
|
121
127
|
sideOffset={4}
|
|
122
128
|
collisionPadding={8}
|
|
123
|
-
onOpenAutoFocus={(e) =>
|
|
124
|
-
|
|
125
|
-
if (menu) {
|
|
126
|
-
if (focusFirst) menuItems(menu)[0]?.focus({ preventScroll: true })
|
|
127
|
-
else focusInitialMenuItem(menu)
|
|
128
|
-
}
|
|
129
|
-
}}
|
|
129
|
+
onOpenAutoFocus={(e) => openFocus.onOpenAutoFocus(e, menu)}
|
|
130
|
+
onfocusin={openFocus.onfocusin}
|
|
130
131
|
onkeydowncapture={(e: KeyboardEvent) => menu && handleMenuKeys(e, menu)}
|
|
131
132
|
>
|
|
132
133
|
<div role="group" aria-label="Kinds" class="ink-highlight-kinds">
|
|
@@ -156,6 +157,7 @@
|
|
|
156
157
|
group="markup"
|
|
157
158
|
label="Highlight colour"
|
|
158
159
|
standalone
|
|
160
|
+
{fallback}
|
|
159
161
|
role="menuitem"
|
|
160
162
|
tabindex={-1}
|
|
161
163
|
/>
|
|
@@ -14,12 +14,7 @@
|
|
|
14
14
|
</script>
|
|
15
15
|
|
|
16
16
|
<script lang="ts">
|
|
17
|
-
import {
|
|
18
|
-
focusInitialMenuItem,
|
|
19
|
-
handleMenuKeys,
|
|
20
|
-
lineStyleMenuModel,
|
|
21
|
-
menuItems,
|
|
22
|
-
} from '@inkbar/core/dom'
|
|
17
|
+
import { handleMenuKeys, lineStyleMenuModel } from '@inkbar/core/dom'
|
|
23
18
|
import { Check, Minus } from '@inkbar/icons'
|
|
24
19
|
import { Popover, Toolbar } from 'bits-ui'
|
|
25
20
|
|
|
@@ -27,6 +22,7 @@
|
|
|
27
22
|
|
|
28
23
|
import { asBitsProps, composeHandlers } from './child-snippet.js'
|
|
29
24
|
import InkIcon from './InkIcon.svelte'
|
|
25
|
+
import { createMenuOpenFocus } from './menu-open-focus.js'
|
|
30
26
|
|
|
31
27
|
let {
|
|
32
28
|
class: className,
|
|
@@ -50,15 +46,14 @@
|
|
|
50
46
|
})
|
|
51
47
|
|
|
52
48
|
/** ArrowDown opens on the first item; a click opens on the checked one. */
|
|
53
|
-
|
|
54
|
-
// Bits runs the open-focus callback more than once, so the flag outlives it; reset on close.
|
|
49
|
+
const openFocus = createMenuOpenFocus()
|
|
55
50
|
$effect(() => {
|
|
56
|
-
if (!open)
|
|
51
|
+
if (!open) openFocus.reset()
|
|
57
52
|
})
|
|
58
53
|
function onTriggerKeydown(e: KeyboardEvent): void {
|
|
59
54
|
if (e.key !== 'ArrowDown' || e.defaultPrevented || open || !model.enabled) return
|
|
60
55
|
e.preventDefault()
|
|
61
|
-
|
|
56
|
+
openFocus.first()
|
|
62
57
|
open = true
|
|
63
58
|
}
|
|
64
59
|
</script>
|
|
@@ -96,13 +91,8 @@
|
|
|
96
91
|
align="start"
|
|
97
92
|
sideOffset={4}
|
|
98
93
|
collisionPadding={8}
|
|
99
|
-
onOpenAutoFocus={(e) =>
|
|
100
|
-
|
|
101
|
-
if (menu) {
|
|
102
|
-
if (focusFirst) menuItems(menu)[0]?.focus({ preventScroll: true })
|
|
103
|
-
else focusInitialMenuItem(menu)
|
|
104
|
-
}
|
|
105
|
-
}}
|
|
94
|
+
onOpenAutoFocus={(e) => openFocus.onOpenAutoFocus(e, menu)}
|
|
95
|
+
onfocusin={openFocus.onfocusin}
|
|
106
96
|
onkeydown={(e) => menu && handleMenuKeys(e, menu)}
|
|
107
97
|
>
|
|
108
98
|
{#each model.sections as section (section.label)}
|
|
@@ -1 +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;
|
|
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;AAyFH,QAAA,MAAM,gBAAgB,2DAAwC,CAAC;AAC/D,KAAK,gBAAgB,GAAG,UAAU,CAAC,OAAO,gBAAgB,CAAC,CAAC;AAC5D,eAAe,gBAAgB,CAAC"}
|