triiiceratops 0.8.2 → 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 (89) hide show
  1. package/dist/components/AnnotationOverlay.svelte +288 -0
  2. package/dist/components/AnnotationOverlay.svelte.d.ts +3 -0
  3. package/dist/components/CanvasNavigation.svelte +32 -0
  4. package/dist/components/CanvasNavigation.svelte.d.ts +11 -0
  5. package/dist/components/DemoHeader.svelte +703 -0
  6. package/dist/components/DemoHeader.svelte.d.ts +9 -0
  7. package/dist/components/FloatingMenu.svelte +208 -0
  8. package/dist/components/FloatingMenu.svelte.d.ts +3 -0
  9. package/dist/components/LeftFab.svelte +69 -0
  10. package/dist/components/LeftFab.svelte.d.ts +3 -0
  11. package/dist/components/MetadataDialog.svelte +151 -0
  12. package/dist/components/MetadataDialog.svelte.d.ts +3 -0
  13. package/dist/components/OSDViewer.svelte +260 -0
  14. package/dist/components/OSDViewer.svelte.d.ts +8 -0
  15. package/dist/components/SearchPanel.svelte +150 -0
  16. package/dist/components/SearchPanel.svelte.d.ts +3 -0
  17. package/dist/components/ThemeToggle.svelte +118 -0
  18. package/dist/components/ThemeToggle.svelte.d.ts +3 -0
  19. package/dist/components/ThumbnailGallery.svelte +601 -0
  20. package/dist/components/ThumbnailGallery.svelte.d.ts +36 -0
  21. package/dist/components/TriiiceratopsViewer.svelte +434 -0
  22. package/dist/components/TriiiceratopsViewer.svelte.d.ts +20 -0
  23. package/dist/components/TriiiceratopsViewerElement.svelte +139 -0
  24. package/dist/components/TriiiceratopsViewerElement.svelte.d.ts +27 -0
  25. package/dist/components/TriiiceratopsViewerElementImage.svelte +143 -0
  26. package/dist/components/TriiiceratopsViewerElementImage.svelte.d.ts +27 -0
  27. package/dist/custom-element-image.d.ts +1 -0
  28. package/dist/custom-element-image.js +2 -0
  29. package/dist/custom-element.d.ts +1 -0
  30. package/dist/custom-element.js +3 -0
  31. package/dist/{src/lib/index.d.ts → index.d.ts} +1 -0
  32. package/dist/index.js +10 -4153
  33. package/dist/plugins/image-manipulation/ImageManipulationPanel.svelte +134 -0
  34. package/dist/plugins/image-manipulation/ImageManipulationPanel.svelte.d.ts +10 -0
  35. package/dist/{src/lib/plugins → plugins}/image-manipulation/ImageManipulationPlugin.svelte.d.ts +2 -2
  36. package/dist/plugins/image-manipulation/ImageManipulationPlugin.svelte.js +122 -0
  37. package/dist/{src/lib/plugins → plugins}/image-manipulation/filters.d.ts +1 -1
  38. package/dist/plugins/image-manipulation/filters.js +48 -0
  39. package/dist/plugins/image-manipulation/index.js +2 -0
  40. package/dist/plugins/image-manipulation/types.js +7 -0
  41. package/dist/state/i18n.svelte.d.ts +4 -0
  42. package/dist/state/i18n.svelte.js +18 -0
  43. package/dist/state/manifests.svelte.js +210 -0
  44. package/dist/state/manifests.test.d.ts +1 -0
  45. package/dist/state/manifests.test.js +242 -0
  46. package/dist/{src/lib/state → state}/viewer.svelte.d.ts +4 -4
  47. package/dist/state/viewer.svelte.js +693 -0
  48. package/dist/theme/colorUtils.js +196 -0
  49. package/dist/theme/colorUtils.test.d.ts +1 -0
  50. package/dist/theme/colorUtils.test.js +90 -0
  51. package/dist/theme/index.js +52 -0
  52. package/dist/{src/lib/theme → theme}/themeManager.d.ts +4 -1
  53. package/dist/theme/themeManager.js +177 -0
  54. package/dist/theme/types.js +40 -0
  55. package/dist/triiiceratops-bundle.js +4676 -0
  56. package/dist/types/config.js +1 -0
  57. package/dist/{src/lib/types → types}/plugin.d.ts +3 -3
  58. package/dist/types/plugin.js +36 -0
  59. package/dist/utils/annotationAdapter.js +354 -0
  60. package/dist/utils/annotationAdapter.test.d.ts +1 -0
  61. package/dist/utils/annotationAdapter.test.js +91 -0
  62. package/package.json +6 -5
  63. package/dist/plugin-CHYleMsW.js +0 -538
  64. package/dist/plugins/image-manipulation.js +0 -411
  65. package/dist/src/lib/components/AnnotationOverlay.svelte.d.ts +0 -1
  66. package/dist/src/lib/components/CanvasNavigation.svelte.d.ts +0 -1
  67. package/dist/src/lib/components/FloatingMenu.svelte.d.ts +0 -1
  68. package/dist/src/lib/components/LeftFab.svelte.d.ts +0 -1
  69. package/dist/src/lib/components/MetadataDialog.svelte.d.ts +0 -1
  70. package/dist/src/lib/components/OSDViewer.svelte.d.ts +0 -1
  71. package/dist/src/lib/components/SearchPanel.svelte.d.ts +0 -1
  72. package/dist/src/lib/components/ThumbnailGallery.svelte.d.ts +0 -1
  73. package/dist/src/lib/components/TriiiceratopsViewer.svelte.d.ts +0 -1
  74. package/dist/src/lib/custom-element-image.d.ts +0 -0
  75. package/dist/src/lib/custom-element.d.ts +0 -0
  76. package/dist/src/lib/paraglide/messages/de.d.ts +0 -96
  77. package/dist/src/lib/paraglide/messages/en.d.ts +0 -96
  78. package/dist/src/lib/paraglide/messages.d.ts +0 -272
  79. package/dist/src/lib/paraglide/runtime.d.ts +0 -52
  80. package/dist/src/lib/plugins/image-manipulation/ImageManipulationPanel.svelte.d.ts +0 -1
  81. package/dist/src/lib/state/i18n.svelte.d.ts +0 -5
  82. /package/dist/{src/lib/plugins → plugins}/image-manipulation/index.d.ts +0 -0
  83. /package/dist/{src/lib/plugins → plugins}/image-manipulation/types.d.ts +0 -0
  84. /package/dist/{src/lib/state → state}/manifests.svelte.d.ts +0 -0
  85. /package/dist/{src/lib/theme → theme}/colorUtils.d.ts +0 -0
  86. /package/dist/{src/lib/theme → theme}/index.d.ts +0 -0
  87. /package/dist/{src/lib/theme → theme}/types.d.ts +0 -0
  88. /package/dist/{src/lib/types → types}/config.d.ts +0 -0
  89. /package/dist/{src/lib/utils → utils}/annotationAdapter.d.ts +0 -0
