@inkbar/svelte 0.8.0 → 0.10.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 (86) hide show
  1. package/README.md +71 -6
  2. package/dist/components/InkDownloadButton.svelte +62 -0
  3. package/dist/components/InkDownloadButton.svelte.d.ts +14 -0
  4. package/dist/components/InkDownloadButton.svelte.d.ts.map +1 -0
  5. package/dist/components/InkFindBar.svelte +18 -15
  6. package/dist/components/InkFindBar.svelte.d.ts.map +1 -1
  7. package/dist/components/InkHighlightMenu.svelte +101 -93
  8. package/dist/components/InkHighlightMenu.svelte.d.ts.map +1 -1
  9. package/dist/components/InkPanel.svelte +73 -0
  10. package/dist/components/InkPanel.svelte.d.ts +18 -0
  11. package/dist/components/InkPanel.svelte.d.ts.map +1 -0
  12. package/dist/components/InkPrintButton.svelte +57 -0
  13. package/dist/components/InkPrintButton.svelte.d.ts +12 -0
  14. package/dist/components/InkPrintButton.svelte.d.ts.map +1 -0
  15. package/dist/components/InkRecordList.svelte +91 -0
  16. package/dist/components/InkRecordList.svelte.d.ts +72 -0
  17. package/dist/components/InkRecordList.svelte.d.ts.map +1 -0
  18. package/dist/components/InkRoot.svelte +53 -5
  19. package/dist/components/InkRoot.svelte.d.ts +7 -2
  20. package/dist/components/InkRoot.svelte.d.ts.map +1 -1
  21. package/dist/components/InkSearchResults.svelte +72 -0
  22. package/dist/components/InkSearchResults.svelte.d.ts +9 -0
  23. package/dist/components/InkSearchResults.svelte.d.ts.map +1 -0
  24. package/dist/components/InkShapeMenu.svelte +89 -84
  25. package/dist/components/InkShapeMenu.svelte.d.ts.map +1 -1
  26. package/dist/components/InkSidebar.svelte +179 -0
  27. package/dist/components/InkSidebar.svelte.d.ts +22 -0
  28. package/dist/components/InkSidebar.svelte.d.ts.map +1 -0
  29. package/dist/components/InkSignatureDialog.svelte +7 -3
  30. package/dist/components/InkSignatureDialog.svelte.d.ts +4 -3
  31. package/dist/components/InkSignatureDialog.svelte.d.ts.map +1 -1
  32. package/dist/components/InkSignatureMenu.svelte +85 -77
  33. package/dist/components/InkSignatureMenu.svelte.d.ts.map +1 -1
  34. package/dist/components/InkThumbnail.svelte +74 -0
  35. package/dist/components/InkThumbnail.svelte.d.ts +19 -0
  36. package/dist/components/InkThumbnail.svelte.d.ts.map +1 -0
  37. package/dist/components/InkThumbnails.svelte +146 -0
  38. package/dist/components/InkThumbnails.svelte.d.ts +24 -0
  39. package/dist/components/InkThumbnails.svelte.d.ts.map +1 -0
  40. package/dist/components/InkToolButton.svelte +23 -24
  41. package/dist/components/InkToolButton.svelte.d.ts.map +1 -1
  42. package/dist/components/InkToolbarButton.svelte +33 -0
  43. package/dist/components/InkToolbarButton.svelte.d.ts +12 -0
  44. package/dist/components/InkToolbarButton.svelte.d.ts.map +1 -0
  45. package/dist/components/InkViewport.svelte +4 -0
  46. package/dist/components/InkViewport.svelte.d.ts.map +1 -1
  47. package/dist/components/InkZoomSelect.svelte +1 -1
  48. package/dist/components/RecordCards.svelte +173 -0
  49. package/dist/components/RecordCards.svelte.d.ts +16 -0
  50. package/dist/components/RecordCards.svelte.d.ts.map +1 -0
  51. package/dist/components/SidebarTrigger.svelte +20 -0
  52. package/dist/components/SidebarTrigger.svelte.d.ts +9 -0
  53. package/dist/components/SidebarTrigger.svelte.d.ts.map +1 -0
  54. package/dist/components/SignatureForm.svelte +9 -3
  55. package/dist/components/SignatureForm.svelte.d.ts.map +1 -1
  56. package/dist/components/body-lock.d.ts +3 -0
  57. package/dist/components/body-lock.d.ts.map +1 -0
  58. package/dist/components/body-lock.js +43 -0
  59. package/dist/components/sidebar-context.d.ts +24 -0
  60. package/dist/components/sidebar-context.d.ts.map +1 -0
  61. package/dist/components/sidebar-context.js +13 -0
  62. package/dist/context.d.ts +6 -0
  63. package/dist/context.d.ts.map +1 -1
  64. package/dist/hooks/useAnnotations.svelte.d.ts +78 -0
  65. package/dist/hooks/useAnnotations.svelte.d.ts.map +1 -1
  66. package/dist/hooks/useRecordSelection.svelte.d.ts +11 -0
  67. package/dist/hooks/useRecordSelection.svelte.d.ts.map +1 -0
  68. package/dist/hooks/useRecordSelection.svelte.js +19 -0
  69. package/dist/hooks/useRecords.svelte.d.ts +14 -0
  70. package/dist/hooks/useRecords.svelte.d.ts.map +1 -0
  71. package/dist/hooks/useRecords.svelte.js +24 -0
  72. package/dist/hooks/useRegisteredTool.svelte.d.ts +24 -0
  73. package/dist/hooks/useRegisteredTool.svelte.d.ts.map +1 -0
  74. package/dist/hooks/useRegisteredTool.svelte.js +47 -0
  75. package/dist/hooks/useSearch.svelte.d.ts.map +1 -1
  76. package/dist/hooks/useSearch.svelte.js +1 -0
  77. package/dist/hooks/useToolOptions.svelte.d.ts +8 -0
  78. package/dist/hooks/useToolOptions.svelte.d.ts.map +1 -1
  79. package/dist/hooks/useToolOptions.svelte.js +12 -2
  80. package/dist/index.d.ts +20 -0
  81. package/dist/index.d.ts.map +1 -1
  82. package/dist/index.js +11 -0
  83. package/dist/to-readable.svelte.d.ts +8 -0
  84. package/dist/to-readable.svelte.d.ts.map +1 -0
  85. package/dist/to-readable.svelte.js +15 -0
  86. package/package.json +5 -5
