@codingfactory/mediables-vue 2.9.2 → 2.10.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 (29) hide show
  1. package/dist/{PixiFrameExporter-CUlCdDi7.cjs → PixiFrameExporter-CZ20kvpH.cjs} +2 -2
  2. package/dist/{PixiFrameExporter-CUlCdDi7.cjs.map → PixiFrameExporter-CZ20kvpH.cjs.map} +1 -1
  3. package/dist/{PixiFrameExporter-zsVyDi1A.js → PixiFrameExporter-hR8D733U.js} +2 -2
  4. package/dist/{PixiFrameExporter-zsVyDi1A.js.map → PixiFrameExporter-hR8D733U.js.map} +1 -1
  5. package/dist/components/MediaLibraryPicker.vue.d.ts +14 -1
  6. package/dist/components/MediaManagementView.vue.d.ts +19 -1
  7. package/dist/composables/useAssetImport.d.ts +48 -0
  8. package/dist/composables/useEditorShortcuts.d.ts +53 -0
  9. package/dist/composables/useMediaLibraryPickerController.d.ts +2 -1
  10. package/dist/composables/useProjectDraftSync.d.ts +43 -0
  11. package/dist/composables/useVideoEditor.d.ts +16 -8
  12. package/dist/index-CeeQFiXM.cjs +357 -0
  13. package/dist/{index-DeXGbdCM.cjs.map → index-CeeQFiXM.cjs.map} +1 -1
  14. package/dist/index-DDDfEJgO.js +34969 -0
  15. package/dist/{index-DwIO7GBg.js.map → index-DDDfEJgO.js.map} +1 -1
  16. package/dist/mediables-vue.cjs +1 -1
  17. package/dist/mediables-vue.mjs +1 -1
  18. package/dist/style.css +1 -1
  19. package/dist/types/mediaLibraryPicker.d.ts +119 -0
  20. package/dist/video/project/assetBlobCache.d.ts +41 -0
  21. package/dist/video/project/assetLifecycle.d.ts +95 -0
  22. package/dist/video/project/editorCommandResult.d.ts +25 -0
  23. package/dist/video/project/projectDraftStore.d.ts +91 -0
  24. package/dist/video/project/sourceUsage.d.ts +20 -0
  25. package/dist/video/project/timelineCollision.d.ts +41 -0
  26. package/dist/video/project/trackCompatibility.d.ts +16 -0
  27. package/package.json +1 -1
  28. package/dist/index-DeXGbdCM.cjs +0 -350
  29. package/dist/index-DwIO7GBg.js +0 -33044
@@ -125,6 +125,55 @@ export interface MediaLibraryPickerLabels {
125
125
  albumUnavailableTitle?: string;
126
126
  albumUnavailableBody?: string;
127
127
  goToLibrary?: string;
128
+ /**
129
+ * Tab label for the "all media" view inside the picker. Hosts can override
130
+ * to e.g. "Saved Media" or "Saved uploads" to match brand vocabulary.
131
+ * BUG-008-CR3-R13-009-A.
132
+ */
133
+ allMediaTab?: string;
134
+ /**
135
+ * Loading copy shown inside the media grid before the first page resolves.
136
+ */
137
+ loadingMedia?: string;
138
+ /**
139
+ * Label for the bottom "load more" button when more results are available.
140
+ */
141
+ loadMore?: string;
142
+ /**
143
+ * Label rendered while the load-more button is fetching the next page.
144
+ */
145
+ loadingMore?: string;
146
+ /**
147
+ * Visible label inside the upload dropzone. Defaults to
148
+ * "Drop files here or browse".
149
+ */
150
+ uploadDropzone?: string;
151
+ /**
152
+ * Optional helper text under the upload dropzone explaining accepted file
153
+ * types. Hosts that know their accept rule should pass concrete copy.
154
+ */
155
+ uploadDropzoneHelper?: string;
156
+ /**
157
+ * Label for the upload sheet's "Done" close button.
158
+ */
159
+ uploadDone?: string;
160
+ /**
161
+ * aria-label for the upload sheet's close button.
162
+ */
163
+ uploadDoneAria?: string;
164
+ /**
165
+ * Visually-hidden label tied to the upload file input for screen readers.
166
+ */
167
+ uploadFileInputLabel?: string;
168
+ /**
169
+ * Preview-sheet labels — exposed here so the picker can pass them down to
170
+ * MediaPreviewSheet without each host wiring slot overrides.
171
+ */
172
+ previewClose?: string;
173
+ previewPrevious?: string;
174
+ previewNext?: string;
175
+ previewSelect?: string;
176
+ previewDeselect?: string;
128
177
  }
