@codingfactory/mediables-vue 2.4.22 → 2.5.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 (38) hide show
  1. package/README.md +7 -1
  2. package/dist/{PixiFrameExporter-plVSI1Cq.cjs → PixiFrameExporter-BrsHCMHb.cjs} +2 -2
  3. package/dist/{PixiFrameExporter-plVSI1Cq.cjs.map → PixiFrameExporter-BrsHCMHb.cjs.map} +1 -1
  4. package/dist/{PixiFrameExporter-B3vV4VNa.js → PixiFrameExporter-DFq2mc-y.js} +2 -2
  5. package/dist/{PixiFrameExporter-B3vV4VNa.js.map → PixiFrameExporter-DFq2mc-y.js.map} +1 -1
  6. package/dist/components/ExistingMediaSelector.vue.d.ts +13 -1
  7. package/dist/components/MediaAlbumInlineCreate.vue.d.ts +2 -0
  8. package/dist/components/MediaAlbumUnavailableState.vue.d.ts +2 -0
  9. package/dist/components/MediaLibraryPicker.vue.d.ts +2 -0
  10. package/dist/components/MediaLibraryPickerModal.vue.d.ts +2 -0
  11. package/dist/components/MediaPreviewSheet.vue.d.ts +2 -0
  12. package/dist/components/MediaSelectionTray.vue.d.ts +2 -0
  13. package/dist/components/MediaSourceSheet.vue.d.ts +2 -0
  14. package/dist/components/MediaThumbnailCell.vue.d.ts +2 -0
  15. package/dist/components/MediaUploadSheet.vue.d.ts +2 -0
  16. package/dist/components/VirtualMediaGrid.vue.d.ts +2 -0
  17. package/dist/composables/useMediaLibraryPickerController.d.ts +60 -0
  18. package/dist/composables/useMediaLibraryQuery.d.ts +25 -0
  19. package/dist/composables/useMediaLibrarySession.d.ts +12 -0
  20. package/dist/composables/useMediaLibraryStateMachine.d.ts +6 -0
  21. package/dist/composables/useMediaLibraryTelemetry.d.ts +4 -0
  22. package/dist/composables/useMediaUploadQueue.d.ts +2 -0
  23. package/dist/index-BTGCVCn6.cjs +342 -0
  24. package/dist/index-BTGCVCn6.cjs.map +1 -0
  25. package/dist/{index-CKq0Hx5v.js → index-CaQc9GBh.js} +11864 -10106
  26. package/dist/index-CaQc9GBh.js.map +1 -0
  27. package/dist/index.d.ts +18 -0
  28. package/dist/mediables-vue.cjs +1 -1
  29. package/dist/mediables-vue.mjs +89 -74
  30. package/dist/style.css +1 -1
  31. package/dist/types/collection.d.ts +1 -1
  32. package/dist/types/mediaLibraryPicker.d.ts +182 -0
  33. package/dist/types/mediaLibraryState.d.ts +29 -0
  34. package/docs/media-library-api.md +76 -0
  35. package/package.json +5 -2
  36. package/dist/index-CKq0Hx5v.js.map +0 -1
  37. package/dist/index-Dqxd2fFw.cjs +0 -342
  38. package/dist/index-Dqxd2fFw.cjs.map +0 -1
