@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/dist/react.d.cts
CHANGED
|
@@ -446,14 +446,18 @@ declare function CaptureRail({ actions, defaultExpanded, className, }: CaptureRa
|
|
|
446
446
|
* pointerdown → arm a hold timer (HOLD_MS)
|
|
447
447
|
* timer fires → start recording, ring begins filling
|
|
448
448
|
* pointerup < HOLD_MS → cancel the timer, take a photo
|
|
449
|
-
* pointerup >= HOLD_MS → stop recording
|
|
449
|
+
* pointerup >= HOLD_MS → stop recording (recording follows the finger)
|
|
450
|
+
* slide up ≥ LOCK_DRAG_PX while holding → LOCK: release keeps recording,
|
|
451
|
+
* the next tap stops
|
|
450
452
|
* pointercancel / leave → same as pointerup (a drag off the button must not
|
|
451
453
|
* leave the camera recording with nothing to stop it)
|
|
452
454
|
*
|
|
453
|
-
* A LOCK is
|
|
454
|
-
*
|
|
455
|
-
*
|
|
456
|
-
*
|
|
455
|
+
* A LOCK is a GESTURE, never a timer (Arman, 2026-08-30: recording follows
|
|
456
|
+
* the finger — releasing stops, full stop — unless the user deliberately
|
|
457
|
+
* locks): while holding a recording, slide UP to the lock target that
|
|
458
|
+
* appears above the button. Locked, release keeps recording and the next
|
|
459
|
+
* tap stops. An automatic time-based latch shipped first and was wrong —
|
|
460
|
+
* it silently converted "I let go" into "still recording".
|
|
457
461
|
*
|
|
458
462
|
* Presentational only — the host owns the recorder.
|
|
459
463
|
*/
|
package/dist/react.d.ts
CHANGED
|
@@ -446,14 +446,18 @@ declare function CaptureRail({ actions, defaultExpanded, className, }: CaptureRa
|
|
|
446
446
|
* pointerdown → arm a hold timer (HOLD_MS)
|
|
447
447
|
* timer fires → start recording, ring begins filling
|
|
448
448
|
* pointerup < HOLD_MS → cancel the timer, take a photo
|
|
449
|
-
* pointerup >= HOLD_MS → stop recording
|
|
449
|
+
* pointerup >= HOLD_MS → stop recording (recording follows the finger)
|
|
450
|
+
* slide up ≥ LOCK_DRAG_PX while holding → LOCK: release keeps recording,
|
|
451
|
+
* the next tap stops
|
|
450
452
|
* pointercancel / leave → same as pointerup (a drag off the button must not
|
|
451
453
|
* leave the camera recording with nothing to stop it)
|
|
452
454
|
*
|
|
453
|
-
* A LOCK is
|
|
454
|
-
*
|
|
455
|
-
*
|
|
456
|
-
*
|
|
455
|
+
* A LOCK is a GESTURE, never a timer (Arman, 2026-08-30: recording follows
|
|
456
|
+
* the finger — releasing stops, full stop — unless the user deliberately
|
|
457
|
+
* locks): while holding a recording, slide UP to the lock target that
|
|
458
|
+
* appears above the button. Locked, release keeps recording and the next
|
|
459
|
+
* tap stops. An automatic time-based latch shipped first and was wrong —
|
|
460
|
+
* it silently converted "I let go" into "still recording".
|
|
457
461
|
*
|
|
458
462
|
* Presentational only — the host owns the recorder.
|
|
459
463
|
*/
|
package/dist/react.js
CHANGED
|
@@ -601,7 +601,7 @@ import {
|
|
|
601
601
|
useRef,
|
|
602
602
|
useState as useState3
|
|
603
603
|
} from "react";
|
|
604
|
-
import { ChevronLeft, ChevronRight, Pencil, Trash2, X as X2 } from "lucide-react";
|
|
604
|
+
import { ChevronLeft, ChevronRight, Pencil, Play, Trash2, X as X2 } from "lucide-react";
|
|
605
605
|
import { jsx as jsx9, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
606
606
|
var SWIPE_TRIGGER_PX = 40;
|
|
607
607
|
var SWIPE_TRIGGER_VELOCITY = 0.25;
|
|
@@ -624,6 +624,7 @@ function MediaViewer({
|
|
|
624
624
|
const clamped = Math.min(index, count - 1);
|
|
625
625
|
const current = count > 0 ? items[clamped] : void 0;
|
|
626
626
|
const [confirmingDelete, setConfirmingDelete] = useState3(false);
|
|
627
|
+
const activeVideoRef = useRef(null);
|
|
627
628
|
const dragRef = useRef(null);
|
|
628
629
|
const [drag, setDrag] = useState3(null);
|
|
629
630
|
const [settling, setSettling] = useState3(false);
|
|
@@ -664,7 +665,7 @@ function MediaViewer({
|
|
|
664
665
|
return () => window.removeEventListener("keydown", onKey);
|
|
665
666
|
}, [go, onClose, keysDisabled]);
|
|
666
667
|
const onPointerDown = useCallback2((e) => {
|
|
667
|
-
if (e.target.closest("
|
|
668
|
+
if (e.target.closest("audio")) return;
|
|
668
669
|
dragRef.current = {
|
|
669
670
|
x0: e.clientX,
|
|
670
671
|
y0: e.clientY,
|
|
@@ -711,6 +712,14 @@ function MediaViewer({
|
|
|
711
712
|
return;
|
|
712
713
|
}
|
|
713
714
|
}
|
|
715
|
+
if (d.axis === null) {
|
|
716
|
+
const video = activeVideoRef.current;
|
|
717
|
+
if (video) {
|
|
718
|
+
if (video.paused) void video.play().catch(() => {
|
|
719
|
+
});
|
|
720
|
+
else video.pause();
|
|
721
|
+
}
|
|
722
|
+
}
|
|
714
723
|
setDrag(null);
|
|
715
724
|
setSettling(true);
|
|
716
725
|
},
|
|
@@ -787,7 +796,15 @@ function MediaViewer({
|
|
|
787
796
|
onPointerMove,
|
|
788
797
|
onPointerUp: endDrag,
|
|
789
798
|
onPointerCancel: endDrag,
|
|
790
|
-
children: /* @__PURE__ */ jsx9(
|
|
799
|
+
children: /* @__PURE__ */ jsx9(
|
|
800
|
+
ViewerSlide,
|
|
801
|
+
{
|
|
802
|
+
item: current,
|
|
803
|
+
active: true,
|
|
804
|
+
videoRef: activeVideoRef
|
|
805
|
+
},
|
|
806
|
+
current.key
|
|
807
|
+
)
|
|
791
808
|
}
|
|
792
809
|
),
|
|
793
810
|
/* @__PURE__ */ jsx9(
|
|
@@ -880,7 +897,8 @@ function MediaViewer({
|
|
|
880
897
|
}
|
|
881
898
|
function ViewerSlide({
|
|
882
899
|
item,
|
|
883
|
-
active
|
|
900
|
+
active,
|
|
901
|
+
videoRef
|
|
884
902
|
}) {
|
|
885
903
|
const url = useMediaUrl(item);
|
|
886
904
|
if (!url) {
|
|
@@ -909,20 +927,7 @@ function ViewerSlide({
|
|
|
909
927
|
] });
|
|
910
928
|
}
|
|
911
929
|
if (item.kind === "video") {
|
|
912
|
-
return /* @__PURE__ */ jsx9(
|
|
913
|
-
"video",
|
|
914
|
-
{
|
|
915
|
-
src: url,
|
|
916
|
-
controls: active,
|
|
917
|
-
playsInline: true,
|
|
918
|
-
preload: active ? "auto" : "metadata",
|
|
919
|
-
muted: !active,
|
|
920
|
-
className: cn(
|
|
921
|
-
"absolute inset-0 h-full w-full object-contain",
|
|
922
|
-
active ? "pointer-events-auto" : "pointer-events-none"
|
|
923
|
-
)
|
|
924
|
-
}
|
|
925
|
-
);
|
|
930
|
+
return /* @__PURE__ */ jsx9(VideoSlide, { url, active, videoRef });
|
|
926
931
|
}
|
|
927
932
|
return /* @__PURE__ */ jsx9(
|
|
928
933
|
"img",
|
|
@@ -935,6 +940,51 @@ function ViewerSlide({
|
|
|
935
940
|
}
|
|
936
941
|
);
|
|
937
942
|
}
|
|
943
|
+
function VideoSlide({
|
|
944
|
+
url,
|
|
945
|
+
active,
|
|
946
|
+
videoRef
|
|
947
|
+
}) {
|
|
948
|
+
const [paused, setPaused] = useState3(true);
|
|
949
|
+
const [progress, setProgress] = useState3(0);
|
|
950
|
+
const localRef = useRef(null);
|
|
951
|
+
useEffect3(() => {
|
|
952
|
+
if (!active || !videoRef) return;
|
|
953
|
+
videoRef.current = localRef.current;
|
|
954
|
+
return () => {
|
|
955
|
+
if (videoRef.current === localRef.current) videoRef.current = null;
|
|
956
|
+
};
|
|
957
|
+
}, [active, videoRef]);
|
|
958
|
+
return /* @__PURE__ */ jsxs7("div", { className: "pointer-events-none absolute inset-0", children: [
|
|
959
|
+
/* @__PURE__ */ jsx9(
|
|
960
|
+
"video",
|
|
961
|
+
{
|
|
962
|
+
ref: localRef,
|
|
963
|
+
src: url,
|
|
964
|
+
playsInline: true,
|
|
965
|
+
preload: active ? "auto" : "metadata",
|
|
966
|
+
muted: !active,
|
|
967
|
+
loop: false,
|
|
968
|
+
onPlay: () => setPaused(false),
|
|
969
|
+
onPause: () => setPaused(true),
|
|
970
|
+
onEnded: () => setPaused(true),
|
|
971
|
+
onTimeUpdate: (e) => {
|
|
972
|
+
const v = e.currentTarget;
|
|
973
|
+
setProgress(v.duration > 0 ? v.currentTime / v.duration : 0);
|
|
974
|
+
},
|
|
975
|
+
className: "absolute inset-0 h-full w-full object-contain"
|
|
976
|
+
}
|
|
977
|
+
),
|
|
978
|
+
active && paused && /* @__PURE__ */ jsx9("span", { className: "absolute inset-0 flex items-center justify-center", children: /* @__PURE__ */ jsx9("span", { className: "flex h-16 w-16 items-center justify-center rounded-full bg-black/45", children: /* @__PURE__ */ jsx9(Play, { className: "ml-1 h-8 w-8 fill-white text-white" }) }) }),
|
|
979
|
+
active && !paused && /* @__PURE__ */ jsx9("span", { className: "absolute inset-x-4 bottom-2 h-0.5 overflow-hidden rounded-full bg-white/25", children: /* @__PURE__ */ jsx9(
|
|
980
|
+
"span",
|
|
981
|
+
{
|
|
982
|
+
className: "block h-full rounded-full bg-white",
|
|
983
|
+
style: { width: `${Math.round(progress * 100)}%` }
|
|
984
|
+
}
|
|
985
|
+
) })
|
|
986
|
+
] });
|
|
987
|
+
}
|
|
938
988
|
|
|
939
989
|
// src/components/ImageEditSheet.tsx
|
|
940
990
|
import {
|
|
@@ -1726,9 +1776,10 @@ function Chevron({ className }) {
|
|
|
1726
1776
|
|
|
1727
1777
|
// src/components/HoldShutter.tsx
|
|
1728
1778
|
import { useCallback as useCallback5, useEffect as useEffect6, useRef as useRef4, useState as useState7 } from "react";
|
|
1779
|
+
import { Lock } from "lucide-react";
|
|
1729
1780
|
import { jsx as jsx13, jsxs as jsxs11 } from "react/jsx-runtime";
|
|
1730
1781
|
var HOLD_MS = 320;
|
|
1731
|
-
var
|
|
1782
|
+
var LOCK_DRAG_PX = 64;
|
|
1732
1783
|
function HoldShutter({
|
|
1733
1784
|
recording,
|
|
1734
1785
|
elapsedSeconds,
|
|
@@ -1741,6 +1792,7 @@ function HoldShutter({
|
|
|
1741
1792
|
}) {
|
|
1742
1793
|
const holdTimer = useRef4(null);
|
|
1743
1794
|
const startedRecording = useRef4(false);
|
|
1795
|
+
const pressOriginY = useRef4(0);
|
|
1744
1796
|
const [pressed, setPressed] = useState7(false);
|
|
1745
1797
|
const [locked, setLocked] = useState7(false);
|
|
1746
1798
|
const clearHold = useCallback5(() => {
|
|
@@ -1750,17 +1802,14 @@ function HoldShutter({
|
|
|
1750
1802
|
}
|
|
1751
1803
|
}, []);
|
|
1752
1804
|
useEffect6(() => {
|
|
1753
|
-
if (!recording)
|
|
1754
|
-
|
|
1755
|
-
return;
|
|
1756
|
-
}
|
|
1757
|
-
if (elapsedSeconds * 1e3 >= LOCK_AFTER_MS) setLocked(true);
|
|
1758
|
-
}, [recording, elapsedSeconds]);
|
|
1805
|
+
if (!recording) setLocked(false);
|
|
1806
|
+
}, [recording]);
|
|
1759
1807
|
useEffect6(() => clearHold, [clearHold]);
|
|
1760
1808
|
const onPointerDown = useCallback5(
|
|
1761
1809
|
(event) => {
|
|
1762
1810
|
if (disabled) return;
|
|
1763
1811
|
event.currentTarget.setPointerCapture?.(event.pointerId);
|
|
1812
|
+
pressOriginY.current = event.clientY;
|
|
1764
1813
|
setPressed(true);
|
|
1765
1814
|
onPressStart?.();
|
|
1766
1815
|
if (recording && locked) return;
|
|
@@ -1773,6 +1822,16 @@ function HoldShutter({
|
|
|
1773
1822
|
},
|
|
1774
1823
|
[disabled, locked, onPressStart, onStartRecording, recording]
|
|
1775
1824
|
);
|
|
1825
|
+
const onPointerMove = useCallback5(
|
|
1826
|
+
(event) => {
|
|
1827
|
+
if (!pressed || locked || !startedRecording.current) return;
|
|
1828
|
+
if (pressOriginY.current - event.clientY >= LOCK_DRAG_PX) {
|
|
1829
|
+
setLocked(true);
|
|
1830
|
+
navigator.vibrate?.(30);
|
|
1831
|
+
}
|
|
1832
|
+
},
|
|
1833
|
+
[pressed, locked]
|
|
1834
|
+
);
|
|
1776
1835
|
const endPress = useCallback5(() => {
|
|
1777
1836
|
if (!pressed) return;
|
|
1778
1837
|
setPressed(false);
|
|
@@ -1828,12 +1887,34 @@ function HoldShutter({
|
|
|
1828
1887
|
]
|
|
1829
1888
|
}
|
|
1830
1889
|
) : null,
|
|
1890
|
+
recording && pressed && !locked && /* @__PURE__ */ jsxs11(
|
|
1891
|
+
"span",
|
|
1892
|
+
{
|
|
1893
|
+
className: "pointer-events-none absolute left-1/2 flex -translate-x-1/2 flex-col items-center gap-0.5",
|
|
1894
|
+
style: { top: -LOCK_DRAG_PX - 22 },
|
|
1895
|
+
"aria-hidden": true,
|
|
1896
|
+
children: [
|
|
1897
|
+
/* @__PURE__ */ jsx13("span", { className: "flex h-9 w-9 items-center justify-center rounded-full bg-black/60 ring-1 ring-white/30", children: /* @__PURE__ */ jsx13(Lock, { className: "h-4 w-4 text-white/90" }) }),
|
|
1898
|
+
/* @__PURE__ */ jsx13("span", { className: "text-[10px] font-medium text-white/80 drop-shadow", children: "Slide up to lock" })
|
|
1899
|
+
]
|
|
1900
|
+
}
|
|
1901
|
+
),
|
|
1902
|
+
recording && locked && /* @__PURE__ */ jsx13(
|
|
1903
|
+
"span",
|
|
1904
|
+
{
|
|
1905
|
+
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]",
|
|
1906
|
+
style: { top: -LOCK_DRAG_PX - 22 },
|
|
1907
|
+
"aria-hidden": true,
|
|
1908
|
+
children: /* @__PURE__ */ jsx13(Lock, { className: "h-4 w-4 text-white", fill: "currentColor" })
|
|
1909
|
+
}
|
|
1910
|
+
),
|
|
1831
1911
|
/* @__PURE__ */ jsx13(
|
|
1832
1912
|
"button",
|
|
1833
1913
|
{
|
|
1834
1914
|
type: "button",
|
|
1835
1915
|
disabled,
|
|
1836
1916
|
onPointerDown,
|
|
1917
|
+
onPointerMove,
|
|
1837
1918
|
onPointerUp: endPress,
|
|
1838
1919
|
onPointerCancel: endPress,
|
|
1839
1920
|
onLostPointerCapture: endPress,
|