@inkbar/svelte 0.0.0-stage → 0.1.1
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/LICENSE +21 -0
- package/README.md +154 -2
- package/dist/class-names.d.ts +3 -0
- package/dist/class-names.d.ts.map +1 -0
- package/dist/class-names.js +5 -0
- package/dist/components/InkAnnotation.svelte +73 -0
- package/dist/components/InkAnnotation.svelte.d.ts +13 -0
- package/dist/components/InkAnnotation.svelte.d.ts.map +1 -0
- package/dist/components/InkNotePopover.svelte +193 -0
- package/dist/components/InkNotePopover.svelte.d.ts +9 -0
- package/dist/components/InkNotePopover.svelte.d.ts.map +1 -0
- package/dist/components/InkPage.svelte +125 -0
- package/dist/components/InkPage.svelte.d.ts +22 -0
- package/dist/components/InkPage.svelte.d.ts.map +1 -0
- package/dist/components/InkPageCount.svelte +20 -0
- package/dist/components/InkPageCount.svelte.d.ts +6 -0
- package/dist/components/InkPageCount.svelte.d.ts.map +1 -0
- package/dist/components/InkPageInput.svelte +49 -0
- package/dist/components/InkPageInput.svelte.d.ts +6 -0
- package/dist/components/InkPageInput.svelte.d.ts.map +1 -0
- package/dist/components/InkRoot.svelte +358 -0
- package/dist/components/InkRoot.svelte.d.ts +51 -0
- package/dist/components/InkRoot.svelte.d.ts.map +1 -0
- package/dist/components/InkSuggestionCard.svelte +121 -0
- package/dist/components/InkSuggestionCard.svelte.d.ts +9 -0
- package/dist/components/InkSuggestionCard.svelte.d.ts.map +1 -0
- package/dist/components/InkToolButton.svelte +54 -0
- package/dist/components/InkToolButton.svelte.d.ts +17 -0
- package/dist/components/InkToolButton.svelte.d.ts.map +1 -0
- package/dist/components/InkToolbar.svelte +40 -0
- package/dist/components/InkToolbar.svelte.d.ts +13 -0
- package/dist/components/InkToolbar.svelte.d.ts.map +1 -0
- package/dist/components/InkViewport.svelte +306 -0
- package/dist/components/InkViewport.svelte.d.ts +27 -0
- package/dist/components/InkViewport.svelte.d.ts.map +1 -0
- package/dist/components/InkZoomIn.svelte +34 -0
- package/dist/components/InkZoomIn.svelte.d.ts +5 -0
- package/dist/components/InkZoomIn.svelte.d.ts.map +1 -0
- package/dist/components/InkZoomOut.svelte +34 -0
- package/dist/components/InkZoomOut.svelte.d.ts +5 -0
- package/dist/components/InkZoomOut.svelte.d.ts.map +1 -0
- package/dist/components/InkZoomSelect.svelte +56 -0
- package/dist/components/InkZoomSelect.svelte.d.ts +6 -0
- package/dist/components/InkZoomSelect.svelte.d.ts.map +1 -0
- package/dist/components/ThreadForm.svelte +42 -0
- package/dist/components/ThreadForm.svelte.d.ts +11 -0
- package/dist/components/ThreadForm.svelte.d.ts.map +1 -0
- package/dist/components/annotation-element.svelte.d.ts +9 -0
- package/dist/components/annotation-element.svelte.d.ts.map +1 -0
- package/dist/components/annotation-element.svelte.js +25 -0
- package/dist/components/child-snippet.d.ts +22 -0
- package/dist/components/child-snippet.d.ts.map +1 -0
- package/dist/components/child-snippet.js +14 -0
- package/dist/components/popover-focus.d.ts +33 -0
- package/dist/components/popover-focus.d.ts.map +1 -0
- package/dist/components/popover-focus.js +63 -0
- package/dist/components/portal.d.ts +26 -0
- package/dist/components/portal.d.ts.map +1 -0
- package/dist/components/portal.js +55 -0
- package/dist/components/zoom-button.d.ts +10 -0
- package/dist/components/zoom-button.d.ts.map +1 -0
- package/dist/components/zoom-button.js +1 -0
- package/dist/compositions/DefaultToolbar.svelte +90 -0
- package/dist/compositions/DefaultToolbar.svelte.d.ts +10 -0
- package/dist/compositions/DefaultToolbar.svelte.d.ts.map +1 -0
- package/dist/context.d.ts +57 -0
- package/dist/context.d.ts.map +1 -0
- package/dist/context.js +34 -0
- package/dist/hooks/useAnnotations.svelte.d.ts +1683 -0
- package/dist/hooks/useAnnotations.svelte.d.ts.map +1 -0
- package/dist/hooks/useAnnotations.svelte.js +33 -0
- package/dist/hooks/useDocument.svelte.d.ts +12 -0
- package/dist/hooks/useDocument.svelte.d.ts.map +1 -0
- package/dist/hooks/useDocument.svelte.js +28 -0
- package/dist/hooks/useEmbedded.svelte.d.ts +7 -0
- package/dist/hooks/useEmbedded.svelte.d.ts.map +1 -0
- package/dist/hooks/useEmbedded.svelte.js +17 -0
- package/dist/hooks/useHistory.svelte.d.ts +7 -0
- package/dist/hooks/useHistory.svelte.d.ts.map +1 -0
- package/dist/hooks/useHistory.svelte.js +8 -0
- package/dist/hooks/usePage.svelte.d.ts +10 -0
- package/dist/hooks/usePage.svelte.d.ts.map +1 -0
- package/dist/hooks/usePage.svelte.js +29 -0
- package/dist/hooks/useSelection.svelte.d.ts +6 -0
- package/dist/hooks/useSelection.svelte.d.ts.map +1 -0
- package/dist/hooks/useSelection.svelte.js +12 -0
- package/dist/hooks/useTool.svelte.d.ts +6 -0
- package/dist/hooks/useTool.svelte.d.ts.map +1 -0
- package/dist/hooks/useTool.svelte.js +10 -0
- package/dist/hooks/useZoom.svelte.d.ts +9 -0
- package/dist/hooks/useZoom.svelte.d.ts.map +1 -0
- package/dist/hooks/useZoom.svelte.js +18 -0
- package/dist/index.d.ts +45 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +24 -0
- package/dist/use-readable.svelte.d.ts +13 -0
- package/dist/use-readable.svelte.d.ts.map +1 -0
- package/dist/use-readable.svelte.js +28 -0
- package/package.json +56 -3
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { ChildSnippet } from './child-snippet.js';
|
|
2
|
+
import type { ToolId } from '@inkbar/core/dom';
|
|
3
|
+
import type { Snippet } from 'svelte';
|
|
4
|
+
import type { HTMLButtonAttributes } from 'svelte/elements';
|
|
5
|
+
export interface InkToolButtonProps extends Omit<HTMLButtonAttributes, 'children'> {
|
|
6
|
+
tool: ToolId;
|
|
7
|
+
label?: string | undefined;
|
|
8
|
+
children?: Snippet<[{
|
|
9
|
+
active: boolean;
|
|
10
|
+
label: string;
|
|
11
|
+
}]> | undefined;
|
|
12
|
+
child?: ChildSnippet | undefined;
|
|
13
|
+
}
|
|
14
|
+
declare const InkToolButton: import("svelte").Component<InkToolButtonProps, {}, "">;
|
|
15
|
+
type InkToolButton = ReturnType<typeof InkToolButton>;
|
|
16
|
+
export default InkToolButton;
|
|
17
|
+
//# sourceMappingURL=InkToolButton.svelte.d.ts.map
|
|
@@ -0,0 +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;AA4CH,QAAA,MAAM,aAAa,wDAAwC,CAAC;AAC5D,KAAK,aAAa,GAAG,UAAU,CAAC,OAAO,aAAa,CAAC,CAAC;AACtD,eAAe,aAAa,CAAC"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
<script module lang="ts">
|
|
2
|
+
import type { ChildSnippet } from './child-snippet.js'
|
|
3
|
+
import type { Snippet } from 'svelte'
|
|
4
|
+
import type { HTMLAttributes } from 'svelte/elements'
|
|
5
|
+
|
|
6
|
+
export interface InkToolbarProps extends Omit<
|
|
7
|
+
HTMLAttributes<HTMLDivElement>,
|
|
8
|
+
'children'
|
|
9
|
+
> {
|
|
10
|
+
label?: string | undefined
|
|
11
|
+
children?: Snippet | undefined
|
|
12
|
+
/** Render your own toolbar element: spread `props` on it and put the controls inside. */
|
|
13
|
+
child?: ChildSnippet | undefined
|
|
14
|
+
}
|
|
15
|
+
</script>
|
|
16
|
+
|
|
17
|
+
<script lang="ts">
|
|
18
|
+
import { Toolbar } from 'bits-ui'
|
|
19
|
+
|
|
20
|
+
import { asBitsProps } from './child-snippet.js'
|
|
21
|
+
|
|
22
|
+
let {
|
|
23
|
+
label = 'Document tools',
|
|
24
|
+
class: className,
|
|
25
|
+
children,
|
|
26
|
+
child,
|
|
27
|
+
...rest
|
|
28
|
+
}: InkToolbarProps = $props()
|
|
29
|
+
</script>
|
|
30
|
+
|
|
31
|
+
<Toolbar.Root
|
|
32
|
+
orientation="horizontal"
|
|
33
|
+
aria-label={label}
|
|
34
|
+
data-ink-toolbar=""
|
|
35
|
+
class={['ink-toolbar', className]}
|
|
36
|
+
{...child ? { child } : {}}
|
|
37
|
+
{...asBitsProps(rest)}
|
|
38
|
+
>
|
|
39
|
+
{@render children?.()}
|
|
40
|
+
</Toolbar.Root>
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { ChildSnippet } from './child-snippet.js';
|
|
2
|
+
import type { Snippet } from 'svelte';
|
|
3
|
+
import type { HTMLAttributes } from 'svelte/elements';
|
|
4
|
+
export interface InkToolbarProps extends Omit<HTMLAttributes<HTMLDivElement>, 'children'> {
|
|
5
|
+
label?: string | undefined;
|
|
6
|
+
children?: Snippet | undefined;
|
|
7
|
+
/** Render your own toolbar element: spread `props` on it and put the controls inside. */
|
|
8
|
+
child?: ChildSnippet | undefined;
|
|
9
|
+
}
|
|
10
|
+
declare const InkToolbar: import("svelte").Component<InkToolbarProps, {}, "">;
|
|
11
|
+
type InkToolbar = ReturnType<typeof InkToolbar>;
|
|
12
|
+
export default InkToolbar;
|
|
13
|
+
//# sourceMappingURL=InkToolbar.svelte.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"InkToolbar.svelte.d.ts","sourceRoot":"","sources":["../../src/lib/components/InkToolbar.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,cAAc,EAAE,MAAM,iBAAiB,CAAA;AAErD,MAAM,WAAW,eAAgB,SAAQ,IAAI,CAC3C,cAAc,CAAC,cAAc,CAAC,EAC9B,UAAU,CACX;IACC,KAAK,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;IAC1B,QAAQ,CAAC,EAAE,OAAO,GAAG,SAAS,CAAA;IAC9B,yFAAyF;IACzF,KAAK,CAAC,EAAE,YAAY,GAAG,SAAS,CAAA;CACjC;AA6BH,QAAA,MAAM,UAAU,qDAAwC,CAAC;AACzD,KAAK,UAAU,GAAG,UAAU,CAAC,OAAO,UAAU,CAAC,CAAC;AAChD,eAAe,UAAU,CAAC"}
|
|
@@ -0,0 +1,306 @@
|
|
|
1
|
+
<script module lang="ts">
|
|
2
|
+
import type { InkError } from '@inkbar/core'
|
|
3
|
+
import type { AnnotationSlotProps, LayoutMode, ViewRotation } from '@inkbar/core/dom'
|
|
4
|
+
import type { Snippet } from 'svelte'
|
|
5
|
+
import type { HTMLAttributes } from 'svelte/elements'
|
|
6
|
+
|
|
7
|
+
export interface InkViewportProps extends Omit<
|
|
8
|
+
HTMLAttributes<HTMLDivElement>,
|
|
9
|
+
'children'
|
|
10
|
+
> {
|
|
11
|
+
mode?: LayoutMode
|
|
12
|
+
rotation?: ViewRotation
|
|
13
|
+
/** Gap between pages, CSS px. */
|
|
14
|
+
gap?: number
|
|
15
|
+
/** Pages kept mounted either side of the visible ones. */
|
|
16
|
+
buffer?: number
|
|
17
|
+
loading?: Snippet | undefined
|
|
18
|
+
empty?: Snippet | undefined
|
|
19
|
+
error?: Snippet<[{ error: InkError }]> | undefined
|
|
20
|
+
annotation?: Snippet<[AnnotationSlotProps]> | undefined
|
|
21
|
+
/** Shown inside a page that failed to render. The rest of the document stays usable. */
|
|
22
|
+
pageError?: Snippet<[{ error: InkError; page: number }]> | undefined
|
|
23
|
+
}
|
|
24
|
+
</script>
|
|
25
|
+
|
|
26
|
+
<script lang="ts">
|
|
27
|
+
/*
|
|
28
|
+
* The scroll element is handed to core's `mountViewer`, which builds the viewport, the page
|
|
29
|
+
* layers, the tool controller and the keyboard shortcuts in it; Svelte never renders children
|
|
30
|
+
* into it. The engine reports each page host it mounts, and InkPage components rendered here
|
|
31
|
+
* (they render nothing visible in place) portal the page-error and annotation snippets into
|
|
32
|
+
* them.
|
|
33
|
+
*
|
|
34
|
+
* Two-way zoom and page: the viewport's own changes are reported through the context, and
|
|
35
|
+
* the host's props are applied only when they differ from the viewport's state, so the echo
|
|
36
|
+
* of a reported value does nothing.
|
|
37
|
+
*/
|
|
38
|
+
import { mountViewer } from '@inkbar/core/dom'
|
|
39
|
+
import { untrack } from 'svelte'
|
|
40
|
+
|
|
41
|
+
import { useInkContext } from '../context.js'
|
|
42
|
+
|
|
43
|
+
import InkPage from './InkPage.svelte'
|
|
44
|
+
|
|
45
|
+
import type { InkDocument, InkStatus } from '@inkbar/core'
|
|
46
|
+
import type { PageMount, ViewerEngine, ZoomValue } from '@inkbar/core/dom'
|
|
47
|
+
|
|
48
|
+
let {
|
|
49
|
+
mode = 'continuous',
|
|
50
|
+
rotation = 0,
|
|
51
|
+
gap = 16,
|
|
52
|
+
buffer = 2,
|
|
53
|
+
loading,
|
|
54
|
+
empty,
|
|
55
|
+
error,
|
|
56
|
+
annotation,
|
|
57
|
+
pageError,
|
|
58
|
+
class: className,
|
|
59
|
+
...rest
|
|
60
|
+
}: InkViewportProps = $props()
|
|
61
|
+
|
|
62
|
+
interface MountedPage {
|
|
63
|
+
/** The engine build the host belongs to; part of the InkPage key. */
|
|
64
|
+
build: number
|
|
65
|
+
page: number
|
|
66
|
+
host: HTMLElement
|
|
67
|
+
mount: PageMount
|
|
68
|
+
error: InkError | null
|
|
69
|
+
/** Whether the build that mounted the page renders the annotation snippet. */
|
|
70
|
+
custom: boolean
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const ctx = useInkContext('InkViewport')
|
|
74
|
+
let scroll = $state<HTMLDivElement | null>(null)
|
|
75
|
+
/** Page hosts, mounts and errors the engine reported; InkPage renders portals for them. */
|
|
76
|
+
let pages = $state.raw<MountedPage[]>([])
|
|
77
|
+
|
|
78
|
+
let engine: ViewerEngine | null = null
|
|
79
|
+
/**
|
|
80
|
+
* Numbered before each build starts, so the pages a build mounts carry its own number. A
|
|
81
|
+
* rebuild gives each page a new host and mount, so pages from an earlier build must remount,
|
|
82
|
+
* not be reused.
|
|
83
|
+
*/
|
|
84
|
+
let build = 0
|
|
85
|
+
/** The construction options the engine was last built with, so a rebuild runs only on a change. */
|
|
86
|
+
let builtWith: { gap: number; buffer: number; custom: boolean } | null = null
|
|
87
|
+
let stops: (() => void)[] = []
|
|
88
|
+
/** The zoom and page last reported to the host. */
|
|
89
|
+
let lastReportedZoom: ZoomValue | undefined
|
|
90
|
+
let lastReportedPage: number | undefined
|
|
91
|
+
|
|
92
|
+
// The engine's page callbacks fire synchronously inside the effects below (and on scroll).
|
|
93
|
+
// They read and write `pages`, so every call into the engine from an effect runs untracked:
|
|
94
|
+
// tracked, the effect would depend on the list it changes and run again.
|
|
95
|
+
function teardown(): void {
|
|
96
|
+
for (const stop of stops) stop()
|
|
97
|
+
stops = []
|
|
98
|
+
// Unmounts every page, which empties `pages` through `onPageUnmount`.
|
|
99
|
+
engine?.destroy()
|
|
100
|
+
engine = null
|
|
101
|
+
builtWith = null
|
|
102
|
+
ctx.setViewport(null, null)
|
|
103
|
+
if (pages.length > 0) pages = []
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function setup(
|
|
107
|
+
doc: InkDocument | null,
|
|
108
|
+
state: InkStatus['state'],
|
|
109
|
+
el: HTMLDivElement | null
|
|
110
|
+
): void {
|
|
111
|
+
lastReportedZoom = undefined
|
|
112
|
+
lastReportedPage = undefined
|
|
113
|
+
if (!doc || !el || state !== 'ready') return
|
|
114
|
+
let first = true
|
|
115
|
+
build += 1
|
|
116
|
+
const custom = !!annotation
|
|
117
|
+
builtWith = { gap, buffer, custom }
|
|
118
|
+
engine = mountViewer({
|
|
119
|
+
container: el,
|
|
120
|
+
document: doc,
|
|
121
|
+
zoom: ctx.zoomModel ?? 'fit-width',
|
|
122
|
+
...(ctx.pageModel === undefined ? {} : { page: ctx.pageModel }),
|
|
123
|
+
mode,
|
|
124
|
+
rotation,
|
|
125
|
+
gap,
|
|
126
|
+
buffer,
|
|
127
|
+
tool: ctx.tool,
|
|
128
|
+
customAnnotations: custom,
|
|
129
|
+
onActivate: (id) => ctx.setActivated(id),
|
|
130
|
+
onBuild: (vp, tc) => {
|
|
131
|
+
ctx.setViewport(vp, tc)
|
|
132
|
+
if (first) {
|
|
133
|
+
// A new document starts from where the viewport opens (its zoom, on the first page:
|
|
134
|
+
// `createViewport` always starts at page 0); only later changes are reported, the
|
|
135
|
+
// restored page among them, since the engine has already scrolled to it.
|
|
136
|
+
first = false
|
|
137
|
+
lastReportedZoom = vp.zoom.get()
|
|
138
|
+
lastReportedPage = 0
|
|
139
|
+
}
|
|
140
|
+
for (const stop of stops) stop()
|
|
141
|
+
const reportZoom = (z: ZoomValue): void => {
|
|
142
|
+
if (z === lastReportedZoom) return
|
|
143
|
+
lastReportedZoom = z
|
|
144
|
+
ctx.reportZoom(z)
|
|
145
|
+
}
|
|
146
|
+
const reportPage = (p: number): void => {
|
|
147
|
+
if (p === lastReportedPage) return
|
|
148
|
+
lastReportedPage = p
|
|
149
|
+
ctx.reportPage(p)
|
|
150
|
+
}
|
|
151
|
+
stops = [vp.zoom.subscribe(reportZoom), vp.currentPage.subscribe(reportPage)]
|
|
152
|
+
// Whatever the build restored is reported once; nothing is when it matched.
|
|
153
|
+
reportZoom(vp.zoom.get())
|
|
154
|
+
reportPage(vp.currentPage.get())
|
|
155
|
+
},
|
|
156
|
+
onPageMount: (page, mount, host) => {
|
|
157
|
+
// `build` and `builtWith` belong to the build in progress: both are set before it starts.
|
|
158
|
+
const own = {
|
|
159
|
+
build,
|
|
160
|
+
page,
|
|
161
|
+
host,
|
|
162
|
+
mount,
|
|
163
|
+
error: null,
|
|
164
|
+
custom: builtWith?.custom ?? false,
|
|
165
|
+
}
|
|
166
|
+
pages = [...pages, own]
|
|
167
|
+
},
|
|
168
|
+
onPageUnmount: (page) => {
|
|
169
|
+
pages = pages.filter((p) => p.page !== page)
|
|
170
|
+
},
|
|
171
|
+
onPageError: (page, err) => {
|
|
172
|
+
pages = pages.map((p) => (p.page === page ? { ...p, error: err } : p))
|
|
173
|
+
},
|
|
174
|
+
})
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// Build for a new document or status; tear down on the next change and on unmount. Only the
|
|
178
|
+
// values read before `untrack` are dependencies, each through a derived, which notifies only
|
|
179
|
+
// when the value changes: the status is a new object on every engine report.
|
|
180
|
+
const buildDocument = $derived(ctx.document)
|
|
181
|
+
const buildState = $derived(ctx.status.state)
|
|
182
|
+
$effect(() => {
|
|
183
|
+
const doc = buildDocument
|
|
184
|
+
const state = buildState
|
|
185
|
+
const el = scroll
|
|
186
|
+
untrack(() => setup(doc, state, el))
|
|
187
|
+
return () => untrack(teardown)
|
|
188
|
+
})
|
|
189
|
+
|
|
190
|
+
// Construction-only options rebuild the engine for the same document, keeping zoom and page.
|
|
191
|
+
// The deriveds notify only on a real change: a host that spreads its props invalidates `gap`
|
|
192
|
+
// and `buffer` without changing them. One effect for all three, so a gap and a snippet change
|
|
193
|
+
// in the same tick rebuild once. It skips its first run (and any run after a build that
|
|
194
|
+
// already used these values) by comparing with `builtWith`.
|
|
195
|
+
//
|
|
196
|
+
// A snippet toggle never puts a portal into an overlay built with the other setting. The
|
|
197
|
+
// template updates before this effect runs, so for a moment the old build's pages see the new
|
|
198
|
+
// `annotation` prop, but each InkPage reads targets only when the build that mounted it was
|
|
199
|
+
// custom (`p.custom`), and shows them only while the snippet is given. Then this effect swaps
|
|
200
|
+
// `pages` for the new build's pages. The rebuild keeps the scroll position.
|
|
201
|
+
const buildGap = $derived(gap)
|
|
202
|
+
const buildBuffer = $derived(buffer)
|
|
203
|
+
const buildCustom = $derived(!!annotation)
|
|
204
|
+
$effect(() => {
|
|
205
|
+
const next = { gap: buildGap, buffer: buildBuffer, custom: buildCustom }
|
|
206
|
+
untrack(() => {
|
|
207
|
+
const built = builtWith
|
|
208
|
+
if (!engine || !built) return
|
|
209
|
+
if (
|
|
210
|
+
built.gap === next.gap &&
|
|
211
|
+
built.buffer === next.buffer &&
|
|
212
|
+
built.custom === next.custom
|
|
213
|
+
)
|
|
214
|
+
return
|
|
215
|
+
build += 1
|
|
216
|
+
builtWith = next
|
|
217
|
+
engine.rebuild({
|
|
218
|
+
gap: next.gap,
|
|
219
|
+
buffer: next.buffer,
|
|
220
|
+
customAnnotations: next.custom,
|
|
221
|
+
})
|
|
222
|
+
})
|
|
223
|
+
})
|
|
224
|
+
|
|
225
|
+
// The setters below run untracked: core relays out inside them and the engine's page
|
|
226
|
+
// callbacks read and write `pages`. Tracked, each effect would depend on the list it changes
|
|
227
|
+
// and run again, without end when the host holds a zoom the viewport clamps.
|
|
228
|
+
$effect(() => {
|
|
229
|
+
const z = ctx.zoomModel
|
|
230
|
+
const vp = engine?.viewport
|
|
231
|
+
if (vp && z !== undefined && z !== vp.zoom.get()) untrack(() => vp.setZoom(z))
|
|
232
|
+
})
|
|
233
|
+
$effect(() => {
|
|
234
|
+
const p = ctx.pageModel
|
|
235
|
+
const vp = engine?.viewport
|
|
236
|
+
if (!vp || p === undefined || p === lastReportedPage) return
|
|
237
|
+
if (p !== vp.currentPage.get()) untrack(() => vp.scrollToPage(p))
|
|
238
|
+
})
|
|
239
|
+
$effect(() => {
|
|
240
|
+
const t = ctx.tool
|
|
241
|
+
const tc = engine?.tools
|
|
242
|
+
if (tc && tc.active.get() !== t) untrack(() => tc.set(t))
|
|
243
|
+
})
|
|
244
|
+
// The build applied both; these only act on a later change, not on mount.
|
|
245
|
+
$effect(() => {
|
|
246
|
+
const m = mode
|
|
247
|
+
const vp = engine?.viewport
|
|
248
|
+
if (vp && vp.mode.get() !== m) untrack(() => vp.setMode(m))
|
|
249
|
+
})
|
|
250
|
+
$effect(() => {
|
|
251
|
+
const r = rotation
|
|
252
|
+
const vp = engine?.viewport
|
|
253
|
+
if (vp && vp.rotation.get() !== r) untrack(() => vp.setRotation(r))
|
|
254
|
+
})
|
|
255
|
+
|
|
256
|
+
// Not `state`: next to the `$state` rune, Svelte reads that name as a store.
|
|
257
|
+
const viewState = $derived(buildState)
|
|
258
|
+
const err = $derived(ctx.status.state === 'error' ? ctx.status.error : null)
|
|
259
|
+
</script>
|
|
260
|
+
|
|
261
|
+
<div
|
|
262
|
+
{...rest}
|
|
263
|
+
class={['ink-viewport-root', className]}
|
|
264
|
+
data-ink-viewport-root=""
|
|
265
|
+
data-state={viewState}
|
|
266
|
+
>
|
|
267
|
+
{#if viewState === 'loading'}
|
|
268
|
+
<div class="ink-status" role="status" aria-live="polite">
|
|
269
|
+
{#if loading}{@render loading()}{:else}Loading document…{/if}
|
|
270
|
+
</div>
|
|
271
|
+
{:else if err}
|
|
272
|
+
<div class="ink-status ink-error" role="alert">
|
|
273
|
+
{#if error}
|
|
274
|
+
{@render error({ error: err })}
|
|
275
|
+
{:else}
|
|
276
|
+
<p class="ink-error-title">This document could not be opened.</p>
|
|
277
|
+
<!-- The message comes from the PDF engine and is not written for readers: it stays
|
|
278
|
+
off the page, on the code's tooltip. -->
|
|
279
|
+
<p><code class="ink-error-code" title={err.message}>{err.code}</code></p>
|
|
280
|
+
{/if}
|
|
281
|
+
</div>
|
|
282
|
+
{:else if viewState === 'idle'}
|
|
283
|
+
<div class="ink-status ink-empty">{@render empty?.()}</div>
|
|
284
|
+
{/if}
|
|
285
|
+
<!-- The scroll element takes focus so the keyboard can scroll, zoom and page through it. -->
|
|
286
|
+
<!-- svelte-ignore a11y_no_noninteractive_tabindex -->
|
|
287
|
+
<div
|
|
288
|
+
bind:this={scroll}
|
|
289
|
+
class="ink-scroll"
|
|
290
|
+
data-ink-scroll=""
|
|
291
|
+
tabindex="0"
|
|
292
|
+
role="document"
|
|
293
|
+
aria-label="Document"
|
|
294
|
+
></div>
|
|
295
|
+
{#each pages as p (`${p.build}:${p.page}`)}
|
|
296
|
+
<InkPage
|
|
297
|
+
page={p.page}
|
|
298
|
+
host={p.host}
|
|
299
|
+
mount={p.mount}
|
|
300
|
+
error={p.error}
|
|
301
|
+
customAnnotations={p.custom}
|
|
302
|
+
{annotation}
|
|
303
|
+
{pageError}
|
|
304
|
+
/>
|
|
305
|
+
{/each}
|
|
306
|
+
</div>
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { InkError } from '@inkbar/core';
|
|
2
|
+
import type { AnnotationSlotProps, LayoutMode, ViewRotation } from '@inkbar/core/dom';
|
|
3
|
+
import type { Snippet } from 'svelte';
|
|
4
|
+
import type { HTMLAttributes } from 'svelte/elements';
|
|
5
|
+
export interface InkViewportProps extends Omit<HTMLAttributes<HTMLDivElement>, 'children'> {
|
|
6
|
+
mode?: LayoutMode;
|
|
7
|
+
rotation?: ViewRotation;
|
|
8
|
+
/** Gap between pages, CSS px. */
|
|
9
|
+
gap?: number;
|
|
10
|
+
/** Pages kept mounted either side of the visible ones. */
|
|
11
|
+
buffer?: number;
|
|
12
|
+
loading?: Snippet | undefined;
|
|
13
|
+
empty?: Snippet | undefined;
|
|
14
|
+
error?: Snippet<[{
|
|
15
|
+
error: InkError;
|
|
16
|
+
}]> | undefined;
|
|
17
|
+
annotation?: Snippet<[AnnotationSlotProps]> | undefined;
|
|
18
|
+
/** Shown inside a page that failed to render. The rest of the document stays usable. */
|
|
19
|
+
pageError?: Snippet<[{
|
|
20
|
+
error: InkError;
|
|
21
|
+
page: number;
|
|
22
|
+
}]> | undefined;
|
|
23
|
+
}
|
|
24
|
+
declare const InkViewport: import("svelte").Component<InkViewportProps, {}, "">;
|
|
25
|
+
type InkViewport = ReturnType<typeof InkViewport>;
|
|
26
|
+
export default InkViewport;
|
|
27
|
+
//# sourceMappingURL=InkViewport.svelte.d.ts.map
|
|
@@ -0,0 +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;AAuRH,QAAA,MAAM,WAAW,sDAAwC,CAAC;AAC1D,KAAK,WAAW,GAAG,UAAU,CAAC,OAAO,WAAW,CAAC,CAAC;AAClD,eAAe,WAAW,CAAC"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { Toolbar } from 'bits-ui'
|
|
3
|
+
|
|
4
|
+
import { useZoom } from '../hooks/useZoom.svelte.js'
|
|
5
|
+
|
|
6
|
+
import { asBitsProps } from './child-snippet.js'
|
|
7
|
+
|
|
8
|
+
import type { InkZoomButtonProps } from './zoom-button.js'
|
|
9
|
+
|
|
10
|
+
let {
|
|
11
|
+
class: className,
|
|
12
|
+
children,
|
|
13
|
+
child,
|
|
14
|
+
onclick,
|
|
15
|
+
...rest
|
|
16
|
+
}: InkZoomButtonProps = $props()
|
|
17
|
+
|
|
18
|
+
const zoom = useZoom()
|
|
19
|
+
</script>
|
|
20
|
+
|
|
21
|
+
<Toolbar.Button
|
|
22
|
+
class={['ink-zoom-in', className]}
|
|
23
|
+
data-ink-zoom-in=""
|
|
24
|
+
aria-label="Zoom in"
|
|
25
|
+
title="Zoom in"
|
|
26
|
+
{...child ? { child } : {}}
|
|
27
|
+
{...asBitsProps(rest)}
|
|
28
|
+
onclick={(e) => {
|
|
29
|
+
onclick?.(e)
|
|
30
|
+
if (!e.defaultPrevented) zoom.zoomIn()
|
|
31
|
+
}}
|
|
32
|
+
>
|
|
33
|
+
{#if children}{@render children()}{:else}+{/if}
|
|
34
|
+
</Toolbar.Button>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"InkZoomIn.svelte.d.ts","sourceRoot":"","sources":["../../src/lib/components/InkZoomIn.svelte.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AA+B3D,QAAA,MAAM,SAAS,wDAAwC,CAAC;AACxD,KAAK,SAAS,GAAG,UAAU,CAAC,OAAO,SAAS,CAAC,CAAC;AAC9C,eAAe,SAAS,CAAC"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { Toolbar } from 'bits-ui'
|
|
3
|
+
|
|
4
|
+
import { useZoom } from '../hooks/useZoom.svelte.js'
|
|
5
|
+
|
|
6
|
+
import { asBitsProps } from './child-snippet.js'
|
|
7
|
+
|
|
8
|
+
import type { InkZoomButtonProps } from './zoom-button.js'
|
|
9
|
+
|
|
10
|
+
let {
|
|
11
|
+
class: className,
|
|
12
|
+
children,
|
|
13
|
+
child,
|
|
14
|
+
onclick,
|
|
15
|
+
...rest
|
|
16
|
+
}: InkZoomButtonProps = $props()
|
|
17
|
+
|
|
18
|
+
const zoom = useZoom()
|
|
19
|
+
</script>
|
|
20
|
+
|
|
21
|
+
<Toolbar.Button
|
|
22
|
+
class={['ink-zoom-out', className]}
|
|
23
|
+
data-ink-zoom-out=""
|
|
24
|
+
aria-label="Zoom out"
|
|
25
|
+
title="Zoom out"
|
|
26
|
+
{...child ? { child } : {}}
|
|
27
|
+
{...asBitsProps(rest)}
|
|
28
|
+
onclick={(e) => {
|
|
29
|
+
onclick?.(e)
|
|
30
|
+
if (!e.defaultPrevented) zoom.zoomOut()
|
|
31
|
+
}}
|
|
32
|
+
>
|
|
33
|
+
{#if children}{@render children()}{:else}−{/if}
|
|
34
|
+
</Toolbar.Button>
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { InkZoomButtonProps } from './zoom-button.js';
|
|
2
|
+
declare const InkZoomOut: import("svelte").Component<InkZoomButtonProps, {}, "">;
|
|
3
|
+
type InkZoomOut = ReturnType<typeof InkZoomOut>;
|
|
4
|
+
export default InkZoomOut;
|
|
5
|
+
//# sourceMappingURL=InkZoomOut.svelte.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"InkZoomOut.svelte.d.ts","sourceRoot":"","sources":["../../src/lib/components/InkZoomOut.svelte.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AA+B3D,QAAA,MAAM,UAAU,wDAAwC,CAAC;AACzD,KAAK,UAAU,GAAG,UAAU,CAAC,OAAO,UAAU,CAAC,CAAC;AAChD,eAAe,UAAU,CAAC"}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
<script module lang="ts">
|
|
2
|
+
import type { HTMLSelectAttributes } from 'svelte/elements'
|
|
3
|
+
|
|
4
|
+
export type InkZoomSelectProps = Omit<
|
|
5
|
+
HTMLSelectAttributes,
|
|
6
|
+
'value' | 'onchange' | 'children'
|
|
7
|
+
>
|
|
8
|
+
</script>
|
|
9
|
+
|
|
10
|
+
<script lang="ts">
|
|
11
|
+
import { ZOOM_LEVELS, type ZoomValue } from '@inkbar/core/dom'
|
|
12
|
+
|
|
13
|
+
import { useZoom } from '../hooks/useZoom.svelte.js'
|
|
14
|
+
|
|
15
|
+
let { class: className, ...rest }: InkZoomSelectProps = $props()
|
|
16
|
+
|
|
17
|
+
const CUSTOM = 'custom:'
|
|
18
|
+
const level = (z: number) => ZOOM_LEVELS.find((l) => Math.abs(l - z) < 0.001)
|
|
19
|
+
|
|
20
|
+
const zoom = useZoom()
|
|
21
|
+
/** A numeric zoom that is not one of the levels (zoom in and out multiply). */
|
|
22
|
+
const custom = $derived(
|
|
23
|
+
typeof zoom.zoom === 'number' && level(zoom.zoom) === undefined ? zoom.zoom : null
|
|
24
|
+
)
|
|
25
|
+
const value = $derived(
|
|
26
|
+
typeof zoom.zoom !== 'number'
|
|
27
|
+
? zoom.zoom
|
|
28
|
+
: custom === null
|
|
29
|
+
? String(level(zoom.zoom))
|
|
30
|
+
: `${CUSTOM}${zoom.zoom}`
|
|
31
|
+
)
|
|
32
|
+
function onchange(e: Event & { currentTarget: HTMLSelectElement }): void {
|
|
33
|
+
const v = e.currentTarget.value
|
|
34
|
+
const next: ZoomValue =
|
|
35
|
+
v === 'fit-width' || v === 'fit-page' ? v : Number(v.replace(CUSTOM, ''))
|
|
36
|
+
zoom.setZoom(next)
|
|
37
|
+
}
|
|
38
|
+
</script>
|
|
39
|
+
|
|
40
|
+
<select
|
|
41
|
+
class={['ink-zoom-select', className]}
|
|
42
|
+
data-ink-zoom-select=""
|
|
43
|
+
aria-label="Zoom"
|
|
44
|
+
{value}
|
|
45
|
+
{onchange}
|
|
46
|
+
{...rest}
|
|
47
|
+
>
|
|
48
|
+
<option value="fit-width">Fit width</option>
|
|
49
|
+
<option value="fit-page">Fit page</option>
|
|
50
|
+
{#each ZOOM_LEVELS as l (l)}
|
|
51
|
+
<option value={String(l)}>{Math.round(l * 100)}%</option>
|
|
52
|
+
{/each}
|
|
53
|
+
{#if custom !== null}
|
|
54
|
+
<option value={`${CUSTOM}${custom}`}>{Math.round(zoom.scale * 100)}%</option>
|
|
55
|
+
{/if}
|
|
56
|
+
</select>
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { HTMLSelectAttributes } from 'svelte/elements';
|
|
2
|
+
export type InkZoomSelectProps = Omit<HTMLSelectAttributes, 'value' | 'onchange' | 'children'>;
|
|
3
|
+
declare const InkZoomSelect: import("svelte").Component<InkZoomSelectProps, {}, "">;
|
|
4
|
+
type InkZoomSelect = ReturnType<typeof InkZoomSelect>;
|
|
5
|
+
export default InkZoomSelect;
|
|
6
|
+
//# sourceMappingURL=InkZoomSelect.svelte.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"InkZoomSelect.svelte.d.ts","sourceRoot":"","sources":["../../src/lib/components/InkZoomSelect.svelte.ts"],"names":[],"mappings":"AAGE,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,iBAAiB,CAAA;AAE3D,MAAM,MAAM,kBAAkB,GAAG,IAAI,CACnC,oBAAoB,EACpB,OAAO,GAAG,UAAU,GAAG,UAAU,CAClC,CACF;AAmDD,QAAA,MAAM,aAAa,wDAAwC,CAAC;AAC5D,KAAK,aAAa,GAAG,UAAU,CAAC,OAAO,aAAa,CAAC,CAAC;AACtD,eAAe,aAAa,CAAC"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
/** The default form: the root note's own text while it is empty, replies after that. */
|
|
3
|
+
let {
|
|
4
|
+
editingRoot,
|
|
5
|
+
locked,
|
|
6
|
+
onSubmit,
|
|
7
|
+
onRemove,
|
|
8
|
+
onClose,
|
|
9
|
+
}: {
|
|
10
|
+
editingRoot: boolean
|
|
11
|
+
locked: boolean
|
|
12
|
+
onSubmit: (text: string) => void
|
|
13
|
+
onRemove: () => void
|
|
14
|
+
onClose: () => void
|
|
15
|
+
} = $props()
|
|
16
|
+
|
|
17
|
+
let draft = $state('')
|
|
18
|
+
function submit(e: SubmitEvent): void {
|
|
19
|
+
e.preventDefault()
|
|
20
|
+
const text = draft.trim()
|
|
21
|
+
if (!text) return
|
|
22
|
+
onSubmit(text)
|
|
23
|
+
draft = ''
|
|
24
|
+
}
|
|
25
|
+
</script>
|
|
26
|
+
|
|
27
|
+
<form class="ink-thread-reply" data-ink-thread-reply="" onsubmit={submit}>
|
|
28
|
+
<textarea
|
|
29
|
+
bind:value={draft}
|
|
30
|
+
aria-label={editingRoot ? 'Comment' : 'Reply'}
|
|
31
|
+
rows="2"
|
|
32
|
+
placeholder={editingRoot ? 'Comment' : 'Reply'}></textarea>
|
|
33
|
+
<div class="ink-thread-actions">
|
|
34
|
+
<button type="submit" disabled={!draft.trim()}
|
|
35
|
+
>{editingRoot ? 'Comment' : 'Reply'}</button
|
|
36
|
+
>
|
|
37
|
+
{#if !locked}
|
|
38
|
+
<button type="button" onclick={onRemove}>Delete</button>
|
|
39
|
+
{/if}
|
|
40
|
+
<button type="button" onclick={onClose}>Close</button>
|
|
41
|
+
</div>
|
|
42
|
+
</form>
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
type $$ComponentProps = {
|
|
2
|
+
editingRoot: boolean;
|
|
3
|
+
locked: boolean;
|
|
4
|
+
onSubmit: (text: string) => void;
|
|
5
|
+
onRemove: () => void;
|
|
6
|
+
onClose: () => void;
|
|
7
|
+
};
|
|
8
|
+
declare const ThreadForm: import("svelte").Component<$$ComponentProps, {}, "">;
|
|
9
|
+
type ThreadForm = ReturnType<typeof ThreadForm>;
|
|
10
|
+
export default ThreadForm;
|
|
11
|
+
//# sourceMappingURL=ThreadForm.svelte.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ThreadForm.svelte.d.ts","sourceRoot":"","sources":["../../src/lib/components/ThreadForm.svelte.ts"],"names":[],"mappings":"AAGC,KAAK,gBAAgB,GAAI;IACtB,WAAW,EAAE,OAAO,CAAA;IACpB,MAAM,EAAE,OAAO,CAAA;IACf,QAAQ,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAA;IAChC,QAAQ,EAAE,MAAM,IAAI,CAAA;IACpB,OAAO,EAAE,MAAM,IAAI,CAAA;CACpB,CAAC;AAkCJ,QAAA,MAAM,UAAU,sDAAwC,CAAC;AACzD,KAAK,UAAU,GAAG,UAAU,CAAC,OAAO,UAAU,CAAC,CAAC;AAChD,eAAe,UAAU,CAAC"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The overlay element core draws for an annotation, for anchoring a popover to it. Core
|
|
3
|
+
* creates and removes these elements outside Svelte, so this follows the root's subtree while
|
|
4
|
+
* an id is wanted.
|
|
5
|
+
*/
|
|
6
|
+
export declare function useAnnotationElement(root: () => HTMLElement | null, id: () => string | null): {
|
|
7
|
+
readonly current: HTMLElement | null;
|
|
8
|
+
};
|
|
9
|
+
//# sourceMappingURL=annotation-element.svelte.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"annotation-element.svelte.d.ts","sourceRoot":"","sources":["../../src/lib/components/annotation-element.svelte.ts"],"names":[],"mappings":"AAEA;;;;GAIG;AACH,wBAAgB,oBAAoB,CAClC,IAAI,EAAE,MAAM,WAAW,GAAG,IAAI,EAC9B,EAAE,EAAE,MAAM,MAAM,GAAG,IAAI,GACtB;IAAE,QAAQ,CAAC,OAAO,EAAE,WAAW,GAAG,IAAI,CAAA;CAAE,CAkB1C"}
|