@ai-matrx/capture 0.2.1 → 0.3.1

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/dist/react.js CHANGED
@@ -1622,15 +1622,680 @@ function CameraCapture({
1622
1622
  ] });
1623
1623
  }
1624
1624
 
1625
+ // src/components/CameraCaptureV3.tsx
1626
+ import { useCallback as useCallback6, useEffect as useEffect7, useRef as useRef5, useState as useState8 } from "react";
1627
+ import {
1628
+ Grid3x3 as Grid3x32,
1629
+ Images,
1630
+ Proportions as Proportions2,
1631
+ RefreshCw as RefreshCw2,
1632
+ SunMedium as SunMedium2,
1633
+ Timer as Timer2,
1634
+ X as X5,
1635
+ Zap as Zap2,
1636
+ ZapOff as ZapOff2
1637
+ } from "lucide-react";
1638
+
1639
+ // src/components/CaptureRail.tsx
1640
+ import { useState as useState6 } from "react";
1641
+ import { jsx as jsx12, jsxs as jsxs10 } from "react/jsx-runtime";
1642
+ function CaptureRail({
1643
+ actions,
1644
+ defaultExpanded = false,
1645
+ className
1646
+ }) {
1647
+ const [expanded, setExpanded] = useState6(defaultExpanded);
1648
+ const primary = actions.filter((a) => a.primary);
1649
+ const extra = actions.filter((a) => !a.primary);
1650
+ const shown = primary.length === 0 ? actions : expanded ? actions : primary;
1651
+ const collapsible = primary.length > 0 && extra.length > 0;
1652
+ if (actions.length === 0) return null;
1653
+ return /* @__PURE__ */ jsxs10(
1654
+ "div",
1655
+ {
1656
+ className: cn(
1657
+ "pointer-events-auto flex flex-col items-center gap-1",
1658
+ className
1659
+ ),
1660
+ children: [
1661
+ shown.map((action) => /* @__PURE__ */ jsx12(RailButton, { action }, action.id)),
1662
+ collapsible ? /* @__PURE__ */ jsx12(
1663
+ "button",
1664
+ {
1665
+ type: "button",
1666
+ onClick: () => setExpanded((open) => !open),
1667
+ "aria-expanded": expanded,
1668
+ "aria-label": expanded ? "Fewer options" : "More options",
1669
+ className: cn(
1670
+ "mt-0.5 flex h-9 w-9 touch-manipulation items-center justify-center",
1671
+ "rounded-full text-white/90 transition-colors hover:bg-white/10"
1672
+ ),
1673
+ children: /* @__PURE__ */ jsx12(
1674
+ Chevron,
1675
+ {
1676
+ className: cn(
1677
+ "h-5 w-5 transition-transform duration-200",
1678
+ expanded && "rotate-180"
1679
+ )
1680
+ }
1681
+ )
1682
+ }
1683
+ ) : null
1684
+ ]
1685
+ }
1686
+ );
1687
+ }
1688
+ function RailButton({ action }) {
1689
+ return /* @__PURE__ */ jsxs10(
1690
+ "button",
1691
+ {
1692
+ type: "button",
1693
+ onClick: action.onPress,
1694
+ disabled: action.disabled,
1695
+ "aria-label": action.label,
1696
+ "aria-pressed": action.active ?? void 0,
1697
+ className: cn(
1698
+ "flex w-12 shrink-0 touch-manipulation flex-col items-center justify-center gap-0.5",
1699
+ "rounded-2xl py-1.5 transition-colors",
1700
+ action.active ? "text-[#FFCC00]" : "text-white",
1701
+ action.disabled ? "opacity-40" : "hover:bg-white/10 active:bg-white/15"
1702
+ ),
1703
+ children: [
1704
+ /* @__PURE__ */ jsx12("span", { className: "drop-shadow-[0_1px_3px_rgba(0,0,0,0.6)]", children: action.icon }),
1705
+ action.valueLabel ? /* @__PURE__ */ jsx12("span", { className: "text-[10px] font-semibold leading-none tabular-nums drop-shadow-[0_1px_2px_rgba(0,0,0,0.7)]", children: action.valueLabel }) : null
1706
+ ]
1707
+ }
1708
+ );
1709
+ }
1710
+ function Chevron({ className }) {
1711
+ return /* @__PURE__ */ jsx12(
1712
+ "svg",
1713
+ {
1714
+ viewBox: "0 0 24 24",
1715
+ fill: "none",
1716
+ stroke: "currentColor",
1717
+ strokeWidth: "2.5",
1718
+ strokeLinecap: "round",
1719
+ strokeLinejoin: "round",
1720
+ className,
1721
+ "aria-hidden": "true",
1722
+ children: /* @__PURE__ */ jsx12("path", { d: "m6 9 6 6 6-6" })
1723
+ }
1724
+ );
1725
+ }
1726
+
1727
+ // src/components/HoldShutter.tsx
1728
+ import { useCallback as useCallback5, useEffect as useEffect6, useRef as useRef4, useState as useState7 } from "react";
1729
+ import { jsx as jsx13, jsxs as jsxs11 } from "react/jsx-runtime";
1730
+ var HOLD_MS = 320;
1731
+ var LOCK_AFTER_MS = 1200;
1732
+ function HoldShutter({
1733
+ recording,
1734
+ elapsedSeconds,
1735
+ maxSeconds,
1736
+ disabled = false,
1737
+ onPressStart,
1738
+ onPhoto,
1739
+ onStartRecording,
1740
+ onStopRecording
1741
+ }) {
1742
+ const holdTimer = useRef4(null);
1743
+ const startedRecording = useRef4(false);
1744
+ const [pressed, setPressed] = useState7(false);
1745
+ const [locked, setLocked] = useState7(false);
1746
+ const clearHold = useCallback5(() => {
1747
+ if (holdTimer.current !== null) {
1748
+ clearTimeout(holdTimer.current);
1749
+ holdTimer.current = null;
1750
+ }
1751
+ }, []);
1752
+ useEffect6(() => {
1753
+ if (!recording) {
1754
+ setLocked(false);
1755
+ return;
1756
+ }
1757
+ if (elapsedSeconds * 1e3 >= LOCK_AFTER_MS) setLocked(true);
1758
+ }, [recording, elapsedSeconds]);
1759
+ useEffect6(() => clearHold, [clearHold]);
1760
+ const onPointerDown = useCallback5(
1761
+ (event) => {
1762
+ if (disabled) return;
1763
+ event.currentTarget.setPointerCapture?.(event.pointerId);
1764
+ setPressed(true);
1765
+ onPressStart?.();
1766
+ if (recording && locked) return;
1767
+ startedRecording.current = false;
1768
+ holdTimer.current = setTimeout(() => {
1769
+ holdTimer.current = null;
1770
+ startedRecording.current = true;
1771
+ onStartRecording();
1772
+ }, HOLD_MS);
1773
+ },
1774
+ [disabled, locked, onPressStart, onStartRecording, recording]
1775
+ );
1776
+ const endPress = useCallback5(() => {
1777
+ if (!pressed) return;
1778
+ setPressed(false);
1779
+ const wasHold = startedRecording.current;
1780
+ clearHold();
1781
+ startedRecording.current = false;
1782
+ if (recording && locked && !wasHold) {
1783
+ onStopRecording();
1784
+ return;
1785
+ }
1786
+ if (wasHold) {
1787
+ if (!locked) onStopRecording();
1788
+ return;
1789
+ }
1790
+ if (!recording) onPhoto();
1791
+ }, [clearHold, locked, onPhoto, onStopRecording, pressed, recording]);
1792
+ const progress = maxSeconds && maxSeconds > 0 ? Math.min(1, elapsedSeconds / maxSeconds) : 0;
1793
+ const R = 39;
1794
+ const C = 2 * Math.PI * R;
1795
+ return /* @__PURE__ */ jsxs11("div", { className: "relative flex h-[86px] w-[86px] shrink-0 items-center justify-center", children: [
1796
+ recording && maxSeconds ? /* @__PURE__ */ jsxs11(
1797
+ "svg",
1798
+ {
1799
+ className: "pointer-events-none absolute inset-0 -rotate-90",
1800
+ viewBox: "0 0 86 86",
1801
+ "aria-hidden": "true",
1802
+ children: [
1803
+ /* @__PURE__ */ jsx13(
1804
+ "circle",
1805
+ {
1806
+ cx: "43",
1807
+ cy: "43",
1808
+ r: R,
1809
+ fill: "none",
1810
+ stroke: "rgba(255,255,255,0.25)",
1811
+ strokeWidth: "4"
1812
+ }
1813
+ ),
1814
+ /* @__PURE__ */ jsx13(
1815
+ "circle",
1816
+ {
1817
+ cx: "43",
1818
+ cy: "43",
1819
+ r: R,
1820
+ fill: "none",
1821
+ stroke: "#FF3B30",
1822
+ strokeWidth: "4",
1823
+ strokeLinecap: "round",
1824
+ strokeDasharray: C,
1825
+ strokeDashoffset: C * (1 - progress)
1826
+ }
1827
+ )
1828
+ ]
1829
+ }
1830
+ ) : null,
1831
+ /* @__PURE__ */ jsx13(
1832
+ "button",
1833
+ {
1834
+ type: "button",
1835
+ disabled,
1836
+ onPointerDown,
1837
+ onPointerUp: endPress,
1838
+ onPointerCancel: endPress,
1839
+ onLostPointerCapture: endPress,
1840
+ onContextMenu: (e) => e.preventDefault(),
1841
+ "aria-label": recording ? locked ? "Stop recording" : "Recording \u2014 release to stop" : "Tap for a photo, hold to record",
1842
+ className: cn(
1843
+ "group flex h-[74px] w-[74px] shrink-0 touch-none select-none items-center justify-center",
1844
+ "rounded-full border-[3.5px] border-white transition-opacity",
1845
+ disabled && "opacity-30"
1846
+ ),
1847
+ children: /* @__PURE__ */ jsx13(
1848
+ "span",
1849
+ {
1850
+ className: cn(
1851
+ "block transition-all duration-200 ease-out",
1852
+ recording ? "h-7 w-7 rounded-[6px] bg-[#FF3B30]" : cn(
1853
+ "h-[62px] w-[62px] rounded-full bg-white",
1854
+ pressed && "scale-90"
1855
+ )
1856
+ )
1857
+ }
1858
+ )
1859
+ }
1860
+ ),
1861
+ recording ? /* @__PURE__ */ jsxs11(
1862
+ "span",
1863
+ {
1864
+ className: cn(
1865
+ "pointer-events-none absolute -bottom-1 left-1/2 -translate-x-1/2 translate-y-full",
1866
+ "rounded-full bg-black/60 px-2 py-0.5 text-[11px] font-medium tabular-nums text-white"
1867
+ ),
1868
+ children: [
1869
+ formatElapsed2(elapsedSeconds),
1870
+ locked ? " \xB7 tap to stop" : ""
1871
+ ]
1872
+ }
1873
+ ) : null
1874
+ ] });
1875
+ }
1876
+ function formatElapsed2(totalSeconds) {
1877
+ const m = Math.floor(totalSeconds / 60);
1878
+ const s = String(Math.floor(totalSeconds % 60)).padStart(2, "0");
1879
+ return `${m}:${s}`;
1880
+ }
1881
+
1882
+ // src/components/CameraCaptureV3.tsx
1883
+ import { jsx as jsx14, jsxs as jsxs12 } from "react/jsx-runtime";
1884
+ var ASPECT_CYCLE2 = ["full", "4:3", "1:1", "16:9"];
1885
+ function CameraCaptureV3({
1886
+ engine,
1887
+ cloud,
1888
+ preview,
1889
+ media,
1890
+ onRecordIntent,
1891
+ maxRecordSeconds,
1892
+ onReviewOpenChange,
1893
+ onClose,
1894
+ blockedSheet,
1895
+ controlsHidden = false,
1896
+ shutterDisabled = false,
1897
+ slots = {}
1898
+ }) {
1899
+ const [viewerKey, setViewerKey] = useState8(null);
1900
+ const [editTarget, setEditTarget] = useState8(
1901
+ null
1902
+ );
1903
+ const [gridOn, setGridOn] = useState8(false);
1904
+ const [timerSetting, setTimerSetting] = useState8(0);
1905
+ const [aspect, setAspect] = useState8("full");
1906
+ const [countdown, setCountdown] = useState8(null);
1907
+ const [blockedDismissed, setBlockedDismissed] = useState8(false);
1908
+ const countdownRef = useRef5(null);
1909
+ const controls = useTrackControls(engine.stream);
1910
+ const reviewOpen = viewerKey !== null || editTarget !== null;
1911
+ useEffect7(() => {
1912
+ onReviewOpenChange?.(reviewOpen);
1913
+ }, [reviewOpen, onReviewOpenChange]);
1914
+ const clearCountdown = useCallback6(() => {
1915
+ if (countdownRef.current) clearInterval(countdownRef.current);
1916
+ countdownRef.current = null;
1917
+ setCountdown(null);
1918
+ }, []);
1919
+ useEffect7(() => clearCountdown, [clearCountdown]);
1920
+ useEffect7(() => {
1921
+ if (engine.recording) clearCountdown();
1922
+ }, [engine.recording, clearCountdown]);
1923
+ const onPhoto = useCallback6(() => {
1924
+ if (countdown !== null) {
1925
+ clearCountdown();
1926
+ return;
1927
+ }
1928
+ if (timerSetting === 0) {
1929
+ engine.onCapturePhoto({ aspect });
1930
+ return;
1931
+ }
1932
+ let remaining = timerSetting;
1933
+ setCountdown(remaining);
1934
+ countdownRef.current = setInterval(() => {
1935
+ remaining -= 1;
1936
+ if (remaining <= 0) {
1937
+ clearCountdown();
1938
+ engine.onCapturePhoto({ aspect });
1939
+ } else {
1940
+ setCountdown(remaining);
1941
+ }
1942
+ }, 1e3);
1943
+ }, [aspect, clearCountdown, countdown, engine, timerSetting]);
1944
+ const blocked = engine.blocked !== null;
1945
+ const coreRail = [
1946
+ ...engine.onFlipCamera ? [
1947
+ {
1948
+ id: "flip",
1949
+ label: "Switch camera",
1950
+ icon: /* @__PURE__ */ jsx14(RefreshCw2, { className: "h-[22px] w-[22px]" }),
1951
+ primary: true,
1952
+ onPress: engine.onFlipCamera
1953
+ }
1954
+ ] : [],
1955
+ ...controls.torchSupported ? [
1956
+ {
1957
+ id: "flash",
1958
+ label: "Flash",
1959
+ icon: controls.torchOn ? /* @__PURE__ */ jsx14(Zap2, { className: "h-[22px] w-[22px]", fill: "currentColor" }) : /* @__PURE__ */ jsx14(ZapOff2, { className: "h-[22px] w-[22px]" }),
1960
+ active: controls.torchOn,
1961
+ primary: true,
1962
+ onPress: controls.toggleTorch
1963
+ }
1964
+ ] : [],
1965
+ {
1966
+ id: "timer",
1967
+ label: "Timer",
1968
+ icon: /* @__PURE__ */ jsx14(Timer2, { className: "h-[22px] w-[22px]" }),
1969
+ active: timerSetting !== 0,
1970
+ valueLabel: timerSetting === 0 ? void 0 : `${timerSetting}s`,
1971
+ onPress: () => setTimerSetting((t) => t === 0 ? 3 : t === 3 ? 10 : 0)
1972
+ },
1973
+ {
1974
+ id: "grid",
1975
+ label: "Grid",
1976
+ icon: /* @__PURE__ */ jsx14(Grid3x32, { className: "h-[22px] w-[22px]" }),
1977
+ active: gridOn,
1978
+ onPress: () => setGridOn((g) => !g)
1979
+ },
1980
+ {
1981
+ id: "aspect",
1982
+ label: "Aspect",
1983
+ icon: /* @__PURE__ */ jsx14(Proportions2, { className: "h-[22px] w-[22px]" }),
1984
+ active: aspect !== "full",
1985
+ valueLabel: aspect === "full" ? void 0 : aspect,
1986
+ onPress: () => setAspect(
1987
+ (a) => ASPECT_CYCLE2[(ASPECT_CYCLE2.indexOf(a) + 1) % ASPECT_CYCLE2.length] ?? "full"
1988
+ )
1989
+ },
1990
+ ...controls.exposureSupported ? [
1991
+ {
1992
+ id: "exposure",
1993
+ label: "Exposure",
1994
+ icon: /* @__PURE__ */ jsx14(SunMedium2, { className: "h-[22px] w-[22px]" }),
1995
+ active: controls.exposure !== 0,
1996
+ valueLabel: controls.exposure === 0 ? void 0 : `${controls.exposure > 0 ? "+" : ""}${controls.exposure}`,
1997
+ onPress: () => controls.setExposure(controls.exposure === 0 ? 1 : 0)
1998
+ }
1999
+ ] : []
2000
+ ];
2001
+ const railActions = [
2002
+ ...coreRail.map((a) => blocked ? { ...a, disabled: true } : a),
2003
+ ...slots.railActions ?? []
2004
+ ];
2005
+ return /* @__PURE__ */ jsxs12("div", { className: "absolute inset-0 select-none overflow-hidden bg-black", children: [
2006
+ /* @__PURE__ */ jsx14("div", { className: "absolute inset-0", children: preview }),
2007
+ /* @__PURE__ */ jsx14(GridOverlay, { visible: gridOn && !blocked }),
2008
+ aspect !== "full" && !blocked && !engine.recording && /* @__PURE__ */ jsx14(
2009
+ "div",
2010
+ {
2011
+ "aria-hidden": true,
2012
+ className: "pointer-events-none absolute inset-0 z-10 flex items-center justify-center",
2013
+ children: /* @__PURE__ */ jsx14(
2014
+ "div",
2015
+ {
2016
+ className: "shadow-[0_0_0_9999px_rgba(0,0,0,0.45)]",
2017
+ style: {
2018
+ aspectRatio: aspect === "1:1" ? "1 / 1" : aspect === "4:3" ? "3 / 4" : "9 / 16",
2019
+ width: aspect === "16:9" ? "100%" : void 0,
2020
+ height: aspect === "16:9" ? void 0 : "70%",
2021
+ maxWidth: "100%",
2022
+ maxHeight: "100%"
2023
+ }
2024
+ }
2025
+ )
2026
+ }
2027
+ ),
2028
+ /* @__PURE__ */ jsx14(CountdownOverlay, { seconds: countdown }),
2029
+ !controlsHidden && /* @__PURE__ */ jsxs12(
2030
+ "div",
2031
+ {
2032
+ className: "pointer-events-none absolute inset-x-0 top-0 z-20 px-2",
2033
+ style: safeTop,
2034
+ children: [
2035
+ /* @__PURE__ */ jsxs12("div", { className: "flex min-h-[44px] items-center gap-2 py-1.5", children: [
2036
+ onClose ? /* @__PURE__ */ jsx14(
2037
+ "button",
2038
+ {
2039
+ type: "button",
2040
+ onClick: onClose,
2041
+ "aria-label": "Close camera",
2042
+ 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",
2043
+ children: /* @__PURE__ */ jsx14(X5, { className: "h-6 w-6" })
2044
+ }
2045
+ ) : /* @__PURE__ */ jsx14("span", { className: "h-10 w-10 shrink-0" }),
2046
+ /* @__PURE__ */ jsx14("div", { className: "pointer-events-auto flex min-w-0 flex-1 justify-center", children: slots.topEntry }),
2047
+ /* @__PURE__ */ jsx14("span", { className: "h-10 w-10 shrink-0" })
2048
+ ] }),
2049
+ slots.topCenter ? /* @__PURE__ */ jsx14("div", { className: "pointer-events-none pb-1 text-center", children: slots.topCenter }) : null
2050
+ ]
2051
+ }
2052
+ ),
2053
+ slots.statusChips ? /* @__PURE__ */ jsx14(
2054
+ "div",
2055
+ {
2056
+ className: "pointer-events-none absolute inset-x-0 z-20 flex flex-col items-center gap-1.5 px-3",
2057
+ style: {
2058
+ // Clears the top row AND the topCenter line beneath it — chips
2059
+ // rendered at the label's own height read as a collision.
2060
+ top: `calc(env(safe-area-inset-top, 0px) + ${controlsHidden ? 12 : slots.topCenter ? 92 : 64}px)`
2061
+ },
2062
+ children: slots.statusChips
2063
+ }
2064
+ ) : null,
2065
+ !controlsHidden && /* @__PURE__ */ jsx14(
2066
+ "div",
2067
+ {
2068
+ className: "pointer-events-none absolute right-1 z-20 flex justify-end",
2069
+ style: { top: `calc(env(safe-area-inset-top, 0px) + 64px)` },
2070
+ children: /* @__PURE__ */ jsx14(CaptureRail, { actions: railActions })
2071
+ }
2072
+ ),
2073
+ !controlsHidden && /* @__PURE__ */ jsxs12("div", { className: "absolute inset-x-0 bottom-0 z-20", style: safeBottom, children: [
2074
+ media && media.items.length > 0 ? /* @__PURE__ */ jsx14("div", { className: "px-2 pb-1.5", children: /* @__PURE__ */ jsx14(
2075
+ CaptureFilmstrip,
2076
+ {
2077
+ items: media.items,
2078
+ onOpen: (item) => setViewerKey(item.key)
2079
+ }
2080
+ ) }) : null,
2081
+ slots.aboveShutter ? /* @__PURE__ */ jsx14("div", { className: "px-2 pb-1.5", children: slots.aboveShutter }) : null,
2082
+ /* @__PURE__ */ jsxs12("div", { className: "flex items-center justify-between px-3 pb-2", children: [
2083
+ /* @__PURE__ */ jsx14("div", { className: "flex w-[86px] justify-start", children: /* @__PURE__ */ jsx14(
2084
+ "button",
2085
+ {
2086
+ type: "button",
2087
+ onClick: cloud.onOpenLibrary,
2088
+ "aria-label": "Your media \u2014 library and upload",
2089
+ 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",
2090
+ children: cloud.recentsThumb ?? /* @__PURE__ */ jsx14("span", { className: "flex h-full w-full items-center justify-center", children: /* @__PURE__ */ jsx14(Images, { className: "h-5 w-5 text-white/80" }) })
2091
+ }
2092
+ ) }),
2093
+ /* @__PURE__ */ jsx14(
2094
+ HoldShutter,
2095
+ {
2096
+ recording: engine.recording,
2097
+ elapsedSeconds: engine.recordElapsedSeconds,
2098
+ ...maxRecordSeconds !== void 0 ? { maxSeconds: maxRecordSeconds } : {},
2099
+ disabled: shutterDisabled || blocked,
2100
+ ...onRecordIntent ? { onPressStart: onRecordIntent } : {},
2101
+ onPhoto,
2102
+ onStartRecording: engine.onStartRecording,
2103
+ onStopRecording: engine.onStopRecording
2104
+ }
2105
+ ),
2106
+ /* @__PURE__ */ jsx14("div", { className: "flex w-[86px] justify-end", children: slots.shutterTrailing })
2107
+ ] }),
2108
+ !engine.recording && !blocked ? /* @__PURE__ */ jsx14("p", { className: "pb-1.5 text-center text-[11px] text-white/60 drop-shadow-[0_1px_2px_rgba(0,0,0,0.8)]", children: "Tap for a photo \xB7 hold to record" }) : null
2109
+ ] }),
2110
+ blocked && blockedSheet && !blockedDismissed && /* @__PURE__ */ jsx14(
2111
+ CaptureSheet,
2112
+ {
2113
+ open: true,
2114
+ onClose: () => setBlockedDismissed(true),
2115
+ body: blockedSheet.body,
2116
+ title: "Camera unavailable",
2117
+ actions: blockedSheet.actions
2118
+ }
2119
+ ),
2120
+ media && viewerKey !== null && /* @__PURE__ */ jsx14(
2121
+ MediaViewer,
2122
+ {
2123
+ items: media.items,
2124
+ initialKey: viewerKey,
2125
+ keysDisabled: editTarget !== null,
2126
+ onClose: () => setViewerKey(null),
2127
+ onDelete: media.onDelete ? (item) => {
2128
+ media.onDelete?.(item.key);
2129
+ invalidateMedia(item.key);
2130
+ } : void 0,
2131
+ onEdit: media.onReplacePhoto ? (item) => {
2132
+ const url = getMediaUrl(item);
2133
+ if (url) setEditTarget({ key: item.key, url });
2134
+ } : void 0
2135
+ }
2136
+ ),
2137
+ media?.onReplacePhoto && /* @__PURE__ */ jsx14(
2138
+ ImageEditSheet,
2139
+ {
2140
+ open: editTarget !== null,
2141
+ src: editTarget?.url ?? null,
2142
+ onClose: () => setEditTarget(null),
2143
+ onSave: (blob) => {
2144
+ const target = editTarget;
2145
+ setEditTarget(null);
2146
+ setViewerKey(null);
2147
+ if (target) {
2148
+ media.onReplacePhoto?.(target.key, blob);
2149
+ invalidateMedia(target.key);
2150
+ }
2151
+ }
2152
+ }
2153
+ ),
2154
+ slots.overlays
2155
+ ] });
2156
+ }
2157
+
2158
+ // src/components/CaptureExpandingField.tsx
2159
+ import { useCallback as useCallback7, useEffect as useEffect8, useRef as useRef6, useState as useState9 } from "react";
2160
+ import { jsx as jsx15, jsxs as jsxs13 } from "react/jsx-runtime";
2161
+ function CaptureExpandingField({
2162
+ label,
2163
+ placeholder,
2164
+ icon,
2165
+ onCommit,
2166
+ defaultExpanded = false,
2167
+ disabled = false,
2168
+ currentValue = null,
2169
+ className
2170
+ }) {
2171
+ const [expanded, setExpanded] = useState9(defaultExpanded);
2172
+ const [draft, setDraft] = useState9("");
2173
+ const inputRef = useRef6(null);
2174
+ const draftRef = useRef6(draft);
2175
+ draftRef.current = draft;
2176
+ const onCommitRef = useRef6(onCommit);
2177
+ onCommitRef.current = onCommit;
2178
+ useEffect8(() => {
2179
+ return () => {
2180
+ const trimmed = draftRef.current.trim();
2181
+ if (trimmed) onCommitRef.current(trimmed);
2182
+ };
2183
+ }, []);
2184
+ const commit = useCallback7(() => {
2185
+ const trimmed = draftRef.current.trim();
2186
+ if (trimmed) onCommitRef.current(trimmed);
2187
+ setDraft("");
2188
+ }, []);
2189
+ const open = useCallback7(() => {
2190
+ setExpanded(true);
2191
+ requestAnimationFrame(() => inputRef.current?.focus());
2192
+ }, []);
2193
+ const close = useCallback7(() => {
2194
+ commit();
2195
+ setExpanded(false);
2196
+ }, [commit]);
2197
+ if (!expanded) {
2198
+ return /* @__PURE__ */ jsxs13(
2199
+ "button",
2200
+ {
2201
+ type: "button",
2202
+ onClick: open,
2203
+ disabled,
2204
+ "aria-label": label,
2205
+ "aria-expanded": false,
2206
+ className: cn(
2207
+ "flex h-9 max-w-[62vw] items-center gap-1.5 rounded-full px-3.5",
2208
+ "bg-black/45 text-[13px] font-semibold text-white backdrop-blur-md",
2209
+ "transition-colors hover:bg-black/60 active:bg-black/70",
2210
+ disabled && "opacity-40",
2211
+ className
2212
+ ),
2213
+ children: [
2214
+ icon ? /* @__PURE__ */ jsx15("span", { className: "shrink-0", children: icon }) : null,
2215
+ /* @__PURE__ */ jsx15("span", { className: "truncate", children: currentValue?.trim() || label })
2216
+ ]
2217
+ }
2218
+ );
2219
+ }
2220
+ return /* @__PURE__ */ jsxs13(
2221
+ "div",
2222
+ {
2223
+ className: cn(
2224
+ "flex h-9 w-[min(78vw,320px)] items-center gap-1.5 rounded-full px-3",
2225
+ "bg-black/60 backdrop-blur-md",
2226
+ className
2227
+ ),
2228
+ children: [
2229
+ icon ? /* @__PURE__ */ jsx15("span", { className: "shrink-0 text-white/70", children: icon }) : null,
2230
+ /* @__PURE__ */ jsx15(
2231
+ "input",
2232
+ {
2233
+ ref: inputRef,
2234
+ value: draft,
2235
+ onChange: (e) => setDraft(e.target.value),
2236
+ onBlur: close,
2237
+ onKeyDown: (e) => {
2238
+ if (e.key === "Enter") {
2239
+ commit();
2240
+ e.target.blur();
2241
+ }
2242
+ if (e.key === "Escape") {
2243
+ setDraft("");
2244
+ setExpanded(false);
2245
+ }
2246
+ },
2247
+ placeholder: placeholder ?? label,
2248
+ "aria-label": label,
2249
+ enterKeyHint: "done",
2250
+ autoCapitalize: "characters",
2251
+ autoCorrect: "off",
2252
+ spellCheck: false,
2253
+ className: cn(
2254
+ "min-w-0 flex-1 bg-transparent text-base text-white",
2255
+ "placeholder:text-white/40 focus:outline-none"
2256
+ )
2257
+ }
2258
+ ),
2259
+ /* @__PURE__ */ jsx15(
2260
+ "button",
2261
+ {
2262
+ type: "button",
2263
+ onMouseDown: (e) => e.preventDefault(),
2264
+ onClick: () => {
2265
+ setDraft("");
2266
+ setExpanded(false);
2267
+ },
2268
+ "aria-label": `Close ${label}`,
2269
+ className: "flex h-6 w-6 shrink-0 items-center justify-center rounded-full text-white/70 hover:bg-white/10 hover:text-white",
2270
+ children: /* @__PURE__ */ jsx15(
2271
+ "svg",
2272
+ {
2273
+ viewBox: "0 0 24 24",
2274
+ fill: "none",
2275
+ stroke: "currentColor",
2276
+ strokeWidth: "2.5",
2277
+ strokeLinecap: "round",
2278
+ className: "h-3.5 w-3.5",
2279
+ "aria-hidden": "true",
2280
+ children: /* @__PURE__ */ jsx15("path", { d: "M18 6 6 18M6 6l12 12" })
2281
+ }
2282
+ )
2283
+ }
2284
+ )
2285
+ ]
2286
+ }
2287
+ );
2288
+ }
2289
+
1625
2290
  // src/components/CameraFeed.tsx
