@inkbar/svelte 0.5.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.
Files changed (33) hide show
  1. package/README.md +21 -3
  2. package/dist/components/InkColorMenu.svelte +7 -18
  3. package/dist/components/InkColorMenu.svelte.d.ts.map +1 -1
  4. package/dist/components/InkCommand.svelte +63 -0
  5. package/dist/components/InkCommand.svelte.d.ts +17 -0
  6. package/dist/components/InkCommand.svelte.d.ts.map +1 -0
  7. package/dist/components/InkHighlightMenu.svelte +6 -13
  8. package/dist/components/InkHighlightMenu.svelte.d.ts.map +1 -1
  9. package/dist/components/InkLineStyleMenu.svelte +7 -17
  10. package/dist/components/InkLineStyleMenu.svelte.d.ts.map +1 -1
  11. package/dist/components/InkRoot.svelte +98 -3
  12. package/dist/components/InkRoot.svelte.d.ts +7 -1
  13. package/dist/components/InkRoot.svelte.d.ts.map +1 -1
  14. package/dist/components/InkShapeMenu.svelte +6 -13
  15. package/dist/components/InkShapeMenu.svelte.d.ts.map +1 -1
  16. package/dist/components/InkSignatureMenu.svelte +6 -13
  17. package/dist/components/InkSignatureMenu.svelte.d.ts.map +1 -1
  18. package/dist/components/InkToolButton.svelte +11 -3
  19. package/dist/components/InkToolButton.svelte.d.ts.map +1 -1
  20. package/dist/components/InkViewport.svelte +13 -0
  21. package/dist/components/InkViewport.svelte.d.ts.map +1 -1
  22. package/dist/components/menu-open-focus.d.ts +12 -0
  23. package/dist/components/menu-open-focus.d.ts.map +1 -0
  24. package/dist/components/menu-open-focus.js +35 -0
  25. package/dist/context.d.ts +15 -4
  26. package/dist/context.d.ts.map +1 -1
  27. package/dist/hooks/useCommand.svelte.d.ts +23 -0
  28. package/dist/hooks/useCommand.svelte.d.ts.map +1 -0
  29. package/dist/hooks/useCommand.svelte.js +44 -0
  30. package/dist/index.d.ts +5 -1
  31. package/dist/index.d.ts.map +1 -1
  32. package/dist/index.js +2 -0
  33. package/package.json +5 -5
package/README.md CHANGED
@@ -157,11 +157,29 @@ The highlight tool leaves `contents` free for the comment. `doc.text(page)` retu
157
157
 
158
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.
159
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
+
160
178
  ## Custom rendering
161
179
 
162
- 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 }`. The props each receives are typed as `AnnotationSlotProps` and `ThreadPopoverSlotProps`, the same types the Vue and React packages export.
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.
163
181
 
164
- `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`.
165
183
 
166
184
  ```svelte
167
185
  <InkToolbar>
