@happyvertical/smrt-assets 0.30.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 (119) hide show
  1. package/AGENTS.md +78 -0
  2. package/CLAUDE.md +1 -0
  3. package/LICENSE +7 -0
  4. package/README.md +136 -0
  5. package/dist/__smrt-register__.d.ts +2 -0
  6. package/dist/__smrt-register__.d.ts.map +1 -0
  7. package/dist/asset-association.d.ts +16 -0
  8. package/dist/asset-association.d.ts.map +1 -0
  9. package/dist/asset-associations.d.ts +27 -0
  10. package/dist/asset-associations.d.ts.map +1 -0
  11. package/dist/asset-capabilities.d.ts +137 -0
  12. package/dist/asset-capabilities.d.ts.map +1 -0
  13. package/dist/asset-conventions.d.ts +76 -0
  14. package/dist/asset-conventions.d.ts.map +1 -0
  15. package/dist/asset-metafield.d.ts +27 -0
  16. package/dist/asset-metafield.d.ts.map +1 -0
  17. package/dist/asset-metafields.d.ts +27 -0
  18. package/dist/asset-metafields.d.ts.map +1 -0
  19. package/dist/asset-runtime.d.ts +218 -0
  20. package/dist/asset-runtime.d.ts.map +1 -0
  21. package/dist/asset-serving.d.ts +146 -0
  22. package/dist/asset-serving.d.ts.map +1 -0
  23. package/dist/asset-status.d.ts +15 -0
  24. package/dist/asset-status.d.ts.map +1 -0
  25. package/dist/asset-statuses.d.ts +25 -0
  26. package/dist/asset-statuses.d.ts.map +1 -0
  27. package/dist/asset-store.d.ts +200 -0
  28. package/dist/asset-store.d.ts.map +1 -0
  29. package/dist/asset-type.d.ts +15 -0
  30. package/dist/asset-type.d.ts.map +1 -0
  31. package/dist/asset-types.d.ts +28 -0
  32. package/dist/asset-types.d.ts.map +1 -0
  33. package/dist/asset.d.ts +158 -0
  34. package/dist/asset.d.ts.map +1 -0
  35. package/dist/assets.d.ts +125 -0
  36. package/dist/assets.d.ts.map +1 -0
  37. package/dist/folder.d.ts +16 -0
  38. package/dist/folder.d.ts.map +1 -0
  39. package/dist/folders.d.ts +45 -0
  40. package/dist/folders.d.ts.map +1 -0
  41. package/dist/index.d.ts +21 -0
  42. package/dist/index.d.ts.map +1 -0
  43. package/dist/index.js +2285 -0
  44. package/dist/index.js.map +1 -0
  45. package/dist/manifest.json +4079 -0
  46. package/dist/media-bundle-persistence.d.ts +99 -0
  47. package/dist/media-bundle-persistence.d.ts.map +1 -0
  48. package/dist/owned-asset-helpers.d.ts +20 -0
  49. package/dist/owned-asset-helpers.d.ts.map +1 -0
  50. package/dist/playground.d.ts +2 -0
  51. package/dist/playground.d.ts.map +1 -0
  52. package/dist/playground.js +127 -0
  53. package/dist/playground.js.map +1 -0
  54. package/dist/smrt-knowledge.json +1922 -0
  55. package/dist/svelte/ActionBar.svelte +203 -0
  56. package/dist/svelte/ActionBar.svelte.d.ts +5 -0
  57. package/dist/svelte/ActionBar.svelte.d.ts.map +1 -0
  58. package/dist/svelte/AssetDetail.svelte +521 -0
  59. package/dist/svelte/AssetDetail.svelte.d.ts +35 -0
  60. package/dist/svelte/AssetDetail.svelte.d.ts.map +1 -0
  61. package/dist/svelte/AssetGrid.svelte +351 -0
  62. package/dist/svelte/AssetGrid.svelte.d.ts +5 -0
  63. package/dist/svelte/AssetGrid.svelte.d.ts.map +1 -0
  64. package/dist/svelte/AssetList.svelte +436 -0
  65. package/dist/svelte/AssetList.svelte.d.ts +5 -0
  66. package/dist/svelte/AssetList.svelte.d.ts.map +1 -0
  67. package/dist/svelte/AssetManager.svelte +381 -0
  68. package/dist/svelte/AssetManager.svelte.d.ts +5 -0
  69. package/dist/svelte/AssetManager.svelte.d.ts.map +1 -0
  70. package/dist/svelte/AssetToolbar.svelte +388 -0
  71. package/dist/svelte/AssetToolbar.svelte.d.ts +5 -0
  72. package/dist/svelte/AssetToolbar.svelte.d.ts.map +1 -0
  73. package/dist/svelte/CreateAssetModal.svelte +373 -0
  74. package/dist/svelte/CreateAssetModal.svelte.d.ts +19 -0
  75. package/dist/svelte/CreateAssetModal.svelte.d.ts.map +1 -0
  76. package/dist/svelte/__tests__/ActionBar.test.js +72 -0
  77. package/dist/svelte/__tests__/AssetDetail.test.js +57 -0
  78. package/dist/svelte/__tests__/AssetGrid.test.js +69 -0
  79. package/dist/svelte/__tests__/AssetList.test.js +72 -0
  80. package/dist/svelte/__tests__/AssetManager.test.js +21 -0
  81. package/dist/svelte/__tests__/AssetManagerRoute.test.js +16 -0
  82. package/dist/svelte/__tests__/AssetToolbar.test.js +39 -0
  83. package/dist/svelte/__tests__/CreateAssetModal.test.js +42 -0
  84. package/dist/svelte/i18n.d.ts +76 -0
  85. package/dist/svelte/i18n.d.ts.map +1 -0
  86. package/dist/svelte/i18n.js +87 -0
  87. package/dist/svelte/index.d.ts +19 -0
  88. package/dist/svelte/index.d.ts.map +1 -0
  89. package/dist/svelte/index.js +30 -0
  90. package/dist/svelte/playground/AssetDetailPreview.svelte +131 -0
  91. package/dist/svelte/playground/AssetDetailPreview.svelte.d.ts +8 -0
  92. package/dist/svelte/playground/AssetDetailPreview.svelte.d.ts.map +1 -0
  93. package/dist/svelte/playground/CreateAssetModalPreview.svelte +151 -0
  94. package/dist/svelte/playground/CreateAssetModalPreview.svelte.d.ts +4 -0
  95. package/dist/svelte/playground/CreateAssetModalPreview.svelte.d.ts.map +1 -0
  96. package/dist/svelte/playground.d.ts +60 -0
  97. package/dist/svelte/playground.d.ts.map +1 -0
  98. package/dist/svelte/playground.js +93 -0
  99. package/dist/svelte/routes/AssetManagerRoute.svelte +209 -0
  100. package/dist/svelte/routes/AssetManagerRoute.svelte.d.ts +4 -0
  101. package/dist/svelte/routes/AssetManagerRoute.svelte.d.ts.map +1 -0
  102. package/dist/svelte/routes/index.d.ts +2 -0
  103. package/dist/svelte/routes/index.d.ts.map +1 -0
  104. package/dist/svelte/routes/index.js +1 -0
  105. package/dist/svelte/routes/shared.d.ts +25 -0
  106. package/dist/svelte/routes/shared.d.ts.map +1 -0
  107. package/dist/svelte/routes/shared.js +31 -0
  108. package/dist/svelte/types.d.ts +179 -0
  109. package/dist/svelte/types.d.ts.map +1 -0
  110. package/dist/svelte/types.js +6 -0
  111. package/dist/types.d.ts +80 -0
  112. package/dist/types.d.ts.map +1 -0
  113. package/dist/types.js +2 -0
  114. package/dist/types.js.map +1 -0
  115. package/dist/ui.d.ts +10 -0
  116. package/dist/ui.d.ts.map +1 -0
  117. package/dist/ui.js +85 -0
  118. package/dist/ui.js.map +1 -0
  119. package/package.json +102 -0
