@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
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
<script module lang="ts">
|
|
2
|
+
import type { HTMLAttributes } from 'svelte/elements'
|
|
3
|
+
|
|
4
|
+
export interface InkSearchResultsProps extends Omit<
|
|
5
|
+
HTMLAttributes<HTMLDivElement>,
|
|
6
|
+
'children'
|
|
7
|
+
> {
|
|
8
|
+
/** Show the query field. Default true; turn it off when a find bar is on the page. */
|
|
9
|
+
field?: boolean | undefined
|
|
10
|
+
}
|
|
11
|
+
</script>
|
|
12
|
+
|
|
13
|
+
<script lang="ts">
|
|
14
|
+
/*
|
|
15
|
+
* The search hits as a list of cards, for a sidebar panel: `createSearchResults` on core's
|
|
16
|
+
* record list controller over the root's search. One card per hit, with its page and the text
|
|
17
|
+
* around it. Choosing a card makes its hit current, as the find bar's next and previous do, and
|
|
18
|
+
* the current hit is the selected card. Above the cards, a query field (`field={false}`
|
|
19
|
+
* leaves it out when a find bar is on the page); under them, the find bar's match count.
|
|
20
|
+
* Field and find bar share the search state, so they never disagree. It fills its parent's
|
|
21
|
+
* height. Inside `InkRoot`; it renders empty on the server.
|
|
22
|
+
*/
|
|
23
|
+
import {
|
|
24
|
+
findCountText,
|
|
25
|
+
handleResultsFieldKey,
|
|
26
|
+
isBlankQuery,
|
|
27
|
+
searchSource,
|
|
28
|
+
} from '@inkbar/core/dom'
|
|
29
|
+
import { Search } from '@inkbar/icons'
|
|
30
|
+
|
|
31
|
+
import { useInkContext } from '../context.js'
|
|
32
|
+
import { useSearch } from '../hooks/useSearch.svelte.js'
|
|
33
|
+
|
|
34
|
+
import InkIcon from './InkIcon.svelte'
|
|
35
|
+
import RecordCards from './RecordCards.svelte'
|
|
36
|
+
|
|
37
|
+
let { field = true, class: className, ...rest }: InkSearchResultsProps = $props()
|
|
38
|
+
|
|
39
|
+
const ctx = useInkContext('InkSearchResults')
|
|
40
|
+
const search = useSearch()
|
|
41
|
+
const source = $derived(ctx.search ? searchSource(ctx.search) : null)
|
|
42
|
+
// Nothing found yet is the count's to say while a query is in: "No results", or a scan.
|
|
43
|
+
const blank = $derived(isBlankQuery(search.state.query))
|
|
44
|
+
</script>
|
|
45
|
+
|
|
46
|
+
<div {...rest} class={['ink-search-results', className]} data-ink-search-results="">
|
|
47
|
+
{#if field}
|
|
48
|
+
<div class="ink-search-results-bar" role="search">
|
|
49
|
+
<!-- The field is the search state's: a query typed in the find bar shows here. -->
|
|
50
|
+
<div class="ink-input-group">
|
|
51
|
+
<span class="ink-input-accessory"><InkIcon icon={Search} /></span>
|
|
52
|
+
<input
|
|
53
|
+
type="text"
|
|
54
|
+
aria-label="Find in document"
|
|
55
|
+
placeholder="Search for a word or phrase"
|
|
56
|
+
autocomplete="off"
|
|
57
|
+
spellcheck={false}
|
|
58
|
+
value={search.state.query}
|
|
59
|
+
oninput={(e) => search.setQuery(e.currentTarget.value)}
|
|
60
|
+
onkeydown={(e) => handleResultsFieldKey(e, search)}
|
|
61
|
+
/>
|
|
62
|
+
</div>
|
|
63
|
+
</div>
|
|
64
|
+
{/if}
|
|
65
|
+
<RecordCards {source} emptyShown={blank}>
|
|
66
|
+
{#snippet empty()}Search to list every match.{/snippet}
|
|
67
|
+
</RecordCards>
|
|
68
|
+
<!-- A footer under the cards, hidden while empty. -->
|
|
69
|
+
<div class="ink-search-results-count" data-ink-search-results-count="">
|
|
70
|
+
{findCountText(search.state)}
|
|
71
|
+
</div>
|
|
72
|
+
</div>
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { HTMLAttributes } from 'svelte/elements';
|
|
2
|
+
export interface InkSearchResultsProps extends Omit<HTMLAttributes<HTMLDivElement>, 'children'> {
|
|
3
|
+
/** Show the query field. Default true; turn it off when a find bar is on the page. */
|
|
4
|
+
field?: boolean | undefined;
|
|
5
|
+
}
|
|
6
|
+
declare const InkSearchResults: import("svelte").Component<InkSearchResultsProps, {}, "">;
|
|
7
|
+
type InkSearchResults = ReturnType<typeof InkSearchResults>;
|
|
8
|
+
export default InkSearchResults;
|
|
9
|
+
//# sourceMappingURL=InkSearchResults.svelte.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"InkSearchResults.svelte.d.ts","sourceRoot":"","sources":["../../src/lib/components/InkSearchResults.svelte.ts"],"names":[],"mappings":"AAGE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAA;AAErD,MAAM,WAAW,qBAAsB,SAAQ,IAAI,CACjD,cAAc,CAAC,cAAc,CAAC,EAC9B,UAAU,CACX;IACC,sFAAsF;IACtF,KAAK,CAAC,EAAE,OAAO,GAAG,SAAS,CAAA;CAC5B;AAqEH,QAAA,MAAM,gBAAgB,2DAAwC,CAAC;AAC/D,KAAK,gBAAgB,GAAG,UAAU,CAAC,OAAO,gBAAgB,CAAC,CAAC;AAC5D,eAAe,gBAAgB,CAAC"}
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
<script module lang="ts">
|
|
2
|
+
import type { SidebarLayout, SidebarOpen, SidebarSide } from '@inkbar/core/dom'
|
|
3
|
+
import type { Snippet } from 'svelte'
|
|
4
|
+
import type { HTMLAttributes } from 'svelte/elements'
|
|
5
|
+
|
|
6
|
+
export interface InkSidebarProps extends Omit<
|
|
7
|
+
HTMLAttributes<HTMLDivElement>,
|
|
8
|
+
'children'
|
|
9
|
+
> {
|
|
10
|
+
/** Which edge the rail and border sit on. Default `'left'`. */
|
|
11
|
+
side?: SidebarSide | undefined
|
|
12
|
+
/** `'rail'` (default), `'tabs'` or `'stack'`. */
|
|
13
|
+
layout?: SidebarLayout | undefined
|
|
14
|
+
/** Default true. False hides the whole sidebar and keeps its open panel, content and scroll for when it shows again. */
|
|
15
|
+
visible?: boolean | undefined
|
|
16
|
+
/**
|
|
17
|
+
* Bindable: the open panel's id (or null) for rail and tabs, the expanded ids for stack.
|
|
18
|
+
* Left out, the first panel starts open.
|
|
19
|
+
*/
|
|
20
|
+
open?: SidebarOpen | undefined
|
|
21
|
+
/** The `InkPanel`s. */
|
|
22
|
+
children?: Snippet | undefined
|
|
23
|
+
}
|
|
24
|
+
</script>
|
|
25
|
+
|
|
26
|
+
<script lang="ts">
|
|
27
|
+
/*
|
|
28
|
+
* A sidebar of `InkPanel`s: `createSidebar`'s markup on core's sidebar controller, which holds
|
|
29
|
+
* the open state, the ARIA, the keys and the clicks. Rail and tabs render their triggers here;
|
|
30
|
+
* in a stack each panel renders its own header. Panels register as they initialise and take
|
|
31
|
+
* their place in DOM order once mounted, so a panel added later with `{#if}` slots in where
|
|
32
|
+
* it stands. A closed panel stays mounted and hidden, and the panels' content stays in place
|
|
33
|
+
* across layouts, so its state survives switching.
|
|
34
|
+
*
|
|
35
|
+
* `bind:open` is the open panel's id (or null) for rail and tabs, and the expanded ids for
|
|
36
|
+
* stack. Left out, the first panel starts open. It needs no document, so it works outside
|
|
37
|
+
* `InkRoot`. The server renders the root, the panels (the open one shown) and the stack
|
|
38
|
+
* headers; rail and tab buttons follow once mounted, as the panels register after the root
|
|
39
|
+
* has rendered.
|
|
40
|
+
*/
|
|
41
|
+
import { createSidebarController } from '@inkbar/core/dom'
|
|
42
|
+
import { untrack } from 'svelte'
|
|
43
|
+
|
|
44
|
+
import { useReadable } from '../use-readable.svelte.js'
|
|
45
|
+
|
|
46
|
+
import { setSidebarContext, type SidebarPanelEntry } from './sidebar-context.js'
|
|
47
|
+
import SidebarTrigger from './SidebarTrigger.svelte'
|
|
48
|
+
|
|
49
|
+
let {
|
|
50
|
+
side = 'left',
|
|
51
|
+
layout = 'rail',
|
|
52
|
+
visible = true,
|
|
53
|
+
open = $bindable(),
|
|
54
|
+
class: className,
|
|
55
|
+
children,
|
|
56
|
+
...rest
|
|
57
|
+
}: InkSidebarProps = $props()
|
|
58
|
+
|
|
59
|
+
// The same ids on the server and the client.
|
|
60
|
+
const idPrefix = $props.id()
|
|
61
|
+
|
|
62
|
+
let entries = $state.raw<SidebarPanelEntry[]>([])
|
|
63
|
+
const infos = () => entries.map((e) => e.info())
|
|
64
|
+
|
|
65
|
+
// Pending until mounted: the panels register one by one as they initialise, and an open id
|
|
66
|
+
// not registered yet is not a removed panel.
|
|
67
|
+
const controller = untrack(() =>
|
|
68
|
+
createSidebarController({
|
|
69
|
+
layout,
|
|
70
|
+
side,
|
|
71
|
+
visible,
|
|
72
|
+
panels: [],
|
|
73
|
+
...(open !== undefined && { open }),
|
|
74
|
+
pending: true,
|
|
75
|
+
onOpenChange: (value) => {
|
|
76
|
+
open = value
|
|
77
|
+
},
|
|
78
|
+
idPrefix,
|
|
79
|
+
})
|
|
80
|
+
)
|
|
81
|
+
const current = useReadable(() => controller.state, controller.state.get())
|
|
82
|
+
// Destroyed before the panels go: this pre-effect comes before the template's, so its
|
|
83
|
+
// teardown runs first. Unregistering into a live controller would take each panel for a
|
|
84
|
+
// removed one and write the fallback to the bound `open`.
|
|
85
|
+
$effect.pre(() => () => controller.destroy())
|
|
86
|
+
|
|
87
|
+
// Before the DOM updates, so the panels render the attributes these give.
|
|
88
|
+
$effect.pre(() => {
|
|
89
|
+
const next = { layout, side, visible, open }
|
|
90
|
+
untrack(() =>
|
|
91
|
+
controller.update({
|
|
92
|
+
layout: next.layout,
|
|
93
|
+
side: next.side,
|
|
94
|
+
visible: next.visible,
|
|
95
|
+
...(next.open !== undefined && { open: next.open }),
|
|
96
|
+
})
|
|
97
|
+
)
|
|
98
|
+
})
|
|
99
|
+
// A panel's label, icon or badge changing.
|
|
100
|
+
$effect.pre(() => {
|
|
101
|
+
const panels = infos()
|
|
102
|
+
untrack(() => controller.update({ panels }))
|
|
103
|
+
})
|
|
104
|
+
|
|
105
|
+
// Panels register while they initialise, inside the parent's render: untracked, so the
|
|
106
|
+
// writes are not taken for state changed by a template expression. On the server too.
|
|
107
|
+
function register(entry: SidebarPanelEntry): () => void {
|
|
108
|
+
untrack(() => {
|
|
109
|
+
entries = [...entries, entry]
|
|
110
|
+
controller.update({ panels: infos() })
|
|
111
|
+
})
|
|
112
|
+
return () =>
|
|
113
|
+
untrack(() => {
|
|
114
|
+
entries = entries.filter((e) => e !== entry)
|
|
115
|
+
controller.update({ panels: infos() })
|
|
116
|
+
})
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function arrange(): void {
|
|
120
|
+
untrack(() => {
|
|
121
|
+
const list = entries
|
|
122
|
+
const sorted = [...list].sort((a, b) => {
|
|
123
|
+
const x = a.element()
|
|
124
|
+
const y = b.element()
|
|
125
|
+
if (!x || !y || x === y) return 0
|
|
126
|
+
return x.compareDocumentPosition(y) & Node.DOCUMENT_POSITION_FOLLOWING ? -1 : 1
|
|
127
|
+
})
|
|
128
|
+
if (sorted.some((e, i) => e !== list[i])) {
|
|
129
|
+
entries = sorted
|
|
130
|
+
controller.update({ panels: infos() })
|
|
131
|
+
}
|
|
132
|
+
})
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
setSidebarContext({ controller, state: current, register, arrange })
|
|
136
|
+
|
|
137
|
+
const view = $derived.by(() => {
|
|
138
|
+
const s = current.current
|
|
139
|
+
return {
|
|
140
|
+
layout: s.layout,
|
|
141
|
+
root: controller.rootAttrs(),
|
|
142
|
+
tablist: controller.tablistAttrs(),
|
|
143
|
+
triggers: s.panels.map((panel) => ({
|
|
144
|
+
panel,
|
|
145
|
+
attrs: controller.triggerAttrs(panel.id),
|
|
146
|
+
})),
|
|
147
|
+
}
|
|
148
|
+
})
|
|
149
|
+
|
|
150
|
+
let root = $state<HTMLDivElement | null>(null)
|
|
151
|
+
// The controller handles the triggers' clicks and keys, stack headers included. The panels
|
|
152
|
+
// have registered by now.
|
|
153
|
+
$effect(() => {
|
|
154
|
+
const el = root
|
|
155
|
+
if (!el) return
|
|
156
|
+
const unbind = untrack(() => controller.bindTriggers(el))
|
|
157
|
+
untrack(() => controller.update({ pending: false }))
|
|
158
|
+
return unbind
|
|
159
|
+
})
|
|
160
|
+
</script>
|
|
161
|
+
|
|
162
|
+
<div {...rest} bind:this={root} class={['ink-sidebar', className]} {...view.root}>
|
|
163
|
+
{#if view.layout !== 'stack'}
|
|
164
|
+
<div
|
|
165
|
+
class={view.layout === 'rail' ? 'ink-sidebar-rail' : 'ink-sidebar-tabs'}
|
|
166
|
+
{...view.tablist}
|
|
167
|
+
>
|
|
168
|
+
{#each view.triggers as t (t.panel.id)}
|
|
169
|
+
<button {...t.attrs}>
|
|
170
|
+
<SidebarTrigger panel={t.panel} rail={view.layout === 'rail'} />
|
|
171
|
+
</button>
|
|
172
|
+
{/each}
|
|
173
|
+
</div>
|
|
174
|
+
{/if}
|
|
175
|
+
<!-- One element in every layout, so the panels are never remounted. -->
|
|
176
|
+
<div class={{ 'ink-sidebar-panels': view.layout !== 'stack' }}>
|
|
177
|
+
{@render children?.()}
|
|
178
|
+
</div>
|
|
179
|
+
</div>
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { SidebarLayout, SidebarOpen, SidebarSide } from '@inkbar/core/dom';
|
|
2
|
+
import type { Snippet } from 'svelte';
|
|
3
|
+
import type { HTMLAttributes } from 'svelte/elements';
|
|
4
|
+
export interface InkSidebarProps extends Omit<HTMLAttributes<HTMLDivElement>, 'children'> {
|
|
5
|
+
/** Which edge the rail and border sit on. Default `'left'`. */
|
|
6
|
+
side?: SidebarSide | undefined;
|
|
7
|
+
/** `'rail'` (default), `'tabs'` or `'stack'`. */
|
|
8
|
+
layout?: SidebarLayout | undefined;
|
|
9
|
+
/** Default true. False hides the whole sidebar and keeps its open panel, content and scroll for when it shows again. */
|
|
10
|
+
visible?: boolean | undefined;
|
|
11
|
+
/**
|
|
12
|
+
* Bindable: the open panel's id (or null) for rail and tabs, the expanded ids for stack.
|
|
13
|
+
* Left out, the first panel starts open.
|
|
14
|
+
*/
|
|
15
|
+
open?: SidebarOpen | undefined;
|
|
16
|
+
/** The `InkPanel`s. */
|
|
17
|
+
children?: Snippet | undefined;
|
|
18
|
+
}
|
|
19
|
+
declare const InkSidebar: import("svelte").Component<InkSidebarProps, {}, "open">;
|
|
20
|
+
type InkSidebar = ReturnType<typeof InkSidebar>;
|
|
21
|
+
export default InkSidebar;
|
|
22
|
+
//# sourceMappingURL=InkSidebar.svelte.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"InkSidebar.svelte.d.ts","sourceRoot":"","sources":["../../src/lib/components/InkSidebar.svelte.ts"],"names":[],"mappings":"AAGE,OAAO,KAAK,EAAE,aAAa,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAA;AAC/E,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAA;AACrC,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAA;AAErD,MAAM,WAAW,eAAgB,SAAQ,IAAI,CAC3C,cAAc,CAAC,cAAc,CAAC,EAC9B,UAAU,CACX;IACC,+DAA+D;IAC/D,IAAI,CAAC,EAAE,WAAW,GAAG,SAAS,CAAA;IAC9B,iDAAiD;IACjD,MAAM,CAAC,EAAE,aAAa,GAAG,SAAS,CAAA;IAClC,wHAAwH;IACxH,OAAO,CAAC,EAAE,OAAO,GAAG,SAAS,CAAA;IAC7B;;;OAGG;IACH,IAAI,CAAC,EAAE,WAAW,GAAG,SAAS,CAAA;IAC9B,uBAAuB;IACvB,QAAQ,CAAC,EAAE,OAAO,GAAG,SAAS,CAAA;CAC/B;AAwKH,QAAA,MAAM,UAAU,yDAAwC,CAAC;AACzD,KAAK,UAAU,GAAG,UAAU,CAAC,OAAO,UAAU,CAAC,CAAC;AAChD,eAAe,UAAU,CAAC"}
|
|
@@ -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"}
|
|
@@ -14,6 +14,8 @@
|
|
|
14
14
|
gap?: number
|
|
15
15
|
/** Pages kept mounted either side of the visible ones. */
|
|
16
16
|
buffer?: number
|
|
17
|
+
/** Pinch to zoom the pages. Off leaves the browser's own touch handling alone. */
|
|
18
|
+
pinchZoom?: boolean
|
|
17
19
|
loading?: Snippet | undefined
|
|
18
20
|
empty?: Snippet | undefined
|
|
19
21
|
error?: Snippet<[{ error: InkError }]> | undefined
|
|
@@ -63,6 +65,7 @@
|
|
|
63
65
|
rotation = 0,
|
|
64
66
|
gap = 16,
|
|
65
67
|
buffer = 2,
|
|
68
|
+
pinchZoom = true,
|
|
66
69
|
loading,
|
|
67
70
|
empty,
|
|
68
71
|
error,
|
|
@@ -100,7 +103,12 @@
|
|
|
100
103
|
*/
|
|
101
104
|
let build = 0
|
|
102
105
|
/** The construction options the engine was last built with, so a rebuild runs only on a change. */
|
|
103
|
-
let builtWith: {
|
|
106
|
+
let builtWith: {
|
|
107
|
+
gap: number
|
|
108
|
+
buffer: number
|
|
109
|
+
pinchZoom: boolean
|
|
110
|
+
custom: boolean
|
|
111
|
+
} | null = null
|
|
104
112
|
let stops: (() => void)[] = []
|
|
105
113
|
/** The zoom and page last reported to the host. */
|
|
106
114
|
let lastReportedZoom: ZoomValue | undefined
|
|
@@ -135,7 +143,7 @@
|
|
|
135
143
|
let first = true
|
|
136
144
|
build += 1
|
|
137
145
|
const custom = !!annotation
|
|
138
|
-
builtWith = { gap, buffer, custom }
|
|
146
|
+
builtWith = { gap, buffer, pinchZoom, custom }
|
|
139
147
|
// The tools this viewer registers, from the same inputs; a later `tools` waits for a rebuild.
|
|
140
148
|
ctx.setOpenedToolIds(toolIdsOf(ctx.hostTools, ctx.pluginHost))
|
|
141
149
|
engine = mountViewer({
|
|
@@ -147,6 +155,7 @@
|
|
|
147
155
|
rotation,
|
|
148
156
|
gap,
|
|
149
157
|
buffer,
|
|
158
|
+
pinchZoom,
|
|
150
159
|
tool: ctx.tool,
|
|
151
160
|
...(ctx.hostTools ? { tools: ctx.hostTools } : {}),
|
|
152
161
|
// The engine adds the plugins' tools and layers, and decorates through the host, which
|
|
@@ -253,7 +262,7 @@
|
|
|
253
262
|
|
|
254
263
|
// Construction-only options rebuild the engine for the same document, keeping zoom and page.
|
|
255
264
|
// The deriveds notify only on a real change: a host that spreads its props invalidates `gap`
|
|
256
|
-
// and `buffer` without changing them. One effect for all
|
|
265
|
+
// and `buffer` without changing them. One effect for all of them, so a gap and a snippet change
|
|
257
266
|
// in the same tick rebuild once. It skips its first run (and any run after a build that
|
|
258
267
|
// already used these values) by comparing with `builtWith`.
|
|
259
268
|
//
|
|
@@ -264,15 +273,22 @@
|
|
|
264
273
|
// `pages` for the new build's pages. The rebuild keeps the scroll position.
|
|
265
274
|
const buildGap = $derived(gap)
|
|
266
275
|
const buildBuffer = $derived(buffer)
|
|
276
|
+
const buildPinchZoom = $derived(pinchZoom)
|
|
267
277
|
const buildCustom = $derived(!!annotation)
|
|
268
278
|
$effect(() => {
|
|
269
|
-
const next = {
|
|
279
|
+
const next = {
|
|
280
|
+
gap: buildGap,
|
|
281
|
+
buffer: buildBuffer,
|
|
282
|
+
pinchZoom: buildPinchZoom,
|
|
283
|
+
custom: buildCustom,
|
|
284
|
+
}
|
|
270
285
|
untrack(() => {
|
|
271
286
|
const built = builtWith
|
|
272
287
|
if (!engine || !built) return
|
|
273
288
|
if (
|
|
274
289
|
built.gap === next.gap &&
|
|
275
290
|
built.buffer === next.buffer &&
|
|
291
|
+
built.pinchZoom === next.pinchZoom &&
|
|
276
292
|
built.custom === next.custom
|
|
277
293
|
)
|
|
278
294
|
return
|
|
@@ -281,6 +297,7 @@
|
|
|
281
297
|
engine.rebuild({
|
|
282
298
|
gap: next.gap,
|
|
283
299
|
buffer: next.buffer,
|
|
300
|
+
pinchZoom: next.pinchZoom,
|
|
284
301
|
customAnnotations: next.custom,
|
|
285
302
|
})
|
|
286
303
|
})
|
|
@@ -9,6 +9,8 @@ export interface InkViewportProps extends Omit<HTMLAttributes<HTMLDivElement>, '
|
|
|
9
9
|
gap?: number;
|
|
10
10
|
/** Pages kept mounted either side of the visible ones. */
|
|
11
11
|
buffer?: number;
|
|
12
|
+
/** Pinch to zoom the pages. Off leaves the browser's own touch handling alone. */
|
|
13
|
+
pinchZoom?: boolean;
|
|
12
14
|
loading?: Snippet | undefined;
|
|
13
15
|
empty?: Snippet | undefined;
|
|
14
16
|
error?: Snippet<[{
|
|
@@ -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;
|
|
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,kFAAkF;IAClF,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB,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;AA0XH,QAAA,MAAM,WAAW,sDAAwC,CAAC;AAC1D,KAAK,WAAW,GAAG,UAAU,CAAC,OAAO,WAAW,CAAC,CAAC;AAClD,eAAe,WAAW,CAAC"}
|