@@ -14,7 +14,7 @@ export interface UploadProgress {
14
14
  /** Upload progress percentage (0-100) */
15
15
  progress: number;
16
16
  /** Current upload status */
17
- status: 'pending' | 'queued' | 'uploading' | 'processing' | 'completed' | 'error';
17
+ status: 'pending' | 'queued' | 'uploading' | 'attaching' | 'processing' | 'completed' | 'error';
18
18
  /** Error message if status is 'error' */
19
19
  error?: string | undefined;
20
20
  /** Uploaded media item (available when status is 'completed') */
@@ -0,0 +1,182 @@
1
+ import type { VNode } from 'vue';
2
+ import type { StorageAdapter } from './adapter';
3
+ import type { Media } from './media';
4
+ export type MediaKind = 'image' | 'video' | 'audio' | 'document' | (string & {});
5
+ export type MediaSort = 'newest' | 'oldest';
6
+ export type MediaSelectionMode = 'single' | 'multiple';
7
+ export type MediaLibraryUploadBehavior = 'adapter' | 'delegate';
8
+ export type MediaLibraryView = {
9
+ scope: 'library';
10
+ } | {
11
+ scope: 'album';
12
+ albumId: string;
13
+ albumName?: string;
14
+ };
15
+ export interface MediaLibraryFilters {
16
+ query?: string;
17
+ kinds?: MediaKind[];
18
+ sort?: MediaSort;
19
+ }
20
+ export interface MediaUploadTarget {
21
+ type: 'library' | 'album';
22
+ albumId?: string;
23
+ }
24
+ export interface MediaLibraryRequestConfig {
25
+ params?: Record<string, unknown>;
26
+ signal?: AbortSignal;
27
+ headers?: Record<string, string>;
28
+ }
29
+ export interface MediaLibraryHttpClient {
30
+ get<T>(url: string, config?: MediaLibraryRequestConfig): Promise<T>;
31
+ post<T>(url: string, body?: unknown, config?: MediaLibraryRequestConfig): Promise<T>;
32
+ put?<T>(url: string, body?: unknown, config?: MediaLibraryRequestConfig): Promise<T>;
33
+ patch?<T>(url: string, body?: unknown, config?: MediaLibraryRequestConfig): Promise<T>;
34
+ delete?<T>(url: string, config?: MediaLibraryRequestConfig): Promise<T>;
35
+ }
36
+ export interface MediaLibraryPickerDeps {
37
+ httpClient: MediaLibraryHttpClient;
38
+ uploadAdapter?: StorageAdapter;
39
+ resolveThumbnailUrl?: (media: Media, variant?: 'sm' | 'md' | 'lg') => string | Promise<string>;
40
+ }
41
+ export interface MediaSessionPolicy {
42
+ key: string;
43
+ ttlMinutes?: number;
44
+ restoreSelection?: boolean;
45
+ restoreView?: boolean;
46
+ restoreUploadsMeta?: boolean;
47
+ }
48
+ export type SelectedItemStatus = 'ready' | 'hydrating' | 'missing' | 'forbidden' | 'uploading' | 'failed' | 'file_lost';
49
+ export interface SelectedMediaItem {
50
+ id: string;
51
+ media: Media | null;
52
+ status: SelectedItemStatus;
53
+ statusMessage?: string;
54
+ countsTowardMax: boolean;
55
+ }
56
+ export interface MediaLibraryHydratedRecord {
57
+ id: string;
58
+ status: 'ready' | 'missing' | 'forbidden';
59
+ media?: Media;
60
+ }
61
+ export interface MediaLibraryResponsePagination {
62
+ next_cursor: string | null;
63
+ previous_cursor: string | null;
64
+ has_more: boolean;
65
+ per_page: number | null;
66
+ count: number | null;
67
+ }
68
+ export interface MediaLibraryResponseMeta {
69
+ cursor: string | null;
70
+ has_more: boolean;
71
+ pagination?: MediaLibraryResponsePagination;
72
+ }
73
+ export interface MediaLibraryResponse {
74
+ data: Media[];
75
+ meta: MediaLibraryResponseMeta;
76
+ hydrated: MediaLibraryHydratedRecord[];
77
+ }
78
+ export interface MediaLibraryApiEnvelope {
79
+ success?: boolean;
80
+ message?: string;
81
+ data?: unknown;
82
+ meta?: unknown;
83
+ hydrated?: unknown;
84
+ }
85
+ export interface MediaLibraryAlbum {
86
+ id: string;
87
+ name: string;
88
+ description?: string;
89
+ mediaCount?: number;
90
+ }
91
+ export interface MediaLibraryPickerMetric {
92
+ name: string;
93
+ payload?: Record<string, unknown>;
94
+ timestamp: string;
95
+ }
96
+ export interface MediaLibraryUploadRequestPayload {
97
+ files: File[];
98
+ view: MediaLibraryView;
99
+ filters: MediaLibraryFilters;
100
+ sessionId: string;
101
+ }
102
+ export type MediaLibraryPickerErrorCode = 'query_failed' | 'load_more_failed' | 'refresh_failed' | 'upload_failed' | 'upload_partial_failure' | 'selection_invalid' | 'permission_denied' | 'hydration_failed' | 'session_restore_failed' | 'configuration_error' | 'album_create_failed';
103
+ export interface MediaLibraryPickerError {
104
+ code: MediaLibraryPickerErrorCode;
105
+ message: string;
106
+ retriable: boolean;
107
+ cause?: unknown;
108
+ }
109
+ export interface MediaLibraryPickerLabels {
110
+ title?: string;
111
+ searchPlaceholder?: string;
112
+ viewLibrary?: string;
113
+ browseAlbums?: string;
114
+ createAlbum?: string;
115
+ createAlbumNameLabel?: string;
116
+ emptyLibrary?: string;
117
+ emptyAlbum?: string;
118
+ networkError?: string;
119
+ retry?: string;
120
+ selectedCount?: (count: number) => string;
121
+ confirmAction?: string;
122
+ cancel?: string;
123
+ dismiss?: string;
124
+ uploadLabel?: string;
125
+ albumUnavailableTitle?: string;
126
+ albumUnavailableBody?: string;
127
+ goToLibrary?: string;
128
+ }
129
+ export interface MediaLibraryPickerProps {
130
+ deps: MediaLibraryPickerDeps;
131
+ modelValue?: string[];
132
+ defaultSelectedIds?: string[];
133
+ selectionMode?: MediaSelectionMode;
134
+ maxSelection?: number;
135
+ preserveSelectionOnViewChange?: boolean;
136
+ initialView?: MediaLibraryView;
137
+ initialFilters?: MediaLibraryFilters;
138
+ enableUpload?: boolean;
139
+ uploadBehavior?: MediaLibraryUploadBehavior;
140
+ enablePreview?: boolean;
141
+ enableAlbumCreation?: boolean;
142
+ allowedMediaKinds?: MediaKind[];
143
+ accept?: string;
144
+ validateSelection?: (items: SelectedMediaItem[]) => string | null;
145
+ uploadTarget?: MediaUploadTarget;
146
+ autoSelectUploaded?: boolean;
147
+ createAlbumDefaultVisibility?: 'private' | 'authenticated' | 'public';
148
+ sessionPolicy?: MediaSessionPolicy;
149
+ labels?: MediaLibraryPickerLabels;
150
+ testIdPrefix?: string;
151
+ telemetry?: (metric: MediaLibraryPickerMetric) => void;
152
+ }
153
+ export interface MediaLibraryConfirmPayload {
154
+ orderedIds: string[];
155
+ items: SelectedMediaItem[];
156
+ view: MediaLibraryView;
157
+ filters: MediaLibraryFilters;
158
+ dirty: boolean;
159
+ sessionId: string;
160
+ }
161
+ export interface MediaLibrarySelectionChangePayload {
162
+ orderedIds: string[];
163
+ items: SelectedMediaItem[];
164
+ dirty: boolean;
165
+ }
166
+ export interface MediaLibraryViewChangePayload {
167
+ from: MediaLibraryView;
168
+ to: MediaLibraryView;
169
+ reason: 'user' | 'restore' | 'permission-unavailable' | 'inline-album-created';
170
+ restoredFromSession: boolean;
171
+ }
172
+ export type PickerCancelReason = 'user' | 'backdrop' | 'escape' | 'host-close' | 'route-change';
173
+ export interface MediaLibraryPickerSlots {
174
+ toolbar?: () => VNode[];
175
+ empty?: (ctx: {
176
+ view: MediaLibraryView;
177
+ filters: MediaLibraryFilters;
178
+ }) => VNode[];
179
+ previewMeta?: (ctx: {
180
+ media: Media;
181
+ }) => VNode[];
182
+ }
@@ -0,0 +1,29 @@
1
+ import type { ComputedRef, Ref } from 'vue';
2
+ import type { UploadProgress } from './collection';
3
+ import type { Media } from './media';
4
+ import type { MediaLibraryAlbum, MediaLibraryFilters, MediaLibraryPickerError, MediaLibraryView, SelectedMediaItem } from './mediaLibraryPicker';
5
+ export interface MediaLibraryQueryState {
6
+ items: Ref<Media[]>;
7
+ cursor: Ref<string | null>;
8
+ hasMore: Ref<boolean>;
9
+ loading: Ref<boolean>;
10
+ loadingMore: Ref<boolean>;
11
+ permissionDenied: Ref<boolean>;
12
+ error: Ref<MediaLibraryPickerError | null>;
13
+ }
14
+ export interface MediaLibrarySelectionState {
15
+ items: Ref<SelectedMediaItem[]>;
16
+ orderedIds: ComputedRef<string[]>;
17
+ }
18
+ export interface MediaLibraryUploadState {
19
+ queue: ComputedRef<UploadProgress[]>;
20
+ isUploading: ComputedRef<boolean>;
21
+ }
22
+ export interface MediaLibraryControllerState {
23
+ view: Ref<MediaLibraryView>;
24
+ filters: Ref<MediaLibraryFilters>;
25
+ albums: Ref<MediaLibraryAlbum[]>;
26
+ query: MediaLibraryQueryState;
27
+ selection: MediaLibrarySelectionState;
28
+ upload: MediaLibraryUploadState;
29
+ }
@@ -0,0 +1,76 @@
1
+ # Media Library API
2
+
3
+ `MediaLibraryPicker` reads media through a host-provided `httpClient`. For blow.glass, the host implements the contract in `socialkit/Modules/Media` at `GET /api/v1/media/library`.
4
+
5
+ ## Request
6
+
7
+ `GET /api/v1/media/library`
8
+
9
+ Query parameters:
10
+
11
+ - `view[scope]`: `library | album`
12
+ - `view[album_id]`: required when `scope=album`
13
+ - `query`: optional search string
14
+ - `kinds[]`: optional `image | video | audio | document`
15
+ - `sort`: `newest | oldest`
16
+ - `created_after`: optional ISO datetime
17
+ - `uploaded_by_me`: optional boolean
18
+ - `cursor`: optional opaque cursor
19
+ - `per_page`: optional `1-100`, default `40`
20
+ - `hydrate_ids[]`: optional UUID list, max `50`
21
+
22
+ ## Success Response
23
+
24
+ ```json
25
+ {
26
+ "data": [
27
+ {
28
+ "uuid": "01981b2d-1111-4f75-8d0a-123456789abc",
29
+ "file_name": "furnace-progress.jpg",
30
+ "mime_type": "image/jpeg",
31
+ "size": 204800,
32
+ "disk": "public",
33
+ "collection_name": "images",
34
+ "original_url": "https://example.test/storage/furnace-progress.jpg",
35
+ "url": "https://example.test/storage/furnace-progress.jpg",
36
+ "thumbnail_url": "https://example.test/storage/conversions/furnace-progress-thumb.jpg",
37
+ "preview_url": "https://example.test/storage/conversions/furnace-progress-preview.jpg",
38
+ "caption": null,
39
+ "alt_text": null,
40
+ "created_at": "2026-04-12T12:34:56+00:00",
41
+ "updated_at": "2026-04-12T12:34:56+00:00"
42
+ }
43
+ ],
44
+ "meta": {
45
+ "cursor": "eyJjIjoiMjAyNi0wNC0xMlQxMjozNDo1Ni4xMjM0NTYrMDA6MDAiLCJpIjoiMDE5ODFiMmQtMTExMS00Zjc1LThkMGEtMTIzNDU2Nzg5YWJjIn0=",
46
+ "has_more": true
47
+ },
48
+ "hydrated": [
49
+ {
50
+ "id": "01981b2d-1111-4f75-8d0a-123456789abc",
51
+ "status": "ready",
52
+ "media": {
53
+ "uuid": "01981b2d-1111-4f75-8d0a-123456789abc"
54
+ }
55
+ },
56
+ {
57
+ "id": "01981b2d-2222-4f75-8d0a-123456789abc",
58
+ "status": "missing"
59
+ }
60
+ ]
61
+ }
62
+ ```
63
+
64
+ ## Cursor Contract
65
+
66
+ - The cursor is base64-encoded JSON with:
67
+ - `c`: ISO datetime with microseconds
68
+ - `i`: media UUID
69
+ - `sort=newest` uses `(created_at DESC, uuid DESC)`
70
+ - `sort=oldest` uses `(created_at ASC, uuid ASC)`
71
+
72
+ ## Host Notes
73
+
74
+ - Host apps should inject a thin client that returns parsed bodies, not Axios responses.
75
+ - Upload flows should reuse `StorageAdapter` and `useMediaUploadQueue`.
76
+ - The picker assumes `media.deleted_at IS NULL` is enforced by the backend query layer, including joined album queries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@codingfactory/mediables-vue",
3
- "version": "2.4.22",
3
+ "version": "2.5.0",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
@@ -16,7 +16,8 @@
16
16
  "types": "./dist/index.d.ts",
17
17
  "files": [
18
18
  "dist",
19
- "docs/video-subsystem"
19
+ "docs/video-subsystem",
20
+ "docs/media-library-api.md"
20
21
  ],
21
22
  "exports": {
22
23
  ".": {
@@ -48,6 +49,7 @@
48
49
  "docs:preview": "redocly preview-docs docs/openapi.yaml"
49
50
  },
50
51
  "peerDependencies": {
52
+ "@tanstack/vue-virtual": "^3.10.8",
51
53
  "@ionic/vue": "^8.0.0",
52
54
  "@pixi/extract": "^7.2.4",
53
55
  "@pixi/filter-color-replace": "^5.1.1",
@@ -84,6 +86,7 @@
84
86
  "dependencies": {
85
87
  "@capacitor/haptics": "^7.0.2",
86
88
  "@capacitor/status-bar": "^7.0.2",
89
+ "@tanstack/vue-virtual": "^3.10.8",
87
90
  "@ionic/vue": "^8.0.0",
88
91
  "@material/material-color-utilities": "^0.3.0",
89
92
  "@vueuse/core": "^12.0.0",