@ai-matrx/capture 0.5.3 → 0.5.4
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.
- package/CHANGELOG.md +5 -0
- package/dist/react.cjs +65 -5
- package/dist/react.cjs.map +1 -1
- package/dist/react.d.cts +3 -0
- package/dist/react.d.ts +3 -0
- package/dist/react.js +65 -5
- package/dist/react.js.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
# Changelog — @ai-matrx/capture
|
|
2
2
|
|
|
3
|
+
## 0.5.4 — 2026-09-01
|
|
4
|
+
|
|
5
|
+
- Persisted-media editing now has an explicit fresh-byte port (`resolveEditBlob`). The package creates and owns an editor-private object URL instead of reusing a viewer URL that the host byte cache may evict and revoke while the decoded viewer frame remains visible.
|
|
6
|
+
- Both camera chromes revoke the editor-private URL on every close/save/unmount path and surface a visible retry remedy when byte preparation fails. Exact fail-first coverage reproduces a stale viewer URL and proves the editor consumes fresh bytes.
|
|
7
|
+
|
|
3
8
|
## 0.5.3 — 2026-09-01
|
|
4
9
|
|
|
5
10
|
- `ImageEditSheet` now keeps Save, crop presets, rotate, and flip unavailable while a newly baked bitmap is decoding, then enables them from the image's real `load` event. A transformed image can no longer hit the zero-dimension silent-return race and leave the editor stuck with no persistence or error.
|
package/dist/react.cjs
CHANGED
|
@@ -727,6 +727,16 @@ function invalidateMedia(key) {
|
|
|
727
727
|
if (entry?.revoke && entry.url) URL.revokeObjectURL(entry.url);
|
|
728
728
|
notify(key);
|
|
729
729
|
}
|
|
730
|
+
async function prepareMediaForEdit(item) {
|
|
731
|
+
if (item.resolveEditBlob) {
|
|
732
|
+
const blob = await item.resolveEditBlob();
|
|
733
|
+
if (blob.size === 0) throw new Error("media snapshot was empty");
|
|
734
|
+
return { url: URL.createObjectURL(blob), revoke: true };
|
|
735
|
+
}
|
|
736
|
+
const url = item.src ?? getMediaUrl(item);
|
|
737
|
+
if (!url) throw new Error("media item has no editable source");
|
|
738
|
+
return { url, revoke: false };
|
|
739
|
+
}
|
|
730
740
|
function useMediaUrl(item) {
|
|
731
741
|
const [, bump] = (0, import_react2.useState)(0);
|
|
732
742
|
const key = item?.key ?? null;
|
|
@@ -1604,6 +1614,10 @@ function CameraCapture({
|
|
|
1604
1614
|
const [optionsOpen, setOptionsOpen] = (0, import_react5.useState)(false);
|
|
1605
1615
|
const [viewerKey, setViewerKey] = (0, import_react5.useState)(null);
|
|
1606
1616
|
const [editTarget, setEditTarget] = (0, import_react5.useState)(null);
|
|
1617
|
+
const [editPreparing, setEditPreparing] = (0, import_react5.useState)(false);
|
|
1618
|
+
const [editPreparationError, setEditPreparationError] = (0, import_react5.useState)(
|
|
1619
|
+
null
|
|
1620
|
+
);
|
|
1607
1621
|
const [gridOn, setGridOn] = (0, import_react5.useState)(false);
|
|
1608
1622
|
const [timerSetting, setTimerSetting] = (0, import_react5.useState)(0);
|
|
1609
1623
|
const [aspect, setAspect] = (0, import_react5.useState)("full");
|
|
@@ -1628,6 +1642,12 @@ function CameraCapture({
|
|
|
1628
1642
|
(0, import_react5.useEffect)(() => {
|
|
1629
1643
|
onReviewOpenChange?.(reviewOpen);
|
|
1630
1644
|
}, [reviewOpen, onReviewOpenChange]);
|
|
1645
|
+
(0, import_react5.useEffect)(
|
|
1646
|
+
() => () => {
|
|
1647
|
+
if (editTarget?.revoke) URL.revokeObjectURL(editTarget.url);
|
|
1648
|
+
},
|
|
1649
|
+
[editTarget]
|
|
1650
|
+
);
|
|
1631
1651
|
const clearCountdown = (0, import_react5.useCallback)(() => {
|
|
1632
1652
|
if (countdownRef.current) clearInterval(countdownRef.current);
|
|
1633
1653
|
countdownRef.current = null;
|
|
@@ -1941,8 +1961,16 @@ function CameraCapture({
|
|
|
1941
1961
|
invalidateMedia(item.key);
|
|
1942
1962
|
} : void 0,
|
|
1943
1963
|
onEdit: media.onReplacePhoto ? (item) => {
|
|
1944
|
-
|
|
1945
|
-
|
|
1964
|
+
if (editPreparing) return;
|
|
1965
|
+
setEditPreparing(true);
|
|
1966
|
+
setEditPreparationError(null);
|
|
1967
|
+
void prepareMediaForEdit(item).then(
|
|
1968
|
+
(prepared) => setEditTarget({ key: item.key, ...prepared })
|
|
1969
|
+
).catch(
|
|
1970
|
+
() => setEditPreparationError(
|
|
1971
|
+
"This photo could not be prepared for editing. Try again."
|
|
1972
|
+
)
|
|
1973
|
+
).finally(() => setEditPreparing(false));
|
|
1946
1974
|
} : void 0
|
|
1947
1975
|
}
|
|
1948
1976
|
),
|
|
@@ -1963,6 +1991,14 @@ function CameraCapture({
|
|
|
1963
1991
|
}
|
|
1964
1992
|
}
|
|
1965
1993
|
),
|
|
1994
|
+
editPreparationError && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
|
|
1995
|
+
"p",
|
|
1996
|
+
{
|
|
1997
|
+
role: "alert",
|
|
1998
|
+
className: "fixed inset-x-4 bottom-24 z-[70] rounded-xl bg-black/85 px-4 py-3 text-center text-sm text-[#FF6961]",
|
|
1999
|
+
children: editPreparationError
|
|
2000
|
+
}
|
|
2001
|
+
),
|
|
1966
2002
|
slots.overlays
|
|
1967
2003
|
] });
|
|
1968
2004
|
}
|
|
@@ -2261,7 +2297,9 @@ function CameraCaptureV3({
|
|
|
2261
2297
|
slots = {}
|
|
2262
2298
|
}) {
|
|
2263
2299
|
const [viewerKey, setViewerKey] = (0, import_react8.useState)(null);
|
|
2264
|
-
const [editTarget, setEditTarget] = (0, import_react8.useState)(
|
|
2300
|
+
const [editTarget, setEditTarget] = (0, import_react8.useState)(null);
|
|
2301
|
+
const [editPreparing, setEditPreparing] = (0, import_react8.useState)(false);
|
|
2302
|
+
const [editPreparationError, setEditPreparationError] = (0, import_react8.useState)(
|
|
2265
2303
|
null
|
|
2266
2304
|
);
|
|
2267
2305
|
const [gridOn, setGridOn] = (0, import_react8.useState)(false);
|
|
@@ -2275,6 +2313,12 @@ function CameraCaptureV3({
|
|
|
2275
2313
|
(0, import_react8.useEffect)(() => {
|
|
2276
2314
|
onReviewOpenChange?.(reviewOpen);
|
|
2277
2315
|
}, [reviewOpen, onReviewOpenChange]);
|
|
2316
|
+
(0, import_react8.useEffect)(
|
|
2317
|
+
() => () => {
|
|
2318
|
+
if (editTarget?.revoke) URL.revokeObjectURL(editTarget.url);
|
|
2319
|
+
},
|
|
2320
|
+
[editTarget]
|
|
2321
|
+
);
|
|
2278
2322
|
const clearCountdown = (0, import_react8.useCallback)(() => {
|
|
2279
2323
|
if (countdownRef.current) clearInterval(countdownRef.current);
|
|
2280
2324
|
countdownRef.current = null;
|
|
@@ -2493,8 +2537,16 @@ function CameraCaptureV3({
|
|
|
2493
2537
|
invalidateMedia(item.key);
|
|
2494
2538
|
} : void 0,
|
|
2495
2539
|
onEdit: media.onReplacePhoto ? (item) => {
|
|
2496
|
-
|
|
2497
|
-
|
|
2540
|
+
if (editPreparing) return;
|
|
2541
|
+
setEditPreparing(true);
|
|
2542
|
+
setEditPreparationError(null);
|
|
2543
|
+
void prepareMediaForEdit(item).then(
|
|
2544
|
+
(prepared) => setEditTarget({ key: item.key, ...prepared })
|
|
2545
|
+
).catch(
|
|
2546
|
+
() => setEditPreparationError(
|
|
2547
|
+
"This photo could not be prepared for editing. Try again."
|
|
2548
|
+
)
|
|
2549
|
+
).finally(() => setEditPreparing(false));
|
|
2498
2550
|
} : void 0
|
|
2499
2551
|
}
|
|
2500
2552
|
),
|
|
@@ -2515,6 +2567,14 @@ function CameraCaptureV3({
|
|
|
2515
2567
|
}
|
|
2516
2568
|
}
|
|
2517
2569
|
),
|
|
2570
|
+
editPreparationError && /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
|
|
2571
|
+
"p",
|
|
2572
|
+
{
|
|
2573
|
+
role: "alert",
|
|
2574
|
+
className: "fixed inset-x-4 bottom-24 z-[70] rounded-xl bg-black/85 px-4 py-3 text-center text-sm text-[#FF6961]",
|
|
2575
|
+
children: editPreparationError
|
|
2576
|
+
}
|
|
2577
|
+
),
|
|
2518
2578
|
slots.overlays
|
|
2519
2579
|
] });
|
|
2520
2580
|
}
|