triiiceratops 0.8.1 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (94) hide show
  1. package/dist/chunks/TriiiceratopsViewer-BGtsfUPF.js +10298 -0
  2. package/dist/chunks/openseadragon-BTypULhm.js +12427 -0
  3. package/dist/components/AnnotationOverlay.svelte +288 -0
  4. package/dist/components/AnnotationOverlay.svelte.d.ts +3 -0
  5. package/dist/components/CanvasNavigation.svelte +32 -0
  6. package/dist/components/CanvasNavigation.svelte.d.ts +11 -0
  7. package/dist/components/DemoHeader.svelte +703 -0
  8. package/dist/components/DemoHeader.svelte.d.ts +9 -0
  9. package/dist/components/FloatingMenu.svelte +208 -0
  10. package/dist/components/FloatingMenu.svelte.d.ts +3 -0
  11. package/dist/components/LeftFab.svelte +69 -0
  12. package/dist/components/LeftFab.svelte.d.ts +3 -0
  13. package/dist/components/MetadataDialog.svelte +151 -0
  14. package/dist/components/MetadataDialog.svelte.d.ts +3 -0
  15. package/dist/components/OSDViewer.svelte +260 -0
  16. package/dist/components/OSDViewer.svelte.d.ts +8 -0
  17. package/dist/components/SearchPanel.svelte +150 -0
  18. package/dist/components/SearchPanel.svelte.d.ts +3 -0
  19. package/dist/components/ThemeToggle.svelte +118 -0
  20. package/dist/components/ThemeToggle.svelte.d.ts +3 -0
  21. package/dist/components/ThumbnailGallery.svelte +601 -0
  22. package/dist/components/ThumbnailGallery.svelte.d.ts +36 -0
  23. package/dist/components/TriiiceratopsViewer.svelte +434 -0
  24. package/dist/components/TriiiceratopsViewer.svelte.d.ts +20 -0
  25. package/dist/components/TriiiceratopsViewerElement.svelte +139 -0
  26. package/dist/components/TriiiceratopsViewerElement.svelte.d.ts +27 -0
  27. package/dist/components/TriiiceratopsViewerElementImage.svelte +143 -0
  28. package/dist/components/TriiiceratopsViewerElementImage.svelte.d.ts +27 -0
  29. package/dist/custom-element-image.d.ts +1 -0
  30. package/dist/custom-element-image.js +2 -0
  31. package/dist/custom-element.d.ts +1 -0
  32. package/dist/custom-element.js +3 -0
  33. package/dist/{src/lib/index.d.ts → index.d.ts} +1 -0
  34. package/dist/index.js +10 -4480
  35. package/dist/plugins/image-manipulation/ImageManipulationPanel.svelte +134 -0
  36. package/dist/plugins/image-manipulation/ImageManipulationPanel.svelte.d.ts +10 -0
  37. package/dist/{src/lib/plugins → plugins}/image-manipulation/ImageManipulationPlugin.svelte.d.ts +2 -2
  38. package/dist/plugins/image-manipulation/ImageManipulationPlugin.svelte.js +122 -0
  39. package/dist/{src/lib/plugins → plugins}/image-manipulation/filters.d.ts +1 -1
  40. package/dist/plugins/image-manipulation/filters.js +48 -0
  41. package/dist/plugins/image-manipulation/index.js +2 -0
  42. package/dist/plugins/image-manipulation/types.js +7 -0
  43. package/dist/state/i18n.svelte.d.ts +4 -0
  44. package/dist/state/i18n.svelte.js +18 -0
  45. package/dist/state/manifests.svelte.js +210 -0
  46. package/dist/state/manifests.test.d.ts +1 -0
  47. package/dist/state/manifests.test.js +242 -0
  48. package/dist/{src/lib/state → state}/viewer.svelte.d.ts +4 -4
  49. package/dist/state/viewer.svelte.js +693 -0
  50. package/dist/theme/colorUtils.js +196 -0
  51. package/dist/theme/colorUtils.test.d.ts +1 -0
  52. package/dist/theme/colorUtils.test.js +90 -0
  53. package/dist/theme/index.js +52 -0
  54. package/dist/{src/lib/theme → theme}/themeManager.d.ts +4 -1
  55. package/dist/theme/themeManager.js +177 -0
  56. package/dist/theme/types.js +40 -0
  57. package/dist/triiiceratops-bundle.js +4676 -0
  58. package/dist/triiiceratops-element-image.js +1 -1
  59. package/dist/triiiceratops-element.js +1 -1
  60. package/dist/types/config.js +1 -0
  61. package/dist/{src/lib/types → types}/plugin.d.ts +3 -3
  62. package/dist/types/plugin.js +36 -0
  63. package/dist/utils/annotationAdapter.js +354 -0
  64. package/dist/utils/annotationAdapter.test.d.ts +1 -0
  65. package/dist/utils/annotationAdapter.test.js +91 -0
  66. package/package.json +6 -5
  67. package/dist/chunks/TriiiceratopsViewer-CyamQrMe.js +0 -22698
  68. package/dist/plugin-De14WKQl.js +0 -546
  69. package/dist/plugins/image-manipulation.js +0 -454
  70. package/dist/src/lib/components/AnnotationOverlay.svelte.d.ts +0 -1
  71. package/dist/src/lib/components/CanvasNavigation.svelte.d.ts +0 -1
  72. package/dist/src/lib/components/FloatingMenu.svelte.d.ts +0 -1
  73. package/dist/src/lib/components/LeftFab.svelte.d.ts +0 -1
  74. package/dist/src/lib/components/MetadataDialog.svelte.d.ts +0 -1
  75. package/dist/src/lib/components/OSDViewer.svelte.d.ts +0 -1
  76. package/dist/src/lib/components/SearchPanel.svelte.d.ts +0 -1
  77. package/dist/src/lib/components/ThumbnailGallery.svelte.d.ts +0 -1
  78. package/dist/src/lib/components/TriiiceratopsViewer.svelte.d.ts +0 -1
  79. package/dist/src/lib/custom-element-image.d.ts +0 -0
  80. package/dist/src/lib/custom-element.d.ts +0 -0
  81. package/dist/src/lib/paraglide/messages/de.d.ts +0 -96
  82. package/dist/src/lib/paraglide/messages/en.d.ts +0 -96
  83. package/dist/src/lib/paraglide/messages.d.ts +0 -272
  84. package/dist/src/lib/paraglide/runtime.d.ts +0 -52
  85. package/dist/src/lib/plugins/image-manipulation/ImageManipulationPanel.svelte.d.ts +0 -1
  86. package/dist/src/lib/state/i18n.svelte.d.ts +0 -5
  87. /package/dist/{src/lib/plugins → plugins}/image-manipulation/index.d.ts +0 -0
  88. /package/dist/{src/lib/plugins → plugins}/image-manipulation/types.d.ts +0 -0
  89. /package/dist/{src/lib/state → state}/manifests.svelte.d.ts +0 -0
  90. /package/dist/{src/lib/theme → theme}/colorUtils.d.ts +0 -0
  91. /package/dist/{src/lib/theme → theme}/index.d.ts +0 -0
  92. /package/dist/{src/lib/theme → theme}/types.d.ts +0 -0
  93. /package/dist/{src/lib/types → types}/config.d.ts +0 -0
  94. /package/dist/{src/lib/utils → utils}/annotationAdapter.d.ts +0 -0
