@rkosafo/cai.components 0.0.39 → 0.0.41

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.
@@ -0,0 +1,205 @@
1
+ <script lang="ts">
2
+ import { fade, fly } from 'svelte/transition';
3
+ import type {
4
+ ChatInputMessage,
5
+ ChatInputProps,
6
+ IMentionableItem,
7
+ PreviewPageSrc
8
+ } from './types.js';
9
+ import { IconifyIcon } from '../../ui/icons/index.js';
10
+ import RichText from './RichText.svelte';
11
+ import PreviewPage from './PreviewPage.svelte';
12
+ import { clickOutsideAction } from '../../index.js';
13
+
14
+ let {
15
+ hideLoad = true,
16
+ clearMessage = false,
17
+ slashCommands = [],
18
+ mentionableItems = [],
19
+ onMessage,
20
+ previewPageTitle = 'Preview Attachment',
21
+ onSlash
22
+ }: ChatInputProps = $props();
23
+
24
+ let message = $state<ChatInputMessage>({
25
+ content: '',
26
+ contentType: 'text',
27
+ files: [],
28
+ mentions: []
29
+ });
30
+
31
+ let mentions = $state<IMentionableItem[]>([]);
32
+
33
+ let imageInput = $state<PreviewPageSrc>({
34
+ name: '',
35
+ content: '',
36
+ file: ''
37
+ });
38
+ let filesToSend = $state<any[]>([]);
39
+ let showImage = $state(false);
40
+ let open = $state(false);
41
+ let showFiles = $state(false);
42
+ let openDropDown = $state(false);
43
+
44
+ function loadFile(e: any) {
45
+ let image = e.target.files[0];
46
+ let reader = new FileReader();
47
+ reader.readAsDataURL(image);
48
+
49
+ reader.onload = (e: any) => {
50
+ const x = { name: image.name, content: e.target.result, file: image };
51
+ imageInput = x;
52
+ };
53
+ showImage = true;
54
+ open = false;
55
+ }
56
+
57
+ function onEmoji(event: any) {
58
+ message.content += event.detail;
59
+ }
60
+
61
+ function sendImage({ detail }: any) {
62
+ const { file } = detail;
63
+ filesToSend = [file, ...filesToSend];
64
+ imageInput = {
65
+ name: '',
66
+ content: '',
67
+ file: ''
68
+ };
69
+ showImage = false;
70
+ }
71
+
72
+ function sendMessage() {
73
+ if (message.content.length > 0) {
74
+ message = { ...message, files: filesToSend, mentions };
75
+ onMessage?.(message);
76
+ message = { content: '', contentType: 'text', files: [], mentions: [] };
77
+ filesToSend = [];
78
+ return;
79
+ }
80
+ }
81
+
82
+ function handleMention(event: any) {
83
+ const { detail } = event;
84
+ if (mentions.some((mention) => mention.id === detail.id)) {
85
+ mentions = mentions.filter((mention) => mention.id !== detail.id);
86
+ } else {
87
+ mentions = [...mentions, detail];
88
+ }
89
+
90
+ // console.log({ mentions, message: message.content });
91
+ }
92
+ </script>
93
+
94
+ <div class="flex flex-col gap-2">
95
+ {#if showFiles}
96
+ {#each filesToSend as file, index}
97
+ <ul class="flex w-full flex-col divide-y py-2" transition:fade>
98
+ <li class="w-full">
99
+ <div class="flex items-center justify-between">
100
+ <p class="w-96 truncate">{index + 1}. {file?.name}</p>
101
+ <button
102
+ class="grid place-content-center"
103
+ onclick={() => {
104
+ filesToSend = filesToSend.filter((_, i) => i !== index);
105
+ }}
106
+ >
107
+ <IconifyIcon
108
+ icon="ic:baseline-delete"
109
+ class="text-red-500"
110
+ style="font-size: 20px;"
111
+ />
112
+ </button>
113
+ </div>
114
+ </li>
115
+ </ul>
116
+ {/each}
117
+ {/if}
118
+ <RichText
119
+ bind:value={message.content}
120
+ loading={!hideLoad}
121
+ {slashCommands}
122
+ {mentionableItems}
123
+ onMention={handleMention}
124
+ {onSlash}
125
+ />
126
+
127
+ <div class="mx-4 flex items-center justify-between">
128
+ <div
129
+ class="flex items-center gap-3"
130
+ use:clickOutsideAction
131
+ onclickoutside={() => (openDropDown = false)}
132
+ >
133
+ <!-- <button class=" relative text-slate-600 hover:text-indigo-600 pb-2">
134
+ <EmojiSelector on:emoji={onEmoji} />
135
+ </button> -->
136
+ <button onclick={() => (open = !open)} class="text-slate-600 hover:text-indigo-600"
137
+ ><IconifyIcon icon="mdi:clippy" style="font-size: 20px;" /></button
138
+ >
139
+ </div>
140
+ {#if filesToSend.length}
141
+ <div class="flex-grow pl-3" in:fade>
142
+ <button class="text-purple-700" onclick={() => (showFiles = !showFiles)}>
143
+ {filesToSend.length}
144
+ Attachments
145
+ </button>
146
+ </div>
147
+ {/if}
148
+ <button
149
+ aria-label="send message"
150
+ onclick={sendMessage}
151
+ class="text-slate-600 hover:text-indigo-600"
152
+ >
153
+ <IconifyIcon icon="majesticons:send" style="font-size: 20px;" />
154
+ </button>
155
+ </div>
156
+ </div>
157
+
158
+ {#if open}
159
+ <div class="relative">
160
+ <ul
161
+ use:clickOutsideAction
162
+ onclickoutside={() => (open = false)}
163
+ transition:fly={{ y: 50 }}
164
+ class="absolute bottom-6 left-8"
165
+ >
166
+ <li
167
+ class="h-10 w-10 cursor-pointer rounded-full bg-gradient-to-b from-blue-500 to-blue-300 p-2 text-white"
168
+ >
169
+ <label for="doc"
170
+ ><IconifyIcon
171
+ icon="bxs:file"
172
+ class="grid cursor-pointer place-content-center"
173
+ style="font-size: 24px;"
174
+ />
175
+ </label>
176
+ <input type="file" id="doc" class="hidden" accept="application/pdf" onchange={loadFile} />
177
+ </li>
178
+ <li
179
+ class="my-2 h-10 w-10 cursor-pointer rounded-full bg-gradient-to-b from-purple-500 to-pink-500 p-2 text-white"
180
+ >
181
+ <label for="photo"
182
+ ><IconifyIcon
183
+ icon="icon-park-solid:pic"
184
+ class="grid cursor-pointer place-content-center"
185
+ style="font-size: 24px;"
186
+ />
187
+ </label>
188
+ <input
189
+ type="file"
190
+ id="photo"
191
+ class="hidden"
192
+ accept="image/png, image/jpg"
193
+ onchange={loadFile}
194
+ />
195
+ </li>
196
+ </ul>
197
+ </div>
198
+ {/if}
199
+
200
+ <PreviewPage
201
+ title={previewPageTitle}
202
+ src={imageInput}
203
+ bind:open={showImage}
204
+ onGetImage={sendImage}
205
+ />
@@ -0,0 +1,4 @@
1
+ import type { ChatInputProps } from './types.js';
2
+ declare const ChatInput: import("svelte").Component<ChatInputProps, {}, "">;
3
+ type ChatInput = ReturnType<typeof ChatInput>;
4
+ export default ChatInput;
@@ -0,0 +1,230 @@
1
+ <script lang="ts">
2
+ import { fade } from 'svelte/transition';
3
+ import type { DraggableWindowProps } from './types.js';
4
+
5
+ let {
6
+ title,
7
+ isOpen = $bindable(true),
8
+ isCollapsed = $bindable(false),
9
+ width = '500px',
10
+ height = '400px',
11
+ x = $bindable(100),
12
+ y = $bindable(100),
13
+ minWidth = '300px',
14
+ minHeight = '200px',
15
+ zIndex = 3000,
16
+ onBringToFront
17
+ }: DraggableWindowProps = $props();
18
+
19
+ let startX = $state<number | undefined>();
20
+ let startY = $state<number | undefined>();
21
+ let isDragging = $state(false);
22
+ let isResizing = $state(false);
23
+ let resizeDirection: 'n' | 'ne' | 'e' | 'se' | 's' | 'sw' | 'w' | 'nw' | null = null;
24
+ let startWidth = $state<number | undefined>();
25
+ let startHeight = $state<number | undefined>();
26
+
27
+ function startDrag(e: MouseEvent) {
28
+ if (isResizing) return;
29
+ isDragging = true;
30
+ startX = e.clientX - x;
31
+ startY = e.clientY - y;
32
+ window.addEventListener('mousemove', handleDrag);
33
+ window.addEventListener('mouseup', stopDrag);
34
+ // bringToFront();
35
+ }
36
+
37
+ function handleDrag(e: MouseEvent) {
38
+ if (isDragging) {
39
+ x = e.clientX - startX!;
40
+ y = e.clientY - startY!;
41
+ } else if (isResizing) {
42
+ const deltaX = e.clientX - startX!;
43
+ const deltaY = e.clientY - startY!;
44
+
45
+ if (resizeDirection?.includes('e')) {
46
+ width = `${Math.max(parseInt(minWidth), startWidth! + deltaX)}px`;
47
+ }
48
+ if (resizeDirection?.includes('w')) {
49
+ const newWidth = Math.max(parseInt(minWidth), startWidth! - deltaX);
50
+ width = `${newWidth}px`;
51
+ x += startWidth! - newWidth;
52
+ }
53
+ if (resizeDirection?.includes('s')) {
54
+ height = `${Math.max(parseInt(minHeight), startHeight! + deltaY)}px`;
55
+ }
56
+ if (resizeDirection?.includes('n')) {
57
+ const newHeight = Math.max(parseInt(minHeight), startHeight! - deltaY);
58
+ height = `${newHeight}px`;
59
+ y += startHeight! - newHeight;
60
+ }
61
+ }
62
+ }
63
+
64
+ function stopDrag() {
65
+ isDragging = false;
66
+ isResizing = false;
67
+ resizeDirection = null;
68
+ window.removeEventListener('mousemove', handleDrag);
69
+ window.removeEventListener('mouseup', stopDrag);
70
+ }
71
+
72
+ function startResize(e: MouseEvent, direction: typeof resizeDirection) {
73
+ e.stopPropagation();
74
+ isResizing = true;
75
+ resizeDirection = direction;
76
+ startX = e.clientX;
77
+ startY = e.clientY;
78
+ startWidth = parseInt(width);
79
+ startHeight = parseInt(height);
80
+ window.addEventListener('mousemove', handleDrag);
81
+ window.addEventListener('mouseup', stopDrag);
82
+ }
83
+
84
+ function toggleCollapse(event: any) {
85
+ event.stopPropagation();
86
+ isCollapsed = !isCollapsed;
87
+ }
88
+
89
+ function bringToFront() {
90
+ onBringToFront?.();
91
+ }
92
+
93
+ function closeWindow(event: any) {
94
+ event.stopPropagation();
95
+ isOpen = false;
96
+ }
97
+ function handleWindowClick() {
98
+ if (!isDragging && !isResizing) {
99
+ bringToFront();
100
+ }
101
+ }
102
+ </script>
103
+
104
+ {#if isOpen}
105
+ <div
106
+ class="absolute flex flex-col overflow-hidden rounded-lg border border-gray-200 bg-white shadow-xl"
107
+ style={`width: ${width}; height: ${isCollapsed ? '40px' : height}; left: ${x}px; top: ${y}px; min-width: ${minWidth}; min-height: ${isCollapsed ? '40px' : minHeight}; z-index: ${zIndex};`}
108
+ transition:fade={{ duration: 150 }}
109
+ onclick={handleWindowClick}
110
+ >
111
+ <div
112
+ class="flex cursor-move items-center justify-between border-b border-gray-200 bg-gray-100 px-3 py-2"
113
+ onmousedown={startDrag}
114
+ >
115
+ <div class="font-medium text-gray-700">{title}</div>
116
+ <div class="flex space-x-2">
117
+ <button
118
+ class="flex h-6 w-6 items-center justify-center text-gray-500 hover:text-gray-700"
119
+ onclick={toggleCollapse}
120
+ >
121
+ {#if isCollapsed}
122
+ <svg
123
+ xmlns="http://www.w3.org/2000/svg"
124
+ width="16"
125
+ height="16"
126
+ viewBox="0 0 24 24"
127
+ fill="none"
128
+ stroke="currentColor"
129
+ stroke-width="2"
130
+ stroke-linecap="round"
131
+ stroke-linejoin="round"
132
+ >
133
+ <polyline points="4 14 10 14 10 20"></polyline>
134
+ <polyline points="20 10 14 10 14 4"></polyline>
135
+ </svg>
136
+ {:else}
137
+ <svg
138
+ xmlns="http://www.w3.org/2000/svg"
139
+ width="16"
140
+ height="16"
141
+ viewBox="0 0 24 24"
142
+ fill="none"
143
+ stroke="currentColor"
144
+ stroke-width="2"
145
+ stroke-linecap="round"
146
+ stroke-linejoin="round"
147
+ >
148
+ <polyline points="4 14 10 14 10 20"></polyline>
149
+ <polyline points="20 10 14 10 14 4"></polyline>
150
+ </svg>
151
+ {/if}
152
+ </button>
153
+ <button
154
+ class="flex h-6 w-6 items-center justify-center text-gray-500 hover:text-gray-700"
155
+ onclick={closeWindow}
156
+ >
157
+ <svg
158
+ xmlns="http://www.w3.org/2000/svg"
159
+ width="16"
160
+ height="16"
161
+ viewBox="0 0 24 24"
162
+ fill="none"
163
+ stroke="currentColor"
164
+ stroke-width="2"
165
+ stroke-linecap="round"
166
+ stroke-linejoin="round"
167
+ >
168
+ <line x1="18" y1="6" x2="6" y2="18"></line>
169
+ <line x1="6" y1="6" x2="18" y2="18"></line>
170
+ </svg>
171
+ </button>
172
+ </div>
173
+ </div>
174
+
175
+ <div class="flex-grow overflow-auto p-4" class:hidden={isCollapsed}>
176
+ <slot />
177
+ </div>
178
+
179
+ <!-- Resize handles -->
180
+ <div
181
+ class="absolute top-0 left-0 h-2 w-2 cursor-nw-resize"
182
+ onmousedown={(e) => startResize(e, 'nw')}
183
+ ></div>
184
+ <div
185
+ class="absolute top-0 left-0 h-2 w-full cursor-n-resize"
186
+ onmousedown={(e) => startResize(e, 'n')}
187
+ ></div>
188
+ <div
189
+ class="absolute top-0 right-0 h-2 w-2 cursor-ne-resize"
190
+ onmousedown={(e) => startResize(e, 'ne')}
191
+ ></div>
192
+ <div
193
+ class="absolute top-0 right-0 h-full w-2 cursor-e-resize"
194
+ onmousedown={(e) => startResize(e, 'e')}
195
+ ></div>
196
+ <div
197
+ class="absolute right-0 bottom-0 h-2 w-2 cursor-se-resize"
198
+ onmousedown={(e) => startResize(e, 'se')}
199
+ ></div>
200
+ <div
201
+ class="absolute bottom-0 left-0 h-2 w-full cursor-s-resize"
202
+ onmousedown={(e) => startResize(e, 's')}
203
+ ></div>
204
+ <div
205
+ class="absolute bottom-0 left-0 h-2 w-2 cursor-sw-resize"
206
+ onmousedown={(e) => startResize(e, 'sw')}
207
+ ></div>
208
+ <div
209
+ class="absolute top-0 left-0 h-full w-2 cursor-w-resize"
210
+ onmousedown={(e) => startResize(e, 'w')}
211
+ ></div>
212
+ </div>
213
+ {/if}
214
+
215
+ <style>
216
+ .cursor-nw-resize,
217
+ .cursor-ne-resize,
218
+ .cursor-se-resize,
219
+ .cursor-sw-resize {
220
+ cursor: nwse-resize;
221
+ }
222
+ .cursor-n-resize,
223
+ .cursor-s-resize {
224
+ cursor: ns-resize;
225
+ }
226
+ .cursor-e-resize,
227
+ .cursor-w-resize {
228
+ cursor: ew-resize;
229
+ }
230
+ </style>
@@ -0,0 +1,28 @@
1
+ import type { DraggableWindowProps } from './types.js';
2
+ interface $$__sveltets_2_IsomorphicComponent<Props extends Record<string, any> = any, Events extends Record<string, any> = any, Slots extends Record<string, any> = any, Exports = {}, Bindings = string> {
3
+ new (options: import('svelte').ComponentConstructorOptions<Props>): import('svelte').SvelteComponent<Props, Events, Slots> & {
4
+ $$bindings?: Bindings;
5
+ } & Exports;
6
+ (internal: unknown, props: Props & {
7
+ $$events?: Events;
8
+ $$slots?: Slots;
9
+ }): Exports & {
10
+ $set?: any;
11
+ $on?: any;
12
+ };
13
+ z_$$bindings?: Bindings;
14
+ }
15
+ type $$__sveltets_2_PropsWithChildren<Props, Slots> = Props & (Slots extends {
16
+ default: any;
17
+ } ? Props extends Record<string, never> ? any : {
18
+ children?: any;
19
+ } : {});
20
+ declare const DraggableWindow: $$__sveltets_2_IsomorphicComponent<$$__sveltets_2_PropsWithChildren<DraggableWindowProps, {
21
+ default: {};
22
+ }>, {
23
+ [evt: string]: CustomEvent<any>;
24
+ }, {
25
+ default: {};
26
+ }, {}, "isOpen" | "x" | "y" | "isCollapsed">;
27
+ type DraggableWindow = InstanceType<typeof DraggableWindow>;
28
+ export default DraggableWindow;
@@ -0,0 +1,182 @@
1
+ <script lang="ts">
2
+ import { fade } from 'svelte/transition';
3
+ import type { PreviewPageProps } from './index.js';
4
+ import { cubicIn, elasticInOut } from 'svelte/easing';
5
+ import IconifyIcon from '../../ui/icons/IconifyIcon.svelte';
6
+ import { isImage, isPdf } from '../../index.js';
7
+
8
+ let {
9
+ open = $bindable(true),
10
+ fullImage = false,
11
+ showActions = true,
12
+ title = '',
13
+ onGetImage,
14
+ src = {
15
+ name: '',
16
+ content: '',
17
+ file: ''
18
+ }
19
+ }: PreviewPageProps = $props();
20
+
21
+ let scale = $state(1);
22
+ const maxScale = $state(3);
23
+ const minScale = $state(1);
24
+
25
+ function zoomin() {
26
+ if (scale < maxScale) {
27
+ scale += 0.1;
28
+ }
29
+ }
30
+
31
+ function zoomout() {
32
+ if (scale > minScale) {
33
+ scale -= 0.1;
34
+ }
35
+ }
36
+ </script>
37
+
38
+ {#if open}
39
+ <div class=" relative p-0 md:p-5 lg:p-10">
40
+ <div
41
+ class="bg-opacity-60 fixed inset-0 z-[3000] mx-auto bg-gray-500 transition-opacity"
42
+ in:fade={{ duration: 300, easing: cubicIn }}
43
+ out:fade={{ duration: 200, easing: elasticInOut }}
44
+ ></div>
45
+ </div>
46
+ {/if}
47
+
48
+ {#if open}
49
+ {#if fullImage}
50
+ <div
51
+ class="bg-opacity-75 fixed inset-0 z-[3000] mx-auto h-12 overflow-y-auto bg-gray-900 shadow-sm shadow-gray-50"
52
+ >
53
+ <div class="mx-4 flex h-full items-center justify-between p-2">
54
+ <span class="font-semiblod tracking-wider text-white">{title}</span>
55
+ <div class="flex items-center gap-4 text-white">
56
+ <!-- <button on:click={zoomin} type="button"
57
+ ><iconify-icon icon="ri:zoom-in-fill" class="hover:fill-red-600" /></button
58
+ >
59
+ <button on:click={zoomout} type="button"
60
+ ><iconify-icon icon="mingcute:zoom-out-fill" class="hover:fill-green-600" /></button
61
+ >
62
+ <button class="grid place-content-center p-[1px]" type="button"
63
+ ><iconify-icon
64
+ icon="line-md:cloud-download-loop"
65
+ class="hover:text-purple-600"
66
+ style="font-size: 27px;"
67
+ /></button
68
+ > -->
69
+ <button
70
+ type="button"
71
+ onclick={() => (open = false)}
72
+ class="grid place-content-center rounded-full bg-red-500 p-[1px] hover:bg-red-600"
73
+ >
74
+ <IconifyIcon icon="ic:round-close" style="font-size: 25px;" />
75
+ </button>
76
+ </div>
77
+ </div>
78
+ </div>
79
+ {/if}
80
+
81
+ <div
82
+ aria-live="assertive"
83
+ class="pointer-events-none fixed inset-0 z-[3100] flex place-content-center items-center sm:items-start"
84
+ >
85
+ <div
86
+ class="pointer-events-none flex h-full max-w-7xl flex-grow flex-col"
87
+ in:fade={{ duration: 300, easing: cubicIn }}
88
+ out:fade={{ duration: 200, easing: elasticInOut }}
89
+ >
90
+ <div class="h-16" style="line-height: 4rem">&nbsp;</div>
91
+ <div class=" bg-opacity-75 pointer-events-auto flex-grow border-gray-200 transition-opacity">
92
+ <div class=" grid h-full w-full">
93
+ <div class="relative transform overflow-hidden transition-all sm:my-6">
94
+ {#if ('content' in src && src?.content
95
+ .substr(5, 10)
96
+ .includes('image/')) || isImage(src?.content)}
97
+ <div class="flex h-full w-full flex-col items-center justify-center gap-1">
98
+ <div class="zoomed-image" style="transform: scale({scale});">
99
+ <img
100
+ id="map"
101
+ src={src?.content}
102
+ alt=""
103
+ class="object-cover"
104
+ class:w-96={!fullImage}
105
+ />
106
+ </div>
107
+ {#if src?.name}
108
+ <p class="text-center text-white">{src?.name}</p>
109
+ {/if}
110
+ </div>
111
+ {:else if isPdf(src?.content)}
112
+ <div class="h-full w-full items-center justify-center">
113
+ <iframe src={src.content} title="attachment" class="h-full w-full"></iframe>
114
+ {#if src?.name}
115
+ <p class="text-center text-white">{src?.name}</p>
116
+ {/if}
117
+ </div>
118
+ {:else}
119
+ <div class="flex h-full flex-col items-center justify-center gap-1">
120
+ <div class="">
121
+ <IconifyIcon
122
+ icon={'name' in src && src?.name.includes('.pdf')
123
+ ? 'vscode-icons:file-type-pdf2'
124
+ : src?.name.includes('.docx')
125
+ ? 'vscode-icons:file-type-word'
126
+ : src?.name.includes('.pptx')
127
+ ? 'vscode-icons:file-type-powerpoint2'
128
+ : src?.name.includes('.xlsx') || src?.name.includes('.cv')
129
+ ? 'vscode-icons:file-type-excel'
130
+ : 'bxs:file'}
131
+ style="font-size: 150px;"
132
+ />
133
+ </div>
134
+ {#if src?.name}
135
+ <p class="text-center text-white">{src?.name}</p>
136
+ {/if}
137
+ </div>
138
+ {/if}
139
+ </div>
140
+ {#if showActions}
141
+ <div class="flex justify-center">
142
+ <div class=" flex gap-6">
143
+ <button
144
+ type="button"
145
+ aria-label="toggle modal"
146
+ onclick={() => (open = false)}
147
+ class="flex h-8 w-8 items-center justify-center rounded-full bg-red-500 text-white ring-8 ring-white hover:bg-red-800"
148
+ >
149
+ <IconifyIcon icon="ic:round-close" style="font-size: 24px;" />
150
+ </button>
151
+ <button
152
+ type="button"
153
+ aria-label="get image"
154
+ onclick={(_) => {
155
+ onGetImage?.(src);
156
+ }}
157
+ class="flex h-8 w-8 items-center justify-center rounded-full bg-green-500 text-white ring-8 ring-white hover:bg-green-800"
158
+ >
159
+ <IconifyIcon icon="majesticons:send" style="font-size: 20px;" />
160
+ </button>
161
+ </div>
162
+ </div>
163
+ {/if}
164
+ </div>
165
+ </div>
166
+ </div>
167
+ </div>
168
+ {/if}
169
+
170
+ <style>
171
+ .image-container {
172
+ overflow: hidden;
173
+ width: 100%;
174
+ max-width: 600px; /* Adjust as needed */
175
+ height: auto;
176
+ }
177
+
178
+ .zoomed-image {
179
+ transform-origin: center;
180
+ transition: transform 0.2s ease;
181
+ }
182
+ </style>
@@ -0,0 +1,4 @@
1
+ import type { PreviewPageProps } from './index.js';
2
+ declare const PreviewPage: import("svelte").Component<PreviewPageProps, {}, "open">;
3
+ type PreviewPage = ReturnType<typeof PreviewPage>;
4
+ export default PreviewPage;