@allshift/ui 0.4.0 → 0.5.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/index.cjs +698 -23
- package/dist/index.d.cts +108 -1
- package/dist/index.d.ts +108 -1
- package/dist/index.js +689 -22
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -33,16 +33,24 @@ var index_exports = {};
|
|
|
33
33
|
__export(index_exports, {
|
|
34
34
|
ACCENT_RGB: () => ACCENT_RGB,
|
|
35
35
|
CanvasLauncher: () => CanvasLauncher,
|
|
36
|
+
CanvasProvider: () => CanvasProvider,
|
|
36
37
|
CanvasTransition: () => CanvasTransition,
|
|
38
|
+
DEFAULT_H: () => DEFAULT_H,
|
|
39
|
+
GAP: () => GAP,
|
|
37
40
|
GenerativeView: () => GenerativeView,
|
|
38
41
|
HolographicShine: () => HolographicShine,
|
|
39
42
|
LiveCard: () => LiveCard,
|
|
40
43
|
LiveStat: () => LiveStat,
|
|
44
|
+
MIN_H: () => MIN_H,
|
|
45
|
+
MIN_W: () => MIN_W,
|
|
41
46
|
PageTransition: () => PageTransition,
|
|
42
47
|
ScrollGenerateCard: () => ScrollGenerateCard,
|
|
43
48
|
ShiftBar: () => ShiftBar,
|
|
44
49
|
ShiftCard: () => ShiftCard,
|
|
45
50
|
TiltCard: () => TiltCard,
|
|
51
|
+
WIDTH_SPAN: () => WIDTH_SPAN,
|
|
52
|
+
tallH: () => tallH,
|
|
53
|
+
useCanvas: () => useCanvas,
|
|
46
54
|
useGenerate: () => useGenerate,
|
|
47
55
|
useHeyShift: () => useHeyShift
|
|
48
56
|
});
|
|
@@ -825,8 +833,667 @@ function PageTransition({ children, className }) {
|
|
|
825
833
|
);
|
|
826
834
|
}
|
|
827
835
|
|
|
828
|
-
// src/
|
|
836
|
+
// src/canvas/CanvasProvider.tsx
|
|
829
837
|
var import_react10 = require("react");
|
|
838
|
+
var import_jsx_runtime11 = require("react/jsx-runtime");
|
|
839
|
+
var GAP = 20;
|
|
840
|
+
var DEFAULT_H = 420;
|
|
841
|
+
var MIN_W = 240;
|
|
842
|
+
var MIN_H = 200;
|
|
843
|
+
var WIDTH_SPAN = {
|
|
844
|
+
sm: 4,
|
|
845
|
+
// 1/3
|
|
846
|
+
md: 6,
|
|
847
|
+
// 1/2
|
|
848
|
+
lg: 8,
|
|
849
|
+
// 2/3
|
|
850
|
+
xl: 12
|
|
851
|
+
// full
|
|
852
|
+
};
|
|
853
|
+
var CanvasContext = (0, import_react10.createContext)(null);
|
|
854
|
+
function useCanvas() {
|
|
855
|
+
const ctx = (0, import_react10.useContext)(CanvasContext);
|
|
856
|
+
if (!ctx) throw new Error("useCanvas must be used inside <CanvasProvider>");
|
|
857
|
+
return ctx;
|
|
858
|
+
}
|
|
859
|
+
var _seq = 0;
|
|
860
|
+
function nextId() {
|
|
861
|
+
return `blk-${Date.now()}-${_seq++}`;
|
|
862
|
+
}
|
|
863
|
+
function blockFromSaved(s, z) {
|
|
864
|
+
const sb = typeof s === "string" ? { key: s } : s;
|
|
865
|
+
const hasCoords = typeof sb.x === "number" && typeof sb.y === "number" && typeof sb.w === "number";
|
|
866
|
+
return {
|
|
867
|
+
id: nextId(),
|
|
868
|
+
key: sb.key,
|
|
869
|
+
width: sb.width,
|
|
870
|
+
z,
|
|
871
|
+
placed: hasCoords,
|
|
872
|
+
x: sb.x ?? 0,
|
|
873
|
+
y: sb.y ?? 0,
|
|
874
|
+
w: sb.w ?? 0,
|
|
875
|
+
h: sb.h ?? DEFAULT_H
|
|
876
|
+
};
|
|
877
|
+
}
|
|
878
|
+
function colWidth(boardW) {
|
|
879
|
+
return (boardW - GAP * 11) / 12;
|
|
880
|
+
}
|
|
881
|
+
function pxWidth(weight, boardW) {
|
|
882
|
+
return Math.max(MIN_W, weight * colWidth(boardW) + (weight - 1) * GAP);
|
|
883
|
+
}
|
|
884
|
+
function tallH(boardH) {
|
|
885
|
+
return Math.max(460, boardH - 150);
|
|
886
|
+
}
|
|
887
|
+
function carveAround(zone, locked) {
|
|
888
|
+
const hit = (a, b) => a.x < b.x + b.w && a.x + a.w > b.x && a.y < b.y + b.h && a.y + a.h > b.y;
|
|
889
|
+
let r = { ...zone };
|
|
890
|
+
for (let i = 0; i < locked.length + 2; i++) {
|
|
891
|
+
const L = locked.find((l) => hit(r, l));
|
|
892
|
+
if (!L) break;
|
|
893
|
+
const ix = Math.max(r.x, L.x), iy = Math.max(r.y, L.y);
|
|
894
|
+
const ir = Math.min(r.x + r.w, L.x + L.w), ib = Math.min(r.y + r.h, L.y + L.h);
|
|
895
|
+
const slabs = [
|
|
896
|
+
{ x: r.x, y: r.y, w: ix - r.x, h: r.h },
|
|
897
|
+
// left of the locked block
|
|
898
|
+
{ x: ir, y: r.y, w: r.x + r.w - ir, h: r.h },
|
|
899
|
+
// right of it
|
|
900
|
+
{ x: r.x, y: r.y, w: r.w, h: iy - r.y },
|
|
901
|
+
// above it
|
|
902
|
+
{ x: r.x, y: ib, w: r.w, h: r.y + r.h - ib }
|
|
903
|
+
// below it
|
|
904
|
+
].filter((s) => s.w > 1 && s.h > 1);
|
|
905
|
+
if (slabs.length === 0) break;
|
|
906
|
+
r = slabs.reduce((a, b) => a.w * a.h >= b.w * b.h ? a : b);
|
|
907
|
+
}
|
|
908
|
+
return r;
|
|
909
|
+
}
|
|
910
|
+
function zoneBlocked(c, locked) {
|
|
911
|
+
if (c.w < MIN_W || c.h < MIN_H) return true;
|
|
912
|
+
return locked.some((L) => c.x < L.x + L.w && c.x + c.w > L.x && c.y < L.y + L.h && c.y + c.h > L.y);
|
|
913
|
+
}
|
|
914
|
+
function snapZones(boardW, H) {
|
|
915
|
+
const halfW = Math.round((boardW - GAP) / 2);
|
|
916
|
+
const halfH = Math.round((H - GAP) / 2);
|
|
917
|
+
const rightX = boardW - halfW;
|
|
918
|
+
const bottomY = H - halfH;
|
|
919
|
+
return {
|
|
920
|
+
full: { x: 0, y: 0, w: boardW, h: H },
|
|
921
|
+
left: { x: 0, y: 0, w: halfW, h: H },
|
|
922
|
+
right: { x: rightX, y: 0, w: halfW, h: H },
|
|
923
|
+
top: { x: 0, y: 0, w: boardW, h: halfH },
|
|
924
|
+
bottom: { x: 0, y: bottomY, w: boardW, h: halfH },
|
|
925
|
+
tl: { x: 0, y: 0, w: halfW, h: halfH },
|
|
926
|
+
tr: { x: rightX, y: 0, w: halfW, h: halfH },
|
|
927
|
+
bl: { x: 0, y: bottomY, w: halfW, h: halfH },
|
|
928
|
+
br: { x: rightX, y: bottomY, w: halfW, h: halfH }
|
|
929
|
+
};
|
|
930
|
+
}
|
|
931
|
+
function tileRect(items, region) {
|
|
932
|
+
const n = items.length;
|
|
933
|
+
if (n === 0) return items;
|
|
934
|
+
const rows = Math.ceil(n / 2);
|
|
935
|
+
const rowH = Math.max(MIN_H, Math.round((region.h - GAP * (rows - 1)) / rows));
|
|
936
|
+
const halfW = Math.round((region.w - GAP) / 2);
|
|
937
|
+
const single = region.w < MIN_W * 2 + GAP;
|
|
938
|
+
return items.map((it, i) => {
|
|
939
|
+
if (single) {
|
|
940
|
+
const h = Math.max(MIN_H, Math.round((region.h - GAP * (n - 1)) / n));
|
|
941
|
+
return { ...it, x: region.x, y: region.y + i * (h + GAP), w: Math.max(MIN_W, region.w), h, placed: true };
|
|
942
|
+
}
|
|
943
|
+
const row = Math.floor(i / 2);
|
|
944
|
+
const lastOdd = row === rows - 1 && n % 2 === 1;
|
|
945
|
+
const y = region.y + row * (rowH + GAP);
|
|
946
|
+
const w = lastOdd ? region.w : halfW;
|
|
947
|
+
const x = lastOdd ? region.x : region.x + i % 2 * (halfW + GAP);
|
|
948
|
+
return { ...it, x, y, w: Math.max(MIN_W, w), h: rowH, placed: true };
|
|
949
|
+
});
|
|
950
|
+
}
|
|
951
|
+
function bestComplement(z, boardW, H) {
|
|
952
|
+
const cands = [
|
|
953
|
+
{ x: z.x + z.w + GAP, y: 0, w: boardW - (z.x + z.w + GAP), h: H },
|
|
954
|
+
// right of zone
|
|
955
|
+
{ x: 0, y: 0, w: z.x - GAP, h: H },
|
|
956
|
+
// left of zone
|
|
957
|
+
{ x: 0, y: z.y + z.h + GAP, w: boardW, h: H - (z.y + z.h + GAP) },
|
|
958
|
+
// below zone
|
|
959
|
+
{ x: 0, y: 0, w: boardW, h: z.y - GAP }
|
|
960
|
+
// above zone
|
|
961
|
+
].filter((c) => c.w >= MIN_W && c.h >= MIN_H);
|
|
962
|
+
if (cands.length === 0) return null;
|
|
963
|
+
return cands.reduce((a, b) => a.w * a.h >= b.w * b.h ? a : b);
|
|
964
|
+
}
|
|
965
|
+
function computeSnap(blocks, id, layout, boardW, H) {
|
|
966
|
+
const target = blocks.find((b) => b.id === id);
|
|
967
|
+
if (!target || target.locked) return null;
|
|
968
|
+
const zone = snapZones(boardW, H)[layout];
|
|
969
|
+
const locked = blocks.filter((b) => b.id !== id && b.locked);
|
|
970
|
+
const tRect = carveAround(zone, locked);
|
|
971
|
+
if (zoneBlocked(tRect, locked)) return null;
|
|
972
|
+
const placements = /* @__PURE__ */ new Map();
|
|
973
|
+
placements.set(id, { x: Math.round(tRect.x), y: Math.round(tRect.y), w: Math.max(MIN_W, Math.round(tRect.w)), h: Math.max(MIN_H, Math.round(tRect.h)) });
|
|
974
|
+
const others = blocks.filter((b) => b.id !== id && !b.locked);
|
|
975
|
+
if (others.length > 0) {
|
|
976
|
+
const comp = bestComplement(zone, boardW, H);
|
|
977
|
+
if (comp) {
|
|
978
|
+
const region = carveAround(comp, locked);
|
|
979
|
+
if (region.w >= MIN_W && region.h >= MIN_H) {
|
|
980
|
+
tileRect(others, region).forEach((o) => placements.set(o.id, { x: Math.round(o.x), y: Math.round(o.y), w: Math.round(o.w), h: Math.round(o.h) }));
|
|
981
|
+
}
|
|
982
|
+
}
|
|
983
|
+
}
|
|
984
|
+
return placements;
|
|
985
|
+
}
|
|
986
|
+
function tileItems(items, boardW, boardH) {
|
|
987
|
+
const n = items.length;
|
|
988
|
+
if (n === 0) return items;
|
|
989
|
+
const availH = Math.max(MIN_H * 2 + GAP, boardH - 150);
|
|
990
|
+
const rows = Math.ceil(n / 2);
|
|
991
|
+
const rowH = Math.max(300, Math.round((availH - GAP * (rows - 1)) / rows));
|
|
992
|
+
const halfW = Math.round((boardW - GAP) / 2);
|
|
993
|
+
const single = boardW < 720;
|
|
994
|
+
return items.map((it, i) => {
|
|
995
|
+
if (single) {
|
|
996
|
+
const h = Math.max(MIN_H, Math.round(tallH(boardH) * 0.72));
|
|
997
|
+
return { ...it, x: 0, y: i * (h + GAP), w: boardW, h };
|
|
998
|
+
}
|
|
999
|
+
const row = Math.floor(i / 2);
|
|
1000
|
+
const lastOdd = row === rows - 1 && n % 2 === 1;
|
|
1001
|
+
const y = row * (rowH + GAP);
|
|
1002
|
+
const w = lastOdd ? boardW : halfW;
|
|
1003
|
+
const x = lastOdd ? 0 : i % 2 * (halfW + GAP);
|
|
1004
|
+
return { ...it, x, y, w, h: rowH };
|
|
1005
|
+
});
|
|
1006
|
+
}
|
|
1007
|
+
function overlaps(a, b) {
|
|
1008
|
+
return a.x < b.x + b.w && a.x + a.w > b.x && a.y < b.y + b.h && a.y + a.h > b.y;
|
|
1009
|
+
}
|
|
1010
|
+
function pushOut(b, m) {
|
|
1011
|
+
const cands = [
|
|
1012
|
+
{ x: m.x - b.w - GAP, y: b.y, d: Math.abs(m.x - b.w - GAP - b.x), ok: m.x - b.w - GAP >= 0 },
|
|
1013
|
+
{ x: m.x + m.w + GAP, y: b.y, d: Math.abs(m.x + m.w + GAP - b.x), ok: true },
|
|
1014
|
+
{ x: b.x, y: m.y - b.h - GAP, d: Math.abs(m.y - b.h - GAP - b.y), ok: m.y - b.h - GAP >= 0 },
|
|
1015
|
+
{ x: b.x, y: m.y + m.h + GAP, d: Math.abs(m.y + m.h + GAP - b.y), ok: true }
|
|
1016
|
+
].filter((c) => c.ok).sort((p, q) => p.d - q.d);
|
|
1017
|
+
const best = cands[0];
|
|
1018
|
+
return { x: Math.max(0, best.x), y: Math.max(0, best.y) };
|
|
1019
|
+
}
|
|
1020
|
+
function CanvasProvider({
|
|
1021
|
+
children,
|
|
1022
|
+
resolveBlock,
|
|
1023
|
+
initialKeys = [],
|
|
1024
|
+
initialLayout,
|
|
1025
|
+
addKey = null,
|
|
1026
|
+
persist = false,
|
|
1027
|
+
savedSession
|
|
1028
|
+
}) {
|
|
1029
|
+
const resolveRef = (0, import_react10.useRef)(resolveBlock);
|
|
1030
|
+
(0, import_react10.useEffect)(() => {
|
|
1031
|
+
resolveRef.current = resolveBlock;
|
|
1032
|
+
}, [resolveBlock]);
|
|
1033
|
+
const resolve = (key) => resolveRef.current(key);
|
|
1034
|
+
const initial = (0, import_react10.useMemo)(() => {
|
|
1035
|
+
let z = 1;
|
|
1036
|
+
const base = initialLayout ? initialLayout.filter((b) => b && typeof b.key === "string" && resolveRef.current(b.key)).map((b) => blockFromSaved(b, z++)) : initialKeys.filter((k) => resolveRef.current(k)).map((k) => blockFromSaved(k, z++));
|
|
1037
|
+
if (addKey && resolveRef.current(addKey) && !base.some((b) => b.key === addKey)) {
|
|
1038
|
+
base.unshift(blockFromSaved(addKey, z++));
|
|
1039
|
+
}
|
|
1040
|
+
return { base, nextZ: z };
|
|
1041
|
+
}, []);
|
|
1042
|
+
const zRef = (0, import_react10.useRef)(initial.nextZ);
|
|
1043
|
+
const [blocks, setBlocks] = (0, import_react10.useState)(initial.base);
|
|
1044
|
+
const [interacting, setInteracting] = (0, import_react10.useState)(false);
|
|
1045
|
+
const [activeId, setActiveId] = (0, import_react10.useState)(null);
|
|
1046
|
+
const [snapGhost, setSnapGhost] = (0, import_react10.useState)(null);
|
|
1047
|
+
const [boardWidth, setBoardWidth] = (0, import_react10.useState)(1280);
|
|
1048
|
+
const boardRef = (0, import_react10.useRef)(1280);
|
|
1049
|
+
const boardHRef = (0, import_react10.useRef)(820);
|
|
1050
|
+
const persistRef = (0, import_react10.useRef)(persist);
|
|
1051
|
+
(0, import_react10.useEffect)(() => {
|
|
1052
|
+
persistRef.current = persist;
|
|
1053
|
+
}, [persist]);
|
|
1054
|
+
(0, import_react10.useEffect)(() => {
|
|
1055
|
+
const measure = () => {
|
|
1056
|
+
const w = Math.max(360, window.innerWidth - 40);
|
|
1057
|
+
boardRef.current = w;
|
|
1058
|
+
boardHRef.current = Math.max(480, window.innerHeight);
|
|
1059
|
+
setBoardWidth(w);
|
|
1060
|
+
setBlocks((prev) => {
|
|
1061
|
+
let changed = false;
|
|
1062
|
+
const next = prev.map((b) => {
|
|
1063
|
+
const bw = Math.min(b.w, w);
|
|
1064
|
+
const bx = Math.max(0, Math.min(b.x, w - bw));
|
|
1065
|
+
if (bw === b.w && bx === b.x) return b;
|
|
1066
|
+
changed = true;
|
|
1067
|
+
return { ...b, w: bw, x: bx };
|
|
1068
|
+
});
|
|
1069
|
+
return changed ? next : prev;
|
|
1070
|
+
});
|
|
1071
|
+
};
|
|
1072
|
+
measure();
|
|
1073
|
+
window.addEventListener("resize", measure);
|
|
1074
|
+
return () => window.removeEventListener("resize", measure);
|
|
1075
|
+
}, []);
|
|
1076
|
+
const layoutDefault = (0, import_react10.useCallback)(
|
|
1077
|
+
(list) => tileItems(list, boardRef.current, boardHRef.current),
|
|
1078
|
+
[]
|
|
1079
|
+
);
|
|
1080
|
+
const tiledOnce = (0, import_react10.useRef)(false);
|
|
1081
|
+
(0, import_react10.useEffect)(() => {
|
|
1082
|
+
if (tiledOnce.current) return;
|
|
1083
|
+
tiledOnce.current = true;
|
|
1084
|
+
setBlocks((prev) => prev.length === 0 || prev.some((b) => b.placed) ? prev : layoutDefault(prev));
|
|
1085
|
+
}, [boardWidth]);
|
|
1086
|
+
const addBlock = (0, import_react10.useCallback)((key, context, width, append = false) => {
|
|
1087
|
+
if (!resolve(key)) return;
|
|
1088
|
+
const w = width ?? resolve(key)?.defaultWidth;
|
|
1089
|
+
setBlocks((prev) => {
|
|
1090
|
+
const existing = prev.find((b) => b.key === key);
|
|
1091
|
+
const customized = prev.some((b) => b.placed);
|
|
1092
|
+
const without = prev.filter((b) => b.key !== key);
|
|
1093
|
+
if (customized) {
|
|
1094
|
+
if (append) {
|
|
1095
|
+
const startY = without.length ? Math.max(...without.map((b) => b.y + b.h)) + GAP : 0;
|
|
1096
|
+
const nb3 = { id: nextId(), key, context, width: w, locked: existing?.locked, z: zRef.current++, placed: true, x: 0, y: startY, w: boardRef.current, h: tallH(boardHRef.current) };
|
|
1097
|
+
return [...without, nb3];
|
|
1098
|
+
}
|
|
1099
|
+
if (existing?.locked) {
|
|
1100
|
+
return prev.map((b) => b.id === existing.id ? { ...b, z: zRef.current++ } : b);
|
|
1101
|
+
}
|
|
1102
|
+
const boardW = boardRef.current;
|
|
1103
|
+
const H = tallH(boardHRef.current);
|
|
1104
|
+
const nb2 = existing ? { ...existing, z: zRef.current++, placed: true } : { id: nextId(), key, context, width: w, z: zRef.current++, placed: true, x: 0, y: 0, w: 0, h: 0 };
|
|
1105
|
+
if (without.length === 0) return [{ ...nb2, x: 0, y: 0, w: boardW, h: H }];
|
|
1106
|
+
const zone = without.length === 1 ? "left" : "tl";
|
|
1107
|
+
const list2 = [nb2, ...without];
|
|
1108
|
+
const placements = computeSnap(list2, nb2.id, zone, boardW, H);
|
|
1109
|
+
if (placements) {
|
|
1110
|
+
return list2.map((b) => {
|
|
1111
|
+
const p = placements.get(b.id);
|
|
1112
|
+
return p ? { ...b, ...p, placed: true } : b;
|
|
1113
|
+
});
|
|
1114
|
+
}
|
|
1115
|
+
const lockedTop = without.filter((b) => b.locked && b.y < H + GAP);
|
|
1116
|
+
const newY = lockedTop.length ? Math.max(...lockedTop.map((b) => b.y + b.h)) + GAP : 0;
|
|
1117
|
+
const shift = newY + H + GAP;
|
|
1118
|
+
const moved = without.map((b) => b.locked ? b : { ...b, y: b.y + shift });
|
|
1119
|
+
return [{ ...nb2, x: 0, y: newY, w: boardW, h: H }, ...moved];
|
|
1120
|
+
}
|
|
1121
|
+
const nb = existing ? { ...existing, z: zRef.current++ } : { id: nextId(), key, context, width: w, z: zRef.current++, placed: false, x: 0, y: 0, w: 0, h: 0 };
|
|
1122
|
+
const list = append ? [...without, nb] : [nb, ...without];
|
|
1123
|
+
return layoutDefault(list);
|
|
1124
|
+
});
|
|
1125
|
+
if (!append && typeof window !== "undefined") {
|
|
1126
|
+
requestAnimationFrame(() => window.scrollTo({ top: 0, behavior: "smooth" }));
|
|
1127
|
+
}
|
|
1128
|
+
}, [layoutDefault]);
|
|
1129
|
+
const removeBlock = (0, import_react10.useCallback)((id) => {
|
|
1130
|
+
setBlocks((prev) => {
|
|
1131
|
+
const remaining = prev.filter((b) => b.id !== id);
|
|
1132
|
+
return remaining.length > 0 && !remaining.some((b) => b.placed) ? layoutDefault(remaining) : remaining;
|
|
1133
|
+
});
|
|
1134
|
+
}, [layoutDefault]);
|
|
1135
|
+
const resizeBlock = (0, import_react10.useCallback)((id, width) => {
|
|
1136
|
+
setBlocks((prev) => prev.map((b) => b.id === id ? { ...b, width, w: pxWidth(WIDTH_SPAN[width], boardRef.current), placed: true, z: zRef.current++ } : b));
|
|
1137
|
+
}, []);
|
|
1138
|
+
const moveTo = (0, import_react10.useCallback)((id, x, y) => {
|
|
1139
|
+
const boardW = boardRef.current;
|
|
1140
|
+
setBlocks((prev) => prev.map((b) => {
|
|
1141
|
+
if (b.id !== id || b.locked) return b;
|
|
1142
|
+
const nx = Math.max(0, Math.min(Math.round(x), Math.max(0, boardW - b.w)));
|
|
1143
|
+
const ny = Math.max(0, Math.round(y));
|
|
1144
|
+
return { ...b, x: nx, y: ny, placed: true };
|
|
1145
|
+
}));
|
|
1146
|
+
}, []);
|
|
1147
|
+
const sizeTo = (0, import_react10.useCallback)((id, w, h) => {
|
|
1148
|
+
const boardW = boardRef.current;
|
|
1149
|
+
setBlocks((prev) => prev.map((b) => {
|
|
1150
|
+
if (b.id !== id || b.locked) return b;
|
|
1151
|
+
const nw = Math.max(MIN_W, Math.min(Math.round(w), Math.max(MIN_W, boardW - b.x)));
|
|
1152
|
+
const nh = Math.max(MIN_H, Math.round(h));
|
|
1153
|
+
return { ...b, w: nw, h: nh, placed: true };
|
|
1154
|
+
}));
|
|
1155
|
+
}, []);
|
|
1156
|
+
const setRect = (0, import_react10.useCallback)((id, r) => {
|
|
1157
|
+
const boardW = boardRef.current;
|
|
1158
|
+
setBlocks((prev) => prev.map((b) => {
|
|
1159
|
+
if (b.id !== id || b.locked) return b;
|
|
1160
|
+
const nx = Math.max(0, Math.round(r.x));
|
|
1161
|
+
const ny = Math.max(0, Math.round(r.y));
|
|
1162
|
+
let nw = Math.max(MIN_W, Math.round(r.w));
|
|
1163
|
+
const nh = Math.max(MIN_H, Math.round(r.h));
|
|
1164
|
+
if (nx + nw > boardW) nw = Math.max(MIN_W, boardW - nx);
|
|
1165
|
+
return { ...b, x: nx, y: ny, w: nw, h: nh, placed: true };
|
|
1166
|
+
}));
|
|
1167
|
+
}, []);
|
|
1168
|
+
const snapTo = (0, import_react10.useCallback)((id, layout) => {
|
|
1169
|
+
const boardW = boardRef.current;
|
|
1170
|
+
const H = tallH(boardHRef.current);
|
|
1171
|
+
setSnapGhost(null);
|
|
1172
|
+
setBlocks((prev) => {
|
|
1173
|
+
const placements = computeSnap(prev, id, layout, boardW, H);
|
|
1174
|
+
if (!placements) return prev;
|
|
1175
|
+
return prev.map((b) => {
|
|
1176
|
+
const p = placements.get(b.id);
|
|
1177
|
+
if (!p) return b;
|
|
1178
|
+
return { ...b, ...p, placed: true, z: b.id === id ? zRef.current++ : b.z };
|
|
1179
|
+
});
|
|
1180
|
+
});
|
|
1181
|
+
}, []);
|
|
1182
|
+
const snapPreview = (0, import_react10.useCallback)((id, layout) => {
|
|
1183
|
+
const boardW = boardRef.current;
|
|
1184
|
+
const H = tallH(boardHRef.current);
|
|
1185
|
+
const zone = snapZones(boardW, H)[layout];
|
|
1186
|
+
const locked = blocks.filter((b) => b.id !== id && b.locked);
|
|
1187
|
+
const c = carveAround(zone, locked);
|
|
1188
|
+
const box = (r) => ({
|
|
1189
|
+
left: r.x / boardW * 100,
|
|
1190
|
+
top: r.y / H * 100,
|
|
1191
|
+
width: r.w / boardW * 100,
|
|
1192
|
+
height: r.h / H * 100
|
|
1193
|
+
});
|
|
1194
|
+
const placements = computeSnap(blocks, id, layout, boardW, H);
|
|
1195
|
+
const others = placements ? [...placements.entries()].filter(([bid]) => bid !== id).map(([, r]) => box(r)) : [];
|
|
1196
|
+
return { rect: box(c), locked: locked.map((L) => box(L)), others, blocked: zoneBlocked(c, locked) };
|
|
1197
|
+
}, [blocks]);
|
|
1198
|
+
const previewSnap = (0, import_react10.useCallback)((id, layout) => {
|
|
1199
|
+
const boardW = boardRef.current;
|
|
1200
|
+
const H = tallH(boardHRef.current);
|
|
1201
|
+
const placements = computeSnap(blocks, id, layout, boardW, H);
|
|
1202
|
+
if (!placements) {
|
|
1203
|
+
setSnapGhost(null);
|
|
1204
|
+
return;
|
|
1205
|
+
}
|
|
1206
|
+
const target = placements.get(id);
|
|
1207
|
+
if (!target) {
|
|
1208
|
+
setSnapGhost(null);
|
|
1209
|
+
return;
|
|
1210
|
+
}
|
|
1211
|
+
const others = [...placements.entries()].filter(([bid]) => bid !== id).map(([, r]) => r);
|
|
1212
|
+
setSnapGhost({ target, others });
|
|
1213
|
+
}, [blocks]);
|
|
1214
|
+
const clearSnapPreview = (0, import_react10.useCallback)(() => setSnapGhost(null), []);
|
|
1215
|
+
const resizeRow = (0, import_react10.useCallback)((id, x, w, h, origin) => {
|
|
1216
|
+
const boardW = boardRef.current;
|
|
1217
|
+
const M0 = origin.find((b) => b.id === id);
|
|
1218
|
+
if (!M0 || M0.locked) return;
|
|
1219
|
+
const vov = (a, b) => a.y < b.y + b.h && a.y + a.h > b.y;
|
|
1220
|
+
const nh = Math.max(MIN_H, Math.round(h));
|
|
1221
|
+
const packed = /* @__PURE__ */ new Map();
|
|
1222
|
+
let mx = M0.x;
|
|
1223
|
+
let mw;
|
|
1224
|
+
if (Math.round(x) === Math.round(M0.x)) {
|
|
1225
|
+
const rights = origin.filter((b) => b.id !== id && b.x >= M0.x && vov(b, M0)).sort((a, b) => a.x - b.x);
|
|
1226
|
+
const wallIdx = rights.findIndex((b) => b.locked);
|
|
1227
|
+
const wallX = wallIdx >= 0 ? rights[wallIdx].x : boardW + GAP;
|
|
1228
|
+
const pushable = wallIdx >= 0 ? rights.slice(0, wallIdx) : rights;
|
|
1229
|
+
const reserved = pushable.length * (MIN_W + GAP);
|
|
1230
|
+
const mRight = Math.max(M0.x + MIN_W, Math.min(M0.x + Math.round(w), wallX - GAP - reserved));
|
|
1231
|
+
mw = mRight - M0.x;
|
|
1232
|
+
let cursor = mRight + GAP;
|
|
1233
|
+
pushable.forEach((N, i) => {
|
|
1234
|
+
const after = (pushable.length - 1 - i) * (MIN_W + GAP);
|
|
1235
|
+
const nw = Math.max(MIN_W, Math.min(N.w, Math.round(wallX - GAP - cursor - after)));
|
|
1236
|
+
packed.set(N.id, { x: Math.round(cursor), w: nw });
|
|
1237
|
+
cursor += nw + GAP;
|
|
1238
|
+
});
|
|
1239
|
+
} else {
|
|
1240
|
+
const fixedRight = M0.x + M0.w;
|
|
1241
|
+
const lefts = origin.filter((b) => b.id !== id && b.x < M0.x && vov(b, M0)).sort((a, b) => b.x - a.x);
|
|
1242
|
+
const wallIdx = lefts.findIndex((b) => b.locked);
|
|
1243
|
+
const wallRight = wallIdx >= 0 ? lefts[wallIdx].x + lefts[wallIdx].w : -GAP;
|
|
1244
|
+
const pushable = wallIdx >= 0 ? lefts.slice(0, wallIdx) : lefts;
|
|
1245
|
+
const reserved = pushable.length * (MIN_W + GAP);
|
|
1246
|
+
const mLeft = Math.min(fixedRight - MIN_W, Math.max(Math.round(x), wallRight + GAP + reserved));
|
|
1247
|
+
mx = mLeft;
|
|
1248
|
+
mw = fixedRight - mLeft;
|
|
1249
|
+
let cursor = mLeft - GAP;
|
|
1250
|
+
pushable.forEach((N, i) => {
|
|
1251
|
+
const after = (pushable.length - 1 - i) * (MIN_W + GAP);
|
|
1252
|
+
const nw = Math.max(MIN_W, Math.min(N.w, Math.round(cursor - (wallRight + GAP) - after)));
|
|
1253
|
+
const nx = Math.max(wallRight + GAP, Math.round(cursor - nw));
|
|
1254
|
+
packed.set(N.id, { x: nx, w: nw });
|
|
1255
|
+
cursor = nx - GAP;
|
|
1256
|
+
});
|
|
1257
|
+
}
|
|
1258
|
+
setBlocks((prev) => prev.map((b) => {
|
|
1259
|
+
if (b.id === id) return { ...b, x: mx, w: Math.max(MIN_W, mw), h: nh, placed: true };
|
|
1260
|
+
const p = packed.get(b.id);
|
|
1261
|
+
return p ? { ...b, x: p.x, w: p.w, placed: true } : b;
|
|
1262
|
+
}));
|
|
1263
|
+
}, []);
|
|
1264
|
+
const bringToFront = (0, import_react10.useCallback)((id) => {
|
|
1265
|
+
setBlocks((prev) => prev.map((b) => b.id === id ? { ...b, z: zRef.current++ } : b));
|
|
1266
|
+
}, []);
|
|
1267
|
+
const resolveCollisions = (0, import_react10.useCallback)((id) => {
|
|
1268
|
+
setBlocks((prev) => {
|
|
1269
|
+
const moved = prev.find((b) => b.id === id);
|
|
1270
|
+
if (!moved) return prev;
|
|
1271
|
+
const hits = prev.filter((b) => b.id !== id && !b.locked && overlaps(b, moved));
|
|
1272
|
+
if (hits.length === 0) return prev;
|
|
1273
|
+
const cx = moved.x + moved.w / 2;
|
|
1274
|
+
const leftN = hits.filter((b) => b.x + b.w / 2 < cx);
|
|
1275
|
+
const rightN = hits.filter((b) => b.x + b.w / 2 >= cx);
|
|
1276
|
+
if (leftN.length && rightN.length) {
|
|
1277
|
+
const leftEdge = Math.max(...leftN.map((b) => b.x + b.w)) + GAP;
|
|
1278
|
+
const rightEdge = Math.min(...rightN.map((b) => b.x)) - GAP;
|
|
1279
|
+
const gapW = rightEdge - leftEdge;
|
|
1280
|
+
if (gapW >= MIN_W) {
|
|
1281
|
+
return prev.map((b) => b.id === id ? { ...b, x: Math.round(leftEdge), w: Math.round(gapW) } : b);
|
|
1282
|
+
}
|
|
1283
|
+
}
|
|
1284
|
+
const boardW = boardRef.current;
|
|
1285
|
+
let changed = false;
|
|
1286
|
+
const next = prev.map((b) => {
|
|
1287
|
+
if (b.id === id || b.locked || !overlaps(b, moved)) return b;
|
|
1288
|
+
const p = pushOut(b, moved);
|
|
1289
|
+
let nx = p.x;
|
|
1290
|
+
let nw = b.w;
|
|
1291
|
+
if (nx + nw > boardW) nw = Math.max(MIN_W, boardW - nx);
|
|
1292
|
+
if (nx + nw > boardW) nx = Math.max(0, boardW - nw);
|
|
1293
|
+
if (nx === b.x && p.y === b.y && nw === b.w) return b;
|
|
1294
|
+
changed = true;
|
|
1295
|
+
return { ...b, x: nx, y: p.y, w: nw };
|
|
1296
|
+
});
|
|
1297
|
+
return changed ? next : prev;
|
|
1298
|
+
});
|
|
1299
|
+
}, []);
|
|
1300
|
+
const toggleLock = (0, import_react10.useCallback)((id) => {
|
|
1301
|
+
setBlocks((prev) => prev.map((b) => b.id === id ? { ...b, locked: !b.locked } : b));
|
|
1302
|
+
}, []);
|
|
1303
|
+
const restoredRef = (0, import_react10.useRef)(false);
|
|
1304
|
+
const [canRestore, setCanRestore] = (0, import_react10.useState)(false);
|
|
1305
|
+
const restoreSession = (0, import_react10.useCallback)(() => {
|
|
1306
|
+
const saved = savedSession?.blocks;
|
|
1307
|
+
if (!saved || saved.length === 0) return;
|
|
1308
|
+
restoredRef.current = true;
|
|
1309
|
+
setCanRestore(false);
|
|
1310
|
+
const built = saved.filter((b) => b && typeof b.key === "string" && resolve(b.key)).map((b) => blockFromSaved(b, zRef.current++));
|
|
1311
|
+
setBlocks(built.some((b) => b.placed) ? built : layoutDefault(built));
|
|
1312
|
+
}, [savedSession, layoutDefault]);
|
|
1313
|
+
const clear = (0, import_react10.useCallback)((keepKey) => {
|
|
1314
|
+
setBlocks((prev) => keepKey ? prev.filter((b) => b.key === keepKey) : []);
|
|
1315
|
+
}, []);
|
|
1316
|
+
(0, import_react10.useEffect)(() => {
|
|
1317
|
+
function onAdd(e) {
|
|
1318
|
+
const d = e.detail;
|
|
1319
|
+
if (d?.key) addBlock(d.key, d.context, d.width, d.append);
|
|
1320
|
+
}
|
|
1321
|
+
function onClear(e) {
|
|
1322
|
+
const d = e.detail;
|
|
1323
|
+
clear(d?.keepKey);
|
|
1324
|
+
}
|
|
1325
|
+
function onResize(e) {
|
|
1326
|
+
const d = e.detail;
|
|
1327
|
+
if (!d?.key || !d?.width) return;
|
|
1328
|
+
const width = d.width;
|
|
1329
|
+
setBlocks((prev) => {
|
|
1330
|
+
const list = prev.map((b) => b.key === d.key ? { ...b, width } : b);
|
|
1331
|
+
return prev.some((b) => b.placed) ? list : layoutDefault(list);
|
|
1332
|
+
});
|
|
1333
|
+
}
|
|
1334
|
+
function onPlace(e) {
|
|
1335
|
+
const d = e.detail;
|
|
1336
|
+
if (!d?.key || !d?.afterKey) return;
|
|
1337
|
+
setBlocks((prev) => {
|
|
1338
|
+
const srcIdx = prev.findIndex((b) => b.key === d.key);
|
|
1339
|
+
const tgtIdx = prev.findIndex((b) => b.key === d.afterKey);
|
|
1340
|
+
if (srcIdx === -1 || tgtIdx === -1) return prev;
|
|
1341
|
+
const next = [...prev];
|
|
1342
|
+
const [moved] = next.splice(srcIdx, 1);
|
|
1343
|
+
const insertAt = next.findIndex((b) => b.key === d.afterKey) + 1;
|
|
1344
|
+
next.splice(insertAt, 0, moved);
|
|
1345
|
+
return prev.some((b) => b.placed) ? next : layoutDefault(next);
|
|
1346
|
+
});
|
|
1347
|
+
}
|
|
1348
|
+
function onSwap(e) {
|
|
1349
|
+
const d = e.detail;
|
|
1350
|
+
const keys = d?.keys ?? [];
|
|
1351
|
+
const contexts = d?.contexts ?? [];
|
|
1352
|
+
const widths = d?.widths ?? [];
|
|
1353
|
+
const built = keys.filter((k) => resolve(k)).map((key, i) => ({
|
|
1354
|
+
id: nextId(),
|
|
1355
|
+
key,
|
|
1356
|
+
context: contexts[i],
|
|
1357
|
+
width: widths[i] ?? resolve(key)?.defaultWidth,
|
|
1358
|
+
z: zRef.current++,
|
|
1359
|
+
placed: false,
|
|
1360
|
+
x: 0,
|
|
1361
|
+
y: 0,
|
|
1362
|
+
w: 0,
|
|
1363
|
+
h: DEFAULT_H
|
|
1364
|
+
}));
|
|
1365
|
+
setBlocks(layoutDefault(built));
|
|
1366
|
+
}
|
|
1367
|
+
function onRestore() {
|
|
1368
|
+
restoreSession();
|
|
1369
|
+
}
|
|
1370
|
+
window.addEventListener("shift:canvas:add", onAdd);
|
|
1371
|
+
window.addEventListener("shift:canvas:clear", onClear);
|
|
1372
|
+
window.addEventListener("shift:canvas:resize", onResize);
|
|
1373
|
+
window.addEventListener("shift:canvas:place", onPlace);
|
|
1374
|
+
window.addEventListener("shift:canvas:swap", onSwap);
|
|
1375
|
+
window.addEventListener("shift:canvas:restore-session", onRestore);
|
|
1376
|
+
return () => {
|
|
1377
|
+
window.removeEventListener("shift:canvas:add", onAdd);
|
|
1378
|
+
window.removeEventListener("shift:canvas:clear", onClear);
|
|
1379
|
+
window.removeEventListener("shift:canvas:resize", onResize);
|
|
1380
|
+
window.removeEventListener("shift:canvas:place", onPlace);
|
|
1381
|
+
window.removeEventListener("shift:canvas:swap", onSwap);
|
|
1382
|
+
window.removeEventListener("shift:canvas:restore-session", onRestore);
|
|
1383
|
+
};
|
|
1384
|
+
}, [addBlock, clear, layoutDefault, restoreSession]);
|
|
1385
|
+
const localDay = () => {
|
|
1386
|
+
try {
|
|
1387
|
+
return (/* @__PURE__ */ new Date()).toLocaleDateString("en-CA");
|
|
1388
|
+
} catch {
|
|
1389
|
+
return "";
|
|
1390
|
+
}
|
|
1391
|
+
};
|
|
1392
|
+
(0, import_react10.useEffect)(() => {
|
|
1393
|
+
if (!persist || restoredRef.current) return;
|
|
1394
|
+
const saved = savedSession?.blocks;
|
|
1395
|
+
if (!saved || saved.length === 0) return;
|
|
1396
|
+
if (savedSession?.savedDay && savedSession.savedDay === localDay()) {
|
|
1397
|
+
restoreSession();
|
|
1398
|
+
} else {
|
|
1399
|
+
setCanRestore(true);
|
|
1400
|
+
}
|
|
1401
|
+
}, []);
|
|
1402
|
+
(0, import_react10.useEffect)(() => {
|
|
1403
|
+
window.dispatchEvent(new CustomEvent("shift:canvas:state", { detail: { count: blocks.length, canRestore } }));
|
|
1404
|
+
return () => {
|
|
1405
|
+
window.dispatchEvent(new CustomEvent("shift:canvas:state", { detail: { count: 0, canRestore: false } }));
|
|
1406
|
+
};
|
|
1407
|
+
}, [blocks.length, canRestore]);
|
|
1408
|
+
const firstRun = (0, import_react10.useRef)(true);
|
|
1409
|
+
const saveTimer = (0, import_react10.useRef)(null);
|
|
1410
|
+
const layoutSignature = blocks.map((b) => `${b.key}:${b.width ?? ""}:${Math.round(b.x)}:${Math.round(b.y)}:${Math.round(b.w)}:${Math.round(b.h)}`).join("|");
|
|
1411
|
+
const payload = (0, import_react10.useMemo)(() => layoutSignature ? {
|
|
1412
|
+
savedDay: localDay(),
|
|
1413
|
+
blocks: layoutSignature.split("|").map((seg) => {
|
|
1414
|
+
const [key, width, x, y, w, h] = seg.split(":");
|
|
1415
|
+
return { key, ...width ? { width } : {}, x: Number(x), y: Number(y), w: Number(w), h: Number(h) };
|
|
1416
|
+
})
|
|
1417
|
+
} : null, [layoutSignature]);
|
|
1418
|
+
const payloadRef = (0, import_react10.useRef)(payload);
|
|
1419
|
+
(0, import_react10.useEffect)(() => {
|
|
1420
|
+
payloadRef.current = payload;
|
|
1421
|
+
}, [payload]);
|
|
1422
|
+
(0, import_react10.useEffect)(() => {
|
|
1423
|
+
if (!persist) return;
|
|
1424
|
+
if (firstRun.current) {
|
|
1425
|
+
firstRun.current = false;
|
|
1426
|
+
return;
|
|
1427
|
+
}
|
|
1428
|
+
if (!payloadRef.current) return;
|
|
1429
|
+
if (saveTimer.current) clearTimeout(saveTimer.current);
|
|
1430
|
+
const body = JSON.stringify(payloadRef.current);
|
|
1431
|
+
saveTimer.current = setTimeout(() => {
|
|
1432
|
+
void fetch("/api/canvas-layout", {
|
|
1433
|
+
method: "PUT",
|
|
1434
|
+
headers: { "Content-Type": "application/json" },
|
|
1435
|
+
body
|
|
1436
|
+
}).catch(() => {
|
|
1437
|
+
});
|
|
1438
|
+
}, 700);
|
|
1439
|
+
return () => {
|
|
1440
|
+
if (saveTimer.current) clearTimeout(saveTimer.current);
|
|
1441
|
+
};
|
|
1442
|
+
}, [persist, layoutSignature]);
|
|
1443
|
+
(0, import_react10.useEffect)(() => {
|
|
1444
|
+
if (!persist) return;
|
|
1445
|
+
const flush = () => {
|
|
1446
|
+
if (!payloadRef.current) return;
|
|
1447
|
+
try {
|
|
1448
|
+
void fetch("/api/canvas-layout", {
|
|
1449
|
+
method: "PUT",
|
|
1450
|
+
headers: { "Content-Type": "application/json" },
|
|
1451
|
+
body: JSON.stringify(payloadRef.current),
|
|
1452
|
+
keepalive: true
|
|
1453
|
+
}).catch(() => {
|
|
1454
|
+
});
|
|
1455
|
+
} catch {
|
|
1456
|
+
}
|
|
1457
|
+
};
|
|
1458
|
+
const onHide = () => {
|
|
1459
|
+
if (document.visibilityState === "hidden") flush();
|
|
1460
|
+
};
|
|
1461
|
+
window.addEventListener("pagehide", flush);
|
|
1462
|
+
document.addEventListener("visibilitychange", onHide);
|
|
1463
|
+
return () => {
|
|
1464
|
+
window.removeEventListener("pagehide", flush);
|
|
1465
|
+
document.removeEventListener("visibilitychange", onHide);
|
|
1466
|
+
};
|
|
1467
|
+
}, [persist]);
|
|
1468
|
+
const api = (0, import_react10.useMemo)(() => ({
|
|
1469
|
+
blocks,
|
|
1470
|
+
addBlock,
|
|
1471
|
+
removeBlock,
|
|
1472
|
+
resizeBlock,
|
|
1473
|
+
snapTo,
|
|
1474
|
+
snapPreview,
|
|
1475
|
+
previewSnap,
|
|
1476
|
+
clearSnapPreview,
|
|
1477
|
+
snapGhost,
|
|
1478
|
+
moveTo,
|
|
1479
|
+
sizeTo,
|
|
1480
|
+
setRect,
|
|
1481
|
+
resizeRow,
|
|
1482
|
+
bringToFront,
|
|
1483
|
+
resolveCollisions,
|
|
1484
|
+
toggleLock,
|
|
1485
|
+
clear,
|
|
1486
|
+
interacting,
|
|
1487
|
+
setInteracting,
|
|
1488
|
+
activeId,
|
|
1489
|
+
setActiveId,
|
|
1490
|
+
boardWidth
|
|
1491
|
+
}), [blocks, addBlock, removeBlock, resizeBlock, snapTo, snapPreview, previewSnap, clearSnapPreview, snapGhost, moveTo, sizeTo, setRect, resizeRow, bringToFront, resolveCollisions, toggleLock, clear, interacting, activeId, boardWidth]);
|
|
1492
|
+
return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(CanvasContext.Provider, { value: api, children });
|
|
1493
|
+
}
|
|
1494
|
+
|
|
1495
|
+
// src/voice/useHeyShift.ts
|
|
1496
|
+
var import_react11 = require("react");
|
|
830
1497
|
function getSpeechRecognitionCtor() {
|
|
831
1498
|
const w = window;
|
|
832
1499
|
return w.SpeechRecognition ?? w.webkitSpeechRecognition;
|
|
@@ -839,28 +1506,28 @@ function useHeyShift(config = {}) {
|
|
|
839
1506
|
onCommand,
|
|
840
1507
|
onStateChange
|
|
841
1508
|
} = config;
|
|
842
|
-
const [state, setState] = (0,
|
|
843
|
-
const [transcript, setTranscript] = (0,
|
|
844
|
-
const [isSupported, setIsSupported] = (0,
|
|
845
|
-
const recRef = (0,
|
|
846
|
-
const silenceTimerRef = (0,
|
|
847
|
-
const activeRef = (0,
|
|
848
|
-
const accumulatedRef = (0,
|
|
849
|
-
const stateRef = (0,
|
|
850
|
-
const updateState = (0,
|
|
1509
|
+
const [state, setState] = (0, import_react11.useState)("idle");
|
|
1510
|
+
const [transcript, setTranscript] = (0, import_react11.useState)("");
|
|
1511
|
+
const [isSupported, setIsSupported] = (0, import_react11.useState)(false);
|
|
1512
|
+
const recRef = (0, import_react11.useRef)(null);
|
|
1513
|
+
const silenceTimerRef = (0, import_react11.useRef)(null);
|
|
1514
|
+
const activeRef = (0, import_react11.useRef)(false);
|
|
1515
|
+
const accumulatedRef = (0, import_react11.useRef)("");
|
|
1516
|
+
const stateRef = (0, import_react11.useRef)("idle");
|
|
1517
|
+
const updateState = (0, import_react11.useCallback)((s) => {
|
|
851
1518
|
stateRef.current = s;
|
|
852
1519
|
setState(s);
|
|
853
1520
|
onStateChange?.(s);
|
|
854
1521
|
}, [onStateChange]);
|
|
855
|
-
(0,
|
|
1522
|
+
(0, import_react11.useEffect)(() => {
|
|
856
1523
|
const SR = getSpeechRecognitionCtor();
|
|
857
1524
|
setIsSupported(!!(SR && window.speechSynthesis));
|
|
858
1525
|
}, []);
|
|
859
|
-
const getVoice = (0,
|
|
1526
|
+
const getVoice = (0, import_react11.useCallback)(() => {
|
|
860
1527
|
const voices = window.speechSynthesis?.getVoices() ?? [];
|
|
861
1528
|
return voices.find((v) => v.name.toLowerCase().includes("will")) ?? voices.find((v) => v.name.toLowerCase().includes("daniel")) ?? voices.find((v) => v.lang.startsWith("en") && v.name.toLowerCase().includes("male")) ?? voices.find((v) => v.lang.startsWith("en")) ?? voices[0] ?? null;
|
|
862
1529
|
}, []);
|
|
863
|
-
const speak = (0,
|
|
1530
|
+
const speak = (0, import_react11.useCallback)(async (text) => {
|
|
864
1531
|
if (!window.speechSynthesis) return;
|
|
865
1532
|
window.speechSynthesis.cancel();
|
|
866
1533
|
return new Promise((resolve) => {
|
|
@@ -890,11 +1557,11 @@ function useHeyShift(config = {}) {
|
|
|
890
1557
|
}
|
|
891
1558
|
});
|
|
892
1559
|
}, [getVoice, updateState]);
|
|
893
|
-
const cancelSpeech = (0,
|
|
1560
|
+
const cancelSpeech = (0, import_react11.useCallback)(() => {
|
|
894
1561
|
window.speechSynthesis?.cancel();
|
|
895
1562
|
if (stateRef.current === "speaking") updateState("idle");
|
|
896
1563
|
}, [updateState]);
|
|
897
|
-
const resetSilenceTimer = (0,
|
|
1564
|
+
const resetSilenceTimer = (0, import_react11.useCallback)((text) => {
|
|
898
1565
|
if (silenceTimerRef.current) clearTimeout(silenceTimerRef.current);
|
|
899
1566
|
silenceTimerRef.current = setTimeout(async () => {
|
|
900
1567
|
if (!activeRef.current || !text.trim()) return;
|
|
@@ -915,7 +1582,7 @@ function useHeyShift(config = {}) {
|
|
|
915
1582
|
}
|
|
916
1583
|
}, silenceMs);
|
|
917
1584
|
}, [silenceMs, onCommand, speak, updateState]);
|
|
918
|
-
const startListening = (0,
|
|
1585
|
+
const startListening = (0, import_react11.useCallback)(() => {
|
|
919
1586
|
const SR = getSpeechRecognitionCtor();
|
|
920
1587
|
if (!SR || recRef.current) return;
|
|
921
1588
|
const rec = new SR();
|
|
@@ -961,7 +1628,7 @@ function useHeyShift(config = {}) {
|
|
|
961
1628
|
rec.start();
|
|
962
1629
|
recRef.current = rec;
|
|
963
1630
|
}, [language, wakeWord, resetSilenceTimer, updateState]);
|
|
964
|
-
const stopListening = (0,
|
|
1631
|
+
const stopListening = (0, import_react11.useCallback)(() => {
|
|
965
1632
|
if (silenceTimerRef.current) clearTimeout(silenceTimerRef.current);
|
|
966
1633
|
const rec = recRef.current;
|
|
967
1634
|
recRef.current = null;
|
|
@@ -971,7 +1638,7 @@ function useHeyShift(config = {}) {
|
|
|
971
1638
|
setTranscript("");
|
|
972
1639
|
updateState("idle");
|
|
973
1640
|
}, [updateState]);
|
|
974
|
-
(0,
|
|
1641
|
+
(0, import_react11.useEffect)(() => {
|
|
975
1642
|
return () => {
|
|
976
1643
|
const rec = recRef.current;
|
|
977
1644
|
recRef.current = null;
|
|
@@ -984,7 +1651,7 @@ function useHeyShift(config = {}) {
|
|
|
984
1651
|
}
|
|
985
1652
|
|
|
986
1653
|
// src/voice/ShiftBar.tsx
|
|
987
|
-
var
|
|
1654
|
+
var import_jsx_runtime12 = require("react/jsx-runtime");
|
|
988
1655
|
var COLORS = {
|
|
989
1656
|
idle: "rgba(255,255,255,0.15)",
|
|
990
1657
|
wake: "#0ed882",
|
|
@@ -996,13 +1663,13 @@ var COLORS = {
|
|
|
996
1663
|
function ShiftBar({ state, dots = 3, size = 7, className = "" }) {
|
|
997
1664
|
const color = COLORS[state];
|
|
998
1665
|
const isAnimated = state !== "idle" && state !== "error";
|
|
999
|
-
return /* @__PURE__ */ (0,
|
|
1000
|
-
/* @__PURE__ */ (0,
|
|
1666
|
+
return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(import_jsx_runtime12.Fragment, { children: [
|
|
1667
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)("style", { children: `
|
|
1001
1668
|
@keyframes _sb_pulse { 0%,100%{opacity:1;transform:scale(1)} 50%{opacity:.35;transform:scale(1.45)} }
|
|
1002
1669
|
@keyframes _sb_wave { 0%,100%{transform:scaleY(1)} 50%{transform:scaleY(1.9)} }
|
|
1003
1670
|
@keyframes _sb_spin { to{transform:rotate(360deg)} }
|
|
1004
1671
|
` }),
|
|
1005
|
-
/* @__PURE__ */ (0,
|
|
1672
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
|
|
1006
1673
|
"span",
|
|
1007
1674
|
{
|
|
1008
1675
|
className,
|
|
@@ -1020,7 +1687,7 @@ function ShiftBar({ state, dots = 3, size = 7, className = "" }) {
|
|
|
1020
1687
|
animation = `_sb_pulse 1.2s ease-in-out ${i * 0.18}s infinite`;
|
|
1021
1688
|
}
|
|
1022
1689
|
}
|
|
1023
|
-
return /* @__PURE__ */ (0,
|
|
1690
|
+
return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
|
|
1024
1691
|
"span",
|
|
1025
1692
|
{
|
|
1026
1693
|
style: {
|
|
@@ -1047,16 +1714,24 @@ function ShiftBar({ state, dots = 3, size = 7, className = "" }) {
|
|
|
1047
1714
|
0 && (module.exports = {
|
|
1048
1715
|
ACCENT_RGB,
|
|
1049
1716
|
CanvasLauncher,
|
|
1717
|
+
CanvasProvider,
|
|
1050
1718
|
CanvasTransition,
|
|
1719
|
+
DEFAULT_H,
|
|
1720
|
+
GAP,
|
|
1051
1721
|
GenerativeView,
|
|
1052
1722
|
HolographicShine,
|
|
1053
1723
|
LiveCard,
|
|
1054
1724
|
LiveStat,
|
|
1725
|
+
MIN_H,
|
|
1726
|
+
MIN_W,
|
|
1055
1727
|
PageTransition,
|
|
1056
1728
|
ScrollGenerateCard,
|
|
1057
1729
|
ShiftBar,
|
|
1058
1730
|
ShiftCard,
|
|
1059
1731
|
TiltCard,
|
|
1732
|
+
WIDTH_SPAN,
|
|
1733
|
+
tallH,
|
|
1734
|
+
useCanvas,
|
|
1060
1735
|
useGenerate,
|
|
1061
1736
|
useHeyShift
|
|
1062
1737
|
});
|