@@ -0,0 +1,434 @@
1
+ <script lang="ts">
2
+ import { setContext, onDestroy } from 'svelte';
3
+ import { ViewerState, VIEWER_STATE_KEY } from '../state/viewer.svelte';
4
+ import type { TriiiceratopsPlugin } from '../types/plugin';
5
+ import type { DaisyUITheme, ThemeConfig } from '../theme/types';
6
+ import type { ViewerConfig } from '../types/config';
7
+ import { applyTheme } from '../theme/themeManager';
8
+ import OSDViewer from './OSDViewer.svelte';
9
+ import CanvasNavigation from './CanvasNavigation.svelte';
10
+ import AnnotationOverlay from './AnnotationOverlay.svelte';
11
+ import ThumbnailGallery from './ThumbnailGallery.svelte';
12
+ import FloatingMenu from './FloatingMenu.svelte';
13
+ import LeftFab from './LeftFab.svelte';
14
+ import MetadataDialog from './MetadataDialog.svelte';
15
+ import SearchPanel from './SearchPanel.svelte';
16
+ import { m } from '../state/i18n.svelte';
17
+
18
+ // SSR-safe browser detection for library consumers
19
+ const browser = typeof window !== 'undefined';
20
+
21
+ let {
22
+ manifestId,
23
+ canvasId,
24
+ plugins = [],
25
+ theme,
26
+ themeConfig,
27
+ config = {},
28
+ viewerState = $bindable(),
29
+ }: {
30
+ manifestId?: string;
31
+ canvasId?: string;
32
+ plugins?: TriiiceratopsPlugin[];
33
+ /** Built-in DaisyUI theme name. Defaults to 'light' or 'dark' based on prefers-color-scheme. */
34
+ theme?: DaisyUITheme;
35
+ /** Custom theme configuration to override the base theme's values. */
36
+ themeConfig?: ThemeConfig;
37
+ /** Configuration options for the viewer UI */
38
+ config?: ViewerConfig;
39
+ /** Bindable viewer state instance for external access (Svelte consumers) */
40
+ viewerState?: ViewerState;
41
+ } = $props();
42
+
43
+ // Reference to root element for applying theme
44
+ let rootElement: HTMLElement | undefined = $state();
45
+
46
+ // Reactively apply theme when element is available or theme/themeConfig changes
47
+ $effect(() => {
48
+ if (rootElement) {
49
+ applyTheme(rootElement, theme, themeConfig);
50
+ }
51
+ });
52
+
53
+ // Create per-instance viewer state
54
+ // Note: We pass empty initial values and use $effect blocks below to set
55
+ // manifestId, canvasId, and plugins reactively, avoiding Svelte's
56
+ // "state_referenced_locally" warning about capturing initial prop values.
57
+ const internalViewerState = new ViewerState(null, undefined, []);
58
+ viewerState = internalViewerState; // Expose via bindable prop
59
+ setContext(VIEWER_STATE_KEY, internalViewerState);
60
+
61
+ onDestroy(() => {
62
+ internalViewerState.destroyAllPlugins();
63
+ });
64
+
65
+ $effect(() => {
66
+ if (manifestId && manifestId !== internalViewerState.manifestId) {
67
+ internalViewerState.setManifest(manifestId);
68
+ }
69
+ });
70
+
71
+ // Track last applied canvasId PROP value to prevent reverting internal navigation
72
+ let lastAppliedCanvasId = '';
73
+
74
+ $effect(() => {
75
+ // Only sync from prop to internal state when PROP actually changes
76
+ // This prevents internal navigation from being reverted when the effect
77
+ // runs due to internal state changes
78
+ if (canvasId && canvasId !== lastAppliedCanvasId) {
79
+ lastAppliedCanvasId = canvasId;
80
+ // Only apply if different from current internal state
81
+ if (canvasId !== internalViewerState.canvasId) {
82
+ internalViewerState.setCanvas(canvasId);
83
+ }
84
+ }
85
+ });
86
+
87
+ // Track last applied config to prevent redundant updates and loops
88
+ let lastConfigStr = '';
89
+
90
+ $effect(() => {
91
+ if (config) {
92
+ const str = JSON.stringify(config);
93
+ if (str !== lastConfigStr) {
94
+ lastConfigStr = str;
95
+ console.log(
96
+ '[Viewer] updateConfig called with new config:',
97
+ config,
98
+ );
99
+ internalViewerState.updateConfig(config);
100
+ }
101
+ }
102
+ });
103
+
104
+ // Register plugins reactively
105
+ $effect(() => {
106
+ for (const plugin of plugins) {
107
+ internalViewerState.registerPlugin(plugin);
108
+ }
109
+ });
110
+
111
+ $effect(() => {
112
+ if (!browser) return;
113
+
114
+ const handleFullScreenChange = () => {
115
+ internalViewerState.isFullScreen = !!document.fullscreenElement;
116
+ };
117
+ document.addEventListener('fullscreenchange', handleFullScreenChange);
118
+ return () => {
119
+ document.removeEventListener(
120
+ 'fullscreenchange',
121
+ handleFullScreenChange,
122
+ );
123
+ };
124
+ });
125
+
126
+ let isLeftSidebarVisible = $derived(
127
+ (internalViewerState.showThumbnailGallery &&
128
+ internalViewerState.dockSide === 'left') ||
129
+ internalViewerState.pluginPanels.some(
130
+ (p) => p.position === 'left' && p.isVisible(),
131
+ ),
132
+ );
133
+
134
+ let isRightSidebarVisible = $derived(
135
+ internalViewerState.showSearchPanel ||
136
+ (internalViewerState.showThumbnailGallery &&
137
+ internalViewerState.dockSide === 'right') ||
138
+ internalViewerState.pluginPanels.some(
139
+ (p) => p.position === 'right' && p.isVisible(),
140
+ ),
141
+ );
142
+
143
+ let manifestData = $derived(internalViewerState.manifest);
144
+ let canvases = $derived(internalViewerState.canvases);
145
+ let currentCanvasIndex = $derived(internalViewerState.currentCanvasIndex);
146
+
147
+ let tileSources = $derived.by(() => {
148
+ if (
149
+ !canvases ||
150
+ currentCanvasIndex === -1 ||
151
+ !canvases[currentCanvasIndex]
152
+ ) {
153
+ if (!manifestData?.isFetching) {
154
+ console.log('TriiiceratopsViewer: No canvas found');
155
+ }
156
+ return null;
157
+ }
158
+
159
+ const canvas = canvases[currentCanvasIndex];
160
+
161
+ // Use Manifesto to get images
162
+ let images = canvas.getImages();
163
+
164
+ // Fallback for IIIF v3: iterate content if images is empty
165
+ if ((!images || !images.length) && canvas.getContent) {
166
+ images = canvas.getContent();
167
+ }
168
+
169
+ if (!images || !images.length) {
170
+ // Check for raw v3 items
171
+ if (canvas.__jsonld && canvas.__jsonld.items) {
172
+ // Try to locate annotation pages -> annotations
173
+ }
174
+ if (!manifestData?.isFetching) {
175
+ console.log('TriiiceratopsViewer: No images/content in canvas');
176
+ }
177
+ return null;
178
+ }
179
+
180
+ const annotation = images[0];
181
+ let resource = annotation.getResource ? annotation.getResource() : null;
182
+
183
+ // v3 fallback: getBody
184
+ if (!resource && annotation.getBody) {
185
+ const body = annotation.getBody();
186
+ if (Array.isArray(body) && body.length > 0) resource = body[0];
187
+ else if (body) resource = body;
188
+ }
189
+
190
+ // Check if resource is valid (Manifesto sometimes returns empty objects for v3 bodies)
191
+ if (
192
+ resource &&
193
+ !resource.id &&
194
+ !resource.__jsonld &&
195
+ (!resource.getServices || resource.getServices().length === 0)
196
+ ) {
197
+ resource = null;
198
+ }
199
+
200
+ if (!resource) {
201
+ // raw json fallback
202
+ const json = annotation.__jsonld || annotation;
203
+ if (json.body) {
204
+ resource = Array.isArray(json.body) ? json.body[0] : json.body;
205
+ }
206
+ }
207
+
208
+ if (!resource) {
209
+ // console.log('TriiiceratopsViewer: No resource in annotation');
210
+ return null;
211
+ }
212
+
213
+ // Helper to normalize ID
214
+ const getId = (thing: any) => thing.id || thing['@id'];
215
+
216
+ // Start of service detection logic
217
+ let services = [];
218
+ if (resource.getServices) {
219
+ services = resource.getServices();
220
+ }
221
+
222
+ // Fallback: check raw json service
223
+ if (!services.length) {
224
+ const rJson = resource.__jsonld || resource;
225
+ // console.log('Checking raw resource for services:', rJson);
226
+ if (rJson.service) {
227
+ services = Array.isArray(rJson.service)
228
+ ? rJson.service
229
+ : [rJson.service];
230
+ }
231
+ }
232
+
233
+ // console.log('Found services:', services);
234
+
235
+ if (services.length > 0) {
236
+ // Find a valid image service
237
+ const service = services.find((s: any) => {
238
+ const type = s.getType ? s.getType() : s.type || '';
239
+ const profile = s.getProfile ? s.getProfile() : s.profile || '';
240
+ return (
241
+ type === 'ImageService1' ||
242
+ type === 'ImageService2' ||
243
+ type === 'ImageService3' ||
244
+ (typeof profile === 'string' &&
245
+ profile.includes('http://iiif.io/api/image')) ||
246
+ (typeof profile === 'string' && profile === 'level0')
247
+ );
248
+ });
249
+
250
+ if (service) {
251
+ let id = getId(service);
252
+ if (id && !id.endsWith('/info.json')) {
253
+ id = `${id}/info.json`;
254
+ }
255
+ return id;
256
+ }
257
+ }
258
+
259
+ // Fallback: Heuristic from Image ID (if it looks like IIIF)
260
+ const resourceId = getId(resource);
261
+ if (resourceId && resourceId.includes('/iiif/')) {
262
+ // Try to strip standard IIIF parameters to find base
263
+ // IIIF URLs often look like .../identifier/region/size/rotation/quality.format
264
+ // We look for the part before the region (often 'full')
265
+ const parts = resourceId.split('/');
266
+ // find index of 'full' or region
267
+ const regionIndex = parts.findIndex(
268
+ (p: string) => p === 'full' || p.match(/^\d+,\d+,\d+,\d+$/),
269
+ );
270
+ if (regionIndex > 0) {
271
+ const base = parts.slice(0, regionIndex).join('/');
272
+ return `${base}/info.json`;
273
+ }
274
+ }
275
+
276
+ console.log(
277
+ 'TriiiceratopsViewer: No service or ID found, returning raw URL',
278
+ );
279
+ const url = resourceId;
280
+ return { type: 'image', url };
281
+ });
282
+ </script>
283
+
284
+ <div
285
+ bind:this={rootElement}
286
+ id="triiiceratops-viewer"
287
+ class="flex w-full h-full relative bg-base-100 overflow-hidden"
288
+ >
289
+ <!-- Left Column -->
290
+ {#if isLeftSidebarVisible}
291
+ <div
292
+ class="flex-none flex flex-row z-20 bg-base-200 border-r border-base-300 transition-all"
293
+ >
294
+ <!-- Gallery (when docked left) -->
295
+ {#if internalViewerState.showThumbnailGallery && internalViewerState.dockSide === 'left'}
296
+ <div class="h-full w-[200px] pointer-events-auto relative">
297
+ <ThumbnailGallery {canvases} />
298
+ </div>
299
+ {/if}
300
+
301
+ {#each internalViewerState.pluginPanels as panel (panel.id)}
302
+ {#if panel.isVisible() && panel.position === 'left'}
303
+ <div class="h-full relative pointer-events-auto">
304
+ <panel.component {...panel.props ?? {}} />
305
+ </div>
306
+ {/if}
307
+ {/each}
308
+ </div>
309
+ {/if}
310
+
311
+ <!-- Center Column -->
312
+ <div
313
+ id="triiiceratops-center-panel"
314
+ class="flex-1 relative min-w-0 flex flex-col"
315
+ >
316
+ <!-- Top Area (Gallery) -->
317
+ {#if internalViewerState.showThumbnailGallery && internalViewerState.dockSide === 'top'}
318
+ <div
319
+ class="flex-none h-[140px] w-full pointer-events-auto relative z-20"
320
+ >
321
+ <ThumbnailGallery {canvases} />
322
+ </div>
323
+ {/if}
324
+
325
+ <!-- Main Viewer Area -->
326
+ <div class="flex-1 relative min-h-0 w-full h-full bg-base-100">
327
+ {#if manifestData?.isFetching}
328
+ <div class="w-full h-full flex items-center justify-center">
329
+ <span
330
+ class="loading loading-spinner loading-lg text-primary"
331
+ ></span>
332
+ </div>
333
+ {:else if manifestData?.error}
334
+ <div
335
+ class="w-full h-full flex items-center justify-center text-error"
336
+ >
337
+ {m.error_prefix()}
338
+ {manifestData.error}
339
+ </div>
340
+ {:else if tileSources}
341
+ {#key tileSources}
342
+ <OSDViewer
343
+ {tileSources}
344
+ viewerState={internalViewerState}
345
+ />
346
+ {/key}
347
+ {:else}
348
+ <div
349
+ class="w-full h-full flex items-center justify-center text-base-content/50"
350
+ >
351
+ {m.no_image_found()}
352
+ </div>
353
+ {/if}
354
+
355
+ <AnnotationOverlay />
356
+ <MetadataDialog />
357
+
358
+ <!-- Floating Menu / FABs -->
359
+ {#if internalViewerState.showRightMenu}
360
+ <FloatingMenu />
361
+ {/if}
362
+ {#if internalViewerState.showLeftMenu}
363
+ <LeftFab />
364
+ {/if}
365
+
366
+ <!-- Overlay Plugin Panels -->
367
+ {#each internalViewerState.pluginPanels as panel (panel.id)}
368
+ {#if panel.isVisible() && panel.position === 'overlay'}
369
+ <div class="absolute inset-0 z-40 pointer-events-none">
370
+ <panel.component {...panel.props ?? {}} />
371
+ </div>
372
+ {/if}
373
+ {/each}
374
+
375
+ <!-- Canvas Nav (Absolute positioned inside center, or floating?) currently assumes absolute -->
376
+ {#if canvases.length > 1 && internalViewerState.showCanvasNav}
377
+ <CanvasNavigation viewerState={internalViewerState} />
378
+ {/if}
379
+
380
+ <!-- Float-mode Gallery -->
381
+ {#if internalViewerState.showThumbnailGallery && internalViewerState.dockSide === 'none'}
382
+ <ThumbnailGallery {canvases} />
383
+ {/if}
384
+ </div>
385
+
386
+ <!-- Bottom Area (Gallery) -->
387
+ {#if internalViewerState.showThumbnailGallery && internalViewerState.dockSide === 'bottom'}
388
+ <div
389
+ class="flex-none h-[140px] w-full pointer-events-auto relative z-20"
390
+ >
391
+ <ThumbnailGallery {canvases} />
392
+ </div>
393
+ {/if}
394
+
395
+ <!-- Bottom Area (Plugin Panels) -->
396
+ {#each internalViewerState.pluginPanels as panel (panel.id)}
397
+ {#if panel.isVisible() && panel.position === 'bottom'}
398
+ <div class="relative w-full z-40 pointer-events-auto">
399
+ <panel.component {...panel.props ?? {}} />
400
+ </div>
401
+ {/if}
402
+ {/each}
403
+ </div>
404
+
405
+ <!-- Right Column -->
406
+ {#if isRightSidebarVisible}
407
+ <div
408
+ class="flex-none flex flex-row z-20 bg-base-200 border-l border-base-300 transition-all"
409
+ >
410
+ <!-- Search Panel -->
411
+ {#if internalViewerState.showSearchPanel}
412
+ <div class="h-full relative pointer-events-auto">
413
+ <SearchPanel />
414
+ </div>
415
+ {/if}
416
+
417
+ <!-- Gallery (when docked right) -->
418
+ {#if internalViewerState.showThumbnailGallery && internalViewerState.dockSide === 'right'}
419
+ <div class="h-full w-[200px] pointer-events-auto relative">
420
+ <ThumbnailGallery {canvases} />
421
+ </div>
422
+ {/if}
423
+
424
+ <!-- Right Plugin Panels -->
425
+ {#each internalViewerState.pluginPanels as panel (panel.id)}
426
+ {#if panel.isVisible() && panel.position === 'right'}
427
+ <div class="h-full relative pointer-events-auto">
428
+ <panel.component {...panel.props ?? {}} />
429
+ </div>
430
+ {/if}
431
+ {/each}
432
+ </div>
433
+ {/if}
434
+ </div>
@@ -0,0 +1,20 @@
1
+ import { ViewerState } from '../state/viewer.svelte';
2
+ import type { TriiiceratopsPlugin } from '../types/plugin';
3
+ import type { DaisyUITheme, ThemeConfig } from '../theme/types';
4
+ import type { ViewerConfig } from '../types/config';
5
+ type $$ComponentProps = {
6
+ manifestId?: string;
7
+ canvasId?: string;
8
+ plugins?: TriiiceratopsPlugin[];
9
+ /** Built-in DaisyUI theme name. Defaults to 'light' or 'dark' based on prefers-color-scheme. */
10
+ theme?: DaisyUITheme;
11
+ /** Custom theme configuration to override the base theme's values. */
12
+ themeConfig?: ThemeConfig;
13
+ /** Configuration options for the viewer UI */
14
+ config?: ViewerConfig;
15
+ /** Bindable viewer state instance for external access (Svelte consumers) */
16
+ viewerState?: ViewerState;
17
+ };
18
+ declare const TriiiceratopsViewer: import("svelte").Component<$$ComponentProps, {}, "viewerState">;
19
+ type TriiiceratopsViewer = ReturnType<typeof TriiiceratopsViewer>;
20
+ export default TriiiceratopsViewer;
@@ -0,0 +1,139 @@
1
+ <svelte:options
2
+ customElement={{
3
+ tag: 'triiiceratops-viewer',
4
+ shadow: 'open',
5
+ props: {
6
+ manifestId: {
7
+ attribute: 'manifest-id',
8
+ type: 'String',
9
+ reflect: true,
10
+ },
11
+ canvasId: {
12
+ attribute: 'canvas-id',
13
+ type: 'String',
14
+ reflect: true,
15
+ },
16
+ theme: {
17
+ attribute: 'theme',
18
+ type: 'String',
19
+ reflect: true,
20
+ },
21
+ themeConfig: {
22
+ attribute: 'theme-config',
23
+ type: 'String',
24
+ reflect: false,
25
+ },
26
+ config: {
27
+ attribute: 'config',
28
+ type: 'String',
29
+ reflect: false,
30
+ },
31
+ },
32
+ }}
33
+ />
34
+
35
+ <script lang="ts">
36
+ import styles from '../../app.css?inline';
37
+ import TriiiceratopsViewer from './TriiiceratopsViewer.svelte';
38
+ import type { TriiiceratopsPlugin } from '../types/plugin';
39
+ import type { DaisyUITheme, ThemeConfig } from '../theme/types';
40
+ import type { ViewerConfig } from '../types/config';
41
+ import { isBuiltInTheme, parseThemeConfig } from '../theme/themeManager';
42
+ import type { ViewerState } from '../state/viewer.svelte';
43
+
44
+ let {
45
+ manifestId = '',
46
+ canvasId = '',
47
+ plugins = [],
48
+ theme = undefined as string | undefined,
49
+ themeConfig = undefined as string | ThemeConfig | undefined,
50
+ config = undefined as string | ViewerConfig | undefined,
51
+ }: {
52
+ manifestId?: string;
53
+ canvasId?: string;
54
+ plugins?: TriiiceratopsPlugin[];
55
+ /**
56
+ * Built-in DaisyUI theme name (e.g., 'light', 'dark', 'cupcake').
57
+ * When not specified, inherits the theme from the parent context.
58
+ */
59
+ theme?: string;
60
+ /**
61
+ * Custom theme configuration to override the base theme.
62
+ * Can be a JSON string (for HTML attribute) or ThemeConfig object (for JS property).
63
+ * @example HTML: theme-config='{"primary":"#3b82f6","radiusBox":"0.5rem"}'
64
+ * @example JS: element.themeConfig = { primary: '#3b82f6', radiusBox: '0.5rem' }
65
+ */
66
+ themeConfig?: string | ThemeConfig;
67
+ /**
68
+ * Configuration options for the viewer UI.
69
+ */
70
+ config?: string | ViewerConfig;
71
+ } = $props();
72
+
73
+ // Reference to host element for event dispatch
74
+ let hostElement: HTMLElement;
75
+
76
+ // ViewerState from the inner component (via bindable prop)
77
+ let internalViewerState: ViewerState | undefined = $state();
78
+
79
+ // Track if we've already wired up the event target (only do once)
80
+ let eventTargetSet = false;
81
+
82
+ // Wire up eventTarget when viewerState is available - only once
83
+ $effect(() => {
84
+ if (internalViewerState && hostElement && !eventTargetSet) {
85
+ eventTargetSet = true;
86
+ internalViewerState.setEventTarget(hostElement);
87
+ }
88
+ });
89
+
90
+ // Validate and convert theme string to DaisyUITheme type
91
+ let validatedTheme = $derived.by((): DaisyUITheme | undefined => {
92
+ if (!theme) return undefined;
93
+ if (isBuiltInTheme(theme)) return theme;
94
+ console.warn(`Invalid theme "${theme}". Using inherited theme.`);
95
+ return undefined;
96
+ });
97
+
98
+ // Parse themeConfig if it's a JSON string, pass through if it's already an object
99
+ let parsedThemeConfig = $derived.by((): ThemeConfig | undefined => {
100
+ if (!themeConfig) return undefined;
101
+ if (typeof themeConfig === 'string') {
102
+ const parsed = parseThemeConfig(themeConfig);
103
+ if (!parsed) {
104
+ console.warn(
105
+ `Invalid theme-config JSON: "${themeConfig}". Ignoring.`,
106
+ );
107
+ }
108
+ return parsed ?? undefined;
109
+ }
110
+ return themeConfig;
111
+ });
112
+ // Parse config if it's a JSON string, pass through if it's already an object
113
+ let parsedConfig = $derived.by((): ViewerConfig | undefined => {
114
+ if (!config) return undefined;
115
+ if (typeof config === 'string') {
116
+ try {
117
+ return JSON.parse(config);
118
+ } catch (e) {
119
+ console.warn(`Invalid config JSON: "${config}". Ignoring.`);
120
+ return undefined;
121
+ }
122
+ }
123
+ return config;
124
+ });
125
+ </script>
126
+
127
+ {@html `<style>${styles}</style>`}
128
+
129
+ <div bind:this={hostElement} class="w-full h-full">
130
+ <TriiiceratopsViewer
131
+ {manifestId}
132
+ {canvasId}
133
+ {plugins}
134
+ theme={validatedTheme}
135
+ themeConfig={parsedThemeConfig}
136
+ config={parsedConfig}
137
+ bind:viewerState={internalViewerState}
138
+ />
139
+ </div>
@@ -0,0 +1,27 @@
1
+ import type { TriiiceratopsPlugin } from '../types/plugin';
2
+ import type { ThemeConfig } from '../theme/types';
3
+ import type { ViewerConfig } from '../types/config';
4
+ type $$ComponentProps = {
5
+ manifestId?: string;
6
+ canvasId?: string;
7
+ plugins?: TriiiceratopsPlugin[];
8
+ /**
9
+ * Built-in DaisyUI theme name (e.g., 'light', 'dark', 'cupcake').
10
+ * When not specified, inherits the theme from the parent context.
11
+ */
12
+ theme?: string;
13
+ /**
14
+ * Custom theme configuration to override the base theme.
15
+ * Can be a JSON string (for HTML attribute) or ThemeConfig object (for JS property).
16
+ * @example HTML: theme-config='{"primary":"#3b82f6","radiusBox":"0.5rem"}'
17
+ * @example JS: element.themeConfig = { primary: '#3b82f6', radiusBox: '0.5rem' }
18
+ */
19
+ themeConfig?: string | ThemeConfig;
20
+ /**
21
+ * Configuration options for the viewer UI.
22
+ */
23
+ config?: string | ViewerConfig;
24
+ };
25
+ declare const TriiiceratopsViewerElement: import("svelte").Component<$$ComponentProps, {}, "">;
26
+ type TriiiceratopsViewerElement = ReturnType<typeof TriiiceratopsViewerElement>;
27
+ export default TriiiceratopsViewerElement;