129
178
  export interface MediaLibraryPickerProps {
130
179
  deps: MediaLibraryPickerDeps;
@@ -181,3 +230,73 @@ export interface MediaLibraryPickerSlots {
181
230
  media: Media;
182
231
  }) => VNode[];
183
232
  }
233
+ /**
234
+ * Scoped slot payloads exposed by Liquid Glass host components in this
235
+ * package. Hosts (e.g., blowglass) bind these slots to branded primitives
236
+ * such as `GlassNavigationTabs`, `GlassSearchInput`, and `GlassBadge`. The
237
+ * default rendering remains in place when a slot is not provided so that
238
+ * non-Blowglass consumers see no change.
239
+ *
240
+ * BUG-008-CR3-R13-008-A: Named-slot host-component contract.
241
+ */
242
+ /** Identifier for the picker tab the user is currently viewing. */
243
+ export type MediaLibraryPickerTabId = 'upload' | 'media' | 'albums';
244
+ /** Identifier for the management-view tab the user is currently viewing. */
245
+ export type MediaManagementTabId = 'library' | 'trash';
246
+ /**
247
+ * Payload for the picker `tabs` slot. Hosts get the active tab id, an
248
+ * activate handler, and the resolved labels so they can render any tab
249
+ * primitive that takes `{ id, label, onChange }`.
250
+ */
251
+ export interface MediaLibraryPickerTabsSlotProps {
252
+ activeTab: MediaLibraryPickerTabId;
253
+ setActiveTab: (tabId: MediaLibraryPickerTabId) => void;
254
+ enableUpload: boolean;
255
+ labels: {
256
+ uploadLabel: string;
257
+ allMediaTab: string;
258
+ browseAlbums: string;
259
+ };
260
+ }
261
+ /** Payload for the picker `search` slot. */
262
+ export interface MediaLibraryPickerSearchSlotProps {
263
+ query: string;
264
+ setQuery: (next: string) => void;
265
+ placeholder: string;
266
+ }
267
+ /** Payload for the management-view `tabs` slot. */
268
+ export interface MediaManagementTabsSlotProps {
269
+ activeTab: MediaManagementTabId;
270
+ setActiveTab: (tabId: MediaManagementTabId) => void;
271
+ trashCount: number;
272
+ showTrash: boolean;
273
+ labels: {
274
+ libraryTab: string;
275
+ trashTab: string;
276
+ };
277
+ }
278
+ /** Payload for the management-view `bulk-actions` slot. */
279
+ export interface MediaManagementBulkActionsSlotProps {
280
+ selectedCount: number;
281
+ isDeleting: boolean;
282
+ onBulkDelete: () => void | Promise<void>;
283
+ labels: {
284
+ deleteButton: string;
285
+ selectedCount: (count: number) => string;
286
+ };
287
+ }
288
+ /**
289
+ * Payload for the management-view `trash-action` slot. Rendered per-row
290
+ * inside the trash grid. Hosts can replace the default Restore button with
291
+ * a branded affordance while keeping per-item gating logic in the package.
292
+ */
293
+ export interface MediaManagementTrashActionSlotProps {
294
+ media: Media;
295
+ canRestore: boolean;
296
+ isRestoring: boolean;
297
+ onRestore: () => void | Promise<void>;
298
+ labels: {
299
+ restoreButton: string;
300
+ restoreUnavailable: string;
301
+ };
302
+ }
@@ -0,0 +1,41 @@
1
+ /**
2
+ * IndexedDB-backed cache for not-yet-uploaded asset blobs — see plan §7.B.
3
+ *
4
+ * Drafts must never serialize `File` or `blob:` URLs as durable state. When
5
+ * the user picks a file, we stash the binary in IndexedDB keyed by the
6
+ * client-side `assetId`. If the page reloads before the upload finishes, the
7
+ * draft restoration code can re-mint a fresh `blob:` URL from the cached
8
+ * binary so the editor still has something to play.
9
+ *
10
+ * Once the upload transitions to 'ready', the cache entry is dropped — the
11
+ * server is the source of truth from that point on.
12
+ *
13
+ * The interface intentionally mirrors `idb-keyval`'s shape so the runtime
14
+ * can swap the underlying store without touching call sites.
15
+ */
16
+ /**
17
+ * Asset-blob cache backed by IndexedDB. The class is fully async; throw / reject
18
+ * paths bubble up so the caller can decide whether to fall back to a plain
19
+ * object-URL for the in-memory session.
20
+ */
21
+ export declare class AssetBlobCache {
22
+ private dbPromise;
23
+ private openDb;
24
+ put(assetId: string, blob: Blob): Promise<void>;
25
+ get(assetId: string): Promise<Blob | null>;
26
+ delete(assetId: string): Promise<void>;
27
+ clear(): Promise<void>;
28
+ }
29
+ export declare function getSharedAssetBlobCache(): AssetBlobCache;
30
+ /**
31
+ * Best-effort wrapper that falls back to in-memory storage when IndexedDB is
32
+ * unavailable (Safari private browsing, certain WebViews). The fallback only
33
+ * survives the current session.
34
+ */
35
+ export declare class InMemoryAssetBlobCache {
36
+ private readonly store;
37
+ put(assetId: string, blob: Blob): Promise<void>;
38
+ get(assetId: string): Promise<Blob | null>;
39
+ delete(assetId: string): Promise<void>;
40
+ clear(): Promise<void>;
41
+ }
@@ -0,0 +1,95 @@
1
+ /**
2
+ * Asset lifecycle types and contracts — see plan §7.B and P0-1.
3
+ *
4
+ * Every imported asset moves through explicit states. The renderer never
5
+ * reads `File` objects or `blob:` URLs from durable state; it only consumes
6
+ * `assetId` references that resolve to a backend-owned media URL once the
7
+ * upload completes.
8
+ *
9
+ * The composable exposes a `MediaAssetUploader` interface so the host
10
+ * application (blowglass) can supply a real Laravel-backed implementation
11
+ * while tests use a mock. Splitting the interface from the composable keeps
12
+ * the package agnostic to the backend shape — a critical boundary requirement
13
+ * from the package's CLAUDE.md.
14
+ */
15
+ export type AssetUploadStatus = 'local-selected' | 'uploading' | 'processing' | 'ready' | 'failed' | 'missing' | 'unauthorized';
16
+ export interface AssetLifecycleRecord {
17
+ /** Stable client-side id assigned at selection time, kept across the lifecycle. */
18
+ assetId: string;
19
+ /** Optional server-side identifier once the upload resolves. */
20
+ mediaUuid?: string;
21
+ /** Backend playback URL once status transitions to 'ready'. */
22
+ mediaUrl?: string;
23
+ /** Original filename of the imported asset, for display. */
24
+ filename: string;
25
+ /** MIME type sniffed by the upload pipeline. */
26
+ mime: string;
27
+ /** Size in bytes; unknown for streaming sources. */
28
+ sizeBytes?: number;
29
+ /** Duration in seconds; populated as soon as the browser can decode metadata. */
30
+ durationSeconds?: number;
31
+ /** Pixel width for video / image; undefined for audio. */
32
+ width?: number;
33
+ /** Pixel height for video / image; undefined for audio. */
34
+ height?: number;
35
+ /** Optional checksum so reloaded drafts can detect drift. */
36
+ checksum?: string;
37
+ /** Current lifecycle state. */
38
+ status: AssetUploadStatus;
39
+ /** Upload progress, 0-1, while status === 'uploading'. */
40
+ uploadProgress?: number;
41
+ /** User-facing failure message when status === 'failed'. */
42
+ failureMessage?: string;
43
+ }
44
+ export interface MediaAssetSelection {
45
+ file: File;
46
+ /** Optional preview URL hint; the uploader is free to mint its own. */
47
+ previewUrl?: string;
48
+ }
49
+ export interface MediaAssetUploadProgress {
50
+ assetId: string;
51
+ progress: number;
52
+ status: AssetUploadStatus;
53
+ }
54
+ /**
55
+ * Host-supplied uploader. blowglass plugs in a Laravel-backed implementation
56
+ * that posts to /api/v1/video/assets; tests can supply a mock. The interface
57
+ * is intentionally narrow.
58
+ */
59
+ export interface MediaAssetUploader {
60
+ /**
61
+ * Upload a freshly selected asset. Returns a record that starts in either
62
+ * 'uploading' or 'ready' (for backends that finalize synchronously).
63
+ *
64
+ * Implementations MUST call `onProgress` at least once with the final state
65
+ * (`'ready'` or `'failed'`).
66
+ */
67
+ upload(selection: MediaAssetSelection, onProgress?: (event: MediaAssetUploadProgress) => void): Promise<AssetLifecycleRecord>;
68
+ /**
69
+ * Re-fetch the lifecycle record for an asset by its backend `mediaUuid`.
70
+ * Used by draft recovery to confirm an asset still exists and is owned by
71
+ * the current user.
72
+ */
73
+ resolve(mediaUuid: string): Promise<AssetLifecycleRecord | null>;
74
+ }
75
+ /** Maximum file size accepted at the frontend preflight. 2 GiB. */
76
+ export declare const ASSET_MAX_BYTES: number;
77
+ /** Maximum video / audio duration accepted, in seconds. 1 hour. */
78
+ export declare const ASSET_MAX_DURATION: number;
79
+ export type AssetPreflightFailure = 'unsupported_mime' | 'oversize' | 'duration_exceeded';
80
+ export interface AssetPreflightResult {
81
+ ok: true;
82
+ kind: 'video' | 'image' | 'audio';
83
+ }
84
+ export interface AssetPreflightFailureResult {
85
+ ok: false;
86
+ reason: AssetPreflightFailure;
87
+ message: string;
88
+ }
89
+ export type AssetPreflightOutcome = AssetPreflightResult | AssetPreflightFailureResult;
90
+ /**
91
+ * Frontend preflight before any upload starts. Backend-side enforcement is
92
+ * still required (MIME sniffing + per-user quota live there); this just
93
+ * catches the common rejections before they touch the network.
94
+ */
95
+ export declare function preflightAsset(file: File, durationSeconds?: number): AssetPreflightOutcome;
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Editor command result contract — see plan §7.C.
3
+ *
4
+ * Every mutation in `useVideoEditor` returns this shape. `VideoEditorV2`
5
+ * consumes it and surfaces failure reasons through a single accessible live
6
+ * region. Callers should never branch on a bare boolean.
7
+ */
8
+ export type EditorCommandFailureReason = 'last_clip' | 'no_selection' | 'track_locked' | 'track_missing' | 'track_type_mismatch' | 'overlap' | 'asset_not_ready' | 'invariant_violation' | 'source_not_found' | 'source_in_use';
9
+ export interface EditorCommandFailure {
10
+ ok: false;
11
+ reason: EditorCommandFailureReason;
12
+ message: string;
13
+ }
14
+ export interface EditorCommandSuccess<T> {
15
+ ok: true;
16
+ value: T;
17
+ }
18
+ export type EditorCommandResult<T = void> = EditorCommandSuccess<T> | EditorCommandFailure;
19
+ export declare function commandOk(): EditorCommandSuccess<void>;
20
+ export declare function commandOk<T>(value: T): EditorCommandSuccess<T>;
21
+ export declare function commandFail(reason: EditorCommandFailureReason, message: string): EditorCommandFailure;
22
+ /**
23
+ * Default user-facing message for a failure reason. Caller can override.
24
+ */
25
+ export declare function defaultMessageFor(reason: EditorCommandFailureReason): string;
@@ -0,0 +1,91 @@
1
+ /**
2
+ * Server-backed project draft client — see plan §7 P0-7.
3
+ *
4
+ * The package exposes a `ProjectDraftStore` interface and a default
5
+ * `RestProjectDraftStore` implementation that hits a JSON endpoint with the
6
+ * canonical contract:
7
+ *
8
+ * GET /api/v1/video/projects/:projectId/drafts/latest
9
+ * → { draft: ProjectDraftEnvelope } | { draft: null }
10
+ *
11
+ * PUT /api/v1/video/projects/:projectId/drafts
12
+ * { recipe, version, sessionId }
13
+ * → 201 { draft: ProjectDraftEnvelope }
14
+ * → 409 { conflict: ProjectDraftEnvelope } when versions diverge
15
+ *
16
+ * GET /api/v1/video/projects/:projectId/drafts/recent
17
+ * → { drafts: ProjectDraftEnvelope[] }
18
+ *
19
+ * Hosts that don't ship the backend yet can plug in `InMemoryProjectDraftStore`
20
+ * for local-only operation. Tests use the in-memory store directly.
21
+ *
22
+ * Multi-tab coordination uses BroadcastChannel: the active tab announces
23
+ * `{ type: 'draft-saved', projectId, version }` after each successful save,
24
+ * and other tabs editing the same project show a "Newer version available"
25
+ * banner. When BroadcastChannel is unavailable (older Safari), the conflict
26
+ * is still detected on the next save attempt via the 409 response.
27
+ */
28
+ import type { VideoRecipe } from '../../types/video';
29
+ export interface ProjectDraftEnvelope {
30
+ projectId: string;
31
+ version: number;
32
+ recipe: VideoRecipe;
33
+ /** ISO 8601 timestamp the server stored the draft. */
34
+ savedAt: string;
35
+ /** Identifier of the editor session that wrote this draft. */
36
+ sessionId: string;
37
+ }
38
+ export interface SaveDraftRequest {
39
+ recipe: VideoRecipe;
40
+ /** Last known server version; the request is rejected if the server is ahead. */
41
+ baseVersion: number | null;
42
+ sessionId: string;
43
+ }
44
+ export type SaveDraftResult = {
45
+ ok: true;
46
+ draft: ProjectDraftEnvelope;
47
+ } | {
48
+ ok: false;
49
+ reason: 'conflict';
50
+ remote: ProjectDraftEnvelope;
51
+ } | {
52
+ ok: false;
53
+ reason: 'unauthorized' | 'network' | 'unknown';
54
+ message: string;
55
+ };
56
+ export interface ProjectDraftStore {
57
+ /** Load the most recent draft for a project, or `null` if none exists. */
58
+ loadLatest(projectId: string): Promise<ProjectDraftEnvelope | null>;
59
+ /** Save a new draft. Returns conflict when the server version is ahead. */
60
+ save(projectId: string, request: SaveDraftRequest): Promise<SaveDraftResult>;
61
+ /** List the recent draft envelopes for the project (server-side rotation). */
62
+ listRecent?(projectId: string): Promise<ProjectDraftEnvelope[]>;
63
+ }
64
+ /**
65
+ * Convenience id generator for editor sessions. Stable across saves within a
66
+ * single tab; new tab opens get a fresh id.
67
+ */
68
+ export declare function generateSessionId(): string;
69
+ /**
70
+ * In-memory store, primarily for tests and for hosts that haven't shipped
71
+ * the backend yet. Persists to a Map keyed by projectId.
72
+ */
73
+ export declare class InMemoryProjectDraftStore implements ProjectDraftStore {
74
+ private readonly latest;
75
+ private readonly history;
76
+ loadLatest(projectId: string): Promise<ProjectDraftEnvelope | null>;
77
+ save(projectId: string, request: SaveDraftRequest): Promise<SaveDraftResult>;
78
+ listRecent(projectId: string): Promise<ProjectDraftEnvelope[]>;
79
+ }
80
+ /**
81
+ * Lightweight REST client. Adapts `fetch` to the contract documented above.
82
+ * Hosts can subclass or wrap this to add CSRF tokens, retries, etc.
83
+ */
84
+ export declare class RestProjectDraftStore implements ProjectDraftStore {
85
+ private readonly baseUrl;
86
+ private readonly fetcher;
87
+ constructor(baseUrl: string, fetcher?: typeof fetch);
88
+ loadLatest(projectId: string): Promise<ProjectDraftEnvelope | null>;
89
+ save(projectId: string, request: SaveDraftRequest): Promise<SaveDraftResult>;
90
+ listRecent(projectId: string): Promise<ProjectDraftEnvelope[]>;
91
+ }
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Single source-of-truth for "which media sources is this project actually
3
+ * using right now?" — see plan §7 P1-11.
4
+ *
5
+ * Used by:
6
+ * - removeMediaSource → block deletion when in-use
7
+ * - export validators → confirm every referenced source resolves
8
+ * - draft serializer → optionally prune orphaned sources
9
+ *
10
+ * The function intentionally checks every place a TimelineClipSource id can be
11
+ * referenced, not just `state.clips`. As soon as graphics layers, text layers,
12
+ * or future visual layers also gain `sourceRefId`, add them here and every
13
+ * caller stays correct.
14
+ */
15
+ import type { TimelineClip } from '../../types/video';
16
+ export interface SourceUsageInput {
17
+ clips: readonly TimelineClip[];
18
+ }
19
+ export declare function collectReferencedSourceIds(input: SourceUsageInput): ReadonlySet<string>;
20
+ export declare function isSourceInUse(input: SourceUsageInput, sourceId: string): boolean;
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Timeline collision resolver — see plan §7 P0-6.
3
+ *
4
+ * Shared by `duplicateSelectedClip`, `appendMediaSourceToTimeline`, and the
5
+ * cross-track drag path. Finds a non-overlapping insertion slot for a clip on
6
+ * a given track, or reports that none exists.
7
+ *
8
+ * All time values are seconds.
9
+ */
10
+ import type { TimelineClip } from '../../types/video';
11
+ export interface InsertionSlotInput {
12
+ /** Clips currently on the target track, in any order. */
13
+ trackClips: readonly TimelineClip[];
14
+ /** Duration the new clip will occupy on the timeline, in seconds. */
15
+ desiredDuration: number;
16
+ /** Preferred start time (e.g. playhead, or the existing clip's end). */
17
+ preferredStart: number;
18
+ /**
19
+ * Clip id to ignore in the collision check, e.g. when the moving clip is
20
+ * already on the track and we want to skip it during slot search.
21
+ */
22
+ ignoreClipId?: string;
23
+ }
24
+ export interface InsertionSlotFound {
25
+ ok: true;
26
+ start: number;
27
+ }
28
+ export interface InsertionSlotMissing {
29
+ ok: false;
30
+ }
31
+ export type InsertionSlotResult = InsertionSlotFound | InsertionSlotMissing;
32
+ /**
33
+ * Find a non-overlapping insertion slot on a track.
34
+ *
35
+ * Strategy:
36
+ * 1. If the preferred start fits, use it.
37
+ * 2. Otherwise, find the next gap large enough at or after the preferred start.
38
+ * 3. If the track is "full" up to the preferred area, append after the last clip.
39
+ * 4. If `desiredDuration` is non-positive, return `{ ok: false }`.
40
+ */
41
+ export declare function findInsertionSlot(input: InsertionSlotInput): InsertionSlotResult;
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Track / clip type compatibility matrix — see plan §7.E.
3
+ *
4
+ * Used by cross-track drag, AssetRail "add to track", and programmatic
5
+ * `appendMediaSourceToTimeline` to decide whether a clip is a legal payload
6
+ * for a given track.
7
+ */
8
+ import type { TimelineClipKind, TimelineTrackType } from '../../types/video';
9
+ export declare function isClipKindAllowedOnTrack(clipKind: TimelineClipKind, trackType: TimelineTrackType): boolean;
10
+ export declare function allowedClipKindsForTrack(trackType: TimelineTrackType): readonly TimelineClipKind[];
11
+ /**
12
+ * Z-order semantics — see plan §7.F.
13
+ * Higher `track.order` = visually higher in the stacking sequence.
14
+ * This constant is the documented default; renderers must read it.
15
+ */
16
+ export declare const TRACK_Z_ORDER_HIGHER_IS_TOP: true;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@codingfactory/mediables-vue",
3
- "version": "2.9.2",
3
+ "version": "2.10.0",
4
4
  "publishConfig": {
5
5
  "access": "restricted"
6
6
  },