@inkbar/svelte 0.9.0 → 0.11.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 (59) hide show
  1. package/README.md +64 -3
  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/InkPanel.svelte +73 -0
  8. package/dist/components/InkPanel.svelte.d.ts +18 -0
  9. package/dist/components/InkPanel.svelte.d.ts.map +1 -0
  10. package/dist/components/InkPrintButton.svelte +57 -0
  11. package/dist/components/InkPrintButton.svelte.d.ts +12 -0
  12. package/dist/components/InkPrintButton.svelte.d.ts.map +1 -0
  13. package/dist/components/InkRecordList.svelte +91 -0
  14. package/dist/components/InkRecordList.svelte.d.ts +72 -0
  15. package/dist/components/InkRecordList.svelte.d.ts.map +1 -0
  16. package/dist/components/InkRoot.svelte +4 -0
  17. package/dist/components/InkRoot.svelte.d.ts.map +1 -1
  18. package/dist/components/InkSearchResults.svelte +72 -0
  19. package/dist/components/InkSearchResults.svelte.d.ts +9 -0
  20. package/dist/components/InkSearchResults.svelte.d.ts.map +1 -0
  21. package/dist/components/InkSidebar.svelte +179 -0
  22. package/dist/components/InkSidebar.svelte.d.ts +22 -0
  23. package/dist/components/InkSidebar.svelte.d.ts.map +1 -0
  24. package/dist/components/InkThumbnail.svelte +74 -0
  25. package/dist/components/InkThumbnail.svelte.d.ts +19 -0
  26. package/dist/components/InkThumbnail.svelte.d.ts.map +1 -0
  27. package/dist/components/InkThumbnails.svelte +146 -0
  28. package/dist/components/InkThumbnails.svelte.d.ts +24 -0
  29. package/dist/components/InkThumbnails.svelte.d.ts.map +1 -0
  30. package/dist/components/InkViewport.svelte +21 -4
  31. package/dist/components/InkViewport.svelte.d.ts +2 -0
  32. package/dist/components/InkViewport.svelte.d.ts.map +1 -1
  33. package/dist/components/InkZoomSelect.svelte +1 -1
  34. package/dist/components/RecordCards.svelte +173 -0
  35. package/dist/components/RecordCards.svelte.d.ts +16 -0
  36. package/dist/components/RecordCards.svelte.d.ts.map +1 -0
  37. package/dist/components/SidebarTrigger.svelte +20 -0
  38. package/dist/components/SidebarTrigger.svelte.d.ts +9 -0
  39. package/dist/components/SidebarTrigger.svelte.d.ts.map +1 -0
  40. package/dist/components/sidebar-context.d.ts +24 -0
  41. package/dist/components/sidebar-context.d.ts.map +1 -0
  42. package/dist/components/sidebar-context.js +13 -0
  43. package/dist/hooks/useAnnotations.svelte.d.ts +78 -0
  44. package/dist/hooks/useAnnotations.svelte.d.ts.map +1 -1
  45. package/dist/hooks/useRecordSelection.svelte.d.ts +11 -0
  46. package/dist/hooks/useRecordSelection.svelte.d.ts.map +1 -0
  47. package/dist/hooks/useRecordSelection.svelte.js +19 -0
  48. package/dist/hooks/useRecords.svelte.d.ts +14 -0
  49. package/dist/hooks/useRecords.svelte.d.ts.map +1 -0
  50. package/dist/hooks/useRecords.svelte.js +24 -0
  51. package/dist/hooks/useSearch.svelte.d.ts.map +1 -1
  52. package/dist/hooks/useSearch.svelte.js +1 -0
  53. package/dist/index.d.ts +18 -0
  54. package/dist/index.d.ts.map +1 -1
  55. package/dist/index.js +10 -0
  56. package/dist/to-readable.svelte.d.ts +8 -0
  57. package/dist/to-readable.svelte.d.ts.map +1 -0
  58. package/dist/to-readable.svelte.js +15 -0
  59. package/package.json +5 -5
