@scaleflex/asset-picker 0.2.10 → 0.2.12

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 (33) hide show
  1. package/.claude/settings.local.json +14 -1
  2. package/README.md +202 -11
  3. package/dist/{asset-picker-CJ4AMXrc.cjs → asset-picker-CMsm4Ewp.cjs} +301 -152
  4. package/dist/{asset-picker-JBTs8qXV.js → asset-picker-DCcLalcQ.js} +1210 -895
  5. package/dist/asset-picker.d.ts.map +1 -1
  6. package/dist/components/filters/ap-filter-type.d.ts +18 -1
  7. package/dist/components/filters/ap-filter-type.d.ts.map +1 -1
  8. package/dist/components/filters/ap-filters-bar.d.ts.map +1 -1
  9. package/dist/components/filters/filters.constants.d.ts +1 -0
  10. package/dist/components/filters/filters.constants.d.ts.map +1 -1
  11. package/dist/components/toolbar/ap-content-toolbar.d.ts +1 -0
  12. package/dist/components/toolbar/ap-content-toolbar.d.ts.map +1 -1
  13. package/dist/define.cjs +1 -1
  14. package/dist/define.js +1 -1
  15. package/dist/index.cjs +1 -1
  16. package/dist/index.d.ts +1 -0
  17. package/dist/index.d.ts.map +1 -1
  18. package/dist/index.js +68 -2
  19. package/dist/react.cjs +1 -1
  20. package/dist/react.d.ts +82 -1
  21. package/dist/react.d.ts.map +1 -1
  22. package/dist/react.js +75 -31
  23. package/dist/services/filters.service.d.ts +1 -1
  24. package/dist/services/filters.service.d.ts.map +1 -1
  25. package/dist/store/index.d.ts.map +1 -1
  26. package/dist/store/store.types.d.ts +2 -1
  27. package/dist/store/store.types.d.ts.map +1 -1
  28. package/dist/types/asset.types.d.ts +6 -1
  29. package/dist/types/asset.types.d.ts.map +1 -1
  30. package/dist/utils/asset-helpers.d.ts +80 -0
  31. package/dist/utils/asset-helpers.d.ts.map +1 -0
  32. package/dist/utils/filter-serialize.d.ts.map +1 -1
  33. package/package.json +1 -1
@@ -45,7 +45,20 @@
45
45
  "Bash(npx playwright:*)",
46
46
  "Bash(find /Users/dmitry_stremous/scaleflex/asset-picker/src/components/views -type f -name *folder* -o -name *row* -o -name *card*)",
47
47
  "Bash(pkill -f \"vite serve\")",