@@ -0,0 +1,74 @@
1
+ <script module lang="ts">
2
+ import type { HTMLAttributes } from 'svelte/elements'
3
+
4
+ export interface InkThumbnailProps extends Omit<
5
+ HTMLAttributes<HTMLDivElement>,
6
+ 'children'
7
+ > {
8
+ /** 0-based. */
9
+ page: number
10
+ /** Width in CSS pixels; the height follows the page. Default 120 unless `height` is given. */
11
+ width?: number | undefined
12
+ /** Height in CSS pixels; the width follows the page. */
13
+ height?: number | undefined
14
+ /** Draw the page's annotations. Default true. */
15
+ annotations?: boolean | undefined
16
+ /** Draw plugin marks. Default true. */
17
+ marks?: boolean | undefined
18
+ /** The accessible name. Default "Page N". */
19
+ label?: string | undefined
20
+ }
21
+ </script>
22
+
23
+ <script lang="ts">
24
+ /*
25
+ * One page as a static image, with its live annotations and plugin marks: core's thumbnail
26
+ * painter on this element, which sizes it, names it ("Page N" unless `label` says otherwise)
27
+ * and draws it once it nears the visible area. It is not focusable and does nothing on click:
28
+ * wrap it in the control you need. Inside `InkRoot`; it renders empty on the server.
29
+ */
30
+ import { createThumbnailPainter, type ThumbnailPainter } from '@inkbar/core/dom'
31
+ import { untrack } from 'svelte'
32
+
33
+ import { useInkContext } from '../context.js'
34
+ import { toReadable } from '../to-readable.svelte.js'
35
+
36
+ let {
37
+ page,
38
+ width,
39
+ height,
40
+ annotations = true,
41
+ marks = true,
42
+ label,
43
+ class: className,
44
+ ...rest
45
+ }: InkThumbnailProps = $props()
46
+
47
+ const ctx = useInkContext('InkThumbnail')
48
+ const deps = {
49
+ document: toReadable(() => ctx.document),
50
+ pluginHost: toReadable(() => ctx.pluginHost),
51
+ }
52
+ const options = () => ({ page, width, height, annotations, marks, label })
53
+
54
+ let el = $state<HTMLDivElement | null>(null)
55
+ let painter: ThumbnailPainter | null = null
56
+ // Before the painter is made, so a new painter does not take its first options twice.
57
+ $effect(() => {
58
+ const next = options()
59
+ untrack(() => painter?.update(next))
60
+ })
61
+ $effect(() => {
62
+ const target = el
63
+ if (!target) return
64
+ const p = untrack(() => createThumbnailPainter(target, deps, options()))
65
+ painter = p
66
+ return () => {
67
+ p.destroy()
68
+ painter = null
69
+ }
70
+ })
71
+ </script>
72
+
73
+ <!-- The class is the painter's too: kept here, a re-render never drops it. -->
74
+ <div {...rest} bind:this={el} class={['ink-thumbnail', className]}></div>
@@ -0,0 +1,19 @@
1
+ import type { HTMLAttributes } from 'svelte/elements';
2
+ export interface InkThumbnailProps extends Omit<HTMLAttributes<HTMLDivElement>, 'children'> {
3
+ /** 0-based. */
4
+ page: number;
5
+ /** Width in CSS pixels; the height follows the page. Default 120 unless `height` is given. */
6
+ width?: number | undefined;
7
+ /** Height in CSS pixels; the width follows the page. */
8
+ height?: number | undefined;
9
+ /** Draw the page's annotations. Default true. */
10
+ annotations?: boolean | undefined;
11
+ /** Draw plugin marks. Default true. */
12
+ marks?: boolean | undefined;
13
+ /** The accessible name. Default "Page N". */
14
+ label?: string | undefined;
15
+ }
16
+ declare const InkThumbnail: import("svelte").Component<InkThumbnailProps, {}, "">;
17
+ type InkThumbnail = ReturnType<typeof InkThumbnail>;
18
+ export default InkThumbnail;
19
+ //# sourceMappingURL=InkThumbnail.svelte.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"InkThumbnail.svelte.d.ts","sourceRoot":"","sources":["../../src/lib/components/InkThumbnail.svelte.ts"],"names":[],"mappings":"AAGE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAA;AAErD,MAAM,WAAW,iBAAkB,SAAQ,IAAI,CAC7C,cAAc,CAAC,cAAc,CAAC,EAC9B,UAAU,CACX;IACC,eAAe;IACf,IAAI,EAAE,MAAM,CAAA;IACZ,8FAA8F;IAC9F,KAAK,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;IAC1B,wDAAwD;IACxD,MAAM,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;IAC3B,iDAAiD;IACjD,WAAW,CAAC,EAAE,OAAO,GAAG,SAAS,CAAA;IACjC,uCAAuC;IACvC,KAAK,CAAC,EAAE,OAAO,GAAG,SAAS,CAAA;IAC3B,6CAA6C;IAC7C,KAAK,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;CAC3B;AAmEH,QAAA,MAAM,YAAY,uDAAwC,CAAC;AAC3D,KAAK,YAAY,GAAG,UAAU,CAAC,OAAO,YAAY,CAAC,CAAC;AACpD,eAAe,YAAY,CAAC"}
@@ -0,0 +1,146 @@
1
+ <script module lang="ts">
2
+ import type { Snippet } from 'svelte'
3
+ import type { HTMLAttributes } from 'svelte/elements'
4
+
5
+ export interface InkThumbnailsProps extends Omit<
6
+ HTMLAttributes<HTMLDivElement>,
7
+ 'children'
8
+ > {
9
+ /** 0-based pages to list, in order. Default: every page. */
10
+ pages?: readonly number[] | undefined
11
+ /** Thumbnail width in CSS pixels. Default: the list's width less a 16px gutter each side. */
12
+ width?: number | undefined
13
+ /** Draw each page's annotations. Default true. */
14
+ annotations?: boolean | undefined
15
+ /** Draw plugin marks. Default true. */
16
+ marks?: boolean | undefined
17
+ /**
18
+ * Renders inside each item, after its page number. The item's height is fixed from the
19
+ * page's, so keep what you add out of the flow (position it absolutely) or small.
20
+ */
21
+ item?: Snippet<[{ page: number; current: boolean }]> | undefined
22
+ }
23
+ </script>
24
+
25
+ <script lang="ts">
26
+ /*
27
+ * The pages as a list of thumbnails: `createThumbnails`' markup on core's thumbnails
28
+ * controller, which holds the current page, the keys, the clicks, the Tab stop and the window.
29
+ * Only the items near the visible part are mounted, each placed at its offset over a spacer of
30
+ * the full height, and each holds an `InkThumbnail`. An item is a button that scrolls the
31
+ * viewport to its page; the current page's has `aria-current="page"`. Inside `InkRoot`; it
32
+ * renders empty on the server.
33
+ */
34
+ import {
35
+ createThumbnailsController,
36
+ THUMBNAIL_WIDTH,
37
+ type ThumbnailsController,
38
+ type ThumbnailsState,
39
+ } from '@inkbar/core/dom'
40
+ import { untrack } from 'svelte'
41
+
42
+ import { useInkContext } from '../context.js'
43
+ import { toReadable } from '../to-readable.svelte.js'
44
+
45
+ import InkThumbnail from './InkThumbnail.svelte'
46
+
47
+ let {
48
+ pages,
49
+ width,
50
+ annotations = true,
51
+ marks = true,
52
+ item,
53
+ class: className,
54
+ ...rest
55
+ }: InkThumbnailsProps = $props()
56
+
57
+ const ctx = useInkContext('InkThumbnails')
58
+ const deps = {
59
+ document: toReadable(() => ctx.document),
60
+ pluginHost: toReadable(() => ctx.pluginHost),
61
+ viewport: toReadable(() => ctx.viewport),
62
+ }
63
+ const options = () => ({ pages, width, annotations, marks })
64
+
65
+ let scroller = $state<HTMLDivElement | null>(null)
66
+ let controller = $state.raw<ThumbnailsController | null>(null)
67
+ /** Until the list mounts, and on the server: nothing listed. */
68
+ let listed = $state.raw<ThumbnailsState>({
69
+ pages: [],
70
+ current: -1,
71
+ window: { start: 0, end: 0 },
72
+ offsets: [0],
73
+ width: THUMBNAIL_WIDTH,
74
+ })
75
+
76
+ // A list unchanged in content publishes nothing, so an inline `pages` array cannot loop.
77
+ $effect(() => {
78
+ const c = controller
79
+ const next = options()
80
+ if (c) untrack(() => c.update(next))
81
+ })
82
+ $effect(() => {
83
+ const el = scroller
84
+ if (!el) return
85
+ return untrack(() => {
86
+ const c = createThumbnailsController(deps, options())
87
+ controller = c
88
+ listed = c.state.get()
89
+ const stop = c.state.subscribe((s) => {
90
+ listed = s
91
+ })
92
+ c.attach(el)
93
+ return () => {
94
+ stop()
95
+ c.destroy()
96
+ controller = null
97
+ }
98
+ })
99
+ })
100
+
101
+ const items = $derived.by(() => {
102
+ const { pages: list, current, window, offsets } = listed
103
+ return list.slice(window.start, window.end).map((page, k) => {
104
+ const top = offsets[window.start + k]!
105
+ const bottom = offsets[window.start + k + 1]!
106
+ // Only a mounted controller has a window, so `controller` is set here.
107
+ const attrs = controller?.itemAttrs(window.start + k) ?? {}
108
+ return { page, current: page === current, top, height: bottom - top, attrs }
109
+ })
110
+ })
111
+ </script>
112
+
113
+ <div
114
+ {...rest}
115
+ bind:this={scroller}
116
+ class={['ink-thumbnails', className]}
117
+ data-ink-thumbnails=""
118
+ role="list"
119
+ >
120
+ <div
121
+ class="ink-thumbnails-spacer"
122
+ aria-hidden="true"
123
+ style:height="{listed.offsets.at(-1) ?? 0}px"
124
+ ></div>
125
+ {#each items as entry (entry.page)}
126
+ <div
127
+ class="ink-thumbnails-entry"
128
+ {...entry.attrs}
129
+ style:top="{entry.top}px"
130
+ style:height="{entry.height}px"
131
+ >
132
+ <!-- The controller sets the tabindex. The thumbnail names the button "Page N"; the
133
+ visible number repeats it. -->
134
+ <button
135
+ type="button"
136
+ class="ink-thumbnails-item"
137
+ data-ink-thumbnail-item={entry.page}
138
+ aria-current={entry.current ? 'page' : undefined}
139
+ >
140
+ <InkThumbnail page={entry.page} width={listed.width} {annotations} {marks} />
141
+ <span class="ink-thumbnails-label" aria-hidden="true">{entry.page + 1}</span>
142
+ {@render item?.({ page: entry.page, current: entry.current })}
143
+ </button>
144
+ </div>
145
+ {/each}
146
+ </div>
@@ -0,0 +1,24 @@
1
+ import type { Snippet } from 'svelte';
2
+ import type { HTMLAttributes } from 'svelte/elements';
3
+ export interface InkThumbnailsProps extends Omit<HTMLAttributes<HTMLDivElement>, 'children'> {
4
+ /** 0-based pages to list, in order. Default: every page. */
5
+ pages?: readonly number[] | undefined;
6
+ /** Thumbnail width in CSS pixels. Default: the list's width less a 16px gutter each side. */
7
+ width?: number | undefined;
8
+ /** Draw each page's annotations. Default true. */
9
+ annotations?: boolean | undefined;
10
+ /** Draw plugin marks. Default true. */
11
+ marks?: boolean | undefined;
12
+ /**
13
+ * Renders inside each item, after its page number. The item's height is fixed from the
14
+ * page's, so keep what you add out of the flow (position it absolutely) or small.
15
+ */
16
+ item?: Snippet<[{
17
+ page: number;
18
+ current: boolean;
19
+ }]> | undefined;
20
+ }
21
+ declare const InkThumbnails: import("svelte").Component<InkThumbnailsProps, {}, "">;
22
+ type InkThumbnails = ReturnType<typeof InkThumbnails>;
23
+ export default InkThumbnails;
24
+ //# sourceMappingURL=InkThumbnails.svelte.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"InkThumbnails.svelte.d.ts","sourceRoot":"","sources":["../../src/lib/components/InkThumbnails.svelte.ts"],"names":[],"mappings":"AAGE,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAA;AACrC,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAA;AAErD,MAAM,WAAW,kBAAmB,SAAQ,IAAI,CAC9C,cAAc,CAAC,cAAc,CAAC,EAC9B,UAAU,CACX;IACC,4DAA4D;IAC5D,KAAK,CAAC,EAAE,SAAS,MAAM,EAAE,GAAG,SAAS,CAAA;IACrC,6FAA6F;IAC7F,KAAK,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;IAC1B,kDAAkD;IAClD,WAAW,CAAC,EAAE,OAAO,GAAG,SAAS,CAAA;IACjC,uCAAuC;IACvC,KAAK,CAAC,EAAE,OAAO,GAAG,SAAS,CAAA;IAC3B;;;OAGG;IACH,IAAI,CAAC,EAAE,OAAO,CAAC,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,OAAO,CAAA;KAAE,CAAC,CAAC,GAAG,SAAS,CAAA;CACjE;AAsHH,QAAA,MAAM,aAAa,wDAAwC,CAAC;AAC5D,KAAK,aAAa,GAAG,UAAU,CAAC,OAAO,aAAa,CAAC,CAAC;AACtD,eAAe,aAAa,CAAC"}
@@ -13,21 +13,12 @@
13
13
  </script>
14
14
 
15
15
  <script lang="ts">
16
- import { TOOL_LABELS } from '@inkbar/core/dom'
17
- import {
18
- Highlighter,
19
- MessageSquare,
20
- PenLine,
21
- PenTool,
22
- Signature,
23
- Square,
24
- SquareDashedMousePointer,
25
- TextCursor,
26
- Type,
27
- } from '@inkbar/icons'
16
+ import { TOOL_LABELS, withHostTools } from '@inkbar/core/dom'
17
+ import { SquareDashedMousePointer, TextCursor } from '@inkbar/icons'
28
18
  import { Toolbar } from 'bits-ui'
29
19
 
30
20
  import { useInkContext } from '../context.js'
21
+ import { useRegisteredTool } from '../hooks/useRegisteredTool.svelte.js'
31
22
  import { useTool } from '../hooks/useTool.svelte.js'
32
23
 
33
24
  import { asBitsProps } from './child-snippet.js'
@@ -38,15 +29,19 @@
38
29
  const ICONS: Readonly<Partial<Record<ToolId, IconNode>>> = {
39
30
  'select-text': TextCursor,
40
31
  select: SquareDashedMousePointer,
41
- highlight: Highlighter,
42
- 'redact-area': Square,
43
- sketch: PenTool,
44
- draw: PenLine,
45
- note: MessageSquare,
46
- freetext: Type,
47
- signature: Signature,
48
32
  }
49
33
 
34
+ // A tool nobody registered leaves the button disabled, hidden from assistive tech, unnamed
35
+ // and inert, whatever the host passed.
36
+ const MISSING = {
37
+ disabled: true,
38
+ 'aria-hidden': 'true',
39
+ 'aria-pressed': undefined,
40
+ 'aria-label': undefined,
41
+ title: undefined,
42
+ onclick: undefined,
43
+ } as const
44
+
50
45
  let {
51
46
  tool,
52
47
  label,
@@ -60,9 +55,10 @@
60
55
  const ctx = useInkContext('InkToolButton')
61
56
  const current = useTool()
62
57
  const active = $derived(current.tool === tool)
63
- /** A host or plugin tool by this id, for the name and icon core has none for. */
58
+ const registered = useRegisteredTool(() => tool)
59
+ /** The registered tool by this id, for the name and icon core has none for. */
64
60
  const own = $derived(
65
- [...(ctx.hostTools ?? []), ...(ctx.pluginHost?.tools ?? [])].find(
61
+ withHostTools([...(ctx.hostTools ?? []), ...(ctx.pluginHost?.tools ?? [])]).find(
66
62
  (t) => t.id === tool
67
63
  )
68
64
  )
@@ -85,8 +81,11 @@
85
81
  onclick?.(e)
86
82
  if (!e.defaultPrevented) current.setTool(tool)
87
83
  }}
84
+ {...registered.current ? {} : MISSING}
88
85
  >
89
- {#if children}{@render children({ active, label: name })}{:else if icon}<InkIcon
90
- {icon}
91
- />{/if}
86
+ {#if registered.current}
87
+ {#if children}{@render children({ active, label: name })}{:else if icon}<InkIcon
88
+ {icon}
89
+ />{/if}
90
+ {/if}
92
91
  </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;AAqFH,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;AAoFH,QAAA,MAAM,aAAa,wDAAwC,CAAC;AAC5D,KAAK,aAAa,GAAG,UAAU,CAAC,OAAO,aAAa,CAAC,CAAC;AACtD,eAAe,aAAa,CAAC"}
@@ -0,0 +1,33 @@
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 InkToolbarButtonProps extends Omit<HTMLButtonAttributes, 'children'> {
7
+ children?: Snippet | undefined
8
+ /** Render your own button: spread `props` on it. */
9
+ child?: ChildSnippet | undefined
10
+ }
11
+ </script>
12
+
13
+ <!--
14
+ Your own button in an `InkToolbar`. It joins the toolbar's arrow-key focus and takes the
15
+ theme's toolbar button styles; what it shows and does is up to you.
16
+ -->
17
+ <script lang="ts">
18
+ import { Toolbar } from 'bits-ui'
19
+
20
+ import { asBitsProps } from './child-snippet.js'
21
+
22
+ let { class: className, children, child, ...rest }: InkToolbarButtonProps = $props()
23
+ </script>
24
+
25
+ <Toolbar.Button
26
+ type="button"
27
+ class={['ink-toolbar-button', className]}
28
+ data-ink-toolbar-button=""
29
+ {...child ? { child } : {}}
30
+ {...asBitsProps(rest)}
31
+ >
32
+ {@render children?.()}
33
+ </Toolbar.Button>
@@ -0,0 +1,12 @@
1
+ import type { ChildSnippet } from './child-snippet.js';
2
+ import type { Snippet } from 'svelte';
3
+ import type { HTMLButtonAttributes } from 'svelte/elements';
4
+ export interface InkToolbarButtonProps extends Omit<HTMLButtonAttributes, 'children'> {
5
+ children?: Snippet | undefined;
6
+ /** Render your own button: spread `props` on it. */
7
+ child?: ChildSnippet | undefined;
8
+ }
9
+ declare const InkToolbarButton: import("svelte").Component<InkToolbarButtonProps, {}, "">;
10
+ type InkToolbarButton = ReturnType<typeof InkToolbarButton>;
11
+ export default InkToolbarButton;
12
+ //# sourceMappingURL=InkToolbarButton.svelte.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"InkToolbarButton.svelte.d.ts","sourceRoot":"","sources":["../../src/lib/components/InkToolbarButton.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,qBAAsB,SAAQ,IAAI,CAAC,oBAAoB,EAAE,UAAU,CAAC;IACnF,QAAQ,CAAC,EAAE,OAAO,GAAG,SAAS,CAAA;IAC9B,oDAAoD;IACpD,KAAK,CAAC,EAAE,YAAY,GAAG,SAAS,CAAA;CACjC;AAwBH,QAAA,MAAM,gBAAgB,2DAAwC,CAAC;AAC/D,KAAK,gBAAgB,GAAG,UAAU,CAAC,OAAO,gBAAgB,CAAC,CAAC;AAC5D,eAAe,gBAAgB,CAAC"}
@@ -43,6 +43,7 @@
43
43
  import { untrack } from 'svelte'
44
44
 
45
45
  import { useInkContext } from '../context.js'
46
+ import { toolIdsOf } from '../hooks/useRegisteredTool.svelte.js'
46
47
 
47
48
  import InkFormAlert from './InkFormAlert.svelte'
48
49
  import InkPage from './InkPage.svelte'
@@ -115,6 +116,7 @@
115
116
  engine?.destroy()
116
117
  engine = null
117
118
  builtWith = null
119
+ ctx.setOpenedToolIds(null)
118
120
  ctx.setViewport(null, null)
119
121
  if (pages.length > 0) pages = []
120
122
  // Their fields and the document they came from are gone.
@@ -134,6 +136,8 @@
134
136
  build += 1
135
137
  const custom = !!annotation
136
138
  builtWith = { gap, buffer, custom }
139
+ // The tools this viewer registers, from the same inputs; a later `tools` waits for a rebuild.
140
+ ctx.setOpenedToolIds(toolIdsOf(ctx.hostTools, ctx.pluginHost))
137
141
  engine = mountViewer({
138
142
  container: el,
139
143
  document: doc,
@@ -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;AAsWH,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;AA2WH,QAAA,MAAM,WAAW,sDAAwC,CAAC;AAC1D,KAAK,WAAW,GAAG,UAAU,CAAC,OAAO,WAAW,CAAC,CAAC;AAClD,eAAe,WAAW,CAAC"}
@@ -59,7 +59,7 @@
59
59
  select-only combobox of the ARIA pattern, as Reka UI and Radix render it. -->
60
60
  <Select.Root type="single" {items} bind:value={() => value, onValueChange}>
61
61
  <Select.Trigger
62
- class={['ink-zoom-select', 'ink-select-trigger', className]}
62
+ class={['ink-select', 'ink-select-trigger', 'ink-zoom-select', className]}
63
63
  data-ink-zoom-select=""
64
64
  role="combobox"
65
65
  aria-label="Zoom"
@@ -0,0 +1,173 @@
1
+ <script lang="ts">
2
+ /*
3
+ * A record list's markup on core's record list controller, shared by `InkRecordList` and
4
+ * `InkSearchResults`: `createRecordList`'s `div.ink-records` holding `ul.ink-record-list` with
5
+ * one `li.ink-record` card per row, the empty text and a polite live region. The controller
6
+ * holds the rows, the count and the announcement, and handles the clicks, keys, Tab stop,
7
+ * focus and scrolling. Empty, with no empty text, until there is a source.
8
+ */
9
+ import {
10
+ createRecordListController,
11
+ type RecordListController,
12
+ type RecordListRow,
13
+ type RecordListSource,
14
+ } from '@inkbar/core/dom'
15
+ import { untrack, type Snippet } from 'svelte'
16
+
17
+ import type { HTMLAttributes } from 'svelte/elements'
18
+
19
+ let {
20
+ source,
21
+ emptyShown = true,
22
+ empty,
23
+ oncount,
24
+ class: className,
25
+ ...rest
26
+ }: Omit<HTMLAttributes<HTMLDivElement>, 'children'> & {
27
+ source: RecordListSource | null
28
+ /** Whether the empty text shows when there are no rows. Default true. */
29
+ emptyShown?: boolean | undefined
30
+ /** Replaces "Nothing here yet.". */
31
+ empty?: Snippet | undefined
32
+ /** Called with the number listed whenever it changes. */
33
+ oncount?: ((count: number) => void) | undefined
34
+ } = $props()
35
+
36
+ /** A focused action button whose row re-renders: where to put focus back if it goes. */
37
+ interface HeldFocus {
38
+ button: HTMLElement
39
+ id: string
40
+ index: number
41
+ }
42
+
43
+ const actionButtons = (row: Element): HTMLElement[] => [
44
+ ...row.querySelectorAll<HTMLElement>('.ink-card-actions > button'),
45
+ ]
46
+
47
+ let root = $state<HTMLDivElement | null>(null)
48
+ let controller = $state.raw<RecordListController | null>(null)
49
+ let rows = $state.raw<readonly RecordListRow[]>([])
50
+ let announcement = $state('')
51
+ const showEmpty = $derived(controller !== null && rows.length === 0 && emptyShown)
52
+
53
+ /** The count last reported, across sources. */
54
+ let reported: number | null = null
55
+ let held: HeldFocus | null = null
56
+
57
+ function report(count: number): void {
58
+ if (count === reported) return
59
+ reported = count
60
+ oncount?.(count)
61
+ }
62
+
63
+ /**
64
+ * Before the rows re-render under a focused action whose button goes (its card lost that
65
+ * action), note where it was; the effect below focuses the same place in the card, else the
66
+ * row. A row that goes is the controller's to re-home.
67
+ */
68
+ function holdActionFocus(el: HTMLElement): void {
69
+ const active = el.ownerDocument.activeElement
70
+ if (!(active instanceof HTMLButtonElement) || !el.contains(active)) return
71
+ const row = active.closest<HTMLElement>('[data-ink-record]')
72
+ const id = row?.dataset.inkRecord
73
+ if (!row || id === undefined) return
74
+ held = { button: active, id, index: actionButtons(row).indexOf(active) }
75
+ }
76
+
77
+ $effect(() => {
78
+ const el = root
79
+ const s = source
80
+ if (!el || !s) return
81
+ return untrack(() => {
82
+ const c = createRecordListController(s)
83
+ controller = c
84
+ rows = c.rows.get()
85
+ announcement = c.announcement.get()
86
+ report(c.count.get())
87
+ const stops = [
88
+ c.rows.subscribe((next) => {
89
+ holdActionFocus(el)
90
+ rows = next
91
+ }),
92
+ c.count.subscribe(report),
93
+ c.announcement.subscribe((text) => {
94
+ announcement = text
95
+ }),
96
+ ]
97
+ const detach = c.attach(el)
98
+ return () => {
99
+ for (const stop of stops) stop()
100
+ detach()
101
+ c.destroy()
102
+ controller = null
103
+ rows = []
104
+ announcement = ''
105
+ }
106
+ })
107
+ })
108
+
109
+ // After the rows have re-rendered.
110
+ $effect(() => {
111
+ void rows
112
+ const el = root
113
+ untrack(() => {
114
+ const focus = held
115
+ held = null
116
+ if (!focus || !el || focus.button.isConnected) return
117
+ const row = [...el.querySelectorAll<HTMLElement>('[data-ink-record]')].find(
118
+ (r) => r.dataset.inkRecord === focus.id
119
+ )
120
+ if (row) (actionButtons(row)[focus.index] ?? row).focus({ preventScroll: true })
121
+ })
122
+ })
123
+ </script>
124
+
125
+ <div
126
+ {...rest}
127
+ bind:this={root}
128
+ class={['ink-records', className]}
129
+ data-ink-records=""
130
+ tabindex="-1"
131
+ >
132
+ <ul class="ink-record-list" role="list" hidden={rows.length === 0}>
133
+ <!-- The controller sets each row's tabindex. -->
134
+ {#each rows as { item, card, selected } (item.id)}
135
+ <li
136
+ class="ink-record"
137
+ data-ink-record={item.id}
138
+ aria-current={selected ? 'true' : undefined}
139
+ data-selected={selected ? '' : undefined}
140
+ >
141
+ <div class={['ink-card', card?.className]}>
142
+ {#if card}
143
+ <p class="ink-card-title">{card.title}</p>
144
+ {#if card.meta !== undefined}
145
+ <!-- prettier-ignore -->
146
+ <p class="ink-card-meta">{#if card.swatch !== undefined}<span class="ink-card-swatch" style:background={card.swatch}></span>{/if}{card.meta}</p>
147
+ {/if}
148
+ {#if card.body !== undefined}
149
+ <p class="ink-card-body">{card.body}</p>
150
+ {/if}
151
+ {#if card.excerpt !== undefined}
152
+ <!-- prettier-ignore -->
153
+ <p class="ink-card-excerpt">{card.excerpt.before}<mark>{card.excerpt.match}</mark>{card.excerpt.after}</p>
154
+ {/if}
155
+ <div class="ink-card-actions">
156
+ {#each Object.keys(card.actions) as label (label)}
157
+ <button type="button" onclick={() => controller?.act(item.id, label)}
158
+ >{label}</button
159
+ >
160
+ {/each}
161
+ </div>
162
+ {/if}
163
+ </div>
164
+ </li>
165
+ {/each}
166
+ </ul>
167
+ <p class="ink-record-list-empty" hidden={!showEmpty}>
168
+ {#if empty}{@render empty()}{:else}Nothing here yet.{/if}
169
+ </p>
170
+ <div class="ink-visually-hidden" aria-live="polite" data-ink-record-status="">
171
+ {announcement}
172
+ </div>
173
+ </div>
@@ -0,0 +1,16 @@
1
+ import { type RecordListSource } from '@inkbar/core/dom';
2
+ import { type Snippet } from 'svelte';
3
+ import type { HTMLAttributes } from 'svelte/elements';
4
+ type $$ComponentProps = Omit<HTMLAttributes<HTMLDivElement>, 'children'> & {
5
+ source: RecordListSource | null;
6
+ /** Whether the empty text shows when there are no rows. Default true. */
7
+ emptyShown?: boolean | undefined;
8
+ /** Replaces "Nothing here yet.". */
9
+ empty?: Snippet | undefined;
10
+ /** Called with the number listed whenever it changes. */
11
+ oncount?: ((count: number) => void) | undefined;
12
+ };
13
+ declare const RecordCards: import("svelte").Component<$$ComponentProps, {}, "">;
14
+ type RecordCards = ReturnType<typeof RecordCards>;
15
+ export default RecordCards;
16
+ //# sourceMappingURL=RecordCards.svelte.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"RecordCards.svelte.d.ts","sourceRoot":"","sources":["../../src/lib/components/RecordCards.svelte.ts"],"names":[],"mappings":"AAUA,OAAO,EAIH,KAAK,gBAAgB,EACtB,MAAM,kBAAkB,CAAA;AAC3B,OAAO,EAAW,KAAK,OAAO,EAAE,MAAM,QAAQ,CAAA;AAE9C,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AASrD,KAAK,gBAAgB,GAAI,IAAI,CAAC,cAAc,CAAC,cAAc,CAAC,EAAE,UAAU,CAAC,GAAG;IACzE,MAAM,EAAE,gBAAgB,GAAG,IAAI,CAAA;IAC/B,yEAAyE;IACzE,UAAU,CAAC,EAAE,OAAO,GAAG,SAAS,CAAA;IAChC,oCAAoC;IACpC,KAAK,CAAC,EAAE,OAAO,GAAG,SAAS,CAAA;IAC3B,yDAAyD;IACzD,OAAO,CAAC,EAAE,CAAC,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC,GAAG,SAAS,CAAA;CAChD,CAAC;AA2IJ,QAAA,MAAM,WAAW,sDAAwC,CAAC;AAC1D,KAAK,WAAW,GAAG,UAAU,CAAC,OAAO,WAAW,CAAC,CAAC;AAClD,eAAe,WAAW,CAAC"}
@@ -0,0 +1,20 @@
1
+ <script lang="ts">
2
+ /*
3
+ * A sidebar trigger's content: `createSidebar`'s markup, so the theme styles both alike. The
4
+ * rail shows the icon alone (the trigger's `title` carries the label); tabs and stack headers
5
+ * show the label too. The badge is hidden from assistive technology: the trigger's name has it.
6
+ */
7
+ import { sidebarBadge, type SidebarPanelInfo } from '@inkbar/core/dom'
8
+
9
+ import InkIcon from './InkIcon.svelte'
10
+
11
+ let { panel, rail }: { panel: SidebarPanelInfo; rail: boolean } = $props()
12
+
13
+ const badge = $derived(sidebarBadge(panel.badge))
14
+ </script>
15
+
16
+ {#if panel.icon}<InkIcon icon={panel.icon} />{/if}{#if !panel.icon || !rail}<span
17
+ class="ink-sidebar-label">{panel.label}</span
18
+ >{/if}<span class="ink-sidebar-badge" aria-hidden="true" hidden={badge === null}
19
+ >{badge}</span
20
+ >