@@ -195,7 +213,7 @@ Components carry state on data attributes, for example `[data-annotation][data-s
195
213
 
196
214
  ## Hooks
197
215
 
198
- `useDocument`, `useAnnotations`, `useTool`, `useSelection`, `useZoom`, `usePage`, `useHistory` and `useEmbedded` 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.
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.
199
217
 
200
218
  ## Licence
201
219
 
@@ -34,13 +34,7 @@
34
34
  </script>
35
35
 
36
36
  <script lang="ts">
37
- import {
38
- COLOR_COLUMNS,
39
- colorMenuModel,
40
- focusInitialMenuItem,
41
- handleMenuKeys,
42
- menuItems,
43
- } from '@inkbar/core/dom'
37
+ import { COLOR_COLUMNS, colorMenuModel, handleMenuKeys } from '@inkbar/core/dom'
44
38
  import { Check } from '@inkbar/icons'
45
39
  import { Popover, Toolbar } from 'bits-ui'
46
40
 
@@ -48,6 +42,7 @@
48
42
 
49
43
  import { asBitsProps, composeHandlers } from './child-snippet.js'
50
44
  import InkIcon from './InkIcon.svelte'
45
+ import { createMenuOpenFocus } from './menu-open-focus.js'
51
46
 
52
47
  let {
53
48
  property,
@@ -82,15 +77,14 @@
82
77
  })
83
78
 
84
79
  /** ArrowDown opens on the first item; a click opens on the checked one. */
85
- let focusFirst = false
86
- // Bits runs the open-focus callback more than once, so the flag outlives it; reset on close.
80
+ const openFocus = createMenuOpenFocus()
87
81
  $effect(() => {
88
- if (!open) focusFirst = false
82
+ if (!open) openFocus.reset()
89
83
  })
90
84
  function onTriggerKeydown(e: KeyboardEvent): void {
91
85
  if (e.key !== 'ArrowDown' || e.defaultPrevented || open || !model.enabled) return
92
86
  e.preventDefault()
93
- focusFirst = true
87
+ openFocus.first()
94
88
  open = true
95
89
  }
96
90
 
@@ -164,13 +158,8 @@
164
158
  align="start"
165
159
  sideOffset={4}
166
160
  collisionPadding={8}
167
- onOpenAutoFocus={(e) => {
168
- e.preventDefault()
169
- if (menu) {
170
- if (focusFirst) menuItems(menu)[0]?.focus({ preventScroll: true })
171
- else focusInitialMenuItem(menu)
172
- }
173
- }}
161
+ onOpenAutoFocus={(e) => openFocus.onOpenAutoFocus(e, menu)}
162
+ onfocusin={openFocus.onfocusin}
174
163
  onkeydown={(e) => {
175
164
  // Nested in another panel, Escape closes this menu alone; Bits would otherwise pick
176
165
  // the layer to close by registration order.
@@ -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;IAC9B,2FAA2F;IAC3F,QAAQ,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;CAC9B;AA6JH,QAAA,MAAM,YAAY,uDAAwC,CAAC;AAC3D,KAAK,YAAY,GAAG,UAAU,CAAC,OAAO,YAAY,CAAC,CAAC;AACpD,eAAe,YAAY,CAAC"}
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,12 +19,10 @@
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'
@@ -37,6 +35,7 @@
37
35
 
38
36
  import InkColorMenu from './InkColorMenu.svelte'
39
37
  import InkIcon from './InkIcon.svelte'
38
+ import { createMenuOpenFocus } from './menu-open-focus.js'
40
39
 
41
40
  let { class: className, children, ...rest }: InkHighlightMenuProps = $props()
42
41
 
@@ -55,15 +54,14 @@
55
54
  let menu = $state<HTMLElement | null>(null)
56
55
 
57
56
  /** ArrowDown opens on the first item; a click opens on the checked one. */
58
- let focusFirst = false
59
- // Bits runs the open-focus callback more than once, so the flag outlives it; reset on close.
57
+ const openFocus = createMenuOpenFocus()
60
58
  $effect(() => {
61
- if (!open) focusFirst = false
59
+ if (!open) openFocus.reset()
62
60
  })
63
61
  function onTriggerKeydown(e: KeyboardEvent): void {
64
62
  if (e.key !== 'ArrowDown' || e.defaultPrevented || open) return
65
63
  e.preventDefault()
66
- focusFirst = true
64
+ openFocus.first()
67
65
  open = true
68
66
  }
69
67
 
@@ -128,13 +126,8 @@
128
126
  align="start"
129
127
  sideOffset={4}
130
128
  collisionPadding={8}
131
- onOpenAutoFocus={(e) => {
132
- e.preventDefault()
133
- if (menu) {
134
- if (focusFirst) menuItems(menu)[0]?.focus({ preventScroll: true })
135
- else focusInitialMenuItem(menu)
136
- }
137
- }}
129
+ onOpenAutoFocus={(e) => openFocus.onOpenAutoFocus(e, menu)}
130
+ onfocusin={openFocus.onfocusin}
138
131
  onkeydowncapture={(e: KeyboardEvent) => menu && handleMenuKeys(e, menu)}
139
132
  >
140
133
  <div role="group" aria-label="Kinds" class="ink-highlight-kinds">
@@ -1 +1 @@
1
- {"version":3,"file":"InkHighlightMenu.svelte.d.ts","sourceRoot":"","sources":["../../src/lib/components/InkHighlightMenu.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;CAC/B;AAkHH,QAAA,MAAM,gBAAgB,2DAAwC,CAAC;AAC/D,KAAK,gBAAgB,GAAG,UAAU,CAAC,OAAO,gBAAgB,CAAC,CAAC;AAC5D,eAAe,gBAAgB,CAAC"}
1
+ {"version":3,"file":"InkHighlightMenu.svelte.d.ts","sourceRoot":"","sources":["../../src/lib/components/InkHighlightMenu.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;CAC/B;AA2GH,QAAA,MAAM,gBAAgB,2DAAwC,CAAC;AAC/D,KAAK,gBAAgB,GAAG,UAAU,CAAC,OAAO,gBAAgB,CAAC,CAAC;AAC5D,eAAe,gBAAgB,CAAC"}
@@ -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
- let focusFirst = false
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) focusFirst = false
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
- focusFirst = true
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
- e.preventDefault()
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;AAmGH,QAAA,MAAM,gBAAgB,2DAAwC,CAAC;AAC/D,KAAK,gBAAgB,GAAG,UAAU,CAAC,OAAO,gBAAgB,CAAC,CAAC;AAC5D,eAAe,gBAAgB,CAAC"}
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"}
@@ -11,7 +11,13 @@
11
11
  PdfAssets,
12
12
  SignatureStore,
13
13
  } from '@inkbar/core'
14
- import type { DecorateAnnotation, Tool, ToolId, ZoomValue } from '@inkbar/core/dom'
14
+ import type {
15
+ DecorateAnnotation,
16
+ InkPlugin,
17
+ Tool,
18
+ ToolId,
19
+ ZoomValue,
20
+ } from '@inkbar/core/dom'
15
21
  import type { Snippet } from 'svelte'
16
22
  import type { HTMLAttributes } from 'svelte/elements'
17
23
 
@@ -39,6 +45,12 @@
39
45
  * document opens.
40
46
  */
41
47
  tools?: Tool[] | undefined
48
+ /**
49
+ * Plugins, set up once when the root is created; a later change to this prop is not
50
+ * applied. Their tools join `tools`, their decoration and page layers apply to every
51
+ * document, and their commands are placed with `InkCommand` or `useCommand`.
52
+ */
53
+ plugins?: InkPlugin[] | undefined
42
54
  /**
43
55
  * Adds `data-*` attributes, classes and a screen-reader description to each annotation's
44
56
  * element (see `DecorateAnnotation`). Decorations re-apply whenever the annotations change
@@ -86,8 +98,11 @@
86
98
  createSnapshotHistory,
87
99
  InkError as InkErrorClass,
88
100
  openDocument,
101
+ readonly,
102
+ writable,
89
103
  } from '@inkbar/core'
90
104
  import {
105
+ createPluginHost,
91
106
  createToolOptions,
92
107
  cssVariableDefaults,
93
108
  FONT_FAMILIES,
@@ -101,7 +116,8 @@
101
116
  import { setInkContext, type InkContext, type InkHistory } from '../context.js'
102
117
  import { useReadable } from '../use-readable.svelte.js'
103
118
 
104
- import type { ToolController, Viewport } from '@inkbar/core/dom'
119
+ import type { PluginHistory } from '@inkbar/core'
120
+ import type { PluginHost, ToolController, Viewport } from '@inkbar/core/dom'
105
121
 
106
122
  let {
107
123
  src,
@@ -114,6 +130,7 @@
114
130
  tool = $bindable(),
115
131
  onToolChange,
116
132
  tools: hostTools,
133
+ plugins,
117
134
  decorateAnnotation,
118
135
  fonts,
119
136
  signatures: signatureStore,
@@ -146,6 +163,7 @@
146
163
  const IDLE: InkStatus = { state: 'idle' }
147
164
  const NO_ANNOTATIONS: readonly Annotation[] = []
148
165
  const NO_IDS: readonly string[] = []
166
+ const noop = (): void => undefined
149
167
 
150
168
  /** Signed URLs change; the key does not. Bytes and Blobs are their own identity. */
151
169
  function identity(source: DocumentSource | null): unknown {
@@ -160,6 +178,8 @@
160
178
  }
161
179
 
162
180
  let document = $state.raw<InkDocument | null>(null)
181
+ /** The same document outside the reactive graph, for the plugins' history. */
182
+ let documentNow: InkDocument | null = null
163
183
  /** Overrides the document's status while opening, and when opening itself failed. */
164
184
  let override = $state.raw<InkStatus | null>(null)
165
185
  let activated = $state<string | null>(null)
@@ -206,6 +226,8 @@
206
226
  const pw = sourcePassword
207
227
  let cancelled = false
208
228
  let opened: InkDocument | null = null
229
+ /** The snapshot history's hold on a controlled document's store. */
230
+ let detach: () => void = noop
209
231
  untrack(() => snapshotHistory.clear())
210
232
  const source = untrack(() => src)
211
233
  if (source === null) {
@@ -249,7 +271,13 @@
249
271
  const current = untrack(() => annotations)
250
272
  if (doc.annotations.mode === 'controlled' && current !== undefined)
251
273
  doc.annotations.setControlledValue(current)
274
+ documentNow = doc
252
275
  document = doc
276
+ // Controlled edits run the plugins' reactions inside their own snapshot step.
277
+ if (doc.annotations.mode === 'controlled')
278
+ detach = snapshotHistory.attach(doc.annotations)
279
+ // The document is current, so the host reads its history when it hears of it.
280
+ hostDocument.set(doc)
253
281
  override = null
254
282
  setTestHook(ref, doc)
255
283
  const s = doc.status.get()
@@ -272,7 +300,11 @@
272
300
  })()
273
301
  return () => {
274
302
  cancelled = true
303
+ // Cleared first, so the plugins close their stores while the document is still alive.
304
+ hostDocument.set(null)
305
+ detach()
275
306
  opened?.destroy()
307
+ documentNow = null
276
308
  document = null
277
309
  setTestHook(ref, undefined)
278
310
  }
@@ -295,6 +327,7 @@
295
327
  function setActivated(id: string | null): void {
296
328
  activated = id
297
329
  activatedNow = id
330
+ hostActivated.set(id)
298
331
  if (id) onActivate?.(id)
299
332
  }
300
333
  function reportZoom(z: ZoomValue): void {
@@ -325,8 +358,65 @@
325
358
  else if (document) document.history.batch(fn)
326
359
  else fn()
327
360
  },
361
+ track: (store) => pluginHistory.track(store),
362
+ react: (fn) => pluginHistory.react(fn),
363
+ }
364
+
365
+ /**
366
+ * The history the plugins reach, picked by the open document's mode when they call. The host
367
+ * reads it as soon as a document opens, so it goes by `documentNow`, which is set by then.
368
+ */
369
+ const activeHistory = (): PluginHistory | undefined => {
370
+ const doc = documentNow
371
+ if (!doc) return undefined
372
+ return doc.annotations.mode === 'controlled' ? snapshotHistory : doc.history
373
+ }
374
+ const pluginHistory: PluginHistory = {
375
+ batch: (fn) => {
376
+ const h = activeHistory()
377
+ if (h) h.batch(fn)
378
+ else fn()
379
+ },
380
+ track: (store) => activeHistory()?.track(store) ?? noop,
381
+ react: (fn) => activeHistory()?.react(fn) ?? noop,
328
382
  }
329
383
 
384
+ /*
385
+ * One plugin host for the root's life, built in an effect so that no `setup` runs on the
386
+ * server, where effects never run and nothing would clean it up. Until then `pluginHost` is
387
+ * null and InkCommand renders its command disabled. The open effect above runs first, but
388
+ * the document it opens arrives after an await, so the host is in place by then. It follows
389
+ * these writables rather than the root's state: the open effect sets `hostDocument` once
390
+ * `documentNow` is current.
391
+ */
392
+ const hostDocument = writable<InkDocument | null>(null)
393
+ const hostActivated = writable<string | null>(null)
394
+ const viewportEl = writable<HTMLElement | null>(null)
395
+ let pluginHost = $state.raw<PluginHost | null>(null)
396
+ $effect(() => {
397
+ const host = untrack(() =>
398
+ createPluginHost({
399
+ // Read once, as documented on the prop.
400
+ plugins: plugins ?? [],
401
+ document: readonly(hostDocument),
402
+ history: () => pluginHistory,
403
+ activated: readonly(hostActivated),
404
+ // As in `createViewer`: an id not in the open document is ignored.
405
+ setActivated: (id) => {
406
+ if (id === null || documentNow?.annotations.get(id)) setActivated(id)
407
+ },
408
+ root: readonly(viewportEl),
409
+ decorate: () => decorateAnnotation,
410
+ viewport: () => viewport,
411
+ })
412
+ )
413
+ pluginHost = host
414
+ // Destroyed after InkViewport's engine and its plugin layers, as in `createViewer`: Svelte
415
+ // creates a component's effects after its template and tears them down in creation order,
416
+ // so the children's teardowns run first.
417
+ return () => host.destroy()
418
+ })
419
+
330
420
  /** The document's tool style; it batches through `inkHistory`, whichever mode is on. */
331
421
  const styleTracker = $derived.by(() => {
332
422
  const doc = document
@@ -335,7 +425,8 @@
335
425
  trackToolStyle({
336
426
  store: doc.annotations,
337
427
  history: { batch: (fn) => inkHistory.batch(fn) },
338
- tools: hostTools,
428
+ // The plugins' tools too, so a plugin tool's style group applies.
429
+ tools: [...(hostTools ?? []), ...(pluginHost?.tools ?? [])],
339
430
  tool: currentTool,
340
431
  })
341
432
  )
@@ -361,6 +452,10 @@
361
452
  get selection() {
362
453
  return selection.current
363
454
  },
455
+ get pluginHost() {
456
+ return pluginHost
457
+ },
458
+ viewportEl,
364
459
  get viewport() {
365
460
  return viewport
366
461
  },
@@ -1,6 +1,6 @@
1
1
  import type { ChildSnippet } from './child-snippet.js';
2
2
  import type { Annotation, AnnotationChange, AnnotationDefaults, DocumentSource, InkDocument, InkError, InkStatus, PdfAssets, SignatureStore } from '@inkbar/core';
3
- import type { DecorateAnnotation, Tool, ToolId, ZoomValue } from '@inkbar/core/dom';
3
+ import type { DecorateAnnotation, InkPlugin, 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'> {
@@ -24,6 +24,12 @@ export interface InkRootProps extends Omit<HTMLAttributes<HTMLDivElement>, 'chil
24
24
  * document opens.
25
25
  */
26
26
  tools?: Tool[] | undefined;
27
+ /**
28
+ * Plugins, set up once when the root is created; a later change to this prop is not
29
+ * applied. Their tools join `tools`, their decoration and page layers apply to every
30
+ * document, and their commands are placed with `InkCommand` or `useCommand`.
31
+ */
32
+ plugins?: InkPlugin[] | undefined;
27
33
  /**
28
34
  * Adds `data-*` attributes, classes and a screen-reader description to each annotation's
29
35
  * element (see `DecorateAnnotation`). Decorations re-apply whenever the annotations change
@@ -1 +1 @@
1
- {"version":3,"file":"InkRoot.svelte.d.ts","sourceRoot":"","sources":["../../src/lib/components/InkRoot.svelte.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"InkRoot.svelte.d.ts","sourceRoot":"","sources":["../../src/lib/components/InkRoot.svelte.ts"],"names":[],"mappings":"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"}
@@ -18,9 +18,7 @@
18
18
 
19
19
  <script lang="ts">
20
20
  import {
21
- focusInitialMenuItem,
22
21
  handleMenuKeys,
23
- menuItems,
24
22
  SHAPE_COLUMNS,
25
23
  SHAPE_ICONS,
26
24
  SHAPE_KINDS,
@@ -35,6 +33,7 @@
35
33
  import { useToolOptions } from '../hooks/useToolOptions.svelte.js'
36
34
 
37
35
  import InkIcon from './InkIcon.svelte'
36
+ import { createMenuOpenFocus } from './menu-open-focus.js'
38
37
 
39
38
  let { class: className, children, ...rest }: InkShapeMenuProps = $props()
40
39
 
@@ -50,15 +49,14 @@
50
49
  let menu = $state<HTMLElement | null>(null)
51
50
 
52
51
  /** 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.
52
+ const openFocus = createMenuOpenFocus()
55
53
  $effect(() => {
56
- if (!open) focusFirst = false
54
+ if (!open) openFocus.reset()
57
55
  })
58
56
  function onTriggerKeydown(e: KeyboardEvent): void {
59
57
  if (e.key !== 'ArrowDown' || e.defaultPrevented || open) return
60
58
  e.preventDefault()
61
- focusFirst = true
59
+ openFocus.first()
62
60
  open = true
63
61
  }
64
62
 
@@ -123,13 +121,8 @@
123
121
  align="start"
124
122
  sideOffset={4}
125
123
  collisionPadding={8}
126
- onOpenAutoFocus={(e) => {
127
- e.preventDefault()
128
- if (menu) {
129
- if (focusFirst) menuItems(menu)[0]?.focus({ preventScroll: true })
130
- else focusInitialMenuItem(menu)
131
- }
132
- }}
124
+ onOpenAutoFocus={(e) => openFocus.onOpenAutoFocus(e, menu)}
125
+ onfocusin={openFocus.onfocusin}
133
126
  onkeydown={(e) => menu && handleMenuKeys(e, menu)}
134
127
  >
135
128
  <div
@@ -1 +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;AAsGH,QAAA,MAAM,YAAY,uDAAwC,CAAC;AAC3D,KAAK,YAAY,GAAG,UAAU,CAAC,OAAO,YAAY,CAAC,CAAC;AACpD,eAAe,YAAY,CAAC"}
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;AA+FH,QAAA,MAAM,YAAY,uDAAwC,CAAC;AAC3D,KAAK,YAAY,GAAG,UAAU,CAAC,OAAO,YAAY,CAAC,CAAC;AACpD,eAAe,YAAY,CAAC"}
@@ -21,9 +21,7 @@
21
21
 
22
22
  <script lang="ts">
23
23
  import {
24
- focusInitialMenuItem,
25
24
  handleMenuKeys,
26
- menuItems,
27
25
  SIGNATURE_LABELS,
28
26
  signatureMenuModel,
29
27
  signaturePreview,
@@ -39,6 +37,7 @@
39
37
 
40
38
  import InkIcon from './InkIcon.svelte'
41
39
  import InkSignatureDialog from './InkSignatureDialog.svelte'
40
+ import { createMenuOpenFocus } from './menu-open-focus.js'
42
41
 
43
42
  import type { SignatureOptions, SignatureTemplate } from '@inkbar/core'
44
43
 
@@ -65,10 +64,9 @@
65
64
  let main = $state<HTMLElement | null>(null)
66
65
 
67
66
  /** 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.
67
+ const openFocus = createMenuOpenFocus()
70
68
  $effect(() => {
71
- if (!open) focusFirst = false
69
+ if (!open) openFocus.reset()
72
70
  })
73
71
  /** Set while the menu closes for the dialog, which takes focus instead of the trigger. */
74
72
  let toDialog = false
@@ -76,7 +74,7 @@
76
74
  function onTriggerKeydown(e: KeyboardEvent): void {
77
75
  if (e.key !== 'ArrowDown' || e.defaultPrevented || open) return
78
76
  e.preventDefault()
79
- focusFirst = true
77
+ openFocus.first()
80
78
  open = true
81
79
  }
82
80
 
@@ -168,13 +166,8 @@
168
166
  align="start"
169
167
  sideOffset={4}
170
168
  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
- }}
169
+ onOpenAutoFocus={(e) => openFocus.onOpenAutoFocus(e, menu)}
170
+ onfocusin={openFocus.onfocusin}
178
171
  onCloseAutoFocus={(e) => {
179
172
  if (toDialog) e.preventDefault()
180
173
  toDialog = false
@@ -1 +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"}
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;AAkLH,QAAA,MAAM,gBAAgB,2DAAwC,CAAC;AAC/D,KAAK,gBAAgB,GAAG,UAAU,CAAC,OAAO,gBAAgB,CAAC,CAAC;AAC5D,eAAe,gBAAgB,CAAC"}
@@ -27,6 +27,7 @@
27
27
  } from '@inkbar/icons'
28
28
  import { Toolbar } from 'bits-ui'
29
29
 
30
+ import { useInkContext } from '../context.js'
30
31
  import { useTool } from '../hooks/useTool.svelte.js'
31
32
 
32
33
  import { asBitsProps } from './child-snippet.js'
@@ -56,11 +57,18 @@
56
57
  ...rest
57
58
  }: InkToolButtonProps = $props()
58
59
 
60
+ const ctx = useInkContext('InkToolButton')
59
61
  const current = useTool()
60
62
  const active = $derived(current.tool === tool)
61
- // A host tool has no built-in name or icon: `label` and `children` supply them.
62
- const name = $derived(label ?? TOOL_LABELS[tool] ?? tool)
63
- const icon = $derived(ICONS[tool])
63
+ /** A host or plugin tool by this id, for the name and icon core has none for. */
64
+ const own = $derived(
65
+ [...(ctx.hostTools ?? []), ...(ctx.pluginHost?.tools ?? [])].find(
66
+ (t) => t.id === tool
67
+ )
68
+ )
69
+ // `label` and `children` win; then core's name and icon; then the tool's own.
70
+ const name = $derived(label ?? TOOL_LABELS[tool] ?? own?.label ?? tool)
71
+ const icon = $derived(ICONS[tool] ?? own?.icon)
64
72
  </script>
65
73
 
66
74
  <Toolbar.Button
@@ -1 +1 @@
1
- {"version":3,"file":"InkToolButton.svelte.d.ts","sourceRoot":"","sources":["../../src/lib/components/InkToolButton.svelte.ts"],"names":[],"mappings":"AAGE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAA;AACtD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAA;AAC9C,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAA;AACrC,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,iBAAiB,CAAA;AAE3D,MAAM,WAAW,kBAAmB,SAAQ,IAAI,CAAC,oBAAoB,EAAE,UAAU,CAAC;IAChF,IAAI,EAAE,MAAM,CAAA;IACZ,KAAK,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;IAC1B,QAAQ,CAAC,EAAE,OAAO,CAAC,CAAC;QAAE,MAAM,EAAE,OAAO,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC,GAAG,SAAS,CAAA;IACpE,KAAK,CAAC,EAAE,YAAY,GAAG,SAAS,CAAA;CACjC;AA4EH,QAAA,MAAM,aAAa,wDAAwC,CAAC;AAC5D,KAAK,aAAa,GAAG,UAAU,CAAC,OAAO,aAAa,CAAC,CAAC;AACtD,eAAe,aAAa,CAAC"}
1
+ {"version":3,"file":"InkToolButton.svelte.d.ts","sourceRoot":"","sources":["../../src/lib/components/InkToolButton.svelte.ts"],"names":[],"mappings":"AAGE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAA;AACtD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAA;AAC9C,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAA;AACrC,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,iBAAiB,CAAA;AAE3D,MAAM,WAAW,kBAAmB,SAAQ,IAAI,CAAC,oBAAoB,EAAE,UAAU,CAAC;IAChF,IAAI,EAAE,MAAM,CAAA;IACZ,KAAK,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;IAC1B,QAAQ,CAAC,EAAE,OAAO,CAAC,CAAC;QAAE,MAAM,EAAE,OAAO,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC,GAAG,SAAS,CAAA;IACpE,KAAK,CAAC,EAAE,YAAY,GAAG,SAAS,CAAA;CACjC;AAqFH,QAAA,MAAM,aAAa,wDAAwC,CAAC;AAC5D,KAAK,aAAa,GAAG,UAAU,CAAC,OAAO,aAAa,CAAC,CAAC;AACtD,eAAe,aAAa,CAAC"}
@@ -126,6 +126,9 @@
126
126
  buffer,
127
127
  tool: ctx.tool,
128
128
  ...(ctx.hostTools ? { tools: ctx.hostTools } : {}),
129
+ // The engine adds the plugins' tools and layers, and decorates through the host, which
130
+ // reads `decorateAnnotation` first.
131
+ ...(ctx.pluginHost ? { plugins: ctx.pluginHost } : {}),
129
132
  // Read through, so the effect below can re-apply a new function without a rebuild.
130
133
  decorateAnnotation: (a) => ctx.decorateAnnotation?.(a),
131
134
  ...(ctx.toolStyle ? { toolStyle: ctx.toolStyle } : {}),
@@ -191,6 +194,16 @@
191
194
  })
192
195
  }
193
196
 
197
+ // The plugins' `root`. Before the build below, so it is set when the engine mounts.
198
+ $effect(() => {
199
+ const el = scroll
200
+ const { viewportEl } = ctx
201
+ viewportEl.set(el)
202
+ return () => {
203
+ if (viewportEl.get() === el) viewportEl.set(null)
204
+ }
205
+ })
206
+
194
207
  // Build for a new document or status; tear down on the next change and on unmount. Only the
195
208
  // values read before `untrack` are dependencies, each through a derived, which notifies only
196
209
  // when the value changes: the status is a new object on every engine report.
@@ -1 +1 @@
1
- {"version":3,"file":"InkViewport.svelte.d.ts","sourceRoot":"","sources":["../../src/lib/components/InkViewport.svelte.ts"],"names":[],"mappings":"AAGE,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAA;AAC5C,OAAO,KAAK,EAAE,mBAAmB,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAA;AACrF,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAA;AACrC,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAA;AAErD,MAAM,WAAW,gBAAiB,SAAQ,IAAI,CAC5C,cAAc,CAAC,cAAc,CAAC,EAC9B,UAAU,CACX;IACC,IAAI,CAAC,EAAE,UAAU,CAAA;IACjB,QAAQ,CAAC,EAAE,YAAY,CAAA;IACvB,iCAAiC;IACjC,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,0DAA0D;IAC1D,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,OAAO,CAAC,EAAE,OAAO,GAAG,SAAS,CAAA;IAC7B,KAAK,CAAC,EAAE,OAAO,GAAG,SAAS,CAAA;IAC3B,KAAK,CAAC,EAAE,OAAO,CAAC,CAAC;QAAE,KAAK,EAAE,QAAQ,CAAA;KAAE,CAAC,CAAC,GAAG,SAAS,CAAA;IAClD,UAAU,CAAC,EAAE,OAAO,CAAC,CAAC,mBAAmB,CAAC,CAAC,GAAG,SAAS,CAAA;IACvD,wFAAwF;IACxF,SAAS,CAAC,EAAE,OAAO,CAAC,CAAC;QAAE,KAAK,EAAE,QAAQ,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC,GAAG,SAAS,CAAA;CACrE;AA+SH,QAAA,MAAM,WAAW,sDAAwC,CAAC;AAC1D,KAAK,WAAW,GAAG,UAAU,CAAC,OAAO,WAAW,CAAC,CAAC;AAClD,eAAe,WAAW,CAAC"}
1
+ {"version":3,"file":"InkViewport.svelte.d.ts","sourceRoot":"","sources":["../../src/lib/components/InkViewport.svelte.ts"],"names":[],"mappings":"AAGE,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAA;AAC5C,OAAO,KAAK,EAAE,mBAAmB,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAA;AACrF,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAA;AACrC,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAA;AAErD,MAAM,WAAW,gBAAiB,SAAQ,IAAI,CAC5C,cAAc,CAAC,cAAc,CAAC,EAC9B,UAAU,CACX;IACC,IAAI,CAAC,EAAE,UAAU,CAAA;IACjB,QAAQ,CAAC,EAAE,YAAY,CAAA;IACvB,iCAAiC;IACjC,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,0DAA0D;IAC1D,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,OAAO,CAAC,EAAE,OAAO,GAAG,SAAS,CAAA;IAC7B,KAAK,CAAC,EAAE,OAAO,GAAG,SAAS,CAAA;IAC3B,KAAK,CAAC,EAAE,OAAO,CAAC,CAAC;QAAE,KAAK,EAAE,QAAQ,CAAA;KAAE,CAAC,CAAC,GAAG,SAAS,CAAA;IAClD,UAAU,CAAC,EAAE,OAAO,CAAC,CAAC,mBAAmB,CAAC,CAAC,GAAG,SAAS,CAAA;IACvD,wFAAwF;IACxF,SAAS,CAAC,EAAE,OAAO,CAAC,CAAC;QAAE,KAAK,EAAE,QAAQ,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC,GAAG,SAAS,CAAA;CACrE;AA4TH,QAAA,MAAM,WAAW,sDAAwC,CAAC;AAC1D,KAAK,WAAW,GAAG,UAAU,CAAC,OAAO,WAAW,CAAC,CAAC;AAClD,eAAe,WAAW,CAAC"}
@@ -0,0 +1,12 @@
1
+ export interface MenuOpenFocus {
2
+ /** Makes the next open start on the first item (ArrowDown on the trigger). */
3
+ first: () => void;
4
+ /** For the content's `onOpenAutoFocus`. */
5
+ onOpenAutoFocus: (e: Event, menu: HTMLElement | null) => void;
6
+ /** For the content's `onfocusin`: remembers where the user is. */
7
+ onfocusin: (e: FocusEvent) => void;
8
+ /** Call when the menu closes. */
9
+ reset: () => void;
10
+ }
11
+ export declare function createMenuOpenFocus(): MenuOpenFocus;
12
+ //# sourceMappingURL=menu-open-focus.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"menu-open-focus.d.ts","sourceRoot":"","sources":["../../src/lib/components/menu-open-focus.ts"],"names":[],"mappings":"AAQA,MAAM,WAAW,aAAa;IAC5B,8EAA8E;IAC9E,KAAK,EAAE,MAAM,IAAI,CAAA;IACjB,2CAA2C;IAC3C,eAAe,EAAE,CAAC,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,WAAW,GAAG,IAAI,KAAK,IAAI,CAAA;IAC7D,kEAAkE;IAClE,SAAS,EAAE,CAAC,CAAC,EAAE,UAAU,KAAK,IAAI,CAAA;IAClC,iCAAiC;IACjC,KAAK,EAAE,MAAM,IAAI,CAAA;CAClB;AAED,wBAAgB,mBAAmB,IAAI,aAAa,CAsBnD"}
@@ -0,0 +1,35 @@
1
+ /*
2
+ * Where a toolbar menu puts focus as it opens: the first item when ArrowDown opened it, else the
3
+ * checked one. Bits can remount a popover's focus scope just after it opens, which sends focus
4
+ * to the trigger and runs the open-focus callback again; by then the user may have moved on, so
5
+ * a repeat run goes back to the item they last focused.
6
+ */
7
+ import { focusInitialMenuItem, menuItems } from '@inkbar/core/dom';
8
+ export function createMenuOpenFocus() {
9
+ let first = false;
10
+ let last = null;
11
+ return {
12
+ first: () => {
13
+ first = true;
14
+ },
15
+ onOpenAutoFocus: (e, menu) => {
16
+ e.preventDefault();
17
+ if (!menu)
18
+ return;
19
+ if (last && menu.contains(last))
20
+ last.focus({ preventScroll: true });
21
+ else if (first)
22
+ menuItems(menu)[0]?.focus({ preventScroll: true });
23
+ else
24
+ focusInitialMenuItem(menu);
25
+ },
26
+ onfocusin: (e) => {
27
+ if (e.target instanceof HTMLElement)
28
+ last = e.target;
29
+ },
30
+ reset: () => {
31
+ first = false;
32
+ last = null;
33
+ },
34
+ };
35
+ }
package/dist/context.d.ts CHANGED
@@ -1,8 +1,11 @@
1
- import type { Annotation, InkDocument, InkStatus, PdfAssets, SignatureLibrary } from '@inkbar/core';
2
- import type { DecorateAnnotation, Tool, ToolController, ToolId, ToolOptions, ToolStyle, Viewport, ZoomValue } from '@inkbar/core/dom';
1
+ import type { Annotation, InkDocument, InkStatus, PdfAssets, PluginHistory, SignatureLibrary, Writable } from '@inkbar/core';
2
+ import type { DecorateAnnotation, PluginHost, Tool, ToolController, ToolId, ToolOptions, ToolStyle, Viewport, ZoomValue } from '@inkbar/core/dom';
3
3
  export type { InkStatus };
4
- /** Undo and redo for the current document, whichever mode its annotations are in. */
5
- export interface InkHistory {
4
+ /**
5
+ * Undo and redo for the current document, whichever mode its annotations are in. `track` and
6
+ * `react` (from `PluginHistory`) reach the same history, so plugin stores undo with the edits.
7
+ */
8
+ export interface InkHistory extends PluginHistory {
6
9
  readonly canUndo: boolean;
7
10
  readonly canRedo: boolean;
8
11
  undo(): void;
@@ -27,6 +30,14 @@ export interface InkContext {
27
30
  readonly status: InkStatus;
28
31
  readonly annotations: readonly Annotation[];
29
32
  readonly selection: readonly string[];
33
+ /**
34
+ * The plugins from InkRoot's `plugins` prop, set up once the root has mounted and kept for
35
+ * its life; `null` before that, and on the server. InkViewport hands it to the engine;
36
+ * InkCommand places its commands.
37
+ */
38
+ readonly pluginHost: PluginHost | null;
39
+ /** InkViewport's scroll element while it is mounted: the plugins' `root`. */
40
+ readonly viewportEl: Writable<HTMLElement | null>;
30
41
  /** Set by InkViewport once it has built its viewport and tool controller. */
31
42
  readonly viewport: Viewport | null;
32
43
  readonly tools: ToolController | null;
@@ -1 +1 @@
1
- {"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../src/lib/context.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../src/lib/context.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,23 @@
1
+ import type { IconNode } from '@inkbar/icons';
2
+ export interface CommandHandle {
3
+ /** Runs the command; does nothing while it is busy or disabled. Never rejects. */
4
+ run(): Promise<void>;
5
+ /** A ready document (or the plugin's own condition), and not busy. */
6
+ readonly enabled: boolean;
7
+ readonly busy: boolean;
8
+ /** The command's progress or result message, `''` without one. */
9
+ readonly status: string;
10
+ /** `''` until the plugins are set up. */
11
+ readonly label: string;
12
+ readonly icon: IconNode | undefined;
13
+ }
14
+ /**
15
+ * A plugin command by its namespaced id (`sandbox.suggest`), for hosts building their own
16
+ * control. Plugins are set up once InkRoot has mounted, so until then (and on the server) the
17
+ * command is disabled with no label; it fills in then. Once they are set up, an id no plugin
18
+ * passed to InkRoot registered stays that way, and logs `InkError('UnknownCommand')` to the
19
+ * console once.
20
+ * The id is read once: a different command is a different component.
21
+ */
22
+ export declare function useCommand(id: string): CommandHandle;
23
+ //# sourceMappingURL=useCommand.svelte.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useCommand.svelte.d.ts","sourceRoot":"","sources":["../../src/lib/hooks/useCommand.svelte.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAA;AAE7C,MAAM,WAAW,aAAa;IAC5B,kFAAkF;IAClF,GAAG,IAAI,OAAO,CAAC,IAAI,CAAC,CAAA;IACpB,sEAAsE;IACtE,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAA;IACzB,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAA;IACtB,kEAAkE;IAClE,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,yCAAyC;IACzC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB,QAAQ,CAAC,IAAI,EAAE,QAAQ,GAAG,SAAS,CAAA;CACpC;AAED;;;;;;;GAOG;AACH,wBAAgB,UAAU,CAAC,EAAE,EAAE,MAAM,GAAG,aAAa,CA+BpD"}
@@ -0,0 +1,44 @@
1
+ import { unknownCommand } from '@inkbar/core/dom';
2
+ import { useInkContext } from '../context.js';
3
+ import { useReadable } from '../use-readable.svelte.js';
4
+ /**
5
+ * A plugin command by its namespaced id (`sandbox.suggest`), for hosts building their own
6
+ * control. Plugins are set up once InkRoot has mounted, so until then (and on the server) the
7
+ * command is disabled with no label; it fills in then. Once they are set up, an id no plugin
8
+ * passed to InkRoot registered stays that way, and logs `InkError('UnknownCommand')` to the
9
+ * console once.
10
+ * The id is read once: a different command is a different component.
11
+ */
12
+ export function useCommand(id) {
13
+ const ctx = useInkContext('useCommand()');
14
+ const command = $derived(ctx.pluginHost?.command(id));
15
+ let logged = false;
16
+ $effect(() => {
17
+ const host = ctx.pluginHost;
18
+ if (!host || command || logged)
19
+ return;
20
+ logged = true;
21
+ console.error(unknownCommand(id, [...host.commands.keys()]));
22
+ });
23
+ const enabled = useReadable(() => command?.enabled, false);
24
+ const busy = useReadable(() => command?.busy, false);
25
+ const status = useReadable(() => command?.status, '');
26
+ return {
27
+ run: async () => command?.run(),
28
+ get enabled() {
29
+ return enabled.current;
30
+ },
31
+ get busy() {
32
+ return busy.current;
33
+ },
34
+ get status() {
35
+ return status.current;
36
+ },
37
+ get label() {
38
+ return command?.label ?? '';
39
+ },
40
+ get icon() {
41
+ return command?.icon;
42
+ },
43
+ };
44
+ }
package/dist/index.d.ts CHANGED
@@ -4,7 +4,7 @@ export type { InkContext, InkHistory, InkStatus } from './context.js';
4
4
  export { useReadable } from './use-readable.svelte.js';
5
5
  export type { ReadableRef } from './use-readable.svelte.js';
6
6
  export type { AnnotationSlotProps, AnnotationState, ThreadPopoverSlotProps, } from '@inkbar/core/dom';
7
- export type { AnnotationDecoration, DecorateAnnotation } from '@inkbar/core/dom';
7
+ export type { AnnotationDecoration, DecorateAnnotation, InkPlugin, } from '@inkbar/core/dom';
8
8
  export { useDocument } from './hooks/useDocument.svelte.js';
9
9
  export { useAnnotations } from './hooks/useAnnotations.svelte.js';
10
10
  export { useTool } from './hooks/useTool.svelte.js';
@@ -16,6 +16,8 @@ export { useZoom } from './hooks/useZoom.svelte.js';
16
16
  export { usePage } from './hooks/usePage.svelte.js';
17
17
  export { useHistory } from './hooks/useHistory.svelte.js';
18
18
  export { useEmbedded } from './hooks/useEmbedded.svelte.js';
19
+ export { useCommand } from './hooks/useCommand.svelte.js';
20
+ export type { CommandHandle } from './hooks/useCommand.svelte.js';
19
21
  export { default as InkRoot } from './components/InkRoot.svelte';
20
22
  export type { InkRootProps } from './components/InkRoot.svelte';
21
23
  export type InkRootHandle = ReturnType<typeof InkRootComponent>;
@@ -31,6 +33,8 @@ export { default as InkIcon } from './components/InkIcon.svelte';
31
33
  export type { InkIconProps } from './components/InkIcon.svelte';
32
34
  export { default as InkToolButton } from './components/InkToolButton.svelte';
33
35
  export type { InkToolButtonProps } from './components/InkToolButton.svelte';
36
+ export { default as InkCommand } from './components/InkCommand.svelte';
37
+ export type { InkCommandProps } from './components/InkCommand.svelte';
34
38
  export { default as InkToolbarSeparator } from './components/InkToolbarSeparator.svelte';
35
39
  export type { InkToolbarSeparatorProps } from './components/InkToolbarSeparator.svelte';
36
40
  export { default as InkUndo } from './components/InkUndo.svelte';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/lib/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/lib/index.ts"],"names":[],"mappings":"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"}
package/dist/index.js CHANGED
@@ -10,6 +10,7 @@ export { useZoom } from './hooks/useZoom.svelte.js';
10
10
  export { usePage } from './hooks/usePage.svelte.js';
11
11
  export { useHistory } from './hooks/useHistory.svelte.js';
12
12
  export { useEmbedded } from './hooks/useEmbedded.svelte.js';
13
+ export { useCommand } from './hooks/useCommand.svelte.js';
13
14
  export { default as InkRoot } from './components/InkRoot.svelte';
14
15
  export { default as InkViewport } from './components/InkViewport.svelte';
15
16
  export { default as InkPage } from './components/InkPage.svelte';
@@ -17,6 +18,7 @@ export { default as InkAnnotation } from './components/InkAnnotation.svelte';
17
18
  export { default as InkToolbar } from './components/InkToolbar.svelte';
18
19
  export { default as InkIcon } from './components/InkIcon.svelte';
19
20
  export { default as InkToolButton } from './components/InkToolButton.svelte';
21
+ export { default as InkCommand } from './components/InkCommand.svelte';
20
22
  export { default as InkToolbarSeparator } from './components/InkToolbarSeparator.svelte';
21
23
  export { default as InkUndo } from './components/InkUndo.svelte';
22
24
  export { default as InkRedo } from './components/InkRedo.svelte';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@inkbar/svelte",
3
- "version": "0.5.0",
3
+ "version": "0.6.0",
4
4
  "description": "Svelte 5 components and hooks for Inkbar, the browser PDF viewer and editor.",
5
5
  "license": "MIT",
6
6
  "homepage": "https://inkbar.dev",
@@ -26,19 +26,19 @@
26
26
  "./package.json": "./package.json"
27
27
  },
28
28
  "dependencies": {
29
- "@inkbar/icons": "^0.5.0",
29
+ "@inkbar/icons": "^0.6.0",
30
30
  "@internationalized/date": "^3.12.4",
31
31
  "bits-ui": "^2.19.5",
32
32
  "esm-env": "^1.2.0"
33
33
  },
34
34
  "peerDependencies": {
35
- "@inkbar/core": "^0.5.0",
35
+ "@inkbar/core": "^0.6.0",
36
36
  "svelte": "^5.46.4"
37
37
  },
38
38
  "devDependencies": {
39
- "@inkbar/core": "0.5.0",
39
+ "@inkbar/core": "0.6.0",
40
40
  "@inkbar/fixtures": "0.0.0",
41
- "@inkbar/theme": "0.5.0",
41
+ "@inkbar/theme": "0.6.0",
42
42
  "@sveltejs/package": "^3.0.0",
43
43
  "@sveltejs/vite-plugin-svelte": "^7.3.1",
44
44
  "@vitest/browser-playwright": "^5.0.4",