@@ -0,0 +1,260 @@
1
+ <script lang="ts">
2
+ import { onMount } from 'svelte';
3
+ import { parseAnnotations } from '../utils/annotationAdapter';
4
+ import { manifestsState } from '../state/manifests.svelte';
5
+ import type { ViewerState } from '../state/viewer.svelte';
6
+
7
+ let {
8
+ tileSources,
9
+ viewerState,
10
+ }: { tileSources: string | object | null; viewerState: ViewerState } =
11
+ $props();
12
+
13
+ let container: HTMLElement | undefined = $state();
14
+ let viewer: any | undefined = $state();
15
+ let OSD: any | undefined = $state();
16
+
17
+ // Track OSD state changes for reactivity
18
+ let osdVersion = $state(0);
19
+
20
+ // Get all annotations for current canvas (manifest + search)
21
+ let allAnnotations = $derived.by(() => {
22
+ if (!viewerState.manifestId || !viewerState.canvasId) {
23
+ return [];
24
+ }
25
+ const manifestAnnotations = manifestsState.getAnnotations(
26
+ viewerState.manifestId,
27
+ viewerState.canvasId,
28
+ );
29
+ const searchAnnotations = viewerState.currentCanvasSearchAnnotations;
30
+ return [...manifestAnnotations, ...searchAnnotations];
31
+ });
32
+
33
+ // Get search hit IDs for styling
34
+ let searchHitIds = $derived.by(() => {
35
+ const ids = new Set<string>();
36
+ viewerState.currentCanvasSearchAnnotations.forEach((anno: any) => {
37
+ const id = anno.id || anno['@id'];
38
+ if (id) ids.add(id);
39
+ });
40
+ return ids;
41
+ });
42
+
43
+ // Parse annotations
44
+ let parsedAnnotations = $derived.by(() => {
45
+ return parseAnnotations(allAnnotations, searchHitIds);
46
+ });
47
+
48
+ // Rendered annotations with pixel coordinates
49
+ let renderedAnnotations = $derived.by(() => {
50
+ // Depend on osdVersion to trigger updates
51
+ osdVersion;
52
+
53
+ if (!viewer || !OSD || !parsedAnnotations.length) {
54
+ return [];
55
+ }
56
+
57
+ const tiledImage = viewer.world.getItemAt(0);
58
+ if (!tiledImage) {
59
+ return [];
60
+ }
61
+
62
+ const results: any[] = [];
63
+
64
+ for (const anno of parsedAnnotations) {
65
+ // Filter based on visibility
66
+ if (anno.isSearchHit) {
67
+ // Search hits are always visible
68
+ } else if (!viewerState.showAnnotations) {
69
+ continue;
70
+ } else if (!viewerState.visibleAnnotationIds.has(anno.id)) {
71
+ continue;
72
+ }
73
+
74
+ if (anno.geometry.type === 'RECTANGLE') {
75
+ // Convert image coordinates to viewport coordinates
76
+ const viewportRect = tiledImage.imageToViewportRectangle(
77
+ anno.geometry.x,
78
+ anno.geometry.y,
79
+ anno.geometry.w,
80
+ anno.geometry.h,
81
+ );
82
+
83
+ // Convert viewport to pixel coordinates
84
+ const pixelRect =
85
+ viewer.viewport.viewportToViewerElementRectangle(
86
+ viewportRect,
87
+ );
88
+
89
+ results.push({
90
+ id: anno.id,
91
+ type: 'RECTANGLE' as const,
92
+ rect: {
93
+ x: pixelRect.x,
94
+ y: pixelRect.y,
95
+ width: pixelRect.width,
96
+ height: pixelRect.height,
97
+ },
98
+ isSearchHit: anno.isSearchHit,
99
+ tooltip: anno.body.value,
100
+ });
101
+ } else if (anno.geometry.type === 'POLYGON') {
102
+ // Convert each point from image to viewport to pixel
103
+ const pixelPoints = anno.geometry.points.map((point) => {
104
+ const viewportPoint = tiledImage.imageToViewportCoordinates(
105
+ new OSD.Point(point[0], point[1]),
106
+ );
107
+ const pixelPoint =
108
+ viewer.viewport.viewportToViewerElementCoordinates(
109
+ viewportPoint,
110
+ );
111
+ return [pixelPoint.x, pixelPoint.y];
112
+ });
113
+
114
+ // Calculate bounding box for SVG positioning
115
+ let minX = Infinity,
116
+ minY = Infinity,
117
+ maxX = -Infinity,
118
+ maxY = -Infinity;
119
+ for (const [x, y] of pixelPoints) {
120
+ minX = Math.min(minX, x);
121
+ minY = Math.min(minY, y);
122
+ maxX = Math.max(maxX, x);
123
+ maxY = Math.max(maxY, y);
124
+ }
125
+
126
+ // Adjust points relative to bounding box
127
+ const relativePoints = pixelPoints.map(([x, y]) => [
128
+ x - minX,
129
+ y - minY,
130
+ ]);
131
+
132
+ results.push({
133
+ id: anno.id,
134
+ type: 'POLYGON' as const,
135
+ bounds: {
136
+ x: minX,
137
+ y: minY,
138
+ width: maxX - minX,
139
+ height: maxY - minY,
140
+ },
141
+ points: relativePoints,
142
+ isSearchHit: anno.isSearchHit,
143
+ tooltip: anno.body.value,
144
+ });
145
+ }
146
+ }
147
+
148
+ return results;
149
+ });
150
+
151
+ onMount(() => {
152
+ if (!container) return;
153
+
154
+ (async () => {
155
+ // Dynamically import OpenSeadragon to avoid SSR issues
156
+ const osdModule = await import('openseadragon');
157
+ OSD = osdModule.default || osdModule;
158
+
159
+ // Initialize OpenSeadragon viewer
160
+ viewer = OSD({
161
+ element: container,
162
+ tileSources: null, // Will be set via effect
163
+ prefixUrl: '', // No navigation UI images needed
164
+ showNavigationControl: false,
165
+ showHomeControl: false,
166
+ showFullPageControl: false,
167
+ showSequenceControl: false,
168
+ showZoomControl: false,
169
+ showRotationControl: false,
170
+ animationTime: 0.5,
171
+ springStiffness: 7.0,
172
+ zoomPerClick: 2.0,
173
+ });
174
+
175
+ // Notify plugins that OSD is ready
176
+ viewerState.notifyOSDReady(viewer);
177
+ })();
178
+
179
+ return () => {
180
+ viewer?.destroy();
181
+ viewerState.osdViewer = null;
182
+ };
183
+ });
184
+
185
+ // Subscribe to OSD events for reactivity
186
+ $effect(() => {
187
+ if (!viewer) return;
188
+
189
+ const update = () => {
190
+ osdVersion++;
191
+ };
192
+
193
+ viewer.addHandler('open', update);
194
+ viewer.addHandler('animation', update);
195
+ viewer.addHandler('resize', update);
196
+ viewer.addHandler('rotate', update);
197
+ viewer.world.addHandler('add-item', update);
198
+ viewer.world.addHandler('remove-item', update);
199
+
200
+ return () => {
201
+ viewer.removeHandler('open', update);
202
+ viewer.removeHandler('animation', update);
203
+ viewer.removeHandler('resize', update);
204
+ viewer.removeHandler('rotate', update);
205
+ viewer.world.removeHandler('add-item', update);
206
+ viewer.world.removeHandler('remove-item', update);
207
+ };
208
+ });
209
+
210
+ // Load tile source when it changes
211
+ $effect(() => {
212
+ if (!viewer || !tileSources) return;
213
+
214
+ viewer.open(tileSources);
215
+ });
216
+ </script>
217
+
218
+ <div class="w-full h-full relative">
219
+ <div
220
+ bind:this={container}
221
+ class="w-full h-full osd-background bg-base-100"
222
+ ></div>
223
+
224
+ <!-- Render annotations -->
225
+ {#each renderedAnnotations as anno (anno.id)}
226
+ {#if anno.type === 'RECTANGLE'}
227
+ <div
228
+ class="absolute border-2 transition-colors cursor-pointer pointer-events-auto {anno.isSearchHit
229
+ ? 'border-yellow-400 bg-yellow-400/40 hover:bg-yellow-400/60'
230
+ : 'border-red-500 bg-red-500/20 hover:bg-red-500/40'}"
231
+ style="
232
+ left: {anno.rect.x}px;
233
+ top: {anno.rect.y}px;
234
+ width: {anno.rect.width}px;
235
+ height: {anno.rect.height}px;
236
+ "
237
+ title={anno.tooltip}
238
+ ></div>
239
+ {:else if anno.type === 'POLYGON'}
240
+ <svg
241
+ class="absolute pointer-events-auto"
242
+ style="
243
+ left: {anno.bounds.x}px;
244
+ top: {anno.bounds.y}px;
245
+ width: {anno.bounds.width}px;
246
+ height: {anno.bounds.height}px;
247
+ "
248
+ >
249
+ <title>{anno.tooltip}</title>
250
+ <polygon
251
+ points={anno.points.map((p: any) => p.join(',')).join(' ')}
252
+ class="cursor-pointer transition-colors {anno.isSearchHit
253
+ ? 'fill-yellow-400/40 stroke-yellow-400 hover:fill-yellow-400/60'
254
+ : 'fill-red-500/20 stroke-red-500 hover:fill-red-500/40'}"
255
+ stroke-width="2"
256
+ />
257
+ </svg>
258
+ {/if}
259
+ {/each}
260
+ </div>
@@ -0,0 +1,8 @@
1
+ import type { ViewerState } from '../state/viewer.svelte';
2
+ type $$ComponentProps = {
3
+ tileSources: string | object | null;
4
+ viewerState: ViewerState;
5
+ };
6
+ declare const OSDViewer: import("svelte").Component<$$ComponentProps, {}, "">;
7
+ type OSDViewer = ReturnType<typeof OSDViewer>;
8
+ export default OSDViewer;
@@ -0,0 +1,150 @@
1
+ <script lang="ts">
2
+ import { getContext, untrack } from 'svelte';
3
+ import MagnifyingGlass from 'phosphor-svelte/lib/MagnifyingGlass';
4
+ import Spinner from 'phosphor-svelte/lib/Spinner';
5
+ import X from 'phosphor-svelte/lib/X';
6
+ import { VIEWER_STATE_KEY, type ViewerState } from '../state/viewer.svelte';
7
+ import { m } from '../state/i18n.svelte';
8
+
9
+ const viewerState = getContext<ViewerState>(VIEWER_STATE_KEY);
10
+
11
+ // We'll initialize from viewerState to preserve context.
12
+ let searchQuery = $state('');
13
+ let resultsContainer: HTMLElement;
14
+
15
+ let showCloseButton = $derived(
16
+ viewerState.config.search?.showCloseButton ?? true,
17
+ );
18
+
19
+ // Sync local query with viewerState
20
+ $effect(() => {
21
+ if (viewerState.searchQuery !== untrack(() => searchQuery)) {
22
+ searchQuery = viewerState.searchQuery;
23
+ }
24
+ });
25
+
26
+ function handleSearch() {
27
+ viewerState.search(searchQuery);
28
+ }
29
+
30
+ function handleKeydown(e: KeyboardEvent) {
31
+ if (e.key === 'Enter') {
32
+ handleSearch();
33
+ }
34
+ }
35
+
36
+ function navigate(result: any) {
37
+ const canvas = viewerState.canvases[result.canvasIndex];
38
+ if (canvas) {
39
+ viewerState.setCanvas(canvas.id);
40
+ }
41
+ }
42
+ </script>
43
+
44
+ <!-- Drawer / Panel -->
45
+ {#if viewerState.showSearchPanel}
46
+ <div
47
+ class="h-full w-80 bg-base-200 shadow-2xl z-100 flex flex-col border-l border-base-300"
48
+ role="dialog"
49
+ aria-label={m.search_panel_title()}
50
+ >
51
+ <!-- Header -->
52
+ <div
53
+ class="flex items-center justify-between p-4 border-b border-base-300"
54
+ >
55
+ <h2 class="font-bold text-lg">{m.search()}</h2>
56
+ {#if showCloseButton}
57
+ <button
58
+ class="btn btn-sm btn-circle btn-ghost"
59
+ onclick={() => viewerState.toggleSearchPanel()}
60
+ aria-label={m.close_search()}
61
+ >
62
+ <X size={20} weight="bold" />
63
+ </button>
64
+ {/if}
65
+ </div>
66
+
67
+ <!-- Search Input -->
68
+ <div class="p-4 border-b border-base-300 shrink-0">
69
+ <div class="relative w-full">
70
+ <input
71
+ type="text"
72
+ bind:value={searchQuery}
73
+ onkeydown={handleKeydown}
74
+ placeholder={m.search_panel_placeholder()}
75
+ class="input input-bordered w-full pr-12"
76
+ />
77
+ <button
78
+ class="btn btn-primary absolute right-0 top-0 h-full rounded-l-none"
79
+ onclick={handleSearch}
80
+ aria-label={m.search_panel_title()}
81
+ >
82
+ {#if viewerState.isSearching}
83
+ <span class="loading loading-spinner loading-xs"></span>
84
+ {:else}
85
+ <MagnifyingGlass size={20} weight="bold" />
86
+ {/if}
87
+ </button>
88
+ </div>
89
+ </div>
90
+
91
+ <!-- Results -->
92
+ <div class="flex-1 overflow-y-auto p-4 space-y-4">
93
+ {#if viewerState.isSearching}
94
+ <div class="flex justify-center p-8">
95
+ <span
96
+ class="loading loading-spinner loading-lg text-primary"
97
+ ></span>
98
+ </div>
99
+ {:else if viewerState.searchResults.length === 0 && viewerState.searchQuery}
100
+ <div class="text-center opacity-50 p-4">
101
+ {m.search_panel_no_results({
102
+ query: viewerState.searchQuery,
103
+ })}
104
+ </div>
105
+ {:else if viewerState.searchResults.length === 0 && !viewerState.searchQuery}
106
+ <div class="text-center opacity-50 p-4 text-sm">
107
+ {m.search_panel_instruction()}
108
+ </div>
109
+ {:else}
110
+ <!-- Results Header -->
111
+ <div
112
+ class="text-xs font-bold opacity-50 uppercase tracking-wider pb-2"
113
+ >
114
+ {m.search_panel_results_count({
115
+ count: viewerState.searchResults.length,
116
+ })}
117
+ </div>
118
+
119
+ {#each viewerState.searchResults as result, i}
120
+ <button
121
+ class="w-full text-left card bg-base-100 shadow hover:shadow-md transition-all p-4 text-sm group border border-transparent hover:border-primary focus:outline-none focus:ring-2 focus:ring-primary"
122
+ onclick={() => navigate(result)}
123
+ >
124
+ <div class="flex justify-between items-baseline mb-1">
125
+ <span
126
+ class="font-bold text-xs opacity-70 bg-base-200 px-1.5 py-0.5 rounded"
127
+ >{result.canvasLabel}</span
128
+ >
129
+ </div>
130
+
131
+ {#if result.type === 'hit'}
132
+ <div class="leading-relaxed">
133
+ <span>{@html result.before}</span>
134
+ <span
135
+ class="bg-yellow-200 text-yellow-900 font-bold px-0.5 rounded"
136
+ >{@html result.match}</span
137
+ >
138
+ <span>{@html result.after}</span>
139
+ </div>
140
+ {:else}
141
+ <div class="leading-relaxed">
142
+ {result.match}
143
+ </div>
144
+ {/if}
145
+ </button>
146
+ {/each}
147
+ {/if}
148
+ </div>
149
+ </div>
150
+ {/if}
@@ -0,0 +1,3 @@
1
+ declare const SearchPanel: import("svelte").Component<Record<string, never>, {}, "">;
2
+ type SearchPanel = ReturnType<typeof SearchPanel>;
3
+ export default SearchPanel;
@@ -0,0 +1,118 @@
1
+ <script lang="ts">
2
+ import CaretDown from 'phosphor-svelte/lib/CaretDown';
3
+ import { onMount } from 'svelte';
4
+ import { m } from '../state/i18n.svelte';
5
+
6
+ let theme = $state('light');
7
+
8
+ const themes = [
9
+ 'light',
10
+ 'dark',
11
+ 'cupcake',
12
+ 'bumblebee',
13
+ 'emerald',
14
+ 'corporate',
15
+ 'synthwave',
16
+ 'retro',
17
+ 'cyberpunk',
18
+ 'valentine',
19
+ 'halloween',
20
+ 'garden',
21
+ 'forest',
22
+ 'aqua',
23
+ 'lofi',
24
+ 'pastel',
25
+ 'fantasy',
26
+ 'wireframe',
27
+ 'black',
28
+ 'luxury',
29
+ 'dracula',
30
+ 'cmyk',
31
+ 'autumn',
32
+ 'business',
33
+ 'acid',
34
+ 'lemonade',
35
+ 'night',
36
+ 'coffee',
37
+ 'winter',
38
+ 'dim',
39
+ 'nord',
40
+ 'sunset',
41
+ ];
42
+
43
+ onMount(() => {
44
+ const storedTheme = localStorage.getItem('theme');
45
+ if (storedTheme) {
46
+ theme = storedTheme;
47
+ } else if (window.matchMedia('(prefers-color-scheme: dark)').matches) {
48
+ theme = 'dark';
49
+ }
50
+ document.documentElement.setAttribute('data-theme', theme);
51
+ });
52
+
53
+ function onThemeChange(newTheme: string) {
54
+ theme = newTheme;
55
+ document.documentElement.setAttribute('data-theme', theme);
56
+ localStorage.setItem('theme', theme);
57
+ }
58
+ </script>
59
+
60
+ <div title={m.change_theme_label()} class="dropdown dropdown-end">
61
+ <div
62
+ tabindex="0"
63
+ role="button"
64
+ class="btn group btn-sm gap-1.5 px-1.5 btn-ghost"
65
+ aria-label={m.change_theme_label()}
66
+ >
67
+ <div
68
+ class="bg-base-100 group-hover:border-base-content/20 border-base-content/10 grid shrink-0 grid-cols-2 gap-0.5 rounded-md border p-1 transition-colors"
69
+ >
70
+ <div class="bg-base-content size-1 rounded-full"></div>
71
+ <div class="bg-primary size-1 rounded-full"></div>
72
+ <div class="bg-secondary size-1 rounded-full"></div>
73
+ <div class="bg-accent size-1 rounded-full"></div>
74
+ </div>
75
+ <CaretDown size={16} />
76
+ </div>
77
+ <div
78
+ tabindex="-1"
79
+ class="dropdown-content bg-base-200 text-base-content rounded-box top-px h-122 max-h-[calc(100vh-8.6rem)] overflow-y-auto border border-white/5 shadow-2xl outline outline-black/5 mt-16"
80
+ >
81
+ <ul class="menu w-56">
82
+ <li class="menu-title text-xs">{m.theme_menu_title()}</li>
83
+ {#each themes as t}
84
+ <li>
85
+ <button class="gap-3 px-2" onclick={() => onThemeChange(t)}>
86
+ <div
87
+ data-theme={t}
88
+ class="bg-base-100 grid shrink-0 grid-cols-2 gap-0.5 rounded-md p-1 shadow-sm"
89
+ >
90
+ <div
91
+ class="bg-base-content size-1 rounded-full"
92
+ ></div>
93
+ <div class="bg-primary size-1 rounded-full"></div>
94
+ <div class="bg-secondary size-1 rounded-full"></div>
95
+ <div class="bg-accent size-1 rounded-full"></div>
96
+ </div>
97
+ <div class="w-32 truncate">
98
+ {t}
99
+ </div>
100
+ <svg
101
+ xmlns="http://www.w3.org/2000/svg"
102
+ width="16"
103
+ height="16"
104
+ viewBox="0 0 24 24"
105
+ fill="currentColor"
106
+ class={theme === t
107
+ ? 'h-3 w-3 shrink-0'
108
+ : 'invisible h-3 w-3 shrink-0'}
109
+ ><path
110
+ d="M20.285 2l-11.285 11.567-5.286-5.011-3.714 3.716 9 8.728 15-15.285z"
111
+ ></path></svg
112
+ >
113
+ </button>
114
+ </li>
115
+ {/each}
116
+ </ul>
117
+ </div>
118
+ </div>
@@ -0,0 +1,3 @@
1
+ declare const ThemeToggle: import("svelte").Component<Record<string, never>, {}, "">;
2
+ type ThemeToggle = ReturnType<typeof ThemeToggle>;
3
+ export default ThemeToggle;