48
- "Bash(pkill -f \"vite\")"
48
+ "Bash(pkill -f \"vite\")",
49
+ "Bash(curl -s \"https://api.filerobot.com/fbmjmuoeb/v5/files?format=json,regvar:api,select:internal&preview=2301&limit=2&sort=created_at:desc\" -H \"X-Filerobot-Key: SECU_47D57B3106A841F7A1FEA951846CC5F3\")",
50
+ "Bash(python3 -c \"import sys,json; d=json.load\\(sys.stdin\\); [print\\(f[''''name''''], f.get\\(''''extension'''',''''?''''\\), f.get\\(''''type'''',''''?''''\\)\\) for f in d.get\\(''''files'''',[]\\)]\")",
51
+ "Bash(python3 -c \"import sys,json; d=json.load\\(sys.stdin\\); print\\(''''total:'''', d.get\\(''''info'''',{}\\).get\\(''''total_files_count'''',0\\)\\); [print\\(f[''''name''''], f.get\\(''''extension'''',''''?''''\\), f.get\\(''''type'''',{}\\).get\\(''''value'''',''''?''''\\)\\) for f in d.get\\(''''files'''',[]\\)]\")",
52
+ "Bash(curl -s \"https://api.filerobot.com/fbmjmuoeb/v5/files?format=json,regvar:api,select:internal&preview=2301&limit=3&sort=created_at:desc\" -H \"X-Filerobot-Key: SECU_47D57B3106A841F7A1FEA951846CC5F3\")",
53
+ "Bash(python3 -c \"import sys,json; d=json.load\\(sys.stdin\\); print\\(''''total:'''', d.get\\(''''info'''',{}\\).get\\(''''total_files_count'''',0\\)\\); [print\\(f[''''name''''], f.get\\(''''extension'''',''''?''''\\), f.get\\(''''type'''',''''?''''\\)\\) for f in d.get\\(''''files'''',[]\\)]\")",
54
+ "Bash(curl -s 'https://api.filerobot.com/fbmjmuoeb/v5/files?preview=2301&q=type:%22image_svg%22&folder=%2F&sort=modified_at:desc&offset=0&limit=2&recursive=1' -H 'x-filerobot-key: SECU_47D57B3106A841F7A1FEA951846CC5F3')",
55
+ "Bash(python3 -c \"import sys,json; d=json.load\\(sys.stdin\\); print\\(''''total:'''', d.get\\(''''info'''',{}\\).get\\(''''total_files_count'''',0\\)\\); [print\\(f[''''name''''], f.get\\(''''type'''',{}\\).get\\(''''value'''',''''?''''\\) if isinstance\\(f.get\\(''''type''''\\),dict\\) else f.get\\(''''type'''',''''?''''\\)\\) for f in d.get\\(''''files'''',[]\\)]\")",
56
+ "Bash(curl -s 'https://api.filerobot.com/fbmjmuoeb/v5/files?preview=2301&q=type:%22image_jpg%22&folder=%2F&sort=modified_at:desc&offset=0&limit=2&recursive=1' -H 'x-filerobot-key: SECU_47D57B3106A841F7A1FEA951846CC5F3')",
57
+ "Bash(python3 -c \"import sys,json; d=json.load\\(sys.stdin\\); print\\(''''total:'''', d.get\\(''''info'''',{}\\).get\\(''''total_files_count'''',0\\)\\); [print\\(f[''''name'''']\\) for f in d.get\\(''''files'''',[]\\)]\")",
58
+ "Bash(curl -s 'https://api.filerobot.com/fbmjmuoeb/v5/filters/config' -H 'x-filerobot-key: SECU_47D57B3106A841F7A1FEA951846CC5F3')",
59
+ "Bash(curl -s 'https://api.filerobot.com/fbmjmuoeb/v5/filters?filter_by=filetype&format=list&limit=200' -H 'x-filerobot-key: SECU_47D57B3106A841F7A1FEA951846CC5F3')",
60
+ "Bash(curl -s 'https://api.filerobot.com/fbmjmuoeb/v5/files?format=json,regvar:api,select:internal&preview=2301&q=type:%22image_svg%22&folder=%2F&sort=created_at:desc&offset=0&limit=2&recursive=1' -H 'x-filerobot-key: SECU_47D57B3106A841F7A1FEA951846CC5F3')",
61
+ "Bash(curl -s 'https://api.filerobot.com/fbmjmuoeb/v5/files?preview=2301&q=type:%22image_png%22,%22image_jpeg%22,%22image_svg%22,%22image_webp%22,%22image_gif%22,%22image_other%22,%22image_psd%22,%22image_avif%22,%22image_tiff%22,%22image_eps%22&folder=%2F&sort=modified_at:desc&offset=0&limit=3&recursive=1' -H 'x-filerobot-key: SECU_47D57B3106A841F7A1FEA951846CC5F3')"
49
62
  ]
50
63
  }
51
64
  }