@@ -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
+ >
@@ -0,0 +1,9 @@
1
+ import { type SidebarPanelInfo } from '@inkbar/core/dom';
2
+ type $$ComponentProps = {
3
+ panel: SidebarPanelInfo;
4
+ rail: boolean;
5
+ };
6
+ declare const SidebarTrigger: import("svelte").Component<$$ComponentProps, {}, "">;
7
+ type SidebarTrigger = ReturnType<typeof SidebarTrigger>;
8
+ export default SidebarTrigger;
9
+ //# sourceMappingURL=SidebarTrigger.svelte.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SidebarTrigger.svelte.d.ts","sourceRoot":"","sources":["../../src/lib/components/SidebarTrigger.svelte.ts"],"names":[],"mappings":"AAQA,OAAO,EAAgB,KAAK,gBAAgB,EAAE,MAAM,kBAAkB,CAAA;AAIrE,KAAK,gBAAgB,GAAI;IAAE,KAAK,EAAE,gBAAgB,CAAC;IAAC,IAAI,EAAE,OAAO,CAAA;CAAE,CAAC;AAgBrE,QAAA,MAAM,cAAc,sDAAwC,CAAC;AAC7D,KAAK,cAAc,GAAG,UAAU,CAAC,OAAO,cAAc,CAAC,CAAC;AACxD,eAAe,cAAc,CAAC"}
@@ -0,0 +1,24 @@
1
+ import type { ReadableRef } from '../use-readable.svelte.js';
2
+ import type { SidebarController, SidebarPanelInfo, SidebarState } from '@inkbar/core/dom';
3
+ /** A panel as `InkPanel` registers it. */
4
+ export interface SidebarPanelEntry {
5
+ /** The panel's props; read inside an effect, so changes reach the sidebar. */
6
+ info(): SidebarPanelInfo;
7
+ /** The panel's element once mounted, to keep the panels in DOM order. */
8
+ element(): HTMLElement | null;
9
+ }
10
+ /** What `InkSidebar` gives its panels. */
11
+ export interface SidebarContext {
12
+ controller: SidebarController;
13
+ /** The controller's state: read it to re-render when the attributes change. */
14
+ state: ReadableRef<SidebarState>;
15
+ /** Adds a panel at the end; returns the function that removes it. */
16
+ register(entry: SidebarPanelEntry): () => void;
17
+ /** Puts the registered panels in the order of their elements in the DOM. */
18
+ arrange(): void;
19
+ }
20
+ /** Called once by InkSidebar during its initialisation. */
21
+ export declare function setSidebarContext(ctx: SidebarContext): void;
22
+ /** The sidebar's context, or a readable error outside `InkSidebar`. */
23
+ export declare function useSidebarContext(consumer: string): SidebarContext;
24
+ //# sourceMappingURL=sidebar-context.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sidebar-context.d.ts","sourceRoot":"","sources":["../../src/lib/components/sidebar-context.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,2BAA2B,CAAA;AAC5D,OAAO,KAAK,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAA;AAEzF,0CAA0C;AAC1C,MAAM,WAAW,iBAAiB;IAChC,8EAA8E;IAC9E,IAAI,IAAI,gBAAgB,CAAA;IACxB,yEAAyE;IACzE,OAAO,IAAI,WAAW,GAAG,IAAI,CAAA;CAC9B;AAED,0CAA0C;AAC1C,MAAM,WAAW,cAAc;IAC7B,UAAU,EAAE,iBAAiB,CAAA;IAC7B,+EAA+E;IAC/E,KAAK,EAAE,WAAW,CAAC,YAAY,CAAC,CAAA;IAChC,qEAAqE;IACrE,QAAQ,CAAC,KAAK,EAAE,iBAAiB,GAAG,MAAM,IAAI,CAAA;IAC9C,4EAA4E;IAC5E,OAAO,IAAI,IAAI,CAAA;CAChB;AAID,2DAA2D;AAC3D,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,cAAc,GAAG,IAAI,CAE3D;AAED,uEAAuE;AACvE,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,MAAM,GAAG,cAAc,CAIlE"}
@@ -0,0 +1,13 @@
1
+ import { getContext, setContext } from 'svelte';
2
+ const KEY = Symbol('inkbar-sidebar');
3
+ /** Called once by InkSidebar during its initialisation. */
4
+ export function setSidebarContext(ctx) {
5
+ setContext(KEY, ctx);
6
+ }
7
+ /** The sidebar's context, or a readable error outside `InkSidebar`. */
8
+ export function useSidebarContext(consumer) {
9
+ const ctx = getContext(KEY);
10
+ if (!ctx)
11
+ throw new Error(`[inkbar] ${consumer} must be inside <InkSidebar>.`);
12
+ return ctx;
13
+ }