@happyvertical/smrt-images 0.34.4 → 0.34.6

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.
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "version": "1.0.0",
3
- "timestamp": 1782257854286,
3
+ "timestamp": 1782278454905,
4
4
  "packageName": "@happyvertical/smrt-images",
5
- "packageVersion": "0.34.4",
5
+ "packageVersion": "0.34.6",
6
6
  "objects": {
7
7
  "@happyvertical/smrt-images:Image": {
8
8
  "name": "image",
@@ -1,13 +1,13 @@
1
1
  {
2
2
  "schemaVersion": 1,
3
- "generatedAt": "2026-06-23T23:37:34.938Z",
3
+ "generatedAt": "2026-06-24T05:20:55.298Z",
4
4
  "packageName": "@happyvertical/smrt-images",
5
- "packageVersion": "0.34.4",
5
+ "packageVersion": "0.34.6",
6
6
  "sourceManifestPath": "dist/manifest.json",
7
7
  "agentDocPath": "AGENTS.md",
8
8
  "sourceHashes": {
9
- "manifest": "b28c1715f58a725cbad0e541ffc0049bc4390040b05e9b935ba5b59cf4465531",
10
- "packageJson": "f7aeb2e03407ed75dc4ff68f9e070a686760d9f92de6f74192c72bd8d4943cc0",
9
+ "manifest": "515005d7f498f29b64950227f55bf717187170d8804964551ce0d4d801ae2edf",
10
+ "packageJson": "92a1d613d753d7e30b192816fede1a97398d170365b5b4c1ca8d23c6a2912678",
11
11
  "agents": "b0cf63bd78f00cc1729ea7f1425291f8e23ab5591161cbbf0d08f4b16ace07fd"
12
12
  },
13
13
  "exports": [
@@ -1,5 +1,6 @@
1
1
  <script lang="ts">
2
2
  import { useI18n } from '@happyvertical/smrt-ui/i18n';
3
+ import { Button } from '@happyvertical/smrt-ui/ui';
3
4
  import { onMount } from 'svelte';
4
5
  import { M } from '../i18n.js';
5
6
  import type {
@@ -195,6 +196,7 @@ function handleDragStart(event: DragEvent, image: ImageLike) {
195
196
  <div class="gallery-grid">
196
197
  {#each images as image (image.id)}
197
198
  {#if onSelect}
199
+ <!-- raw-primitive-allow: large draggable selection card wrapping rich image content, not a standard action button -->
198
200
  <button
199
201
  type="button"
200
202
  class="gallery-item"
@@ -233,13 +235,14 @@ function handleDragStart(event: DragEvent, image: ImageLike) {
233
235
 
234
236
  {#if hasMore}
235
237
  <div class="load-more">
236
- <button
237
- onclick={() => loadImages(false)}
238
+ <Button
239
+ variant="ghost"
240
+ class="load-more-btn--pill"
241
+ onclick={() => loadImages(false)}
238
242
  disabled={isLoading}
239
- class="load-more-btn"
240
243
  >
241
244
  {isLoading ? 'Loading...' : 'Load More'}
242
- </button>
245
+ </Button>
243
246
  </div>
244
247
  {/if}
245
248
  </div>
@@ -421,23 +424,20 @@ function handleDragStart(event: DragEvent, image: ImageLike) {
421
424
  padding: 1rem 0;
422
425
  }
423
426
 
424
- .load-more-btn {
427
+ /* Bespoke pill look for the migrated Load More Button. :global() pierces into
428
+ the Button child's rendered <button>, which carries its own scope hash
429
+ (see #1589 scoping rule). */
430
+ .load-more :global(.load-more-btn--pill) {
425
431
  padding: 0.75rem 2rem;
426
432
  background: var(--smrt-color-surface-container-high, #242424);
427
433
  color: var(--smrt-color-primary, #3b82f6);
428
434
  border: 1px solid var(--smrt-color-outline-variant, #444);
429
435
  border-radius: var(--smrt-radius-full, 9999px);
430
436
  font-weight: var(--smrt-typography-weight-medium, 500);
431
- cursor: pointer;
432
437
  transition: background 0.2s;
433
438
  }
434
439
 
435
- .load-more-btn:hover:not(:disabled) {
440
+ .load-more :global(.load-more-btn--pill:hover:not(:disabled)) {
436
441
  background: var(--smrt-color-surface-container-highest, #333);
437
442
  }
438
-
439
- .load-more-btn:disabled {
440
- opacity: 0.6;
441
- cursor: not-allowed;
442
- }
443
443
  </style>
@@ -1 +1 @@
1
- {"version":3,"file":"AssetsGallery.svelte.d.ts","sourceRoot":"","sources":["../../../src/svelte/components/AssetsGallery.svelte.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EACV,SAAS,EACT,mBAAmB,EAEpB,MAAM,kBAAkB,CAAC;AAEzB,KAAK,gBAAgB,GAAI;IACxB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,MAAM,CAAC,EAAE,mBAAmB,CAAC;IAC7B,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,SAAS,KAAK,IAAI,CAAC;IACtC,kBAAkB,CAAC,EAAE,OAAO,CAAC;CAC9B,CAAC;AAyNF,QAAA,MAAM,aAAa,sDAAwC,CAAC;AAC5D,KAAK,aAAa,GAAG,UAAU,CAAC,OAAO,aAAa,CAAC,CAAC;AACtD,eAAe,aAAa,CAAC"}
1
+ {"version":3,"file":"AssetsGallery.svelte.d.ts","sourceRoot":"","sources":["../../../src/svelte/components/AssetsGallery.svelte.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EACV,SAAS,EACT,mBAAmB,EAEpB,MAAM,kBAAkB,CAAC;AAEzB,KAAK,gBAAgB,GAAI;IACxB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,MAAM,CAAC,EAAE,mBAAmB,CAAC;IAC7B,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,SAAS,KAAK,IAAI,CAAC;IACtC,kBAAkB,CAAC,EAAE,OAAO,CAAC;CAC9B,CAAC;AA2NF,QAAA,MAAM,aAAa,sDAAwC,CAAC;AAC5D,KAAK,aAAa,GAAG,UAAU,CAAC,OAAO,aAAa,CAAC,CAAC;AACtD,eAAe,aAAa,CAAC"}
@@ -1,5 +1,6 @@
1
1
  <script lang="ts">
2
2
  import { useI18n } from '@happyvertical/smrt-ui/i18n';
3
+ import { Button } from '@happyvertical/smrt-ui/ui';
3
4
  import { M } from '../i18n.js';
4
5
  import type {
5
6
  ImageConvertRequest,
@@ -180,7 +181,7 @@ async function handleAIEdit() {
180
181
  <div class="header">
181
182
  <h3>{t(M['images.image_editor.title'])}</h3>
182
183
  {#if onCancel}
183
- <button class="close-btn" onclick={onCancel}>×</button>
184
+ <Button variant="ghost" size="sm" class="close-btn--x" onclick={onCancel}>×</Button>
184
185
  {/if}
185
186
  </div>
186
187
 
@@ -194,8 +195,16 @@ async function handleAIEdit() {
194
195
 
195
196
  <div class="editor-controls">
196
197
  <div class="mode-selector">
197
- <button class:active={mode === 'standard'} onclick={() => mode = 'standard'}>{t(M['images.image_editor.standard_tools'])}</button>
198
- <button class:active={mode === 'ai'} onclick={() => mode = 'ai'}>{t(M['images.image_editor.ai_edit'])}</button>
198
+ <Button
199
+ variant={mode === 'standard' ? 'primary' : 'ghost'}
200
+ class="mode-tab"
201
+ onclick={() => (mode = 'standard')}
202
+ >{t(M['images.image_editor.standard_tools'])}</Button>
203
+ <Button
204
+ variant={mode === 'ai' ? 'primary' : 'ghost'}
205
+ class="mode-tab"
206
+ onclick={() => (mode = 'ai')}
207
+ >{t(M['images.image_editor.ai_edit'])}</Button>
199
208
  </div>
200
209
 
201
210
  {#if error}
@@ -212,10 +221,10 @@ async function handleAIEdit() {
212
221
  <div class="row">
213
222
  <label>Width <input type="number" bind:value={width} onfocus={() => isEditingDimensions = true} /></label>
214
223
  <label>Height <input type="number" bind:value={height} onfocus={() => isEditingDimensions = true} /></label>
215
- <button disabled={isProcessing} onclick={handleResize} class="tonal-btn">{t(M['images.image_editor.apply_resize'])}</button>
224
+ <Button variant="ghost" class="tonal-btn--filled" disabled={isProcessing} onclick={handleResize}>{t(M['images.image_editor.apply_resize'])}</Button>
216
225
  </div>
217
226
  {#if isEditingDimensions}
218
- <div class="row"><button class="text-btn hint" onclick={resetDimensions}>{t(M['images.image_editor.reset_dimensions'])}</button></div>
227
+ <div class="row"><Button variant="ghost" size="sm" class="text-btn--link" onclick={resetDimensions}>{t(M['images.image_editor.reset_dimensions'])}</Button></div>
219
228
  {/if}
220
229
  </div>
221
230
 
@@ -228,10 +237,10 @@ async function handleAIEdit() {
228
237
  <div class="row">
229
238
  <label>W <input type="number" bind:value={cropW} onfocus={() => isCropping = true} /></label>
230
239
  <label>H <input type="number" bind:value={cropH} onfocus={() => isCropping = true} /></label>
231
- <button disabled={isProcessing} onclick={handleCrop} class="tonal-btn">{t(M['images.image_editor.apply_crop'])}</button>
240
+ <Button variant="ghost" class="tonal-btn--filled" disabled={isProcessing} onclick={handleCrop}>{t(M['images.image_editor.apply_crop'])}</Button>
232
241
  </div>
233
242
  {#if isCropping}
234
- <div class="row"><button class="text-btn hint" onclick={resetDimensions}>{t(M['images.image_editor.reset_dimensions'])}</button></div>
243
+ <div class="row"><Button variant="ghost" size="sm" class="text-btn--link" onclick={resetDimensions}>{t(M['images.image_editor.reset_dimensions'])}</Button></div>
235
244
  {/if}
236
245
  </div>
237
246
 
@@ -243,7 +252,7 @@ async function handleAIEdit() {
243
252
  <option value="jpeg">JPEG</option>
244
253
  <option value="png">PNG</option>
245
254
  </select>
246
- <button disabled={isProcessing} onclick={handleConvert} class="tonal-btn">Convert</button>
255
+ <Button variant="ghost" class="tonal-btn--filled" disabled={isProcessing} onclick={handleConvert}>Convert</Button>
247
256
  </div>
248
257
  </div>
249
258
  {:else}
@@ -256,13 +265,14 @@ async function handleAIEdit() {
256
265
  placeholder={t(M['images.image_editor.ai_prompt_placeholder'])}
257
266
  rows="4"
258
267
  ></textarea>
259
- <button
260
- disabled={isProcessing || !prompt.trim()}
268
+ <Button
269
+ variant="primary"
270
+ class="primary-btn--pill"
271
+ disabled={isProcessing || !prompt.trim()}
261
272
  onclick={handleAIEdit}
262
- class="primary-btn"
263
273
  >
264
274
  {isProcessing ? 'Generating...' : 'Apply AI Edit'}
265
- </button>
275
+ </Button>
266
276
  </div>
267
277
  {/if}
268
278
  </div>
@@ -297,12 +307,12 @@ async function handleAIEdit() {
297
307
  font-weight: var(--smrt-typography-weight-medium, 500);
298
308
  }
299
309
 
300
- .close-btn {
301
- background: transparent;
302
- border: none;
310
+ /* The migrated close Button keeps ghost styling; only bump the × glyph size.
311
+ :global() pierces into the Button child's rendered <button> (see #1589). */
312
+ .header :global(.close-btn--x) {
303
313
  color: inherit;
304
314
  font-size: var(--smrt-typography-headline-small-size, 1.5rem);
305
- cursor: pointer;
315
+ line-height: 1;
306
316
  }
307
317
 
308
318
  .empty-state {
@@ -346,22 +356,14 @@ async function handleAIEdit() {
346
356
  gap: 0.25rem;
347
357
  }
348
358
 
349
- .mode-selector button {
359
+ /* The mode toggles are migrated Buttons (variant primary = active, ghost =
360
+ inactive). Keep the segmented-pill sizing by piercing into each Button's
361
+ rendered <button> (see #1589 scoping rule). */
362
+ .mode-selector :global(.mode-tab) {
350
363
  flex: 1;
351
- background: transparent;
352
- border: none;
353
364
  padding: 0.6rem 1rem;
354
- color: var(--smrt-color-outline, #666);
355
- cursor: pointer;
356
365
  font-weight: var(--smrt-typography-weight-medium, 500);
357
366
  border-radius: var(--smrt-radius-full, 9999px);
358
- transition: all 0.2s;
359
- }
360
-
361
- .mode-selector button.active {
362
- background: var(--smrt-color-surface-container, #1a1a1a);
363
- color: var(--smrt-color-on-surface, #fff);
364
- box-shadow: var(--smrt-elevation-1, 0 1px 3px color-mix(in srgb, var(--smrt-color-shadow) 20%, transparent));
365
367
  }
366
368
 
367
369
  .tool-section h4 {
@@ -414,53 +416,41 @@ async function handleAIEdit() {
414
416
  font-family: inherit;
415
417
  }
416
418
 
417
- .tonal-btn {
419
+ /* Migrated Buttons keep their bespoke filled-tonal / text-link / pill looks.
420
+ :global() pierces into each Button's rendered <button> (see #1589). */
421
+ .tool-section :global(.tonal-btn--filled) {
418
422
  background: var(--smrt-color-surface-container-highest, #333);
419
423
  color: var(--smrt-color-primary, #3b82f6);
420
424
  border: 1px solid var(--smrt-color-outline-variant, #444);
421
425
  padding: 0.6rem 1.2rem;
422
426
  border-radius: var(--smrt-radius-full, 9999px);
423
427
  font-weight: var(--smrt-typography-weight-medium, 500);
424
- cursor: pointer;
425
428
  transition: background 0.2s;
426
429
  margin-top: 1.25rem;
427
430
  }
428
431
 
429
- .tonal-btn:hover:not(:disabled) {
432
+ .tool-section :global(.tonal-btn--filled:hover:not(:disabled)) {
430
433
  background: var(--smrt-color-surface-container-high, #3f3f3f);
431
434
  }
432
435
 
433
- .text-btn {
434
- background: transparent;
435
- border: none;
436
- cursor: pointer;
436
+ .tool-section :global(.text-btn--link) {
437
437
  text-decoration: underline;
438
438
  padding: 0;
439
439
  }
440
- .text-btn:hover {
440
+ .tool-section :global(.text-btn--link:hover) {
441
441
  color: var(--smrt-color-on-surface, #fff);
442
442
  }
443
443
 
444
- .primary-btn {
445
- background: var(--smrt-color-primary, #3b82f6);
446
- color: white;
447
- border: none;
444
+ .tool-section :global(.primary-btn--pill) {
448
445
  padding: 0.6rem 1.5rem;
449
446
  border-radius: var(--smrt-radius-full, 9999px);
450
447
  font-weight: var(--smrt-typography-weight-medium, 500);
451
- cursor: pointer;
452
- transition: background 0.2s, opacity 0.2s;
453
448
  }
454
449
 
455
- .primary-btn:hover:not(:disabled) {
450
+ .tool-section :global(.primary-btn--pill:hover:not(:disabled)) {
456
451
  filter: brightness(1.1);
457
452
  }
458
453
 
459
- button:disabled {
460
- opacity: 0.5;
461
- cursor: not-allowed;
462
- }
463
-
464
454
  .hint {
465
455
  font-size: var(--smrt-typography-body-small-size, 0.8rem);
466
456
  color: var(--smrt-color-outline, #888);
@@ -1 +1 @@
1
- {"version":3,"file":"ImageEditor.svelte.d.ts","sourceRoot":"","sources":["../../../src/svelte/components/ImageEditor.svelte.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAGV,iBAAiB,EACjB,SAAS,EAEV,MAAM,kBAAkB,CAAC;AAEzB,KAAK,gBAAgB,GAAI;IACxB,KAAK,CAAC,EAAE,SAAS,GAAG,IAAI,CAAC;IACzB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,MAAM,CAAC,EAAE,iBAAiB,CAAC;IAC3B,MAAM,CAAC,EAAE,CAAC,KAAK,EAAE,SAAS,KAAK,IAAI,CAAC;IACpC,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAC;CACvB,CAAC;AAiQF,QAAA,MAAM,WAAW,sDAAwC,CAAC;AAC1D,KAAK,WAAW,GAAG,UAAU,CAAC,OAAO,WAAW,CAAC,CAAC;AAClD,eAAe,WAAW,CAAC"}
1
+ {"version":3,"file":"ImageEditor.svelte.d.ts","sourceRoot":"","sources":["../../../src/svelte/components/ImageEditor.svelte.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAGV,iBAAiB,EACjB,SAAS,EAEV,MAAM,kBAAkB,CAAC;AAEzB,KAAK,gBAAgB,GAAI;IACxB,KAAK,CAAC,EAAE,SAAS,GAAG,IAAI,CAAC;IACzB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,MAAM,CAAC,EAAE,iBAAiB,CAAC;IAC3B,MAAM,CAAC,EAAE,CAAC,KAAK,EAAE,SAAS,KAAK,IAAI,CAAC;IACpC,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAC;CACvB,CAAC;AAkQF,QAAA,MAAM,WAAW,sDAAwC,CAAC;AAC1D,KAAK,WAAW,GAAG,UAAU,CAAC,OAAO,WAAW,CAAC,CAAC;AAClD,eAAe,WAAW,CAAC"}
@@ -1,5 +1,6 @@
1
1
  <script lang="ts">
2
2
  import { useI18n } from '@happyvertical/smrt-ui/i18n';
3
+ import { Button } from '@happyvertical/smrt-ui/ui';
3
4
  import { onDestroy } from 'svelte';
4
5
  import { M } from '../i18n.js';
5
6
  import type {
@@ -276,14 +277,14 @@ onDestroy(() => {
276
277
  {#if selectedImage}
277
278
  <!-- Gallery Confirmation Step -->
278
279
  <div class="header">
279
- <button type="button" class="back-btn" onclick={handleBackToChooser}>
280
+ <Button variant="ghost" size="sm" class="back-btn--link" onclick={handleBackToChooser}>
280
281
  <svg viewBox="0 0 24 24" width="20" height="20" stroke="currentColor" stroke-width="2" fill="none" stroke-linecap="round" stroke-linejoin="round">
281
282
  <polyline points="15 18 9 12 15 6"></polyline>
282
283
  </svg>
283
284
  Back
284
- </button>
285
+ </Button>
285
286
  {#if onCancel}
286
- <button type="button" class="close-btn" onclick={onCancel}>×</button>
287
+ <Button variant="ghost" size="sm" class="close-btn--x" onclick={onCancel}>×</Button>
287
288
  {/if}
288
289
  </div>
289
290
 
@@ -298,21 +299,22 @@ onDestroy(() => {
298
299
  </div>
299
300
 
300
301
  <div class="confirm-actions">
301
- <button type="button" class="primary-btn" onclick={handleConfirmOriginal}>
302
+ <Button variant="primary" class="primary-btn--pill" onclick={handleConfirmOriginal}>
302
303
  {t(M['images.image_uploader.select_image'])}
303
- </button>
304
- <button
305
- type="button"
306
- class="variation-toggle"
307
- class:active={showVariation}
308
- onclick={() => showVariation = !showVariation}
304
+ </Button>
305
+ <Button
306
+ variant="ghost"
307
+ class={showVariation
308
+ ? 'variation-toggle--chip variation-toggle--active'
309
+ : 'variation-toggle--chip'}
310
+ onclick={() => (showVariation = !showVariation)}
309
311
  >
310
312
  <svg viewBox="0 0 24 24" width="16" height="16" stroke="currentColor" stroke-width="2" fill="none" stroke-linecap="round" stroke-linejoin="round">
311
313
  <path d="M12 20h9"></path>
312
314
  <path d="M16.5 3.5a2.121 2.121 0 0 1 3 3L7 19l-4 1 1-4L16.5 3.5z"></path>
313
315
  </svg>
314
316
  {t(M['images.image_uploader.create_variation'])}
315
- </button>
317
+ </Button>
316
318
  </div>
317
319
 
318
320
  {#if showVariation}
@@ -327,10 +329,10 @@ onDestroy(() => {
327
329
  {#if variationError}
328
330
  <div class="variation-error">{variationError}</div>
329
331
  {/if}
330
- <button
331
- type="button"
332
- class="generate-btn"
333
- disabled={isGenerating || !variationPrompt.trim()}
332
+ <Button
333
+ variant="primary"
334
+ class="generate-btn--pill"
335
+ disabled={isGenerating || !variationPrompt.trim()}
334
336
  onclick={handleGenerateVariation}
335
337
  >
336
338
  {#if isGenerating}
@@ -339,7 +341,7 @@ onDestroy(() => {
339
341
  {:else}
340
342
  {t(M['images.image_uploader.generate_variation'])}
341
343
  {/if}
342
- </button>
344
+ </Button>
343
345
  </div>
344
346
  {/if}
345
347
  </div>
@@ -349,22 +351,38 @@ onDestroy(() => {
349
351
  <div class="header">
350
352
  <h3>{t(M['images.image_uploader.choose_image'])}</h3>
351
353
  {#if onCancel}
352
- <button type="button" class="close-btn" onclick={onCancel}>×</button>
354
+ <Button variant="ghost" size="sm" class="close-btn--x" onclick={onCancel}>×</Button>
353
355
  {/if}
354
356
  </div>
355
357
 
356
358
  <div class="tabs">
357
359
  {#if allowedTabs.includes('gallery')}
358
- <button type="button" class:active={activeTab === 'gallery'} onclick={() => activeTab = 'gallery'}>Gallery</button>
360
+ <Button
361
+ variant="ghost"
362
+ class={activeTab === 'gallery' ? 'uploader-tab uploader-tab--active' : 'uploader-tab'}
363
+ onclick={() => (activeTab = 'gallery')}
364
+ >Gallery</Button>
359
365
  {/if}
360
366
  {#if allowedTabs.includes('upload')}
361
- <button type="button" class:active={activeTab === 'upload'} onclick={() => activeTab = 'upload'}>Upload</button>
367
+ <Button
368
+ variant="ghost"
369
+ class={activeTab === 'upload' ? 'uploader-tab uploader-tab--active' : 'uploader-tab'}
370
+ onclick={() => (activeTab = 'upload')}
371
+ >Upload</Button>
362
372
  {/if}
363
373
  {#if allowedTabs.includes('camera')}
364
- <button type="button" class:active={activeTab === 'camera'} onclick={() => activeTab = 'camera'}>Camera</button>
374
+ <Button
375
+ variant="ghost"
376
+ class={activeTab === 'camera' ? 'uploader-tab uploader-tab--active' : 'uploader-tab'}
377
+ onclick={() => (activeTab = 'camera')}
378
+ >Camera</Button>
365
379
  {/if}
366
380
  {#if allowedTabs.includes('external')}
367
- <button type="button" class:active={activeTab === 'external'} onclick={() => activeTab = 'external'}>{t(M['images.image_uploader.external_url'])}</button>
381
+ <Button
382
+ variant="ghost"
383
+ class={activeTab === 'external' ? 'uploader-tab uploader-tab--active' : 'uploader-tab'}
384
+ onclick={() => (activeTab = 'external')}
385
+ >{t(M['images.image_uploader.external_url'])}</Button>
368
386
  {/if}
369
387
  </div>
370
388
 
@@ -401,7 +419,7 @@ onDestroy(() => {
401
419
  </div>
402
420
  <p>{t(M['images.image_uploader.drag_and_drop'])}</p>
403
421
  <span class="divider">or</span>
404
- <button type="button" class="browse-btn">{t(M['images.image_uploader.browse_files'])}</button>
422
+ <Button variant="primary" class="browse-btn--decorative">{t(M['images.image_uploader.browse_files'])}</Button>
405
423
  <input
406
424
  type="file"
407
425
  accept="image/*"
@@ -419,7 +437,7 @@ onDestroy(() => {
419
437
  {#if cameraError}
420
438
  <div class="error-panel">
421
439
  <p>{cameraError}</p>
422
- <button type="button" onclick={startCamera}>{t(M['images.image_uploader.try_again'])}</button>
440
+ <Button variant="secondary" class="try-again-btn" onclick={startCamera}>{t(M['images.image_uploader.try_again'])}</Button>
423
441
  </div>
424
442
  {:else}
425
443
  <div class="video-container">
@@ -429,9 +447,9 @@ onDestroy(() => {
429
447
  <div class="loading-overlay">{t(M['images.image_uploader.starting_camera'])}</div>
430
448
  {/if}
431
449
  </div>
432
- <button type="button" class="capture-btn" disabled={!isCameraActive} onclick={takePicture}>
450
+ <Button variant="primary" class="capture-btn--pill" disabled={!isCameraActive} onclick={takePicture}>
433
451
  {t(M['images.image_uploader.take_picture'])}
434
- </button>
452
+ </Button>
435
453
  <canvas bind:this={canvasElement} style="display: none;"></canvas>
436
454
  {/if}
437
455
  </div>
@@ -446,14 +464,14 @@ onDestroy(() => {
446
464
  placeholder={t(M['images.image_uploader.external_url_placeholder'])}
447
465
  onkeydown={(e) => e.key === 'Enter' && handleExternalSubmit()}
448
466
  />
449
- <button
450
- type="button"
451
- class="submit-btn"
452
- disabled={!externalUrl.trim()}
467
+ <Button
468
+ variant="primary"
469
+ class="submit-btn--inline"
470
+ disabled={!externalUrl.trim()}
453
471
  onclick={handleExternalSubmit}
454
472
  >
455
473
  Add
456
- </button>
474
+ </Button>
457
475
  </div>
458
476
  </div>
459
477
  {/if}
@@ -488,16 +506,16 @@ onDestroy(() => {
488
506
  font-size: var(--smrt-typography-title-medium-size, 1.15rem);
489
507
  }
490
508
 
491
- .close-btn {
492
- background: transparent;
493
- border: none;
509
+ /* Migrated close Button keeps its muted ghost look; only the × glyph sizing
510
+ is custom. :global() pierces into the Button child's rendered <button>
511
+ (see #1589 scoping rule). */
512
+ .header :global(.close-btn--x) {
494
513
  color: var(--smrt-color-outline, #888);
495
514
  font-size: var(--smrt-typography-headline-small-size, 1.5rem);
496
515
  line-height: 1;
497
- cursor: pointer;
498
516
  }
499
-
500
- .close-btn:hover {
517
+
518
+ .header :global(.close-btn--x:hover) {
501
519
  color: var(--smrt-color-on-surface, #fff);
502
520
  }
503
521
 
@@ -507,27 +525,29 @@ onDestroy(() => {
507
525
  border-bottom: 1px solid var(--smrt-color-outline-variant, #333);
508
526
  }
509
527
 
510
- .tabs button {
528
+ /* The tab strip is migrated to ghost Buttons; the active state is the bottom
529
+ underline rather than a filled variant. :global() pierces into each
530
+ Button's rendered <button> (see #1589 scoping rule). */
531
+ .tabs :global(.uploader-tab) {
511
532
  flex: 1;
512
- background: transparent;
513
533
  border: none;
514
534
  border-bottom: 2px solid var(--smrt-color-outline-variant, #333);
515
535
  padding: 1rem;
516
536
  color: var(--smrt-color-outline, #888);
517
537
  font-weight: var(--smrt-typography-weight-medium, 500);
518
- cursor: pointer;
538
+ border-radius: 0;
519
539
  transition: all 0.2s;
520
540
  text-transform: uppercase;
521
541
  font-size: var(--smrt-typography-label-large-size, 0.85rem);
522
542
  letter-spacing: var(--smrt-typography-label-large-tracking, 0.5px);
523
543
  }
524
544
 
525
- .tabs button:hover {
545
+ .tabs :global(.uploader-tab:hover) {
526
546
  color: var(--smrt-color-on-surface-variant, #ccc);
527
547
  background: color-mix(in srgb, var(--smrt-color-on-surface, #fff) 2%, transparent);
528
548
  }
529
549
 
530
- .tabs button.active {
550
+ .tabs :global(.uploader-tab--active) {
531
551
  color: var(--smrt-color-primary, #3b82f6);
532
552
  border-bottom: 2px solid var(--smrt-color-primary, #3b82f6);
533
553
  background: transparent;
@@ -594,10 +614,10 @@ onDestroy(() => {
594
614
  margin-bottom: 1rem;
595
615
  }
596
616
 
597
- .browse-btn {
598
- background: var(--smrt-color-primary, #3b82f6);
599
- color: white;
600
- border: none;
617
+ /* Decorative label inside the click-catching upload area; the parent handles
618
+ clicks, so this migrated Button stays inert. :global() pierces into the
619
+ Button child's rendered <button> (see #1589 scoping rule). */
620
+ .upload-area :global(.browse-btn--decorative) {
601
621
  padding: 0.5rem 1.5rem;
602
622
  border-radius: var(--smrt-radius-full, 9999px);
603
623
  font-weight: var(--smrt-typography-weight-medium, 500);
@@ -648,21 +668,18 @@ onDestroy(() => {
648
668
  color: white;
649
669
  }
650
670
 
651
- .capture-btn {
652
- background: var(--smrt-color-primary, #3b82f6);
653
- color: white;
654
- border: none;
671
+ /* Large primary capture action; keeps its oversized pill sizing over the
672
+ primary variant. :global() pierces into the Button child (see #1589). */
673
+ .camera-area :global(.capture-btn--pill) {
655
674
  padding: 1rem 3rem;
656
675
  border-radius: var(--smrt-radius-full, 9999px);
657
676
  font-size: var(--smrt-typography-body-large-size, 1.1rem);
658
677
  font-weight: var(--smrt-typography-weight-semibold, 600);
659
- cursor: pointer;
660
678
  }
661
679
 
662
- .capture-btn:disabled {
680
+ .camera-area :global(.capture-btn--pill:disabled) {
663
681
  background: var(--smrt-color-outline-variant, #444);
664
682
  color: var(--smrt-color-outline, #888);
665
- cursor: not-allowed;
666
683
  }
667
684
 
668
685
  .error-panel {
@@ -677,13 +694,14 @@ onDestroy(() => {
677
694
  margin-bottom: 1rem;
678
695
  }
679
696
 
680
- .error-panel button {
697
+ /* Migrated "try again" Button keeps its surface-tinted look over the
698
+ secondary variant. :global() pierces into the Button child (see #1589). */
699
+ .error-panel :global(.try-again-btn) {
681
700
  background: var(--smrt-color-surface-container-high, #242424);
682
701
  color: white;
683
702
  border: 1px solid var(--smrt-color-outline-variant, #444);
684
703
  padding: 0.5rem 1rem;
685
704
  border-radius: var(--smrt-radius-sm, 4px);
686
- cursor: pointer;
687
705
  }
688
706
 
689
707
  /* External Tab */
@@ -725,38 +743,35 @@ onDestroy(() => {
725
743
  box-shadow: inset 0 0 0 1px var(--smrt-color-primary, #3b82f6);
726
744
  }
727
745
 
728
- .submit-btn {
729
- background: var(--smrt-color-primary, #3b82f6);
730
- color: white;
731
- border: none;
746
+ /* Migrated Add Button sits in the URL input group; keeps square-ish radius
747
+ and zero vertical padding. :global() pierces into the Button child
748
+ (see #1589 scoping rule). */
749
+ .input-group :global(.submit-btn--inline) {
732
750
  padding: 0 1.5rem;
733
751
  border-radius: var(--smrt-radius-md, 6px);
734
752
  font-weight: var(--smrt-typography-weight-medium, 500);
735
- cursor: pointer;
736
753
  }
737
754
 
738
- .submit-btn:disabled {
755
+ .input-group :global(.submit-btn--inline:disabled) {
739
756
  background: var(--smrt-color-surface-container-highest, #333);
740
757
  color: var(--smrt-color-outline, #666);
741
- cursor: not-allowed;
742
758
  }
743
759
 
744
760
  /* --- Back Button --- */
745
- .back-btn {
761
+ /* Migrated ghost Button keeps the icon+label inline layout and tinted hover.
762
+ :global() pierces into the Button child (see #1589 scoping rule). */
763
+ .header :global(.back-btn--link) {
746
764
  display: flex;
747
765
  align-items: center;
748
766
  gap: 0.35rem;
749
- background: transparent;
750
- border: none;
751
767
  color: var(--smrt-color-primary, #3b82f6);
752
768
  font-weight: var(--smrt-typography-weight-medium, 500);
753
- cursor: pointer;
754
769
  padding: 0.25rem 0.5rem;
755
770
  border-radius: var(--smrt-radius-sm, 4px);
756
771
  transition: background 0.15s;
757
772
  }
758
773
 
759
- .back-btn:hover {
774
+ .header :global(.back-btn--link:hover) {
760
775
  background: color-mix(in srgb, var(--smrt-color-primary) 8%, transparent);
761
776
  }
762
777
 
@@ -814,22 +829,20 @@ onDestroy(() => {
814
829
  margin-top: 0.5rem;
815
830
  }
816
831
 
817
- .primary-btn {
818
- background: var(--smrt-color-primary, #3b82f6);
819
- color: white;
820
- border: none;
832
+ /* Migrated confirm-action Buttons keep their pill / chip looks. :global()
833
+ pierces into each Button's rendered <button> (see #1589 scoping rule). */
834
+ .confirm-actions :global(.primary-btn--pill) {
821
835
  padding: 0.65rem 1.5rem;
822
836
  border-radius: var(--smrt-radius-full, 9999px);
823
837
  font-weight: var(--smrt-typography-weight-medium, 500);
824
- cursor: pointer;
825
838
  transition: filter 0.15s;
826
839
  }
827
840
 
828
- .primary-btn:hover {
841
+ .confirm-actions :global(.primary-btn--pill:hover) {
829
842
  filter: brightness(1.1);
830
843
  }
831
844
 
832
- .variation-toggle {
845
+ .confirm-actions :global(.variation-toggle--chip) {
833
846
  display: flex;
834
847
  align-items: center;
835
848
  gap: 0.4rem;
@@ -839,15 +852,14 @@ onDestroy(() => {
839
852
  padding: 0.65rem 1.25rem;
840
853
  border-radius: var(--smrt-radius-full, 9999px);
841
854
  font-weight: var(--smrt-typography-weight-medium, 500);
842
- cursor: pointer;
843
855
  transition: all 0.2s;
844
856
  }
845
857
 
846
- .variation-toggle:hover {
858
+ .confirm-actions :global(.variation-toggle--chip:hover) {
847
859
  background: var(--smrt-color-surface-container-high, #3f3f3f);
848
860
  }
849
861
 
850
- .variation-toggle.active {
862
+ .confirm-actions :global(.variation-toggle--active) {
851
863
  color: var(--smrt-color-primary, #3b82f6);
852
864
  border-color: var(--smrt-color-primary, #3b82f6);
853
865
  background: color-mix(in srgb, var(--smrt-color-primary) 8%, transparent);
@@ -902,31 +914,20 @@ onDestroy(() => {
902
914
  font-size: var(--smrt-typography-body-medium-size, 0.85rem);
903
915
  }
904
916
 
905
- .generate-btn {
906
- display: flex;
907
- align-items: center;
908
- justify-content: center;
909
- gap: 0.5rem;
917
+ /* Migrated generate Button keeps its left-aligned pill look and inline
918
+ spinner. :global() pierces into the Button child (see #1589 scoping rule). */
919
+ .variation-form :global(.generate-btn--pill) {
910
920
  align-self: flex-start;
911
- background: var(--smrt-color-primary, #3b82f6);
912
- color: white;
913
- border: none;
914
921
  padding: 0.65rem 1.5rem;
915
922
  border-radius: var(--smrt-radius-full, 9999px);
916
923
  font-weight: var(--smrt-typography-weight-medium, 500);
917
- cursor: pointer;
918
924
  transition: filter 0.15s, opacity 0.15s;
919
925
  }
920
926
 
921
- .generate-btn:hover:not(:disabled) {
927
+ .variation-form :global(.generate-btn--pill:hover:not(:disabled)) {
922
928
  filter: brightness(1.1);
923
929
  }
924
930
 
925
- .generate-btn:disabled {
926
- opacity: 0.5;
927
- cursor: not-allowed;
928
- }
929
-
930
931
  @keyframes spin {
931
932
  to { transform: rotate(360deg); }
932
933
  }
@@ -1 +1 @@
1
- {"version":3,"file":"ImageUploader.svelte.d.ts","sourceRoot":"","sources":["../../../src/svelte/components/ImageUploader.svelte.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EACV,iBAAiB,EACjB,SAAS,EACT,mBAAmB,EACpB,MAAM,kBAAkB,CAAC;AAGzB,KAAK,gBAAgB,GAAI;IACxB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,YAAY,CAAC,EAAE,iBAAiB,CAAC;IACjC,aAAa,CAAC,EAAE,mBAAmB,CAAC;IACpC,mDAAmD;IACnD,QAAQ,EAAE,CAAC,KAAK,EAAE,SAAS,GAAG,IAAI,GAAG,MAAM,KAAK,IAAI,CAAC;IACrD,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAC;IACtB,WAAW,CAAC,EAAE,CAAC,SAAS,GAAG,QAAQ,GAAG,QAAQ,GAAG,UAAU,CAAC,EAAE,CAAC;IAC/D,kBAAkB,CAAC,EAAE,OAAO,CAAC;CAC9B,CAAC;AA0ZF,QAAA,MAAM,aAAa,sDAAwC,CAAC;AAC5D,KAAK,aAAa,GAAG,UAAU,CAAC,OAAO,aAAa,CAAC,CAAC;AACtD,eAAe,aAAa,CAAC"}
1
+ {"version":3,"file":"ImageUploader.svelte.d.ts","sourceRoot":"","sources":["../../../src/svelte/components/ImageUploader.svelte.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EACV,iBAAiB,EACjB,SAAS,EACT,mBAAmB,EACpB,MAAM,kBAAkB,CAAC;AAGzB,KAAK,gBAAgB,GAAI;IACxB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,YAAY,CAAC,EAAE,iBAAiB,CAAC;IACjC,aAAa,CAAC,EAAE,mBAAmB,CAAC;IACpC,mDAAmD;IACnD,QAAQ,EAAE,CAAC,KAAK,EAAE,SAAS,GAAG,IAAI,GAAG,MAAM,KAAK,IAAI,CAAC;IACrD,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAC;IACtB,WAAW,CAAC,EAAE,CAAC,SAAS,GAAG,QAAQ,GAAG,QAAQ,GAAG,UAAU,CAAC,EAAE,CAAC;IAC/D,kBAAkB,CAAC,EAAE,OAAO,CAAC;CAC9B,CAAC;AA6ZF,QAAA,MAAM,aAAa,sDAAwC,CAAC;AAC5D,KAAK,aAAa,GAAG,UAAU,CAAC,OAAO,aAAa,CAAC,CAAC;AACtD,eAAe,aAAa,CAAC"}
package/package.json CHANGED
@@ -1,8 +1,9 @@
1
1
  {
2
2
  "name": "@happyvertical/smrt-images",
3
- "version": "0.34.4",
3
+ "version": "0.34.6",
4
4
  "description": "Image asset management with AI-powered categorization, search, editing, and metadata extraction for SMRT framework",
5
5
  "type": "module",
6
+ "smrtRawPrimitives": "strict-buttons",
6
7
  "main": "./dist/index.js",
7
8
  "types": "./dist/index.d.ts",
8
9
  "files": [
@@ -38,12 +39,12 @@
38
39
  "@happyvertical/utils": "^0.74.7",
39
40
  "jimp": "^1.6.1",
40
41
  "sharp": "^0.34.5",
41
- "@happyvertical/smrt-assets": "0.34.4",
42
- "@happyvertical/smrt-core": "0.34.4",
43
- "@happyvertical/smrt-prompts": "0.34.4",
44
- "@happyvertical/smrt-tenancy": "0.34.4",
45
- "@happyvertical/smrt-types": "0.34.4",
46
- "@happyvertical/smrt-ui": "0.34.4"
42
+ "@happyvertical/smrt-assets": "0.34.6",
43
+ "@happyvertical/smrt-core": "0.34.6",
44
+ "@happyvertical/smrt-tenancy": "0.34.6",
45
+ "@happyvertical/smrt-types": "0.34.6",
46
+ "@happyvertical/smrt-prompts": "0.34.6",
47
+ "@happyvertical/smrt-ui": "0.34.6"
47
48
  },
48
49
  "peerDependencies": {
49
50
  "svelte": "^5.18.0"
@@ -59,8 +60,8 @@
59
60
  "typescript": "^5.9.3",
60
61
  "vite": "^7.3.1",
61
62
  "vitest": "^4.0.17",
62
- "@happyvertical/smrt-vitest": "0.34.4",
63
- "@happyvertical/smrt-playground": "0.34.4"
63
+ "@happyvertical/smrt-playground": "0.34.6",
64
+ "@happyvertical/smrt-vitest": "0.34.6"
64
65
  },
65
66
  "keywords": [
66
67
  "ai",