package/README.md CHANGED
@@ -44,6 +44,8 @@
44
44
  - [Public Methods](#public-methods)
45
45
  - [Events](#events)
46
46
  - [React API](#react-api)
47
+ - [Provider + Hook](#provider--hook-recommended)
48
+ - [Asset Utilities](#asset-utilities)
47
49
  - [Theming](#theming)
48
50
  - [Brand Color](#brand-color)
49
51
  - [CSS Custom Properties](#css-custom-properties)
@@ -112,8 +114,8 @@ pnpm add @scaleflex/asset-picker
112
114
 
113
115
  | Export path | Description |
114
116
  |---|---|
115
- | `@scaleflex/asset-picker` | `AssetPicker` class + all TypeScript types |
116
- | `@scaleflex/asset-picker/react` | React wrapper component |
117
+ | `@scaleflex/asset-picker` | `AssetPicker` class + all TypeScript types + asset utility functions |
118
+ | `@scaleflex/asset-picker/react` | React wrapper component + `AssetPickerProvider` + `useAssetPicker` hook |
117
119
  | `@scaleflex/asset-picker/define` | Side-effect import — registers `<sfx-asset-picker>` custom element |
118
120
 
119
121
  Both ESM (`import`) and CJS (`require`) builds are provided.
@@ -248,7 +250,13 @@ Use when your application already has a SASS key — e.g. inside the Scaleflex H
248
250
  | `rememberLastTab` | `boolean` | `false` | Persist the last active tab (assets/folders) and restore on next open |
249
251
  | `defaultFilters` | `FiltersInput` | `undefined` | Filters pre-applied on open. User can modify/remove |
250
252
  | `forcedFilters` | `FiltersInput` | `undefined` | Filters always active. Shown as locked chips the user cannot remove |
251
- | `onSelect` | `(assets: Asset[]) => void` | `undefined` | Callback when assets are selected |
253
+ | `displayMode` | `'modal' \| 'inline'` | `'modal'` | `'modal'` renders as a dialog overlay, `'inline'` renders in page flow |
254
+ | `gridSize` | `'normal' \| 'large'` | `'normal'` | Grid card density: `'normal'` (4 cols at ~1200px) or `'large'` (3 cols) |
255
+ | `stickyFilters` | `boolean` | `false` | Make the toolbar and filters bar sticky while scrolling content |
256
+ | `folderSelection` | `boolean` | `true` | Allow selecting folders via checkboxes |
257
+ | `folderSelectionMode` | `'folder' \| 'assets'` | `'folder'` | `'folder'` returns Folder objects; `'assets'` fetches folder contents and returns only Assets |
258
+ | `uploader` | `UploaderIntegrationConfig` | `undefined` | Enable integrated uploader. Adds an "Upload" button and drop zone. Requires `@scaleflex/uploader` |
259
+ | `onSelect` | `(assets: Asset[], folders?: Folder[]) => void` | `undefined` | Callback when assets are selected |
252
260
  | `onCancel` | `() => void` | `undefined` | Callback when the picker is cancelled |
253
261
 
254
262
  #### Sort fields
@@ -288,6 +296,13 @@ picker.config = {
288
296
  };
289
297
  ```
290
298
 
299
+ ```ts
300
+ // Lock to specific extensions using subtype values (category_extension)
301
+ forcedFilters: {
302
+ type: { values: ['image_svg', 'image_png'] },
303
+ },
304
+ ```
305
+
291
306
  **Behaviour:**
292
307
 
293
308
  - **`defaultFilters`** are seeded into the applied filters state when `open()` is called. The user sees them as normal filter chips and can modify or remove them freely.
@@ -320,7 +335,7 @@ All events bubble and cross shadow DOM boundaries (`composed: true`).
320
335
  | Event | Detail | Description |
321
336
  |---|---|---|
322
337
  | `ap-select` | `{ assets: Asset[] }` | Fired when the user confirms their selection |
323
- | `ap-cancel` | `{ reason: 'backdrop' \| 'escape' \| 'button' \| 'close-button' }` | Fired when the picker is closed without selecting |
338
+ | `ap-cancel` | `{ reason: 'backdrop' \| 'escape' \| 'button' }` | Fired when the picker is closed without selecting |
324
339
  | `ap-open` | `{ timestamp: number }` | Fired when the picker opens successfully |
325
340
  | `ap-error` | `{ error: Error, context: string }` | Fired on initialisation or runtime errors |
326
341
 
@@ -355,7 +370,8 @@ import { AssetPicker, type AssetPickerRef, type AssetPickerProps } from '@scalef
355
370
  |---|---|---|
356
371
  | `config` | `AssetPickerConfig` | Configuration object (see [Config Options](#config-options)) |
357
372
  | `open` | `boolean` | Controlled open state |
358
- | `onSelect` | `(assets: Asset[]) => void` | Selection callback |
373
+ | `onSelect` | `(assets: Asset[], folders?: Folder[]) => void` | Selection callback (assets + optional folders) |
374
+ | `onSelectWithFolders` | `(result: { assets: Asset[]; folders: Folder[] }) => void` | Alternative callback that always includes folders |
359
375
  | `onCancel` | `() => void` | Cancel callback |
360
376
  | `className` | `string` | CSS class for the wrapper |
361
377
  | `style` | `CSSProperties` | Inline styles for the wrapper |
@@ -392,6 +408,163 @@ const ref = useRef<AssetPickerRef>(null);
392
408
  <AssetPicker ref={ref} config={config} onSelect={handleSelect} />
393
409
  ```
394
410
 
411
+ ### Provider + Hook (recommended)
412
+
413
+ For apps that open the picker from many places, use `AssetPickerProvider` + `useAssetPicker()` to avoid managing open/close state yourself. One picker instance is mounted at the root and shared across the tree.
414
+
415
+ #### Setup
416
+
417
+ ```tsx
418
+ import { AssetPickerProvider } from '@scaleflex/asset-picker/react';
419
+
420
+ function App() {
421
+ return (
422
+ <AssetPickerProvider
423
+ config={{
424
+ auth: {
425
+ mode: 'sassKey',
426
+ sassKey: 'YOUR_SASS_KEY',
427
+ projectToken: 'YOUR_TOKEN',
428
+ },
429
+ }}
430
+ >
431
+ <Dashboard />
432
+ </AssetPickerProvider>
433
+ );
434
+ }
435
+ ```
436
+
437
+ #### Promise mode
438
+
439
+ ```tsx
440
+ import { useAssetPicker } from '@scaleflex/asset-picker/react';
441
+
442
+ function ImageSelector() {
443
+ const picker = useAssetPicker();
444
+
445
+ const handleClick = async () => {
446
+ try {
447
+ const assets = await picker.open({ multiSelect: true });
448
+ console.log('Selected:', assets);
449
+ } catch {
450
+ console.log('User cancelled');
451
+ }
452
+ };
453
+
454
+ return <button onClick={handleClick}>Choose images</button>;
455
+ }
456
+ ```
457
+
458
+ The promise resolves with the selected `Asset[]` on confirm, and rejects with `'cancelled'` when the user closes without selecting. Note: promise mode returns only assets — use callback mode with `onSelect(assets, folders)` if you need folder data.
459
+
460
+ #### Callback mode
461
+
462
+ ```tsx
463
+ function VideoSelector() {
464
+ const picker = useAssetPicker();
465
+
466
+ return (
467
+ <button
468
+ onClick={() =>
469
+ picker.open({
470
+ forcedFilters: { type: { values: ['video'] } },
471
+ onSelect: (assets) => console.log(assets),
472
+ onCancel: () => console.log('Cancelled'),
473
+ })
474
+ }
475
+ >
476
+ Choose video
477
+ </button>
478
+ );
479
+ }
480
+ ```
481
+
482
+ #### Config overrides
483
+
484
+ Any `AssetPickerConfig` property passed to `open()` is merged with (and overrides) the base config from the provider:
485
+
486
+ ```tsx
487
+ // Base config has multiSelect: false
488
+ // This call overrides it to true and adds a forced filter
489
+ const assets = await picker.open({
490
+ multiSelect: true,
491
+ forcedFilters: { type: { values: ['image'] } },
492
+ });
493
+ ```
494
+
495
+ #### Hook return type
496
+
497
+ ```ts
498
+ interface UseAssetPickerReturn {
499
+ open(overrides?: OpenOptions): Promise<Asset[]>;
500
+ close(): void;
501
+ isOpen: boolean;
502
+ }
503
+ ```
504
+
505
+ ---
506
+
507
+ ## Asset Utilities
508
+
509
+ Pure helper functions for working with `Asset` objects. Exported from the main entry point — no React required.
510
+
511
+ ```ts
512
+ import {
513
+ getAltText,
514
+ getCdnUrl,
515
+ getAssetWidth,
516
+ getAssetHeight,
517
+ getAssetDimensions,
518
+ isTranscoded,
519
+ getTranscodedUrl,
520
+ getBestVideoUrl,
521
+ isVideo,
522
+ isImage,
523
+ isAudio,
524
+ } from '@scaleflex/asset-picker';
525
+ ```
526
+
527
+ ### Type checks
528
+
529
+ | Function | Returns | Description |
530
+ |---|---|---|
531
+ | `isImage(asset)` | `boolean` | `true` if `asset.type` starts with `"image"` |
532
+ | `isVideo(asset)` | `boolean` | `true` if `asset.type` starts with `"video"` |
533
+ | `isAudio(asset)` | `boolean` | `true` if `asset.type` starts with `"audio"` |
534
+
535
+ ### URLs
536
+
537
+ | Function | Returns | Description |
538
+ |---|---|---|
539
+ | `getCdnUrl(asset)` | `string` | CDN URL, falling back to public URL, then `""` |
540
+ | `getBestVideoUrl(asset)` | `string` | Transcoded HLS URL > CDN URL > public URL |
541
+ | `getTranscodedUrl(asset)` | `string \| null` | HLS manifest URL, or `null` if not transcoded |
542
+
543
+ ### Alt text
544
+
545
+ ```ts
546
+ const alt = getAltText(asset); // uses first available language
547
+ const alt = getAltText(asset, 'fr'); // prefers French title
548
+ ```
549
+
550
+ Resolution priority: `meta.alt` > `meta.title` (string or localized `Record<string, string>`) > filename without extension.
551
+
552
+ ### Dimensions
553
+
554
+ | Function | Returns | Description |
555
+ |---|---|---|
556
+ | `getAssetWidth(asset)` | `number` | Width in px (`0` if unknown). Works for images and videos. |
557
+ | `getAssetHeight(asset)` | `number` | Height in px (`0` if unknown). Works for images and videos. |
558
+ | `getAssetDimensions(asset)` | `{ width, height }` | Both dimensions as an object. |
559
+
560
+ ### Video transcoding
561
+
562
+ | Function | Returns | Description |
563
+ |---|---|---|
564
+ | `isTranscoded(asset)` | `boolean` | Whether the asset has a transcoded HLS version |
565
+ | `getTranscodedUrl(asset)` | `string \| null` | The HLS manifest URL, or `null` |
566
+ | `getBestVideoUrl(asset)` | `string` | Best playback URL (transcoded > CDN > public) |
567
+
395
568
  ---
396
569
 
397
570
  ## Theming
@@ -628,24 +801,36 @@ interface Asset {
628
801
  };
629
802
  created_at: string; // ISO timestamp
630
803
  modified_at: string; // ISO timestamp
631
- tags: Record<string, any>;
804
+ tags: Record<string, Array<{ label: string; sid: string }>>
805
+ | Record<string, { label: string; sid: string }>
806
+ | string[];
632
807
  labels: string[];
633
808
  meta: {
634
- title?: string;
809
+ title?: string | Record<string, string>; // plain or localized by language code
635
810
  description?: string;
636
811
  alt?: string;
637
812
  [key: string]: unknown;
638
813
  };
639
814
  info: {
815
+ img_type?: string; // Image format (e.g. "jpeg", "png")
640
816
  img_w?: number; // Image width (px)
641
817
  img_h?: number; // Image height (px)
642
- duration?: number; // Video/audio duration (seconds)
818
+ duration?: number; // Audio duration (seconds)
819
+ video_duration?: number; // Video duration (seconds)
643
820
  video_w?: number; // Video width (px)
644
821
  video_h?: number; // Video height (px)
645
822
  thumbnail?: string; // Thumbnail URL
646
- main_colors_hex?: string[]; // Dominant colours
647
- dominant_color_hex?: string; // Most dominant colour
648
- [key: string]: unknown;
823
+ preview?: string; // Preview URL
824
+ video_thumbnail?: string; // Video poster image URL
825
+ video_gif?: string; // Animated GIF preview URL
826
+ image_thumbnail?: string; // Image thumbnail URL
827
+ main_colors?: string[]; // Dominant colours (names)
828
+ main_colors_hex?: string[]; // Dominant colours (hex)
829
+ dominant_color?: string; // Most dominant colour (name)
830
+ dominant_color_hex?: string; // Most dominant colour (hex)
831
+ color_space?: string; // Colour space (e.g. "sRGB")
832
+ metadata?: Record<string, unknown>; // Embedded metadata (EXIF, IPTC, etc.)
833
+ playlists?: Array<{ playlists: string[]; resolution?: string }>; // HLS transcoded playlists
649
834
  };
650
835
  folder?: {
651
836
  uuid: string;
@@ -666,8 +851,10 @@ interface Folder {
666
851
  uuid: string;
667
852
  name: string;
668
853
  path: string;
854
+ owner?: string;
669
855
  created_at: string;
670
856
  modified_at?: string;
857
+ updated_at?: string;
671
858
  count?: {
672
859
  files_recursive?: number;
673
860
  files_direct?: number;
@@ -675,6 +862,10 @@ interface Folder {
675
862
  size?: {
676
863
  total_recursive_bytes?: number;
677
864
  };
865
+ visibility?: {
866
+ in_cdn?: { actual: string; set: string };
867
+ in_dam?: { actual: string; set: string };
868
+ };
678
869
  }
679
870
  ```
680
871