1626
- import { useEffect as useEffect6 } from "react";
1627
- import { jsx as jsx12 } from "react/jsx-runtime";
2291
+ import { useEffect as useEffect9 } from "react";
2292
+ import { jsx as jsx16 } from "react/jsx-runtime";
1628
2293
  function CameraFeed({
1629
2294
  engine,
1630
2295
  mirror = false
1631
2296
  }) {
1632
2297
  const { stream, videoRef } = engine;
1633
- useEffect6(() => {
2298
+ useEffect9(() => {
1634
2299
  const video = videoRef.current;
1635
2300
  if (!video) return;
1636
2301
  if (video.srcObject !== stream) video.srcObject = stream;
@@ -1638,7 +2303,7 @@ function CameraFeed({
1638
2303
  video.srcObject = null;
1639
2304
  };
1640
2305
  }, [stream, videoRef]);
1641
- return /* @__PURE__ */ jsx12(
2306
+ return /* @__PURE__ */ jsx16(
1642
2307
  "video",
1643
2308
  {
1644
2309
  ref: videoRef,
@@ -1657,11 +2322,11 @@ function CameraFeed({
1657
2322
 
1658
2323
  // src/engine/useDefaultEngine.ts
1659
2324
  import {
1660
- useCallback as useCallback5,
1661
- useEffect as useEffect7,
2325
+ useCallback as useCallback8,
2326
+ useEffect as useEffect10,
1662
2327
  useMemo as useMemo3,
1663
- useRef as useRef4,
1664
- useState as useState6
2328
+ useRef as useRef7,
2329
+ useState as useState10
1665
2330
  } from "react";
1666
2331
  var RECORDING_MIME_LADDER = [
1667
2332
  'video/mp4;codecs="avc1.42E01E,mp4a.40.2"',
@@ -1692,18 +2357,18 @@ function useDefaultCaptureEngine(options) {
1692
2357
  facingMode: initialFacing = "environment",
1693
2358
  photoQuality = 0.92
1694
2359
  } = options;
1695
- const [stream, setStream] = useState6(null);
1696
- const [blocked, setBlocked] = useState6(null);
1697
- const [facing, setFacing] = useState6(initialFacing);
1698
- const [multipleCameras, setMultipleCameras] = useState6(false);
1699
- const [recording, setRecording] = useState6(false);
1700
- const [recordElapsedSeconds, setRecordElapsedSeconds] = useState6(0);
1701
- const videoRef = useRef4(null);
1702
- const streamRef = useRef4(null);
1703
- const recorderRef = useRef4(null);
1704
- const callbacksRef = useRef4({ onPhoto, onVideo, onFiles });
2360
+ const [stream, setStream] = useState10(null);
2361
+ const [blocked, setBlocked] = useState10(null);
2362
+ const [facing, setFacing] = useState10(initialFacing);
2363
+ const [multipleCameras, setMultipleCameras] = useState10(false);
2364
+ const [recording, setRecording] = useState10(false);
2365
+ const [recordElapsedSeconds, setRecordElapsedSeconds] = useState10(0);
2366
+ const videoRef = useRef7(null);
2367
+ const streamRef = useRef7(null);
2368
+ const recorderRef = useRef7(null);
2369
+ const callbacksRef = useRef7({ onPhoto, onVideo, onFiles });
1705
2370
  callbacksRef.current = { onPhoto, onVideo, onFiles };
1706
- useEffect7(() => {
2371
+ useEffect10(() => {
1707
2372
  let cancelled = false;
1708
2373
  let acquired = null;
1709
2374
  if (typeof navigator === "undefined" || !navigator.mediaDevices) {
@@ -1746,7 +2411,7 @@ function useDefaultCaptureEngine(options) {
1746
2411
  setStream(null);
1747
2412
  };
1748
2413
  }, [facing]);
1749
- useEffect7(() => {
2414
+ useEffect10(() => {
1750
2415
  return () => {
1751
2416
  const r = recorderRef.current;
1752
2417
  if (r) {
@@ -1756,7 +2421,7 @@ function useDefaultCaptureEngine(options) {
1756
2421
  }
1757
2422
  };
1758
2423
  }, []);
1759
- const onCapturePhoto = useCallback5(
2424
+ const onCapturePhoto = useCallback8(
1760
2425
  (opts) => {
1761
2426
  const video = videoRef.current;
1762
2427
  if (!video || video.videoWidth === 0) return;
@@ -1796,7 +2461,7 @@ function useDefaultCaptureEngine(options) {
1796
2461
  },
1797
2462
  [photoQuality]
1798
2463
  );
1799
- const onStartRecording = useCallback5(() => {
2464
+ const onStartRecording = useCallback8(() => {
1800
2465
  const base = streamRef.current;
1801
2466
  if (!base || recorderRef.current) return;
1802
2467
  void (async () => {
@@ -1863,11 +2528,11 @@ function useDefaultCaptureEngine(options) {
1863
2528
  setRecording(true);
1864
2529
  })();
1865
2530
  }, [withAudio]);
1866
- const onStopRecording = useCallback5(() => {
2531
+ const onStopRecording = useCallback8(() => {
1867
2532
  const r = recorderRef.current;
1868
2533
  if (r && r.recorder.state !== "inactive") r.recorder.stop();
1869
2534
  }, []);
1870
- const onUpload = useCallback5(() => {
2535
+ const onUpload = useCallback8(() => {
1871
2536
  const input = document.createElement("input");
1872
2537
  input.type = "file";
1873
2538
  input.accept = "image/*,video/*";
@@ -1878,7 +2543,7 @@ function useDefaultCaptureEngine(options) {
1878
2543
  };
1879
2544
  input.click();
1880
2545
  }, []);
1881
- const onFlipCamera = useCallback5(() => {
2546
+ const onFlipCamera = useCallback8(() => {
1882
2547
  setFacing((f) => f === "environment" ? "user" : "environment");
1883
2548
  }, []);
1884
2549
  return useMemo3(
@@ -1910,11 +2575,15 @@ function useDefaultCaptureEngine(options) {
1910
2575
  }
1911
2576
  export {
1912
2577
  CameraCapture,
2578
+ CameraCaptureV3,
1913
2579
  CameraFeed,
2580
+ CaptureExpandingField,
1914
2581
  CaptureFilmstrip,
2582
+ CaptureRail,
1915
2583
  CaptureSheet,
1916
2584
  CountdownOverlay,
1917
2585
  GridOverlay,
2586
+ HoldShutter,
1918
2587
  ImageEditSheet,
1919
2588
  MediaViewer,
1920
2589
  ModeSelector,