@@ -0,0 +1,521 @@
1
+ <script lang="ts">
2
+ /**
3
+ * AssetDetail - Detail drawer/modal for a single asset
4
+ *
5
+ * Shows preview, SEO fields (alt text, title, caption, description),
6
+ * metadata, content references ("Used In"), copy utilities, and actions.
7
+ */
8
+
9
+ import { Modal } from '@happyvertical/smrt-ui/feedback';
10
+ import { useI18n } from '@happyvertical/smrt-ui/i18n';
11
+ import { Button } from '@happyvertical/smrt-ui/ui';
12
+ import type { Snippet } from 'svelte';
13
+ import { M } from './i18n.js';
14
+ import type { PersistedAsset } from './types';
15
+
16
+ const { t } = useI18n();
17
+
18
+ export interface AssetDetailProps {
19
+ /** The asset to display */
20
+ asset: PersistedAsset | null;
21
+ /** Whether the detail view is open */
22
+ open: boolean;
23
+ /** Callback when detail is closed */
24
+ onClose?: () => void;
25
+ onclose?: () => void;
26
+ /** Callback when asset is updated (save metadata) */
27
+ onSave?: (
28
+ asset: PersistedAsset,
29
+ updates: AssetDetailUpdates,
30
+ ) => void | Promise<void>;
31
+ onsave?: (
32
+ asset: PersistedAsset,
33
+ updates: AssetDetailUpdates,
34
+ ) => void | Promise<void>;
35
+ /** Callback when asset is deleted */
36
+ onDelete?: (asset: PersistedAsset) => void;
37
+ ondelete?: (asset: PersistedAsset) => void;
38
+ /** Callback to open the image editor */
39
+ onEdit?: (asset: PersistedAsset) => void;
40
+ onedit?: (asset: PersistedAsset) => void;
41
+ /** Content references snippet (injected by smrt-content) */
42
+ contentReferences?: Snippet<[{ assetId: string }]>;
43
+ }
44
+
45
+ export interface AssetDetailUpdates {
46
+ name?: string;
47
+ description?: string;
48
+ alt?: string;
49
+ title?: string;
50
+ caption?: string;
51
+ }
52
+
53
+ let {
54
+ asset,
55
+ open,
56
+ onClose,
57
+ onclose,
58
+ onSave,
59
+ onsave,
60
+ onDelete,
61
+ ondelete,
62
+ onEdit,
63
+ onedit,
64
+ contentReferences,
65
+ }: AssetDetailProps = $props();
66
+
67
+ let saving = $state(false);
68
+ let copyFeedback = $state('');
69
+
70
+ // Editable fields (synced from asset)
71
+ let editName = $state('');
72
+ let editDescription = $state('');
73
+ let editAlt = $state('');
74
+
75
+ // Sync from asset
76
+ $effect(() => {
77
+ if (asset) {
78
+ editName = asset.name || '';
79
+ editDescription = asset.description || '';
80
+ editAlt = (asset as any).alt || '';
81
+ }
82
+ });
83
+
84
+ function handleClose() {
85
+ (onclose ?? onClose)?.();
86
+ }
87
+
88
+ async function handleSave() {
89
+ if (!asset) return;
90
+ saving = true;
91
+ try {
92
+ await (onsave ?? onSave)?.(asset, {
93
+ name: editName,
94
+ description: editDescription,
95
+ alt: editAlt,
96
+ });
97
+ } finally {
98
+ saving = false;
99
+ }
100
+ }
101
+
102
+ function handleDelete() {
103
+ if (!asset) return;
104
+ (ondelete ?? onDelete)?.(asset);
105
+ }
106
+
107
+ function handleEdit() {
108
+ if (!asset) return;
109
+ (onedit ?? onEdit)?.(asset);
110
+ }
111
+
112
+ async function copyToClipboard(text: string, label: string) {
113
+ try {
114
+ await navigator.clipboard.writeText(text);
115
+ copyFeedback = `${label} copied!`;
116
+ setTimeout(() => {
117
+ copyFeedback = '';
118
+ }, 2000);
119
+ } catch {
120
+ copyFeedback = 'Copy failed';
121
+ setTimeout(() => {
122
+ copyFeedback = '';
123
+ }, 2000);
124
+ }
125
+ }
126
+
127
+ function copyUrl() {
128
+ if (asset?.sourceUri) {
129
+ copyToClipboard(asset.sourceUri, 'URL');
130
+ }
131
+ }
132
+
133
+ function copyMarkdown() {
134
+ if (asset?.sourceUri) {
135
+ const alt = (asset as any).alt || asset.name || 'image';
136
+ copyToClipboard(`![${alt}](${asset.sourceUri})`, 'Markdown');
137
+ }
138
+ }
139
+
140
+ const isImage = $derived(asset?.mimeType?.startsWith('image/') ?? false);
141
+ const isVideo = $derived(asset?.mimeType?.startsWith('video/') ?? false);
142
+ const isAudio = $derived(asset?.mimeType?.startsWith('audio/') ?? false);
143
+ const isPdf = $derived(asset?.mimeType?.includes('pdf') ?? false);
144
+ const missingAlt = $derived(isImage && !editAlt);
145
+ const fileSizeWarning = $derived(false); // file size not directly on Asset; can be enhanced later
146
+
147
+ function formatDate(date: Date | string | undefined): string {
148
+ if (!date) return '—';
149
+ const d = date instanceof Date ? date : new Date(date);
150
+ return d.toLocaleDateString(undefined, {
151
+ weekday: 'short',
152
+ month: 'short',
153
+ day: 'numeric',
154
+ year: 'numeric',
155
+ hour: '2-digit',
156
+ minute: '2-digit',
157
+ });
158
+ }
159
+ </script>
160
+
161
+ {#if asset}
162
+ <!-- Shell consolidated onto the library Modal (S10 #1415): title + close X,
163
+ backdrop, Esc, and showModal lifecycle come from Modal. `placement="end"`
164
+ preserves the original right-anchored detail-drawer presentation; this
165
+ file owns only the asset-detail body + action footer. -->
166
+ <Modal
167
+ open={open}
168
+ title={asset.name || 'Untitled Asset'}
169
+ onClose={handleClose}
170
+ size="md"
171
+ placement="end"
172
+ >
173
+ <div class="detail__body">
174
+ <!-- Preview Section -->
175
+ <section class="detail__section">
176
+ <div class="detail__preview">
177
+ {#if isImage && asset.sourceUri}
178
+ <img src={asset.sourceUri} alt={editAlt || asset.name} class="preview-image" />
179
+ {:else if isVideo && asset.sourceUri}
180
+ <!-- svelte-ignore a11y_media_has_caption -->
181
+ <video src={asset.sourceUri} controls class="preview-video"></video>
182
+ {:else if isAudio && asset.sourceUri}
183
+ <audio src={asset.sourceUri} controls class="preview-audio"></audio>
184
+ {:else if isPdf && asset.sourceUri}
185
+ <div class="preview-document">
186
+ <span class="preview-document__icon">📄</span>
187
+ <a href={asset.sourceUri} target="_blank" rel="noopener noreferrer" class="preview-document__link">{t(M['assets.asset_detail.open_pdf_in_new_tab'])}</a>
188
+ </div>
189
+ {:else}
190
+ <div class="preview-generic">
191
+ <span class="preview-generic__icon">📎</span>
192
+ <span class="preview-generic__mime">{asset.mimeType || 'Unknown type'}</span>
193
+ </div>
194
+ {/if}
195
+ </div>
196
+ </section>
197
+
198
+ <!-- SEO & Accessibility -->
199
+ <section class="detail__section">
200
+ <h3 class="section-heading">SEO & Accessibility</h3>
201
+
202
+ <div class="detail__form">
203
+ <div class="form-field">
204
+ <label for="detail-name" class="form-label">Name</label>
205
+ <input id="detail-name" type="text" class="form-input" bind:value={editName} />
206
+ </div>
207
+
208
+ {#if isImage}
209
+ <div class="form-field">
210
+ <label for="detail-alt" class="form-label">
211
+ {t(M['assets.asset_detail.alt_text'])}
212
+ {#if missingAlt}
213
+ <span class="label-warning">{t(M['assets.asset_detail.alt_text_missing_warning'])}</span>
214
+ {/if}
215
+ </label>
216
+ <input id="detail-alt" type="text" class="form-input" bind:value={editAlt} placeholder={t(M['assets.asset_detail.alt_text_placeholder'])} />
217
+ </div>
218
+ {/if}
219
+
220
+ <div class="form-field">
221
+ <label for="detail-desc" class="form-label">Description</label>
222
+ <textarea id="detail-desc" class="form-textarea" bind:value={editDescription} rows="3" placeholder={t(M['assets.asset_detail.description_placeholder'])}></textarea>
223
+ </div>
224
+ </div>
225
+ </section>
226
+
227
+ <!-- Metadata -->
228
+ <section class="detail__section">
229
+ <h3 class="section-heading">Metadata</h3>
230
+ <div class="metadata-grid">
231
+ <div class="metadata-item">
232
+ <span class="metadata-label">Type</span>
233
+ <span class="metadata-value">{asset.mimeType || '—'}</span>
234
+ </div>
235
+ <div class="metadata-item">
236
+ <span class="metadata-label">Status</span>
237
+ <span class="metadata-value">{asset.statusSlug || 'draft'}</span>
238
+ </div>
239
+ <div class="metadata-item">
240
+ <span class="metadata-label">Version</span>
241
+ <span class="metadata-value">{asset.version}</span>
242
+ </div>
243
+ <div class="metadata-item">
244
+ <span class="metadata-label">Created</span>
245
+ <span class="metadata-value">{formatDate(asset.createdAt)}</span>
246
+ </div>
247
+ <div class="metadata-item">
248
+ <span class="metadata-label">Updated</span>
249
+ <span class="metadata-value">{formatDate(asset.updatedAt)}</span>
250
+ </div>
251
+ {#if isImage && (asset as any).width}
252
+ <div class="metadata-item">
253
+ <span class="metadata-label">Dimensions</span>
254
+ <span class="metadata-value">{(asset as any).width} × {(asset as any).height}px</span>
255
+ </div>
256
+ {/if}
257
+ </div>
258
+ </section>
259
+
260
+ <!-- Quick Actions -->
261
+ <section class="detail__section">
262
+ <h3 class="section-heading">{t(M['assets.asset_detail.quick_actions'])}</h3>
263
+ <div class="quick-actions">
264
+ <button type="button" class="quick-btn" onclick={copyUrl} disabled={!asset.sourceUri}>
265
+ {t(M['assets.asset_detail.copy_url'])}
266
+ </button>
267
+ {#if isImage}
268
+ <button type="button" class="quick-btn" onclick={copyMarkdown} disabled={!asset.sourceUri}>
269
+ {t(M['assets.asset_detail.copy_markdown'])}
270
+ </button>
271
+ {/if}
272
+ {#if onedit && isImage}
273
+ <button type="button" class="quick-btn" onclick={handleEdit}>
274
+ {t(M['assets.asset_detail.edit_image'])}
275
+ </button>
276
+ {/if}
277
+ </div>
278
+ {#if copyFeedback}
279
+ <div class="copy-feedback">{copyFeedback}</div>
280
+ {/if}
281
+ </section>
282
+
283
+ <!-- Content References (injected) -->
284
+ {#if contentReferences && asset.id}
285
+ <section class="detail__section">
286
+ <h3 class="section-heading">{t(M['assets.asset_detail.used_in'])}</h3>
287
+ {@render contentReferences({ assetId: asset.id })}
288
+ </section>
289
+ {/if}
290
+ </div>
291
+
292
+ {#snippet footer()}
293
+ <div class="detail-footer">
294
+ <Button variant="danger" onclick={handleDelete}>Delete</Button>
295
+ <div class="detail-footer__right">
296
+ <Button variant="ghost" onclick={handleClose}>Cancel</Button>
297
+ <Button variant="primary" onclick={handleSave} disabled={saving}>
298
+ {#if saving}Saving...{:else}Save{/if}
299
+ </Button>
300
+ </div>
301
+ </div>
302
+ {/snippet}
303
+ </Modal>
304
+ {/if}
305
+
306
+ <style>
307
+ /* Body — negative margin cancels Modal's .modal__body padding so the section
308
+ dividers stay full-bleed (Modal owns the dialog shell + scroll container). */
309
+ .detail__body {
310
+ margin: calc(-1 * var(--smrt-spacing-5, 1.25rem));
311
+ }
312
+
313
+ .detail__section {
314
+ padding: var(--smrt-spacing-4, 1rem) var(--smrt-spacing-5, 1.25rem);
315
+ border-bottom: 1px solid var(--smrt-color-outline-variant, #f3f4f6);
316
+ }
317
+
318
+ .detail__section:last-child {
319
+ border-bottom: none;
320
+ }
321
+
322
+ .section-heading {
323
+ margin: 0 0 var(--smrt-spacing-3, 0.75rem);
324
+ font-weight: var(--smrt-typography-weight-semibold, 600);
325
+ color: var(--smrt-color-on-surface, #111827);
326
+ text-transform: uppercase;
327
+ letter-spacing: var(--smrt-typography-label-medium-tracking, 0.05em);
328
+ font-size: var(--smrt-typography-label-medium-size, 0.75rem);
329
+ }
330
+
331
+ /* Preview */
332
+ .detail__preview {
333
+ border-radius: var(--smrt-radius-medium, 0.5rem);
334
+ overflow: hidden;
335
+ background: var(--smrt-color-surface-container-low, #f9fafb);
336
+ }
337
+
338
+ .preview-image {
339
+ width: 100%;
340
+ max-height: 300px;
341
+ object-fit: contain;
342
+ display: block;
343
+ }
344
+
345
+ .preview-video {
346
+ width: 100%;
347
+ max-height: 300px;
348
+ display: block;
349
+ }
350
+
351
+ .preview-audio {
352
+ width: 100%;
353
+ display: block;
354
+ padding: var(--smrt-spacing-4, 1rem);
355
+ }
356
+
357
+ .preview-document, .preview-generic {
358
+ display: flex;
359
+ flex-direction: column;
360
+ align-items: center;
361
+ padding: var(--smrt-spacing-8, 2rem);
362
+ gap: var(--smrt-spacing-2, 0.5rem);
363
+ }
364
+
365
+ .preview-document__icon, .preview-generic__icon {
366
+ font-size: var(--smrt-typography-display-medium-size, 2.5rem);
367
+ }
368
+
369
+ .preview-document__link {
370
+ color: var(--smrt-color-primary, #005ac1);
371
+ font-weight: var(--smrt-typography-weight-medium, 500);
372
+ text-decoration: none;
373
+ }
374
+
375
+ .preview-document__link:hover {
376
+ text-decoration: underline;
377
+ }
378
+
379
+ .preview-generic__mime {
380
+ font-size: var(--smrt-typography-body-small-size, 0.8rem);
381
+ color: var(--smrt-color-on-surface-variant, #6b7280);
382
+ }
383
+
384
+ /* Form */
385
+ .detail__form {
386
+ display: flex;
387
+ flex-direction: column;
388
+ gap: var(--smrt-spacing-3, 0.75rem);
389
+ }
390
+
391
+ .form-field {
392
+ display: flex;
393
+ flex-direction: column;
394
+ gap: var(--smrt-spacing-1, 0.25rem);
395
+ }
396
+
397
+ .form-label {
398
+ font-size: var(--smrt-typography-label-medium-size, 0.8rem);
399
+ font-weight: var(--smrt-typography-weight-medium, 500);
400
+ color: var(--smrt-color-on-surface, #111827);
401
+ }
402
+
403
+ .label-warning {
404
+ font-weight: var(--smrt-typography-weight-normal, 400);
405
+ font-size: var(--smrt-typography-label-small-size, 0.7rem);
406
+ color: var(--smrt-color-error, #dc2626);
407
+ margin-left: var(--smrt-spacing-1, 4px);
408
+ }
409
+
410
+ .form-input, .form-textarea {
411
+ width: 100%;
412
+ padding: var(--smrt-spacing-2, 0.5rem) var(--smrt-spacing-3, 0.75rem);
413
+ border: 1px solid var(--smrt-color-outline-variant, #e5e7eb);
414
+ border-radius: var(--smrt-radius-medium, 0.5rem);
415
+ font-family: inherit;
416
+ font-size: var(--smrt-typography-body-medium-size, 0.875rem);
417
+ color: var(--smrt-color-on-surface, #111827);
418
+ background: var(--smrt-color-surface, #ffffff);
419
+ box-sizing: border-box;
420
+ }
421
+
422
+ .form-input:focus, .form-textarea:focus {
423
+ outline: none;
424
+ border-color: var(--smrt-color-primary, #005ac1);
425
+ box-shadow: 0 0 0 2px var(--smrt-color-primary-container, rgba(0, 90, 193, 0.1));
426
+ }
427
+
428
+ .form-textarea {
429
+ resize: vertical;
430
+ min-height: 60px;
431
+ }
432
+
433
+ /* Metadata */
434
+ .metadata-grid {
435
+ display: grid;
436
+ grid-template-columns: 1fr 1fr;
437
+ gap: var(--smrt-spacing-2, 0.5rem);
438
+ }
439
+
440
+ .metadata-item {
441
+ display: flex;
442
+ flex-direction: column;
443
+ gap: var(--smrt-spacing-1, 4px);
444
+ }
445
+
446
+ .metadata-label {
447
+ font-size: var(--smrt-typography-label-small-size, 0.7rem);
448
+ font-weight: var(--smrt-typography-weight-semibold, 600);
449
+ text-transform: uppercase;
450
+ letter-spacing: var(--smrt-typography-label-small-tracking, 0.05em);
451
+ color: var(--smrt-color-on-surface-variant, #6b7280);
452
+ }
453
+
454
+ .metadata-value {
455
+ font-size: var(--smrt-typography-body-medium-size, 0.875rem);
456
+ color: var(--smrt-color-on-surface, #111827);
457
+ word-break: break-word;
458
+ }
459
+
460
+ /* Quick Actions */
461
+ .quick-actions {
462
+ display: flex;
463
+ flex-wrap: wrap;
464
+ gap: var(--smrt-spacing-2, 0.5rem);
465
+ }
466
+
467
+ .quick-btn {
468
+ display: inline-flex;
469
+ align-items: center;
470
+ gap: var(--smrt-spacing-1, 4px);
471
+ height: 32px;
472
+ padding: 0 var(--smrt-spacing-3, 0.75rem);
473
+ font-family: inherit;
474
+ font-size: var(--smrt-typography-label-medium-size, 0.8rem);
475
+ font-weight: var(--smrt-typography-weight-medium, 500);
476
+ border: 1px solid var(--smrt-color-outline-variant, #e5e7eb);
477
+ background: var(--smrt-color-surface, #ffffff);
478
+ color: var(--smrt-color-on-surface, #111827);
479
+ border-radius: var(--smrt-radius-medium, 0.5rem);
480
+ cursor: pointer;
481
+ transition: all 150ms ease;
482
+ }
483
+
484
+ .quick-btn:hover:not(:disabled) {
485
+ background: var(--smrt-color-surface-container-low, #f9fafb);
486
+ box-shadow: var(--smrt-elevation-1);
487
+ }
488
+
489
+ .quick-btn:disabled {
490
+ opacity: 0.5;
491
+ cursor: not-allowed;
492
+ }
493
+
494
+ .copy-feedback {
495
+ margin-top: var(--smrt-spacing-2, 0.5rem);
496
+ font-size: var(--smrt-typography-label-medium-size, 0.8rem);
497
+ color: var(--smrt-color-success, #22c55e);
498
+ font-weight: var(--smrt-typography-weight-medium, 500);
499
+ animation: fadeIn 150ms ease;
500
+ }
501
+
502
+ @keyframes fadeIn {
503
+ from { opacity: 0; }
504
+ to { opacity: 1; }
505
+ }
506
+
507
+ /* Footer — split layout: destructive Delete on the left, Cancel/Save on the
508
+ right. The buttons are library <Button>s; only the split layout is local. */
509
+ .detail-footer {
510
+ display: flex;
511
+ align-items: center;
512
+ justify-content: space-between;
513
+ width: 100%;
514
+ gap: var(--smrt-spacing-2, 0.5rem);
515
+ }
516
+
517
+ .detail-footer__right {
518
+ display: flex;
519
+ gap: var(--smrt-spacing-2, 0.5rem);
520
+ }
521
+ </style>
@@ -0,0 +1,35 @@
1
+ import type { Snippet } from 'svelte';
2
+ import type { PersistedAsset } from './types';
3
+ export interface AssetDetailUpdates {
4
+ name?: string;
5
+ description?: string;
6
+ alt?: string;
7
+ title?: string;
8
+ caption?: string;
9
+ }
10
+ export interface AssetDetailProps {
11
+ /** The asset to display */
12
+ asset: PersistedAsset | null;
13
+ /** Whether the detail view is open */
14
+ open: boolean;
15
+ /** Callback when detail is closed */
16
+ onClose?: () => void;
17
+ onclose?: () => void;
18
+ /** Callback when asset is updated (save metadata) */
19
+ onSave?: (asset: PersistedAsset, updates: AssetDetailUpdates) => void | Promise<void>;
20
+ onsave?: (asset: PersistedAsset, updates: AssetDetailUpdates) => void | Promise<void>;
21
+ /** Callback when asset is deleted */
22
+ onDelete?: (asset: PersistedAsset) => void;
23
+ ondelete?: (asset: PersistedAsset) => void;
24
+ /** Callback to open the image editor */
25
+ onEdit?: (asset: PersistedAsset) => void;
26
+ onedit?: (asset: PersistedAsset) => void;
27
+ /** Content references snippet (injected by smrt-content) */
28
+ contentReferences?: Snippet<[{
29
+ assetId: string;
30
+ }]>;
31
+ }
32
+ declare const AssetDetail: import("svelte").Component<AssetDetailProps, {}, "">;
33
+ type AssetDetail = ReturnType<typeof AssetDetail>;
34
+ export default AssetDetail;
35
+ //# sourceMappingURL=AssetDetail.svelte.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AssetDetail.svelte.d.ts","sourceRoot":"","sources":["../../src/svelte/AssetDetail.svelte.ts"],"names":[],"mappings":"AAYA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAC;AAEtC,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AAG9C,MAAM,WAAW,kBAAkB;IACjC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,gBAAgB;IAC/B,2BAA2B;IAC3B,KAAK,EAAE,cAAc,GAAG,IAAI,CAAC;IAC7B,sCAAsC;IACtC,IAAI,EAAE,OAAO,CAAC;IACd,qCAAqC;IACrC,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,qDAAqD;IACrD,MAAM,CAAC,EAAE,CACP,KAAK,EAAE,cAAc,EACrB,OAAO,EAAE,kBAAkB,KACxB,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC1B,MAAM,CAAC,EAAE,CACP,KAAK,EAAE,cAAc,EACrB,OAAO,EAAE,kBAAkB,KACxB,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC1B,qCAAqC;IACrC,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,cAAc,KAAK,IAAI,CAAC;IAC3C,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,cAAc,KAAK,IAAI,CAAC;IAC3C,wCAAwC;IACxC,MAAM,CAAC,EAAE,CAAC,KAAK,EAAE,cAAc,KAAK,IAAI,CAAC;IACzC,MAAM,CAAC,EAAE,CAAC,KAAK,EAAE,cAAc,KAAK,IAAI,CAAC;IACzC,4DAA4D;IAC5D,iBAAiB,CAAC,EAAE,OAAO,CAAC,CAAC;QAAE,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC,CAAC;CACpD;AAuQD,QAAA,MAAM,WAAW,sDAAwC,CAAC;AAC1D,KAAK,WAAW,GAAG,UAAU,CAAC,OAAO,WAAW,CAAC,CAAC;AAClD,eAAe,WAAW,CAAC"}