@ai-matrx/capture 0.3.1 → 0.4.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.
- package/CHANGELOG.md +5 -0
- package/dist/react.cjs +114 -33
- package/dist/react.cjs.map +1 -1
- package/dist/react.d.cts +9 -5
- package/dist/react.d.ts +9 -5
- package/dist/react.js +106 -25
- package/dist/react.js.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -66,3 +66,8 @@ Deliberately not copied from the app this borrows its shape from: fixed duration
|
|
|
66
66
|
## 0.3.1 — 2026-08-30
|
|
67
67
|
|
|
68
68
|
- v3: the rail renders while the camera is blocked (host actions keep working through the upload lane; core camera actions disable instead of vanishing), and status chips clear the `topCenter` line instead of colliding with it.
|
|
69
|
+
|
|
70
|
+
## 0.4.0 — 2026-08-30
|
|
71
|
+
|
|
72
|
+
- **Viewer: videos swipe exactly like photos.** The video slide is now a package-owned control-less player — the STAGE owns every gesture (native `controls` made the whole slide a pointer sink), a tap toggles playback, a centre glyph shows the paused state, and a thin bottom bar shows progress. Audio keeps native controls.
|
|
73
|
+
- **HoldShutter: lock is a GESTURE, never a timer.** Recording follows the finger — release stops, full stop. While holding, slide UP ~64px to the lock target that appears above the button; locked, release keeps recording and the next tap stops. The 1.2s auto-latch is gone (it silently converted "I let go" into "still recording").
|
package/dist/react.cjs
CHANGED
|
@@ -654,6 +654,7 @@ function MediaViewer({
|
|
|
654
654
|
const clamped = Math.min(index, count - 1);
|
|
655
655
|
const current = count > 0 ? items[clamped] : void 0;
|
|
656
656
|
const [confirmingDelete, setConfirmingDelete] = (0, import_react3.useState)(false);
|
|
657
|
+
const activeVideoRef = (0, import_react3.useRef)(null);
|
|
657
658
|
const dragRef = (0, import_react3.useRef)(null);
|
|
658
659
|
const [drag, setDrag] = (0, import_react3.useState)(null);
|
|
659
660
|
const [settling, setSettling] = (0, import_react3.useState)(false);
|
|
@@ -694,7 +695,7 @@ function MediaViewer({
|
|
|
694
695
|
return () => window.removeEventListener("keydown", onKey);
|
|
695
696
|
}, [go, onClose, keysDisabled]);
|
|
696
697
|
const onPointerDown = (0, import_react3.useCallback)((e) => {
|
|
697
|
-
if (e.target.closest("
|
|
698
|
+
if (e.target.closest("audio")) return;
|
|
698
699
|
dragRef.current = {
|
|
699
700
|
x0: e.clientX,
|
|
700
701
|
y0: e.clientY,
|
|
@@ -741,6 +742,14 @@ function MediaViewer({
|
|
|
741
742
|
return;
|
|
742
743
|
}
|
|
743
744
|
}
|
|
745
|
+
if (d.axis === null) {
|
|
746
|
+
const video = activeVideoRef.current;
|
|
747
|
+
if (video) {
|
|
748
|
+
if (video.paused) void video.play().catch(() => {
|
|
749
|
+
});
|
|
750
|
+
else video.pause();
|
|
751
|
+
}
|
|
752
|
+
}
|
|
744
753
|
setDrag(null);
|
|
745
754
|
setSettling(true);
|
|
746
755
|
},
|
|
@@ -817,7 +826,15 @@ function MediaViewer({
|
|
|
817
826
|
onPointerMove,
|
|
818
827
|
onPointerUp: endDrag,
|
|
819
828
|
onPointerCancel: endDrag,
|
|
820
|
-
children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
|
|
829
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
|
|
830
|
+
ViewerSlide,
|
|
831
|
+
{
|
|
832
|
+
item: current,
|
|
833
|
+
active: true,
|
|
834
|
+
videoRef: activeVideoRef
|
|
835
|
+
},
|
|
836
|
+
current.key
|
|
837
|
+
)
|
|
821
838
|
}
|
|
822
839
|
),
|
|
823
840
|
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
|
|
@@ -910,7 +927,8 @@ function MediaViewer({
|
|
|
910
927
|
}
|
|
911
928
|
function ViewerSlide({
|
|
912
929
|
item,
|
|
913
|
-
active
|
|
930
|
+
active,
|
|
931
|
+
videoRef
|
|
914
932
|
}) {
|
|
915
933
|
const url = useMediaUrl(item);
|
|
916
934
|
if (!url) {
|
|
@@ -939,20 +957,7 @@ function ViewerSlide({
|
|
|
939
957
|
] });
|
|
940
958
|
}
|
|
941
959
|
if (item.kind === "video") {
|
|
942
|
-
return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
|
|
943
|
-
"video",
|
|
944
|
-
{
|
|
945
|
-
src: url,
|
|
946
|
-
controls: active,
|
|
947
|
-
playsInline: true,
|
|
948
|
-
preload: active ? "auto" : "metadata",
|
|
949
|
-
muted: !active,
|
|
950
|
-
className: cn(
|
|
951
|
-
"absolute inset-0 h-full w-full object-contain",
|
|
952
|
-
active ? "pointer-events-auto" : "pointer-events-none"
|
|
953
|
-
)
|
|
954
|
-
}
|
|
955
|
-
);
|
|
960
|
+
return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(VideoSlide, { url, active, videoRef });
|
|
956
961
|
}
|
|
957
962
|
return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
|
|
958
963
|
"img",
|
|
@@ -965,6 +970,51 @@ function ViewerSlide({
|
|
|
965
970
|
}
|
|
966
971
|
);
|
|
967
972
|
}
|
|
973
|
+
function VideoSlide({
|
|
974
|
+
url,
|
|
975
|
+
active,
|
|
976
|
+
videoRef
|
|
977
|
+
}) {
|
|
978
|
+
const [paused, setPaused] = (0, import_react3.useState)(true);
|
|
979
|
+
const [progress, setProgress] = (0, import_react3.useState)(0);
|
|
980
|
+
const localRef = (0, import_react3.useRef)(null);
|
|
981
|
+
(0, import_react3.useEffect)(() => {
|
|
982
|
+
if (!active || !videoRef) return;
|
|
983
|
+
videoRef.current = localRef.current;
|
|
984
|
+
return () => {
|
|
985
|
+
if (videoRef.current === localRef.current) videoRef.current = null;
|
|
986
|
+
};
|
|
987
|
+
}, [active, videoRef]);
|
|
988
|
+
return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "pointer-events-none absolute inset-0", children: [
|
|
989
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
|
|
990
|
+
"video",
|
|
991
|
+
{
|
|
992
|
+
ref: localRef,
|
|
993
|
+
src: url,
|
|
994
|
+
playsInline: true,
|
|
995
|
+
preload: active ? "auto" : "metadata",
|
|
996
|
+
muted: !active,
|
|
997
|
+
loop: false,
|
|
998
|
+
onPlay: () => setPaused(false),
|
|
999
|
+
onPause: () => setPaused(true),
|
|
1000
|
+
onEnded: () => setPaused(true),
|
|
1001
|
+
onTimeUpdate: (e) => {
|
|
1002
|
+
const v = e.currentTarget;
|
|
1003
|
+
setProgress(v.duration > 0 ? v.currentTime / v.duration : 0);
|
|
1004
|
+
},
|
|
1005
|
+
className: "absolute inset-0 h-full w-full object-contain"
|
|
1006
|
+
}
|
|
1007
|
+
),
|
|
1008
|
+
active && paused && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "absolute inset-0 flex items-center justify-center", children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "flex h-16 w-16 items-center justify-center rounded-full bg-black/45", children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_lucide_react2.Play, { className: "ml-1 h-8 w-8 fill-white text-white" }) }) }),
|
|
1009
|
+
active && !paused && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "absolute inset-x-4 bottom-2 h-0.5 overflow-hidden rounded-full bg-white/25", children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
|
|
1010
|
+
"span",
|
|
1011
|
+
{
|
|
1012
|
+
className: "block h-full rounded-full bg-white",
|
|
1013
|
+
style: { width: `${Math.round(progress * 100)}%` }
|
|
1014
|
+
}
|
|
1015
|
+
) })
|
|
1016
|
+
] });
|
|
1017
|
+
}
|
|
968
1018
|
|
|
969
1019
|
// src/components/ImageEditSheet.tsx
|
|
970
1020
|
var import_react4 = require("react");
|
|
@@ -1649,7 +1699,7 @@ function CameraCapture({
|
|
|
1649
1699
|
|
|
1650
1700
|
// src/components/CameraCaptureV3.tsx
|
|
1651
1701
|
var import_react8 = require("react");
|
|
1652
|
-
var
|
|
1702
|
+
var import_lucide_react6 = require("lucide-react");
|
|
1653
1703
|
|
|
1654
1704
|
// src/components/CaptureRail.tsx
|
|
1655
1705
|
var import_react6 = require("react");
|
|
@@ -1741,9 +1791,10 @@ function Chevron({ className }) {
|
|
|
1741
1791
|
|
|
1742
1792
|
// src/components/HoldShutter.tsx
|
|
1743
1793
|
var import_react7 = require("react");
|
|
1794
|
+
var import_lucide_react5 = require("lucide-react");
|
|
1744
1795
|
var import_jsx_runtime13 = require("react/jsx-runtime");
|
|
1745
1796
|
var HOLD_MS = 320;
|
|
1746
|
-
var
|
|
1797
|
+
var LOCK_DRAG_PX = 64;
|
|
1747
1798
|
function HoldShutter({
|
|
1748
1799
|
recording,
|
|
1749
1800
|
elapsedSeconds,
|
|
@@ -1756,6 +1807,7 @@ function HoldShutter({
|
|
|
1756
1807
|
}) {
|
|
1757
1808
|
const holdTimer = (0, import_react7.useRef)(null);
|
|
1758
1809
|
const startedRecording = (0, import_react7.useRef)(false);
|
|
1810
|
+
const pressOriginY = (0, import_react7.useRef)(0);
|
|
1759
1811
|
const [pressed, setPressed] = (0, import_react7.useState)(false);
|
|
1760
1812
|
const [locked, setLocked] = (0, import_react7.useState)(false);
|
|
1761
1813
|
const clearHold = (0, import_react7.useCallback)(() => {
|
|
@@ -1765,17 +1817,14 @@ function HoldShutter({
|
|
|
1765
1817
|
}
|
|
1766
1818
|
}, []);
|
|
1767
1819
|
(0, import_react7.useEffect)(() => {
|
|
1768
|
-
if (!recording)
|
|
1769
|
-
|
|
1770
|
-
return;
|
|
1771
|
-
}
|
|
1772
|
-
if (elapsedSeconds * 1e3 >= LOCK_AFTER_MS) setLocked(true);
|
|
1773
|
-
}, [recording, elapsedSeconds]);
|
|
1820
|
+
if (!recording) setLocked(false);
|
|
1821
|
+
}, [recording]);
|
|
1774
1822
|
(0, import_react7.useEffect)(() => clearHold, [clearHold]);
|
|
1775
1823
|
const onPointerDown = (0, import_react7.useCallback)(
|
|
1776
1824
|
(event) => {
|
|
1777
1825
|
if (disabled) return;
|
|
1778
1826
|
event.currentTarget.setPointerCapture?.(event.pointerId);
|
|
1827
|
+
pressOriginY.current = event.clientY;
|
|
1779
1828
|
setPressed(true);
|
|
1780
1829
|
onPressStart?.();
|
|
1781
1830
|
if (recording && locked) return;
|
|
@@ -1788,6 +1837,16 @@ function HoldShutter({
|
|
|
1788
1837
|
},
|
|
1789
1838
|
[disabled, locked, onPressStart, onStartRecording, recording]
|
|
1790
1839
|
);
|
|
1840
|
+
const onPointerMove = (0, import_react7.useCallback)(
|
|
1841
|
+
(event) => {
|
|
1842
|
+
if (!pressed || locked || !startedRecording.current) return;
|
|
1843
|
+
if (pressOriginY.current - event.clientY >= LOCK_DRAG_PX) {
|
|
1844
|
+
setLocked(true);
|
|
1845
|
+
navigator.vibrate?.(30);
|
|
1846
|
+
}
|
|
1847
|
+
},
|
|
1848
|
+
[pressed, locked]
|
|
1849
|
+
);
|
|
1791
1850
|
const endPress = (0, import_react7.useCallback)(() => {
|
|
1792
1851
|
if (!pressed) return;
|
|
1793
1852
|
setPressed(false);
|
|
@@ -1843,12 +1902,34 @@ function HoldShutter({
|
|
|
1843
1902
|
]
|
|
1844
1903
|
}
|
|
1845
1904
|
) : null,
|
|
1905
|
+
recording && pressed && !locked && /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(
|
|
1906
|
+
"span",
|
|
1907
|
+
{
|
|
1908
|
+
className: "pointer-events-none absolute left-1/2 flex -translate-x-1/2 flex-col items-center gap-0.5",
|
|
1909
|
+
style: { top: -LOCK_DRAG_PX - 22 },
|
|
1910
|
+
"aria-hidden": true,
|
|
1911
|
+
children: [
|
|
1912
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)("span", { className: "flex h-9 w-9 items-center justify-center rounded-full bg-black/60 ring-1 ring-white/30", children: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_lucide_react5.Lock, { className: "h-4 w-4 text-white/90" }) }),
|
|
1913
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)("span", { className: "text-[10px] font-medium text-white/80 drop-shadow", children: "Slide up to lock" })
|
|
1914
|
+
]
|
|
1915
|
+
}
|
|
1916
|
+
),
|
|
1917
|
+
recording && locked && /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
|
|
1918
|
+
"span",
|
|
1919
|
+
{
|
|
1920
|
+
className: "pointer-events-none absolute left-1/2 flex h-9 w-9 -translate-x-1/2 items-center justify-center rounded-full bg-[#FF3B30]",
|
|
1921
|
+
style: { top: -LOCK_DRAG_PX - 22 },
|
|
1922
|
+
"aria-hidden": true,
|
|
1923
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_lucide_react5.Lock, { className: "h-4 w-4 text-white", fill: "currentColor" })
|
|
1924
|
+
}
|
|
1925
|
+
),
|
|
1846
1926
|
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
|
|
1847
1927
|
"button",
|
|
1848
1928
|
{
|
|
1849
1929
|
type: "button",
|
|
1850
1930
|
disabled,
|
|
1851
1931
|
onPointerDown,
|
|
1932
|
+
onPointerMove,
|
|
1852
1933
|
onPointerUp: endPress,
|
|
1853
1934
|
onPointerCancel: endPress,
|
|
1854
1935
|
onLostPointerCapture: endPress,
|
|
@@ -1962,7 +2043,7 @@ function CameraCaptureV3({
|
|
|
1962
2043
|
{
|
|
1963
2044
|
id: "flip",
|
|
1964
2045
|
label: "Switch camera",
|
|
1965
|
-
icon: /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
|
|
2046
|
+
icon: /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(import_lucide_react6.RefreshCw, { className: "h-[22px] w-[22px]" }),
|
|
1966
2047
|
primary: true,
|
|
1967
2048
|
onPress: engine.onFlipCamera
|
|
1968
2049
|
}
|
|
@@ -1971,7 +2052,7 @@ function CameraCaptureV3({
|
|
|
1971
2052
|
{
|
|
1972
2053
|
id: "flash",
|
|
1973
2054
|
label: "Flash",
|
|
1974
|
-
icon: controls.torchOn ? /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
|
|
2055
|
+
icon: controls.torchOn ? /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(import_lucide_react6.Zap, { className: "h-[22px] w-[22px]", fill: "currentColor" }) : /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(import_lucide_react6.ZapOff, { className: "h-[22px] w-[22px]" }),
|
|
1975
2056
|
active: controls.torchOn,
|
|
1976
2057
|
primary: true,
|
|
1977
2058
|
onPress: controls.toggleTorch
|
|
@@ -1980,7 +2061,7 @@ function CameraCaptureV3({
|
|
|
1980
2061
|
{
|
|
1981
2062
|
id: "timer",
|
|
1982
2063
|
label: "Timer",
|
|
1983
|
-
icon: /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
|
|
2064
|
+
icon: /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(import_lucide_react6.Timer, { className: "h-[22px] w-[22px]" }),
|
|
1984
2065
|
active: timerSetting !== 0,
|
|
1985
2066
|
valueLabel: timerSetting === 0 ? void 0 : `${timerSetting}s`,
|
|
1986
2067
|
onPress: () => setTimerSetting((t) => t === 0 ? 3 : t === 3 ? 10 : 0)
|
|
@@ -1988,14 +2069,14 @@ function CameraCaptureV3({
|
|
|
1988
2069
|
{
|
|
1989
2070
|
id: "grid",
|
|
1990
2071
|
label: "Grid",
|
|
1991
|
-
icon: /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
|
|
2072
|
+
icon: /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(import_lucide_react6.Grid3x3, { className: "h-[22px] w-[22px]" }),
|
|
1992
2073
|
active: gridOn,
|
|
1993
2074
|
onPress: () => setGridOn((g) => !g)
|
|
1994
2075
|
},
|
|
1995
2076
|
{
|
|
1996
2077
|
id: "aspect",
|
|
1997
2078
|
label: "Aspect",
|
|
1998
|
-
icon: /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
|
|
2079
|
+
icon: /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(import_lucide_react6.Proportions, { className: "h-[22px] w-[22px]" }),
|
|
1999
2080
|
active: aspect !== "full",
|
|
2000
2081
|
valueLabel: aspect === "full" ? void 0 : aspect,
|
|
2001
2082
|
onPress: () => setAspect(
|
|
@@ -2006,7 +2087,7 @@ function CameraCaptureV3({
|
|
|
2006
2087
|
{
|
|
2007
2088
|
id: "exposure",
|
|
2008
2089
|
label: "Exposure",
|
|
2009
|
-
icon: /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
|
|
2090
|
+
icon: /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(import_lucide_react6.SunMedium, { className: "h-[22px] w-[22px]" }),
|
|
2010
2091
|
active: controls.exposure !== 0,
|
|
2011
2092
|
valueLabel: controls.exposure === 0 ? void 0 : `${controls.exposure > 0 ? "+" : ""}${controls.exposure}`,
|
|
2012
2093
|
onPress: () => controls.setExposure(controls.exposure === 0 ? 1 : 0)
|
|
@@ -2055,7 +2136,7 @@ function CameraCaptureV3({
|
|
|
2055
2136
|
onClick: onClose,
|
|
2056
2137
|
"aria-label": "Close camera",
|
|
2057
2138
|
className: "pointer-events-auto flex h-10 w-10 shrink-0 touch-manipulation items-center justify-center rounded-full text-white drop-shadow-[0_1px_3px_rgba(0,0,0,0.6)] transition-colors hover:bg-white/10",
|
|
2058
|
-
children: /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
|
|
2139
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(import_lucide_react6.X, { className: "h-6 w-6" })
|
|
2059
2140
|
}
|
|
2060
2141
|
) : /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("span", { className: "h-10 w-10 shrink-0" }),
|
|
2061
2142
|
/* @__PURE__ */ (0, import_jsx_runtime14.jsx)("div", { className: "pointer-events-auto flex min-w-0 flex-1 justify-center", children: slots.topEntry }),
|
|
@@ -2102,7 +2183,7 @@ function CameraCaptureV3({
|
|
|
2102
2183
|
onClick: cloud.onOpenLibrary,
|
|
2103
2184
|
"aria-label": "Your media \u2014 library and upload",
|
|
2104
2185
|
className: "relative h-12 w-12 touch-manipulation overflow-hidden rounded-xl bg-black/40 ring-1 ring-white/30 backdrop-blur-md transition-transform active:scale-95",
|
|
2105
|
-
children: cloud.recentsThumb ?? /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("span", { className: "flex h-full w-full items-center justify-center", children: /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
|
|
2186
|
+
children: cloud.recentsThumb ?? /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("span", { className: "flex h-full w-full items-center justify-center", children: /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(import_lucide_react6.Images, { className: "h-5 w-5 text-white/80" }) })
|
|
2106
2187
|
}
|
|
2107
2188
|
) }),
|
|
2108
2189
|
/* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
|