@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,351 @@
1
+ <script lang="ts">
2
+ /**
3
+ * AssetGrid - Thumbnail grid view for assets
4
+ *
5
+ * Displays assets as cards in a responsive grid, with selection checkboxes,
6
+ * type-based thumbnails, and missing alt-text warnings for images.
7
+ */
8
+
9
+ import { useI18n } from '@happyvertical/smrt-ui/i18n';
10
+ import { M } from './i18n.js';
11
+ import type { AssetGridProps, PersistedAsset } from './types';
12
+
13
+ const { t } = useI18n();
14
+
15
+ let {
16
+ assets,
17
+ selectedIds,
18
+ onSelectionChange,
19
+ onAssetClick,
20
+ onAssetDblClick,
21
+ loading = false,
22
+ mode = 'manage',
23
+ }: AssetGridProps = $props();
24
+
25
+ // A card click opens the detail view in manage mode, or toggles selection in
26
+ // pick mode — label the action accordingly.
27
+ function openCardLabel(name: string): string {
28
+ return mode === 'pick'
29
+ ? t(M['assets.asset_grid.select_named'], { name })
30
+ : t(M['assets.asset_grid.open_named'], { name });
31
+ }
32
+
33
+ function toggleSelection(asset: PersistedAsset, event: Event) {
34
+ event.stopPropagation();
35
+ const next = new Set(selectedIds);
36
+ if (next.has(asset.id)) {
37
+ next.delete(asset.id);
38
+ } else {
39
+ next.add(asset.id);
40
+ }
41
+ onSelectionChange(next);
42
+ }
43
+
44
+ function handleClick(asset: PersistedAsset) {
45
+ onAssetClick(asset);
46
+ }
47
+
48
+ function handleDblClick(asset: PersistedAsset) {
49
+ onAssetDblClick?.(asset);
50
+ }
51
+
52
+ function isImage(asset: PersistedAsset): boolean {
53
+ return asset.mimeType?.startsWith('image/') ?? false;
54
+ }
55
+
56
+ function isVideo(asset: PersistedAsset): boolean {
57
+ return asset.mimeType?.startsWith('video/') ?? false;
58
+ }
59
+
60
+ function isAudio(asset: PersistedAsset): boolean {
61
+ return asset.mimeType?.startsWith('audio/') ?? false;
62
+ }
63
+
64
+ /** True if it's an image-like asset missing alt text */
65
+ function isMissingAlt(asset: PersistedAsset): boolean {
66
+ return isImage(asset) && !getAltText(asset);
67
+ }
68
+
69
+ function getTypeIcon(asset: PersistedAsset): string {
70
+ if (isVideo(asset)) return '🎬';
71
+ if (isAudio(asset)) return '🎵';
72
+ if (asset.mimeType?.includes('pdf')) return '📄';
73
+ if (asset.mimeType?.startsWith('text/')) return '📝';
74
+ return '📎';
75
+ }
76
+
77
+ function getAltText(asset: PersistedAsset): string {
78
+ return 'alt' in asset && typeof asset.alt === 'string' ? asset.alt : '';
79
+ }
80
+ </script>
81
+
82
+ <div class="asset-grid" class:asset-grid--loading={loading}>
83
+ {#if loading}
84
+ <div class="asset-grid__loading">
85
+ <span class="spinner"></span>
86
+ <span>{t(M['assets.asset_grid.loading'])}</span>
87
+ </div>
88
+ {:else if assets.length === 0}
89
+ <div class="empty-state">
90
+ <div class="empty-state__icon">
91
+ <svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
92
+ <rect x="3" y="3" width="18" height="18" rx="2" ry="2"></rect>
93
+ <circle cx="8.5" cy="8.5" r="1.5"></circle>
94
+ <polyline points="21 15 16 10 5 21"></polyline>
95
+ </svg>
96
+ </div>
97
+ <p class="empty-state__title">{t(M['assets.asset_grid.no_assets_found'])}</p>
98
+ <p class="empty-state__desc">{t(M['assets.asset_grid.empty_hint'])}</p>
99
+ </div>
100
+ {:else}
101
+ <div class="grid">
102
+ {#each assets as asset (asset.id)}
103
+ {@const selected = selectedIds.has(asset.id)}
104
+ <div class="asset-card" class:asset-card--selected={selected}>
105
+ <!-- Whole-card "open" action. A stretched, transparent button (vs. a
106
+ role="button" on the card) avoids nesting the interactive checkbox
107
+ inside an interactive control (axe nested-interactive). It sits
108
+ above the non-interactive content but below the checkbox (z-index),
109
+ and is keyboard-activatable natively (Enter/Space). -->
110
+ <button
111
+ type="button"
112
+ class="asset-card__open"
113
+ aria-label={openCardLabel(asset.name || 'Untitled')}
114
+ title={asset.name || 'Untitled'}
115
+ onclick={() => handleClick(asset)}
116
+ ondblclick={() => handleDblClick(asset)}
117
+ ></button>
118
+
119
+ <!-- Selection checkbox -->
120
+ <div class="asset-card__checkbox">
121
+ <input
122
+ type="checkbox"
123
+ checked={selected}
124
+ onchange={(e) => toggleSelection(asset, e)}
125
+ onclick={(e) => e.stopPropagation()}
126
+ aria-label={t(M['assets.asset_grid.select_named_checkbox'], { name: asset.name })}
127
+ />
128
+ </div>
129
+
130
+ <!-- Thumbnail area -->
131
+ <div class="asset-card__thumb">
132
+ {#if isImage(asset) && asset.sourceUri}
133
+ <img
134
+ src={asset.sourceUri}
135
+ alt={getAltText(asset) || asset.name}
136
+ class="asset-card__image"
137
+ loading="lazy"
138
+ />
139
+ {:else}
140
+ <span class="asset-card__type-icon">{getTypeIcon(asset)}</span>
141
+ {/if}
142
+ </div>
143
+
144
+ <!-- Info bar -->
145
+ <div class="asset-card__info">
146
+ <span class="asset-card__name" title={asset.name}>{asset.name || 'Untitled'}</span>
147
+
148
+ <div class="asset-card__meta">
149
+ {#if asset.mimeType}
150
+ <span class="asset-card__type">{asset.mimeType.split('/')[1]?.toUpperCase()}</span>
151
+ {/if}
152
+ </div>
153
+ </div>
154
+
155
+ <!-- Badges / warnings -->
156
+ {#if isMissingAlt(asset)}
157
+ <span class="asset-card__badge asset-card__badge--warning" title={t(M['assets.asset_grid.missing_alt_text'])}>{t(M['assets.asset_grid.no_alt'])}</span>
158
+ {/if}
159
+ </div>
160
+ {/each}
161
+ </div>
162
+ {/if}
163
+ </div>
164
+
165
+ <style>
166
+ .asset-grid {
167
+ padding: var(--smrt-spacing-4, 1rem);
168
+ }
169
+
170
+ .asset-grid--loading {
171
+ opacity: 0.6;
172
+ pointer-events: none;
173
+ }
174
+
175
+ .grid {
176
+ display: grid;
177
+ grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
178
+ gap: var(--smrt-spacing-4, 1rem);
179
+ }
180
+
181
+ /* Card */
182
+ .asset-card {
183
+ position: relative;
184
+ display: flex;
185
+ flex-direction: column;
186
+ background: var(--smrt-color-surface, #ffffff);
187
+ border: 1px solid var(--smrt-color-outline-variant, #e5e7eb);
188
+ border-radius: var(--smrt-radius-medium, 0.5rem);
189
+ overflow: hidden;
190
+ cursor: pointer;
191
+ transition: all 150ms ease;
192
+ }
193
+
194
+ .asset-card:hover {
195
+ box-shadow: var(--smrt-elevation-2, 0 1px 3px rgba(0,0,0,0.1));
196
+ transform: translateY(-1px);
197
+ }
198
+
199
+ /* Stretched whole-card open action. Transparent, covers the card, sits above
200
+ the non-interactive content (z-index 1) but below the checkbox (z-index 2). */
201
+ .asset-card__open {
202
+ position: absolute;
203
+ inset: 0;
204
+ width: 100%;
205
+ height: 100%;
206
+ margin: 0;
207
+ padding: 0;
208
+ border: none;
209
+ background: transparent;
210
+ cursor: pointer;
211
+ z-index: 1;
212
+ border-radius: inherit;
213
+ }
214
+
215
+ .asset-card__open:focus-visible {
216
+ outline: 2px solid var(--smrt-color-primary, #005ac1);
217
+ /* Inset so the card's `overflow: hidden` doesn't clip the focus ring. */
218
+ outline-offset: -2px;
219
+ }
220
+
221
+ .asset-card--selected {
222
+ border-color: var(--smrt-color-primary, #005ac1);
223
+ background: var(--smrt-color-primary-container, #dbeafe);
224
+ }
225
+
226
+ /* Checkbox */
227
+ .asset-card__checkbox {
228
+ position: absolute;
229
+ top: var(--smrt-spacing-2, 0.5rem);
230
+ left: var(--smrt-spacing-2, 0.5rem);
231
+ z-index: 2;
232
+ opacity: 0;
233
+ transition: opacity 150ms ease;
234
+ }
235
+
236
+ .asset-card:hover .asset-card__checkbox,
237
+ .asset-card--selected .asset-card__checkbox {
238
+ opacity: 1;
239
+ }
240
+
241
+ .asset-card__checkbox input {
242
+ width: 18px;
243
+ height: 18px;
244
+ cursor: pointer;
245
+ accent-color: var(--smrt-color-primary, #005ac1);
246
+ }
247
+
248
+ /* Thumbnail */
249
+ .asset-card__thumb {
250
+ display: flex;
251
+ align-items: center;
252
+ justify-content: center;
253
+ height: 140px;
254
+ background: var(--smrt-color-surface-container-low, #f9fafb);
255
+ overflow: hidden;
256
+ }
257
+
258
+ .asset-card__image {
259
+ width: 100%;
260
+ height: 100%;
261
+ object-fit: cover;
262
+ }
263
+
264
+ .asset-card__type-icon {
265
+ font-size: var(--smrt-typography-display-medium-size, 2.5rem);
266
+ }
267
+
268
+ /* Info */
269
+ .asset-card__info {
270
+ padding: var(--smrt-spacing-2, 0.5rem) var(--smrt-spacing-3, 0.75rem);
271
+ }
272
+
273
+ .asset-card__name {
274
+ display: block;
275
+ font-size: var(--smrt-typography-body-medium-size, 0.875rem);
276
+ font-weight: var(--smrt-typography-weight-medium, 500);
277
+ color: var(--smrt-color-on-surface, #111827);
278
+ white-space: nowrap;
279
+ overflow: hidden;
280
+ text-overflow: ellipsis;
281
+ }
282
+
283
+ .asset-card__meta {
284
+ display: flex;
285
+ align-items: center;
286
+ gap: var(--smrt-spacing-1, 0.25rem);
287
+ margin-top: var(--smrt-spacing-1, 4px);
288
+ }
289
+
290
+ .asset-card__type {
291
+ font-size: var(--smrt-typography-label-small-size, 0.7rem);
292
+ font-weight: var(--smrt-typography-weight-semibold, 600);
293
+ color: var(--smrt-color-on-surface-variant, #6b7280);
294
+ text-transform: uppercase;
295
+ letter-spacing: var(--smrt-typography-label-small-tracking, 0.05em);
296
+ }
297
+
298
+ /* Badge */
299
+ .asset-card__badge {
300
+ position: absolute;
301
+ top: var(--smrt-spacing-2, 0.5rem);
302
+ right: var(--smrt-spacing-2, 0.5rem);
303
+ padding: var(--smrt-spacing-1, 4px) var(--smrt-spacing-2, 8px);
304
+ font-size: var(--smrt-typography-label-small-size, 0.65rem);
305
+ font-weight: var(--smrt-typography-weight-semibold, 600);
306
+ border-radius: var(--smrt-radius-small, 0.25rem);
307
+ z-index: 2;
308
+ /* Informational only — let clicks fall through to the stretched open button
309
+ below so the badge corner isn't a dead zone for opening the asset. */
310
+ pointer-events: none;
311
+ }
312
+
313
+ .asset-card__badge--warning {
314
+ background: var(--smrt-color-error-container, #fef2f2);
315
+ color: var(--smrt-color-on-error-container, #991b1b);
316
+ }
317
+
318
+ /* Loading */
319
+ .asset-grid__loading {
320
+ display: flex;
321
+ align-items: center;
322
+ justify-content: center;
323
+ gap: var(--smrt-spacing-2, 0.5rem);
324
+ padding: var(--smrt-spacing-8, 2rem);
325
+ color: var(--smrt-color-on-surface-variant, #6b7280);
326
+ }
327
+
328
+ .spinner {
329
+ width: 20px;
330
+ height: 20px;
331
+ border: 2px solid var(--smrt-color-outline-variant, #e5e7eb);
332
+ border-top-color: var(--smrt-color-primary, #005ac1);
333
+ border-radius: var(--smrt-radius-full, 9999px);
334
+ animation: spin 0.8s linear infinite;
335
+ }
336
+
337
+ @keyframes spin {
338
+ to { transform: rotate(360deg); }
339
+ }
340
+
341
+ @media (max-width: 640px) {
342
+ .grid {
343
+ grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
344
+ gap: var(--smrt-spacing-3, 0.75rem);
345
+ }
346
+
347
+ .asset-card__thumb {
348
+ height: 100px;
349
+ }
350
+ }
351
+ </style>
@@ -0,0 +1,5 @@
1
+ import type { AssetGridProps } from './types';
2
+ declare const AssetGrid: import("svelte").Component<AssetGridProps, {}, "">;
3
+ type AssetGrid = ReturnType<typeof AssetGrid>;
4
+ export default AssetGrid;
5
+ //# sourceMappingURL=AssetGrid.svelte.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AssetGrid.svelte.d.ts","sourceRoot":"","sources":["../../src/svelte/AssetGrid.svelte.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,cAAc,EAAkB,MAAM,SAAS,CAAC;AA+I9D,QAAA,MAAM,SAAS,oDAAwC,CAAC;AACxD,KAAK,SAAS,GAAG,UAAU,CAAC,OAAO,SAAS,CAAC,CAAC;AAC9C,eAAe,SAAS,CAAC"}