@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.
- package/README.md +64 -3
- package/dist/components/InkDownloadButton.svelte +62 -0
- package/dist/components/InkDownloadButton.svelte.d.ts +14 -0
- package/dist/components/InkDownloadButton.svelte.d.ts.map +1 -0
- package/dist/components/InkFindBar.svelte +18 -15
- package/dist/components/InkFindBar.svelte.d.ts.map +1 -1
- package/dist/components/InkPanel.svelte +73 -0
- package/dist/components/InkPanel.svelte.d.ts +18 -0
- package/dist/components/InkPanel.svelte.d.ts.map +1 -0
- package/dist/components/InkPrintButton.svelte +57 -0
- package/dist/components/InkPrintButton.svelte.d.ts +12 -0
- package/dist/components/InkPrintButton.svelte.d.ts.map +1 -0
- package/dist/components/InkRecordList.svelte +91 -0
- package/dist/components/InkRecordList.svelte.d.ts +72 -0
- package/dist/components/InkRecordList.svelte.d.ts.map +1 -0
- package/dist/components/InkRoot.svelte +4 -0
- package/dist/components/InkRoot.svelte.d.ts.map +1 -1
- package/dist/components/InkSearchResults.svelte +72 -0
- package/dist/components/InkSearchResults.svelte.d.ts +9 -0
- package/dist/components/InkSearchResults.svelte.d.ts.map +1 -0
- package/dist/components/InkSidebar.svelte +179 -0
- package/dist/components/InkSidebar.svelte.d.ts +22 -0
- package/dist/components/InkSidebar.svelte.d.ts.map +1 -0
- package/dist/components/InkThumbnail.svelte +74 -0
- package/dist/components/InkThumbnail.svelte.d.ts +19 -0
- package/dist/components/InkThumbnail.svelte.d.ts.map +1 -0
- package/dist/components/InkThumbnails.svelte +146 -0
- package/dist/components/InkThumbnails.svelte.d.ts +24 -0
- package/dist/components/InkThumbnails.svelte.d.ts.map +1 -0
- package/dist/components/InkViewport.svelte +21 -4
- package/dist/components/InkViewport.svelte.d.ts +2 -0
- package/dist/components/InkViewport.svelte.d.ts.map +1 -1
- package/dist/components/InkZoomSelect.svelte +1 -1
- package/dist/components/RecordCards.svelte +173 -0
- package/dist/components/RecordCards.svelte.d.ts +16 -0
- package/dist/components/RecordCards.svelte.d.ts.map +1 -0
- package/dist/components/SidebarTrigger.svelte +20 -0
- package/dist/components/SidebarTrigger.svelte.d.ts +9 -0
- package/dist/components/SidebarTrigger.svelte.d.ts.map +1 -0
- package/dist/components/sidebar-context.d.ts +24 -0
- package/dist/components/sidebar-context.d.ts.map +1 -0
- package/dist/components/sidebar-context.js +13 -0
- package/dist/hooks/useAnnotations.svelte.d.ts +78 -0
- package/dist/hooks/useAnnotations.svelte.d.ts.map +1 -1
- package/dist/hooks/useRecordSelection.svelte.d.ts +11 -0
- package/dist/hooks/useRecordSelection.svelte.d.ts.map +1 -0
- package/dist/hooks/useRecordSelection.svelte.js +19 -0
- package/dist/hooks/useRecords.svelte.d.ts +14 -0
- package/dist/hooks/useRecords.svelte.d.ts.map +1 -0
- package/dist/hooks/useRecords.svelte.js +24 -0
- package/dist/hooks/useSearch.svelte.d.ts.map +1 -1
- package/dist/hooks/useSearch.svelte.js +1 -0
- package/dist/index.d.ts +18 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +10 -0
- package/dist/to-readable.svelte.d.ts +8 -0
- package/dist/to-readable.svelte.d.ts.map +1 -0
- package/dist/to-readable.svelte.js +15 -0
- 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-
|
|
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
|
+
}
|