@allshift/ui 0.5.2 → 0.6.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/dist/index.cjs +132 -25
- package/dist/index.d.cts +16 -2
- package/dist/index.d.ts +16 -2
- package/dist/index.js +127 -25
- package/package.json +11 -5
package/dist/index.cjs
CHANGED
|
@@ -36,11 +36,14 @@ __export(index_exports, {
|
|
|
36
36
|
CanvasProvider: () => CanvasProvider,
|
|
37
37
|
CanvasTransition: () => CanvasTransition,
|
|
38
38
|
DEFAULT_H: () => DEFAULT_H,
|
|
39
|
+
DUO_FOCUS_SHARE: () => DUO_FOCUS_SHARE,
|
|
39
40
|
GAP: () => GAP,
|
|
40
41
|
GenerativeView: () => GenerativeView,
|
|
41
42
|
HolographicShine: () => HolographicShine,
|
|
42
43
|
LiveCard: () => LiveCard,
|
|
43
44
|
LiveStat: () => LiveStat,
|
|
45
|
+
MAX_WINDOWS: () => MAX_WINDOWS,
|
|
46
|
+
MAX_WINDOWS_PHONE: () => MAX_WINDOWS_PHONE,
|
|
44
47
|
MIN_H: () => MIN_H,
|
|
45
48
|
MIN_W: () => MIN_W,
|
|
46
49
|
PageTransition: () => PageTransition,
|
|
@@ -49,6 +52,8 @@ __export(index_exports, {
|
|
|
49
52
|
ShiftCard: () => ShiftCard,
|
|
50
53
|
TiltCard: () => TiltCard,
|
|
51
54
|
WIDTH_SPAN: () => WIDTH_SPAN,
|
|
55
|
+
detectMode: () => detectMode,
|
|
56
|
+
maxWindowsFor: () => maxWindowsFor,
|
|
52
57
|
tallH: () => tallH,
|
|
53
58
|
useCanvas: () => useCanvas,
|
|
54
59
|
useGenerate: () => useGenerate,
|
|
@@ -824,7 +829,7 @@ function PageTransition({ children, className }) {
|
|
|
824
829
|
import_framer_motion11.motion.div,
|
|
825
830
|
{
|
|
826
831
|
initial: { opacity: 0, x: dir * 22, filter: "blur(3px)" },
|
|
827
|
-
animate: { opacity: 1, x: 0, filter: "blur(0px)" },
|
|
832
|
+
animate: { opacity: 1, x: 0, filter: "blur(0px)", transitionEnd: { filter: "none" } },
|
|
828
833
|
transition: { type: "spring", stiffness: 400, damping: 34 },
|
|
829
834
|
className: className ?? "flex flex-col flex-1 min-h-0",
|
|
830
835
|
children
|
|
@@ -835,6 +840,59 @@ function PageTransition({ children, className }) {
|
|
|
835
840
|
|
|
836
841
|
// src/canvas/CanvasProvider.tsx
|
|
837
842
|
var import_react10 = require("react");
|
|
843
|
+
|
|
844
|
+
// src/canvas/duo.ts
|
|
845
|
+
var MAX_WINDOWS = 4;
|
|
846
|
+
var MAX_WINDOWS_PHONE = 2;
|
|
847
|
+
var DUO_FOCUS_SHARE = 0.62;
|
|
848
|
+
var PHONE_CHROME = 220;
|
|
849
|
+
var DUO_GAP = 12;
|
|
850
|
+
var DUO_MIN_H = 160;
|
|
851
|
+
function detectMode(innerWidth, innerHeight, coarsePointer) {
|
|
852
|
+
if (innerWidth < 720) return "phone";
|
|
853
|
+
if (coarsePointer && Math.min(innerWidth, innerHeight) < 600) return "phone";
|
|
854
|
+
return "wide";
|
|
855
|
+
}
|
|
856
|
+
function maxWindowsFor(mode, limits) {
|
|
857
|
+
const n = mode === "phone" ? limits?.phone ?? MAX_WINDOWS_PHONE : limits?.wide ?? MAX_WINDOWS;
|
|
858
|
+
return Math.max(1, Math.floor(n));
|
|
859
|
+
}
|
|
860
|
+
function capList(list, max, keepId) {
|
|
861
|
+
let out = list;
|
|
862
|
+
while (out.length > max) {
|
|
863
|
+
const victim = out.filter((b) => !b.locked && b.id !== keepId).sort((a, b) => a.z - b.z)[0];
|
|
864
|
+
if (!victim) break;
|
|
865
|
+
out = out.filter((b) => b.id !== victim.id);
|
|
866
|
+
}
|
|
867
|
+
return out;
|
|
868
|
+
}
|
|
869
|
+
function tileDuo(items, boardW, boardH) {
|
|
870
|
+
const n = items.length;
|
|
871
|
+
if (n === 0) return items;
|
|
872
|
+
const sorted = [...items].sort((a, b) => b.z - a.z);
|
|
873
|
+
const availH = Math.max(DUO_MIN_H * 2 + DUO_GAP, boardH - PHONE_CHROME);
|
|
874
|
+
if (n === 1) return [{ ...sorted[0], x: 0, y: 0, w: boardW, h: availH, placed: false }];
|
|
875
|
+
const minRest = (DUO_MIN_H + DUO_GAP) * (n - 1);
|
|
876
|
+
const focusH = Math.max(DUO_MIN_H, Math.min(Math.round(availH * DUO_FOCUS_SHARE), availH - minRest));
|
|
877
|
+
const restH = Math.max(DUO_MIN_H, Math.round((availH - focusH - DUO_GAP * (n - 1)) / (n - 1)));
|
|
878
|
+
let y = 0;
|
|
879
|
+
return sorted.map((it, i) => {
|
|
880
|
+
const h = i === 0 ? focusH : restH;
|
|
881
|
+
const out = { ...it, x: 0, y, w: boardW, h, placed: false };
|
|
882
|
+
y += h + DUO_GAP;
|
|
883
|
+
return out;
|
|
884
|
+
});
|
|
885
|
+
}
|
|
886
|
+
function sameGeometry(a, b) {
|
|
887
|
+
if (a.length !== b.length) return false;
|
|
888
|
+
const byId = new Map(b.map((x) => [x.id, x]));
|
|
889
|
+
return a.every((x) => {
|
|
890
|
+
const y = byId.get(x.id);
|
|
891
|
+
return !!y && x.x === y.x && x.y === y.y && x.w === y.w && x.h === y.h;
|
|
892
|
+
});
|
|
893
|
+
}
|
|
894
|
+
|
|
895
|
+
// src/canvas/CanvasProvider.tsx
|
|
838
896
|
var import_jsx_runtime11 = require("react/jsx-runtime");
|
|
839
897
|
var GAP = 20;
|
|
840
898
|
var DEFAULT_H = 420;
|
|
@@ -983,9 +1041,10 @@ function computeSnap(blocks, id, layout, boardW, H) {
|
|
|
983
1041
|
}
|
|
984
1042
|
return placements;
|
|
985
1043
|
}
|
|
986
|
-
function tileItems(items, boardW, boardH) {
|
|
1044
|
+
function tileItems(items, boardW, boardH, mode = "wide") {
|
|
987
1045
|
const n = items.length;
|
|
988
1046
|
if (n === 0) return items;
|
|
1047
|
+
if (mode === "phone") return tileDuo(items, boardW, boardH);
|
|
989
1048
|
const availH = Math.max(MIN_H * 2 + GAP, boardH - 150);
|
|
990
1049
|
const rows = Math.ceil(n / 2);
|
|
991
1050
|
const rowH = Math.max(300, Math.round((availH - GAP * (rows - 1)) / rows));
|
|
@@ -1038,7 +1097,8 @@ function CanvasProvider({
|
|
|
1038
1097
|
initialLayout,
|
|
1039
1098
|
addKey = null,
|
|
1040
1099
|
persist = false,
|
|
1041
|
-
savedSession
|
|
1100
|
+
savedSession,
|
|
1101
|
+
limits
|
|
1042
1102
|
}) {
|
|
1043
1103
|
const resolveRef = (0, import_react10.useRef)(resolveBlock);
|
|
1044
1104
|
(0, import_react10.useEffect)(() => {
|
|
@@ -1046,14 +1106,22 @@ function CanvasProvider({
|
|
|
1046
1106
|
}, [resolveBlock]);
|
|
1047
1107
|
const resolve = (key) => resolveRef.current(key);
|
|
1048
1108
|
const initial = (0, import_react10.useMemo)(() => {
|
|
1049
|
-
|
|
1050
|
-
const
|
|
1051
|
-
if (addKey && resolveRef.current(addKey) && !
|
|
1052
|
-
|
|
1109
|
+
const max = maxWindowsFor("wide", limits);
|
|
1110
|
+
const entries = initialLayout ? initialLayout.filter((b) => b && typeof b.key === "string" && resolveRef.current(b.key)) : initialKeys.filter((k) => resolveRef.current(k));
|
|
1111
|
+
if (addKey && resolveRef.current(addKey) && !entries.some((b) => (typeof b === "string" ? b : b.key) === addKey)) {
|
|
1112
|
+
entries.unshift(addKey);
|
|
1053
1113
|
}
|
|
1114
|
+
const kept = entries.slice(0, max);
|
|
1115
|
+
let z = 1;
|
|
1116
|
+
const base = kept.map(() => null);
|
|
1117
|
+
for (let i = kept.length - 1; i >= 0; i--) base[i] = blockFromSaved(kept[i], z++);
|
|
1054
1118
|
return { base, nextZ: z };
|
|
1055
1119
|
}, []);
|
|
1056
1120
|
const zRef = (0, import_react10.useRef)(initial.nextZ);
|
|
1121
|
+
const limitsRef = (0, import_react10.useRef)(limits);
|
|
1122
|
+
(0, import_react10.useEffect)(() => {
|
|
1123
|
+
limitsRef.current = limits;
|
|
1124
|
+
}, [limits]);
|
|
1057
1125
|
const [blocks, setBlocks] = (0, import_react10.useState)(initial.base);
|
|
1058
1126
|
const [interacting, setInteracting] = (0, import_react10.useState)(false);
|
|
1059
1127
|
const [activeId, setActiveId] = (0, import_react10.useState)(null);
|
|
@@ -1061,6 +1129,9 @@ function CanvasProvider({
|
|
|
1061
1129
|
const [boardWidth, setBoardWidth] = (0, import_react10.useState)(1280);
|
|
1062
1130
|
const boardRef = (0, import_react10.useRef)(1280);
|
|
1063
1131
|
const boardHRef = (0, import_react10.useRef)(820);
|
|
1132
|
+
const [mode, setMode] = (0, import_react10.useState)("wide");
|
|
1133
|
+
const modeRef = (0, import_react10.useRef)("wide");
|
|
1134
|
+
const maxFor = (m) => maxWindowsFor(m, limitsRef.current);
|
|
1064
1135
|
const persistRef = (0, import_react10.useRef)(persist);
|
|
1065
1136
|
(0, import_react10.useEffect)(() => {
|
|
1066
1137
|
persistRef.current = persist;
|
|
@@ -1069,12 +1140,26 @@ function CanvasProvider({
|
|
|
1069
1140
|
const hadUserChangeRef = (0, import_react10.useRef)(false);
|
|
1070
1141
|
(0, import_react10.useEffect)(() => {
|
|
1071
1142
|
const measure = () => {
|
|
1072
|
-
const
|
|
1143
|
+
const vw = Math.min(window.innerWidth, window.visualViewport?.width || Infinity);
|
|
1144
|
+
const w = Math.max(320, vw - 40);
|
|
1073
1145
|
boardRef.current = w;
|
|
1074
1146
|
boardHRef.current = Math.max(480, window.innerHeight);
|
|
1075
1147
|
setBoardWidth(w);
|
|
1148
|
+
const coarse = !!window.matchMedia?.("(pointer: coarse)").matches;
|
|
1149
|
+
const m = detectMode(vw, window.innerHeight, coarse);
|
|
1150
|
+
const modeChanged = m !== modeRef.current;
|
|
1151
|
+
modeRef.current = m;
|
|
1152
|
+
setMode(m);
|
|
1076
1153
|
setBlocks((prev) => {
|
|
1077
|
-
|
|
1154
|
+
let next;
|
|
1155
|
+
if (m === "phone") {
|
|
1156
|
+
next = tileDuo(capList(prev, maxFor("phone")), w, boardHRef.current);
|
|
1157
|
+
if (sameGeometry(prev, next)) return prev;
|
|
1158
|
+
} else if (modeChanged) {
|
|
1159
|
+
next = tileItems(capList(prev, maxFor("wide")), w, boardHRef.current);
|
|
1160
|
+
} else {
|
|
1161
|
+
next = fitToBoard(prev, w, boardHRef.current);
|
|
1162
|
+
}
|
|
1078
1163
|
if (next !== prev) fitAdjustRef.current = true;
|
|
1079
1164
|
return next;
|
|
1080
1165
|
});
|
|
@@ -1084,22 +1169,23 @@ function CanvasProvider({
|
|
|
1084
1169
|
return () => window.removeEventListener("resize", measure);
|
|
1085
1170
|
}, []);
|
|
1086
1171
|
const layoutDefault = (0, import_react10.useCallback)(
|
|
1087
|
-
(list) => tileItems(list, boardRef.current, boardHRef.current),
|
|
1172
|
+
(list) => tileItems(list, boardRef.current, boardHRef.current, modeRef.current),
|
|
1088
1173
|
[]
|
|
1089
1174
|
);
|
|
1175
|
+
const customizedIn = (list) => modeRef.current !== "phone" && list.some((b) => b.placed);
|
|
1090
1176
|
const tiledOnce = (0, import_react10.useRef)(false);
|
|
1091
1177
|
(0, import_react10.useEffect)(() => {
|
|
1092
1178
|
if (tiledOnce.current) return;
|
|
1093
1179
|
tiledOnce.current = true;
|
|
1094
|
-
setBlocks((prev) => prev.length === 0 || prev
|
|
1180
|
+
setBlocks((prev) => prev.length === 0 || customizedIn(prev) ? prev : layoutDefault(prev));
|
|
1095
1181
|
}, [boardWidth]);
|
|
1096
1182
|
const addBlock = (0, import_react10.useCallback)((key, context, width, append = false) => {
|
|
1097
1183
|
if (!resolve(key)) return;
|
|
1098
1184
|
const w = width ?? resolve(key)?.defaultWidth;
|
|
1099
1185
|
setBlocks((prev) => {
|
|
1100
1186
|
const existing = prev.find((b) => b.key === key);
|
|
1101
|
-
const customized = prev
|
|
1102
|
-
const without = prev.filter((b) => b.key !== key);
|
|
1187
|
+
const customized = customizedIn(prev);
|
|
1188
|
+
const without = capList(prev.filter((b) => b.key !== key), maxFor(modeRef.current) - 1);
|
|
1103
1189
|
if (customized) {
|
|
1104
1190
|
if (append) {
|
|
1105
1191
|
const startY = without.length ? Math.max(...without.map((b) => b.y + b.h)) + GAP : 0;
|
|
@@ -1139,13 +1225,15 @@ function CanvasProvider({
|
|
|
1139
1225
|
const removeBlock = (0, import_react10.useCallback)((id) => {
|
|
1140
1226
|
setBlocks((prev) => {
|
|
1141
1227
|
const remaining = prev.filter((b) => b.id !== id);
|
|
1142
|
-
return remaining.length > 0 && !remaining
|
|
1228
|
+
return remaining.length > 0 && !customizedIn(remaining) ? layoutDefault(remaining) : remaining;
|
|
1143
1229
|
});
|
|
1144
1230
|
}, [layoutDefault]);
|
|
1145
1231
|
const resizeBlock = (0, import_react10.useCallback)((id, width) => {
|
|
1232
|
+
if (modeRef.current === "phone") return;
|
|
1146
1233
|
setBlocks((prev) => prev.map((b) => b.id === id ? { ...b, width, w: pxWidth(WIDTH_SPAN[width], boardRef.current), placed: true, z: zRef.current++ } : b));
|
|
1147
1234
|
}, []);
|
|
1148
1235
|
const moveTo = (0, import_react10.useCallback)((id, x, y) => {
|
|
1236
|
+
if (modeRef.current === "phone") return;
|
|
1149
1237
|
const boardW = boardRef.current;
|
|
1150
1238
|
setBlocks((prev) => prev.map((b) => {
|
|
1151
1239
|
if (b.id !== id || b.locked) return b;
|
|
@@ -1155,6 +1243,7 @@ function CanvasProvider({
|
|
|
1155
1243
|
}));
|
|
1156
1244
|
}, []);
|
|
1157
1245
|
const sizeTo = (0, import_react10.useCallback)((id, w, h) => {
|
|
1246
|
+
if (modeRef.current === "phone") return;
|
|
1158
1247
|
const boardW = boardRef.current;
|
|
1159
1248
|
setBlocks((prev) => prev.map((b) => {
|
|
1160
1249
|
if (b.id !== id || b.locked) return b;
|
|
@@ -1164,6 +1253,7 @@ function CanvasProvider({
|
|
|
1164
1253
|
}));
|
|
1165
1254
|
}, []);
|
|
1166
1255
|
const setRect = (0, import_react10.useCallback)((id, r) => {
|
|
1256
|
+
if (modeRef.current === "phone") return;
|
|
1167
1257
|
const boardW = boardRef.current;
|
|
1168
1258
|
setBlocks((prev) => prev.map((b) => {
|
|
1169
1259
|
if (b.id !== id || b.locked) return b;
|
|
@@ -1176,6 +1266,7 @@ function CanvasProvider({
|
|
|
1176
1266
|
}));
|
|
1177
1267
|
}, []);
|
|
1178
1268
|
const snapTo = (0, import_react10.useCallback)((id, layout) => {
|
|
1269
|
+
if (modeRef.current === "phone") return;
|
|
1179
1270
|
const boardW = boardRef.current;
|
|
1180
1271
|
const H = tallH(boardHRef.current);
|
|
1181
1272
|
setSnapGhost(null);
|
|
@@ -1223,6 +1314,7 @@ function CanvasProvider({
|
|
|
1223
1314
|
}, [blocks]);
|
|
1224
1315
|
const clearSnapPreview = (0, import_react10.useCallback)(() => setSnapGhost(null), []);
|
|
1225
1316
|
const resizeRow = (0, import_react10.useCallback)((id, x, w, h, origin) => {
|
|
1317
|
+
if (modeRef.current === "phone") return;
|
|
1226
1318
|
const boardW = boardRef.current;
|
|
1227
1319
|
const M0 = origin.find((b) => b.id === id);
|
|
1228
1320
|
if (!M0 || M0.locked) return;
|
|
@@ -1272,9 +1364,14 @@ function CanvasProvider({
|
|
|
1272
1364
|
}));
|
|
1273
1365
|
}, []);
|
|
1274
1366
|
const bringToFront = (0, import_react10.useCallback)((id) => {
|
|
1275
|
-
setBlocks((prev) =>
|
|
1276
|
-
|
|
1367
|
+
setBlocks((prev) => {
|
|
1368
|
+
if (prev.find((b) => b.id === id)?.z === zRef.current - 1) return prev;
|
|
1369
|
+
const next = prev.map((b) => b.id === id ? { ...b, z: zRef.current++ } : b);
|
|
1370
|
+
return modeRef.current === "phone" ? layoutDefault(next) : next;
|
|
1371
|
+
});
|
|
1372
|
+
}, [layoutDefault]);
|
|
1277
1373
|
const resolveCollisions = (0, import_react10.useCallback)((id) => {
|
|
1374
|
+
if (modeRef.current === "phone") return;
|
|
1278
1375
|
setBlocks((prev) => {
|
|
1279
1376
|
const moved = prev.find((b) => b.id === id);
|
|
1280
1377
|
if (!moved) return prev;
|
|
@@ -1317,8 +1414,10 @@ function CanvasProvider({
|
|
|
1317
1414
|
if (!saved || saved.length === 0) return;
|
|
1318
1415
|
restoredRef.current = true;
|
|
1319
1416
|
setCanRestore(false);
|
|
1320
|
-
const
|
|
1321
|
-
const
|
|
1417
|
+
const kept = saved.filter((b) => b && typeof b.key === "string" && resolve(b.key)).slice(0, maxFor(modeRef.current));
|
|
1418
|
+
const built = kept.map(() => null);
|
|
1419
|
+
for (let i = kept.length - 1; i >= 0; i--) built[i] = blockFromSaved(kept[i], zRef.current++);
|
|
1420
|
+
const arranged = customizedIn(built) ? built : layoutDefault(built);
|
|
1322
1421
|
const fitted = fitToBoard(arranged, boardRef.current, boardHRef.current);
|
|
1323
1422
|
if (fitted !== arranged) fitAdjustRef.current = true;
|
|
1324
1423
|
setBlocks(fitted);
|
|
@@ -1341,7 +1440,7 @@ function CanvasProvider({
|
|
|
1341
1440
|
const width = d.width;
|
|
1342
1441
|
setBlocks((prev) => {
|
|
1343
1442
|
const list = prev.map((b) => b.key === d.key ? { ...b, width } : b);
|
|
1344
|
-
return prev
|
|
1443
|
+
return customizedIn(prev) ? list : layoutDefault(list);
|
|
1345
1444
|
});
|
|
1346
1445
|
}
|
|
1347
1446
|
function onPlace(e) {
|
|
@@ -1355,7 +1454,7 @@ function CanvasProvider({
|
|
|
1355
1454
|
const [moved] = next.splice(srcIdx, 1);
|
|
1356
1455
|
const insertAt = next.findIndex((b) => b.key === d.afterKey) + 1;
|
|
1357
1456
|
next.splice(insertAt, 0, moved);
|
|
1358
|
-
return prev
|
|
1457
|
+
return customizedIn(prev) ? next : layoutDefault(next);
|
|
1359
1458
|
});
|
|
1360
1459
|
}
|
|
1361
1460
|
function onSwap(e) {
|
|
@@ -1363,7 +1462,7 @@ function CanvasProvider({
|
|
|
1363
1462
|
const keys = d?.keys ?? [];
|
|
1364
1463
|
const contexts = d?.contexts ?? [];
|
|
1365
1464
|
const widths = d?.widths ?? [];
|
|
1366
|
-
const built = keys.filter((k) => resolve(k)).map((key, i) => ({
|
|
1465
|
+
const built = keys.filter((k) => resolve(k)).slice(0, maxFor(modeRef.current)).map((key, i) => ({
|
|
1367
1466
|
id: nextId(),
|
|
1368
1467
|
key,
|
|
1369
1468
|
context: contexts[i],
|
|
@@ -1420,12 +1519,13 @@ function CanvasProvider({
|
|
|
1420
1519
|
}, [blocks.length, canRestore]);
|
|
1421
1520
|
const firstRun = (0, import_react10.useRef)(true);
|
|
1422
1521
|
const saveTimer = (0, import_react10.useRef)(null);
|
|
1423
|
-
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("|");
|
|
1522
|
+
const layoutSignature = blocks.map((b) => mode === "phone" ? `${b.key}:${b.width ?? ""}` : `${b.key}:${b.width ?? ""}:${Math.round(b.x)}:${Math.round(b.y)}:${Math.round(b.w)}:${Math.round(b.h)}`).join("|");
|
|
1424
1523
|
const payload = (0, import_react10.useMemo)(() => layoutSignature ? {
|
|
1425
1524
|
savedDay: localDay(),
|
|
1426
1525
|
blocks: layoutSignature.split("|").map((seg) => {
|
|
1427
1526
|
const [key, width, x, y, w, h] = seg.split(":");
|
|
1428
|
-
|
|
1527
|
+
const base = { key, ...width ? { width } : {} };
|
|
1528
|
+
return x === void 0 ? base : { ...base, x: Number(x), y: Number(y), w: Number(w), h: Number(h) };
|
|
1429
1529
|
})
|
|
1430
1530
|
} : null, [layoutSignature]);
|
|
1431
1531
|
const payloadRef = (0, import_react10.useRef)(payload);
|
|
@@ -1507,8 +1607,10 @@ function CanvasProvider({
|
|
|
1507
1607
|
setInteracting,
|
|
1508
1608
|
activeId,
|
|
1509
1609
|
setActiveId,
|
|
1510
|
-
boardWidth
|
|
1511
|
-
|
|
1610
|
+
boardWidth,
|
|
1611
|
+
mode,
|
|
1612
|
+
maxBlocks: maxWindowsFor(mode, limits)
|
|
1613
|
+
}), [blocks, addBlock, removeBlock, resizeBlock, snapTo, snapPreview, previewSnap, clearSnapPreview, snapGhost, moveTo, sizeTo, setRect, resizeRow, bringToFront, resolveCollisions, toggleLock, clear, interacting, activeId, boardWidth, mode, limits]);
|
|
1512
1614
|
return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(CanvasContext.Provider, { value: api, children });
|
|
1513
1615
|
}
|
|
1514
1616
|
|
|
@@ -1737,11 +1839,14 @@ function ShiftBar({ state, dots = 3, size = 7, className = "" }) {
|
|
|
1737
1839
|
CanvasProvider,
|
|
1738
1840
|
CanvasTransition,
|
|
1739
1841
|
DEFAULT_H,
|
|
1842
|
+
DUO_FOCUS_SHARE,
|
|
1740
1843
|
GAP,
|
|
1741
1844
|
GenerativeView,
|
|
1742
1845
|
HolographicShine,
|
|
1743
1846
|
LiveCard,
|
|
1744
1847
|
LiveStat,
|
|
1848
|
+
MAX_WINDOWS,
|
|
1849
|
+
MAX_WINDOWS_PHONE,
|
|
1745
1850
|
MIN_H,
|
|
1746
1851
|
MIN_W,
|
|
1747
1852
|
PageTransition,
|
|
@@ -1750,6 +1855,8 @@ function ShiftBar({ state, dots = 3, size = 7, className = "" }) {
|
|
|
1750
1855
|
ShiftCard,
|
|
1751
1856
|
TiltCard,
|
|
1752
1857
|
WIDTH_SPAN,
|
|
1858
|
+
detectMode,
|
|
1859
|
+
maxWindowsFor,
|
|
1753
1860
|
tallH,
|
|
1754
1861
|
useCanvas,
|
|
1755
1862
|
useGenerate,
|
package/dist/index.d.cts
CHANGED
|
@@ -263,6 +263,17 @@ interface PageTransitionProps {
|
|
|
263
263
|
*/
|
|
264
264
|
declare function PageTransition({ children, className }: PageTransitionProps): react.JSX.Element;
|
|
265
265
|
|
|
266
|
+
declare const MAX_WINDOWS = 4;
|
|
267
|
+
declare const MAX_WINDOWS_PHONE = 2;
|
|
268
|
+
type CanvasMode = "wide" | "phone";
|
|
269
|
+
type CanvasLimits = {
|
|
270
|
+
wide?: number;
|
|
271
|
+
phone?: number;
|
|
272
|
+
};
|
|
273
|
+
declare const DUO_FOCUS_SHARE = 0.62;
|
|
274
|
+
declare function detectMode(innerWidth: number, innerHeight: number, coarsePointer: boolean): CanvasMode;
|
|
275
|
+
declare function maxWindowsFor(mode: CanvasMode, limits?: CanvasLimits): number;
|
|
276
|
+
|
|
266
277
|
declare const GAP = 20;
|
|
267
278
|
declare const DEFAULT_H = 420;
|
|
268
279
|
declare const MIN_W = 240;
|
|
@@ -354,16 +365,19 @@ interface CanvasApi {
|
|
|
354
365
|
activeId: string | null;
|
|
355
366
|
setActiveId: (id: string | null) => void;
|
|
356
367
|
boardWidth: number;
|
|
368
|
+
mode: CanvasMode;
|
|
369
|
+
maxBlocks: number;
|
|
357
370
|
}
|
|
358
371
|
declare function useCanvas(): CanvasApi;
|
|
359
372
|
declare function tallH(boardH: number): number;
|
|
360
|
-
declare function CanvasProvider({ children, resolveBlock, initialKeys, initialLayout, addKey, persist, savedSession, }: {
|
|
373
|
+
declare function CanvasProvider({ children, resolveBlock, initialKeys, initialLayout, addKey, persist, savedSession, limits, }: {
|
|
361
374
|
children: ReactNode;
|
|
362
375
|
resolveBlock: ResolveBlock;
|
|
363
376
|
initialKeys?: string[];
|
|
364
377
|
initialLayout?: SavedBlock[];
|
|
365
378
|
addKey?: string | null;
|
|
366
379
|
persist?: boolean;
|
|
380
|
+
limits?: CanvasLimits;
|
|
367
381
|
savedSession?: {
|
|
368
382
|
blocks?: SavedBlock[];
|
|
369
383
|
savedDay?: string | null;
|
|
@@ -399,4 +413,4 @@ interface ShiftBarProps {
|
|
|
399
413
|
}
|
|
400
414
|
declare function ShiftBar({ state, dots, size, className }: ShiftBarProps): react.JSX.Element;
|
|
401
415
|
|
|
402
|
-
export { ACCENT_RGB, type Accent, type BlockWidth, type CanvasBlock, CanvasLauncher, type CanvasLauncherProps, CanvasProvider, CanvasTransition, type CanvasTransitionProps, DEFAULT_H, GAP, type GenPhase, GenerativeView, type GenerativeViewProps, type HeyShiftConfig, type HeyShiftReturn, HolographicShine, type HolographicShineProps, type LauncherGroup, type LauncherItem, LiveCard, type LiveCardProps, LiveStat, type LiveStatProps, MIN_H, MIN_W, PageTransition, type PageTransitionProps, type ResolveBlock, type SavedBlock, ScrollGenerateCard, type ScrollGenerateCardProps, ShiftBar, type ShiftBarProps, type ShiftBarState, ShiftCard, type ShiftCardProps, type SnapBox, type SnapGhost, type SnapLayout, type SnapPreview, TiltCard, type TiltCardProps, type UseGenerateOptions, WIDTH_SPAN, tallH, useCanvas, useGenerate, useHeyShift };
|
|
416
|
+
export { ACCENT_RGB, type Accent, type BlockWidth, type CanvasBlock, CanvasLauncher, type CanvasLauncherProps, type CanvasLimits, type CanvasMode, CanvasProvider, CanvasTransition, type CanvasTransitionProps, DEFAULT_H, DUO_FOCUS_SHARE, GAP, type GenPhase, GenerativeView, type GenerativeViewProps, type HeyShiftConfig, type HeyShiftReturn, HolographicShine, type HolographicShineProps, type LauncherGroup, type LauncherItem, LiveCard, type LiveCardProps, LiveStat, type LiveStatProps, MAX_WINDOWS, MAX_WINDOWS_PHONE, MIN_H, MIN_W, PageTransition, type PageTransitionProps, type ResolveBlock, type SavedBlock, ScrollGenerateCard, type ScrollGenerateCardProps, ShiftBar, type ShiftBarProps, type ShiftBarState, ShiftCard, type ShiftCardProps, type SnapBox, type SnapGhost, type SnapLayout, type SnapPreview, TiltCard, type TiltCardProps, type UseGenerateOptions, WIDTH_SPAN, detectMode, maxWindowsFor, tallH, useCanvas, useGenerate, useHeyShift };
|
package/dist/index.d.ts
CHANGED
|
@@ -263,6 +263,17 @@ interface PageTransitionProps {
|
|
|
263
263
|
*/
|
|
264
264
|
declare function PageTransition({ children, className }: PageTransitionProps): react.JSX.Element;
|
|
265
265
|
|
|
266
|
+
declare const MAX_WINDOWS = 4;
|
|
267
|
+
declare const MAX_WINDOWS_PHONE = 2;
|
|
268
|
+
type CanvasMode = "wide" | "phone";
|
|
269
|
+
type CanvasLimits = {
|
|
270
|
+
wide?: number;
|
|
271
|
+
phone?: number;
|
|
272
|
+
};
|
|
273
|
+
declare const DUO_FOCUS_SHARE = 0.62;
|
|
274
|
+
declare function detectMode(innerWidth: number, innerHeight: number, coarsePointer: boolean): CanvasMode;
|
|
275
|
+
declare function maxWindowsFor(mode: CanvasMode, limits?: CanvasLimits): number;
|
|
276
|
+
|
|
266
277
|
declare const GAP = 20;
|
|
267
278
|
declare const DEFAULT_H = 420;
|
|
268
279
|
declare const MIN_W = 240;
|
|
@@ -354,16 +365,19 @@ interface CanvasApi {
|
|
|
354
365
|
activeId: string | null;
|
|
355
366
|
setActiveId: (id: string | null) => void;
|
|
356
367
|
boardWidth: number;
|
|
368
|
+
mode: CanvasMode;
|
|
369
|
+
maxBlocks: number;
|
|
357
370
|
}
|
|
358
371
|
declare function useCanvas(): CanvasApi;
|
|
359
372
|
declare function tallH(boardH: number): number;
|
|
360
|
-
declare function CanvasProvider({ children, resolveBlock, initialKeys, initialLayout, addKey, persist, savedSession, }: {
|
|
373
|
+
declare function CanvasProvider({ children, resolveBlock, initialKeys, initialLayout, addKey, persist, savedSession, limits, }: {
|
|
361
374
|
children: ReactNode;
|
|
362
375
|
resolveBlock: ResolveBlock;
|
|
363
376
|
initialKeys?: string[];
|
|
364
377
|
initialLayout?: SavedBlock[];
|
|
365
378
|
addKey?: string | null;
|
|
366
379
|
persist?: boolean;
|
|
380
|
+
limits?: CanvasLimits;
|
|
367
381
|
savedSession?: {
|
|
368
382
|
blocks?: SavedBlock[];
|
|
369
383
|
savedDay?: string | null;
|
|
@@ -399,4 +413,4 @@ interface ShiftBarProps {
|
|
|
399
413
|
}
|
|
400
414
|
declare function ShiftBar({ state, dots, size, className }: ShiftBarProps): react.JSX.Element;
|
|
401
415
|
|
|
402
|
-
export { ACCENT_RGB, type Accent, type BlockWidth, type CanvasBlock, CanvasLauncher, type CanvasLauncherProps, CanvasProvider, CanvasTransition, type CanvasTransitionProps, DEFAULT_H, GAP, type GenPhase, GenerativeView, type GenerativeViewProps, type HeyShiftConfig, type HeyShiftReturn, HolographicShine, type HolographicShineProps, type LauncherGroup, type LauncherItem, LiveCard, type LiveCardProps, LiveStat, type LiveStatProps, MIN_H, MIN_W, PageTransition, type PageTransitionProps, type ResolveBlock, type SavedBlock, ScrollGenerateCard, type ScrollGenerateCardProps, ShiftBar, type ShiftBarProps, type ShiftBarState, ShiftCard, type ShiftCardProps, type SnapBox, type SnapGhost, type SnapLayout, type SnapPreview, TiltCard, type TiltCardProps, type UseGenerateOptions, WIDTH_SPAN, tallH, useCanvas, useGenerate, useHeyShift };
|
|
416
|
+
export { ACCENT_RGB, type Accent, type BlockWidth, type CanvasBlock, CanvasLauncher, type CanvasLauncherProps, type CanvasLimits, type CanvasMode, CanvasProvider, CanvasTransition, type CanvasTransitionProps, DEFAULT_H, DUO_FOCUS_SHARE, GAP, type GenPhase, GenerativeView, type GenerativeViewProps, type HeyShiftConfig, type HeyShiftReturn, HolographicShine, type HolographicShineProps, type LauncherGroup, type LauncherItem, LiveCard, type LiveCardProps, LiveStat, type LiveStatProps, MAX_WINDOWS, MAX_WINDOWS_PHONE, MIN_H, MIN_W, PageTransition, type PageTransitionProps, type ResolveBlock, type SavedBlock, ScrollGenerateCard, type ScrollGenerateCardProps, ShiftBar, type ShiftBarProps, type ShiftBarState, ShiftCard, type ShiftCardProps, type SnapBox, type SnapGhost, type SnapLayout, type SnapPreview, TiltCard, type TiltCardProps, type UseGenerateOptions, WIDTH_SPAN, detectMode, maxWindowsFor, tallH, useCanvas, useGenerate, useHeyShift };
|
package/dist/index.js
CHANGED
|
@@ -784,7 +784,7 @@ function PageTransition({ children, className }) {
|
|
|
784
784
|
motion10.div,
|
|
785
785
|
{
|
|
786
786
|
initial: { opacity: 0, x: dir * 22, filter: "blur(3px)" },
|
|
787
|
-
animate: { opacity: 1, x: 0, filter: "blur(0px)" },
|
|
787
|
+
animate: { opacity: 1, x: 0, filter: "blur(0px)", transitionEnd: { filter: "none" } },
|
|
788
788
|
transition: { type: "spring", stiffness: 400, damping: 34 },
|
|
789
789
|
className: className ?? "flex flex-col flex-1 min-h-0",
|
|
790
790
|
children
|
|
@@ -795,6 +795,59 @@ function PageTransition({ children, className }) {
|
|
|
795
795
|
|
|
796
796
|
// src/canvas/CanvasProvider.tsx
|
|
797
797
|
import { createContext, useCallback, useContext, useEffect as useEffect6, useMemo as useMemo2, useRef as useRef6, useState as useState6 } from "react";
|
|
798
|
+
|
|
799
|
+
// src/canvas/duo.ts
|
|
800
|
+
var MAX_WINDOWS = 4;
|
|
801
|
+
var MAX_WINDOWS_PHONE = 2;
|
|
802
|
+
var DUO_FOCUS_SHARE = 0.62;
|
|
803
|
+
var PHONE_CHROME = 220;
|
|
804
|
+
var DUO_GAP = 12;
|
|
805
|
+
var DUO_MIN_H = 160;
|
|
806
|
+
function detectMode(innerWidth, innerHeight, coarsePointer) {
|
|
807
|
+
if (innerWidth < 720) return "phone";
|
|
808
|
+
if (coarsePointer && Math.min(innerWidth, innerHeight) < 600) return "phone";
|
|
809
|
+
return "wide";
|
|
810
|
+
}
|
|
811
|
+
function maxWindowsFor(mode, limits) {
|
|
812
|
+
const n = mode === "phone" ? limits?.phone ?? MAX_WINDOWS_PHONE : limits?.wide ?? MAX_WINDOWS;
|
|
813
|
+
return Math.max(1, Math.floor(n));
|
|
814
|
+
}
|
|
815
|
+
function capList(list, max, keepId) {
|
|
816
|
+
let out = list;
|
|
817
|
+
while (out.length > max) {
|
|
818
|
+
const victim = out.filter((b) => !b.locked && b.id !== keepId).sort((a, b) => a.z - b.z)[0];
|
|
819
|
+
if (!victim) break;
|
|
820
|
+
out = out.filter((b) => b.id !== victim.id);
|
|
821
|
+
}
|
|
822
|
+
return out;
|
|
823
|
+
}
|
|
824
|
+
function tileDuo(items, boardW, boardH) {
|
|
825
|
+
const n = items.length;
|
|
826
|
+
if (n === 0) return items;
|
|
827
|
+
const sorted = [...items].sort((a, b) => b.z - a.z);
|
|
828
|
+
const availH = Math.max(DUO_MIN_H * 2 + DUO_GAP, boardH - PHONE_CHROME);
|
|
829
|
+
if (n === 1) return [{ ...sorted[0], x: 0, y: 0, w: boardW, h: availH, placed: false }];
|
|
830
|
+
const minRest = (DUO_MIN_H + DUO_GAP) * (n - 1);
|
|
831
|
+
const focusH = Math.max(DUO_MIN_H, Math.min(Math.round(availH * DUO_FOCUS_SHARE), availH - minRest));
|
|
832
|
+
const restH = Math.max(DUO_MIN_H, Math.round((availH - focusH - DUO_GAP * (n - 1)) / (n - 1)));
|
|
833
|
+
let y = 0;
|
|
834
|
+
return sorted.map((it, i) => {
|
|
835
|
+
const h = i === 0 ? focusH : restH;
|
|
836
|
+
const out = { ...it, x: 0, y, w: boardW, h, placed: false };
|
|
837
|
+
y += h + DUO_GAP;
|
|
838
|
+
return out;
|
|
839
|
+
});
|
|
840
|
+
}
|
|
841
|
+
function sameGeometry(a, b) {
|
|
842
|
+
if (a.length !== b.length) return false;
|
|
843
|
+
const byId = new Map(b.map((x) => [x.id, x]));
|
|
844
|
+
return a.every((x) => {
|
|
845
|
+
const y = byId.get(x.id);
|
|
846
|
+
return !!y && x.x === y.x && x.y === y.y && x.w === y.w && x.h === y.h;
|
|
847
|
+
});
|
|
848
|
+
}
|
|
849
|
+
|
|
850
|
+
// src/canvas/CanvasProvider.tsx
|
|
798
851
|
import { jsx as jsx11 } from "react/jsx-runtime";
|
|
799
852
|
var GAP = 20;
|
|
800
853
|
var DEFAULT_H = 420;
|
|
@@ -943,9 +996,10 @@ function computeSnap(blocks, id, layout, boardW, H) {
|
|
|
943
996
|
}
|
|
944
997
|
return placements;
|
|
945
998
|
}
|
|
946
|
-
function tileItems(items, boardW, boardH) {
|
|
999
|
+
function tileItems(items, boardW, boardH, mode = "wide") {
|
|
947
1000
|
const n = items.length;
|
|
948
1001
|
if (n === 0) return items;
|
|
1002
|
+
if (mode === "phone") return tileDuo(items, boardW, boardH);
|
|
949
1003
|
const availH = Math.max(MIN_H * 2 + GAP, boardH - 150);
|
|
950
1004
|
const rows = Math.ceil(n / 2);
|
|
951
1005
|
const rowH = Math.max(300, Math.round((availH - GAP * (rows - 1)) / rows));
|
|
@@ -998,7 +1052,8 @@ function CanvasProvider({
|
|
|
998
1052
|
initialLayout,
|
|
999
1053
|
addKey = null,
|
|
1000
1054
|
persist = false,
|
|
1001
|
-
savedSession
|
|
1055
|
+
savedSession,
|
|
1056
|
+
limits
|
|
1002
1057
|
}) {
|
|
1003
1058
|
const resolveRef = useRef6(resolveBlock);
|
|
1004
1059
|
useEffect6(() => {
|
|
@@ -1006,14 +1061,22 @@ function CanvasProvider({
|
|
|
1006
1061
|
}, [resolveBlock]);
|
|
1007
1062
|
const resolve = (key) => resolveRef.current(key);
|
|
1008
1063
|
const initial = useMemo2(() => {
|
|
1009
|
-
|
|
1010
|
-
const
|
|
1011
|
-
if (addKey && resolveRef.current(addKey) && !
|
|
1012
|
-
|
|
1064
|
+
const max = maxWindowsFor("wide", limits);
|
|
1065
|
+
const entries = initialLayout ? initialLayout.filter((b) => b && typeof b.key === "string" && resolveRef.current(b.key)) : initialKeys.filter((k) => resolveRef.current(k));
|
|
1066
|
+
if (addKey && resolveRef.current(addKey) && !entries.some((b) => (typeof b === "string" ? b : b.key) === addKey)) {
|
|
1067
|
+
entries.unshift(addKey);
|
|
1013
1068
|
}
|
|
1069
|
+
const kept = entries.slice(0, max);
|
|
1070
|
+
let z = 1;
|
|
1071
|
+
const base = kept.map(() => null);
|
|
1072
|
+
for (let i = kept.length - 1; i >= 0; i--) base[i] = blockFromSaved(kept[i], z++);
|
|
1014
1073
|
return { base, nextZ: z };
|
|
1015
1074
|
}, []);
|
|
1016
1075
|
const zRef = useRef6(initial.nextZ);
|
|
1076
|
+
const limitsRef = useRef6(limits);
|
|
1077
|
+
useEffect6(() => {
|
|
1078
|
+
limitsRef.current = limits;
|
|
1079
|
+
}, [limits]);
|
|
1017
1080
|
const [blocks, setBlocks] = useState6(initial.base);
|
|
1018
1081
|
const [interacting, setInteracting] = useState6(false);
|
|
1019
1082
|
const [activeId, setActiveId] = useState6(null);
|
|
@@ -1021,6 +1084,9 @@ function CanvasProvider({
|
|
|
1021
1084
|
const [boardWidth, setBoardWidth] = useState6(1280);
|
|
1022
1085
|
const boardRef = useRef6(1280);
|
|
1023
1086
|
const boardHRef = useRef6(820);
|
|
1087
|
+
const [mode, setMode] = useState6("wide");
|
|
1088
|
+
const modeRef = useRef6("wide");
|
|
1089
|
+
const maxFor = (m) => maxWindowsFor(m, limitsRef.current);
|
|
1024
1090
|
const persistRef = useRef6(persist);
|
|
1025
1091
|
useEffect6(() => {
|
|
1026
1092
|
persistRef.current = persist;
|
|
@@ -1029,12 +1095,26 @@ function CanvasProvider({
|
|
|
1029
1095
|
const hadUserChangeRef = useRef6(false);
|
|
1030
1096
|
useEffect6(() => {
|
|
1031
1097
|
const measure = () => {
|
|
1032
|
-
const
|
|
1098
|
+
const vw = Math.min(window.innerWidth, window.visualViewport?.width || Infinity);
|
|
1099
|
+
const w = Math.max(320, vw - 40);
|
|
1033
1100
|
boardRef.current = w;
|
|
1034
1101
|
boardHRef.current = Math.max(480, window.innerHeight);
|
|
1035
1102
|
setBoardWidth(w);
|
|
1103
|
+
const coarse = !!window.matchMedia?.("(pointer: coarse)").matches;
|
|
1104
|
+
const m = detectMode(vw, window.innerHeight, coarse);
|
|
1105
|
+
const modeChanged = m !== modeRef.current;
|
|
1106
|
+
modeRef.current = m;
|
|
1107
|
+
setMode(m);
|
|
1036
1108
|
setBlocks((prev) => {
|
|
1037
|
-
|
|
1109
|
+
let next;
|
|
1110
|
+
if (m === "phone") {
|
|
1111
|
+
next = tileDuo(capList(prev, maxFor("phone")), w, boardHRef.current);
|
|
1112
|
+
if (sameGeometry(prev, next)) return prev;
|
|
1113
|
+
} else if (modeChanged) {
|
|
1114
|
+
next = tileItems(capList(prev, maxFor("wide")), w, boardHRef.current);
|
|
1115
|
+
} else {
|
|
1116
|
+
next = fitToBoard(prev, w, boardHRef.current);
|
|
1117
|
+
}
|
|
1038
1118
|
if (next !== prev) fitAdjustRef.current = true;
|
|
1039
1119
|
return next;
|
|
1040
1120
|
});
|
|
@@ -1044,22 +1124,23 @@ function CanvasProvider({
|
|
|
1044
1124
|
return () => window.removeEventListener("resize", measure);
|
|
1045
1125
|
}, []);
|
|
1046
1126
|
const layoutDefault = useCallback(
|
|
1047
|
-
(list) => tileItems(list, boardRef.current, boardHRef.current),
|
|
1127
|
+
(list) => tileItems(list, boardRef.current, boardHRef.current, modeRef.current),
|
|
1048
1128
|
[]
|
|
1049
1129
|
);
|
|
1130
|
+
const customizedIn = (list) => modeRef.current !== "phone" && list.some((b) => b.placed);
|
|
1050
1131
|
const tiledOnce = useRef6(false);
|
|
1051
1132
|
useEffect6(() => {
|
|
1052
1133
|
if (tiledOnce.current) return;
|
|
1053
1134
|
tiledOnce.current = true;
|
|
1054
|
-
setBlocks((prev) => prev.length === 0 || prev
|
|
1135
|
+
setBlocks((prev) => prev.length === 0 || customizedIn(prev) ? prev : layoutDefault(prev));
|
|
1055
1136
|
}, [boardWidth]);
|
|
1056
1137
|
const addBlock = useCallback((key, context, width, append = false) => {
|
|
1057
1138
|
if (!resolve(key)) return;
|
|
1058
1139
|
const w = width ?? resolve(key)?.defaultWidth;
|
|
1059
1140
|
setBlocks((prev) => {
|
|
1060
1141
|
const existing = prev.find((b) => b.key === key);
|
|
1061
|
-
const customized = prev
|
|
1062
|
-
const without = prev.filter((b) => b.key !== key);
|
|
1142
|
+
const customized = customizedIn(prev);
|
|
1143
|
+
const without = capList(prev.filter((b) => b.key !== key), maxFor(modeRef.current) - 1);
|
|
1063
1144
|
if (customized) {
|
|
1064
1145
|
if (append) {
|
|
1065
1146
|
const startY = without.length ? Math.max(...without.map((b) => b.y + b.h)) + GAP : 0;
|
|
@@ -1099,13 +1180,15 @@ function CanvasProvider({
|
|
|
1099
1180
|
const removeBlock = useCallback((id) => {
|
|
1100
1181
|
setBlocks((prev) => {
|
|
1101
1182
|
const remaining = prev.filter((b) => b.id !== id);
|
|
1102
|
-
return remaining.length > 0 && !remaining
|
|
1183
|
+
return remaining.length > 0 && !customizedIn(remaining) ? layoutDefault(remaining) : remaining;
|
|
1103
1184
|
});
|
|
1104
1185
|
}, [layoutDefault]);
|
|
1105
1186
|
const resizeBlock = useCallback((id, width) => {
|
|
1187
|
+
if (modeRef.current === "phone") return;
|
|
1106
1188
|
setBlocks((prev) => prev.map((b) => b.id === id ? { ...b, width, w: pxWidth(WIDTH_SPAN[width], boardRef.current), placed: true, z: zRef.current++ } : b));
|
|
1107
1189
|
}, []);
|
|
1108
1190
|
const moveTo = useCallback((id, x, y) => {
|
|
1191
|
+
if (modeRef.current === "phone") return;
|
|
1109
1192
|
const boardW = boardRef.current;
|
|
1110
1193
|
setBlocks((prev) => prev.map((b) => {
|
|
1111
1194
|
if (b.id !== id || b.locked) return b;
|
|
@@ -1115,6 +1198,7 @@ function CanvasProvider({
|
|
|
1115
1198
|
}));
|
|
1116
1199
|
}, []);
|
|
1117
1200
|
const sizeTo = useCallback((id, w, h) => {
|
|
1201
|
+
if (modeRef.current === "phone") return;
|
|
1118
1202
|
const boardW = boardRef.current;
|
|
1119
1203
|
setBlocks((prev) => prev.map((b) => {
|
|
1120
1204
|
if (b.id !== id || b.locked) return b;
|
|
@@ -1124,6 +1208,7 @@ function CanvasProvider({
|
|
|
1124
1208
|
}));
|
|
1125
1209
|
}, []);
|
|
1126
1210
|
const setRect = useCallback((id, r) => {
|
|
1211
|
+
if (modeRef.current === "phone") return;
|
|
1127
1212
|
const boardW = boardRef.current;
|
|
1128
1213
|
setBlocks((prev) => prev.map((b) => {
|
|
1129
1214
|
if (b.id !== id || b.locked) return b;
|
|
@@ -1136,6 +1221,7 @@ function CanvasProvider({
|
|
|
1136
1221
|
}));
|
|
1137
1222
|
}, []);
|
|
1138
1223
|
const snapTo = useCallback((id, layout) => {
|
|
1224
|
+
if (modeRef.current === "phone") return;
|
|
1139
1225
|
const boardW = boardRef.current;
|
|
1140
1226
|
const H = tallH(boardHRef.current);
|
|
1141
1227
|
setSnapGhost(null);
|
|
@@ -1183,6 +1269,7 @@ function CanvasProvider({
|
|
|
1183
1269
|
}, [blocks]);
|
|
1184
1270
|
const clearSnapPreview = useCallback(() => setSnapGhost(null), []);
|
|
1185
1271
|
const resizeRow = useCallback((id, x, w, h, origin) => {
|
|
1272
|
+
if (modeRef.current === "phone") return;
|
|
1186
1273
|
const boardW = boardRef.current;
|
|
1187
1274
|
const M0 = origin.find((b) => b.id === id);
|
|
1188
1275
|
if (!M0 || M0.locked) return;
|
|
@@ -1232,9 +1319,14 @@ function CanvasProvider({
|
|
|
1232
1319
|
}));
|
|
1233
1320
|
}, []);
|
|
1234
1321
|
const bringToFront = useCallback((id) => {
|
|
1235
|
-
setBlocks((prev) =>
|
|
1236
|
-
|
|
1322
|
+
setBlocks((prev) => {
|
|
1323
|
+
if (prev.find((b) => b.id === id)?.z === zRef.current - 1) return prev;
|
|
1324
|
+
const next = prev.map((b) => b.id === id ? { ...b, z: zRef.current++ } : b);
|
|
1325
|
+
return modeRef.current === "phone" ? layoutDefault(next) : next;
|
|
1326
|
+
});
|
|
1327
|
+
}, [layoutDefault]);
|
|
1237
1328
|
const resolveCollisions = useCallback((id) => {
|
|
1329
|
+
if (modeRef.current === "phone") return;
|
|
1238
1330
|
setBlocks((prev) => {
|
|
1239
1331
|
const moved = prev.find((b) => b.id === id);
|
|
1240
1332
|
if (!moved) return prev;
|
|
@@ -1277,8 +1369,10 @@ function CanvasProvider({
|
|
|
1277
1369
|
if (!saved || saved.length === 0) return;
|
|
1278
1370
|
restoredRef.current = true;
|
|
1279
1371
|
setCanRestore(false);
|
|
1280
|
-
const
|
|
1281
|
-
const
|
|
1372
|
+
const kept = saved.filter((b) => b && typeof b.key === "string" && resolve(b.key)).slice(0, maxFor(modeRef.current));
|
|
1373
|
+
const built = kept.map(() => null);
|
|
1374
|
+
for (let i = kept.length - 1; i >= 0; i--) built[i] = blockFromSaved(kept[i], zRef.current++);
|
|
1375
|
+
const arranged = customizedIn(built) ? built : layoutDefault(built);
|
|
1282
1376
|
const fitted = fitToBoard(arranged, boardRef.current, boardHRef.current);
|
|
1283
1377
|
if (fitted !== arranged) fitAdjustRef.current = true;
|
|
1284
1378
|
setBlocks(fitted);
|
|
@@ -1301,7 +1395,7 @@ function CanvasProvider({
|
|
|
1301
1395
|
const width = d.width;
|
|
1302
1396
|
setBlocks((prev) => {
|
|
1303
1397
|
const list = prev.map((b) => b.key === d.key ? { ...b, width } : b);
|
|
1304
|
-
return prev
|
|
1398
|
+
return customizedIn(prev) ? list : layoutDefault(list);
|
|
1305
1399
|
});
|
|
1306
1400
|
}
|
|
1307
1401
|
function onPlace(e) {
|
|
@@ -1315,7 +1409,7 @@ function CanvasProvider({
|
|
|
1315
1409
|
const [moved] = next.splice(srcIdx, 1);
|
|
1316
1410
|
const insertAt = next.findIndex((b) => b.key === d.afterKey) + 1;
|
|
1317
1411
|
next.splice(insertAt, 0, moved);
|
|
1318
|
-
return prev
|
|
1412
|
+
return customizedIn(prev) ? next : layoutDefault(next);
|
|
1319
1413
|
});
|
|
1320
1414
|
}
|
|
1321
1415
|
function onSwap(e) {
|
|
@@ -1323,7 +1417,7 @@ function CanvasProvider({
|
|
|
1323
1417
|
const keys = d?.keys ?? [];
|
|
1324
1418
|
const contexts = d?.contexts ?? [];
|
|
1325
1419
|
const widths = d?.widths ?? [];
|
|
1326
|
-
const built = keys.filter((k) => resolve(k)).map((key, i) => ({
|
|
1420
|
+
const built = keys.filter((k) => resolve(k)).slice(0, maxFor(modeRef.current)).map((key, i) => ({
|
|
1327
1421
|
id: nextId(),
|
|
1328
1422
|
key,
|
|
1329
1423
|
context: contexts[i],
|
|
@@ -1380,12 +1474,13 @@ function CanvasProvider({
|
|
|
1380
1474
|
}, [blocks.length, canRestore]);
|
|
1381
1475
|
const firstRun = useRef6(true);
|
|
1382
1476
|
const saveTimer = useRef6(null);
|
|
1383
|
-
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("|");
|
|
1477
|
+
const layoutSignature = blocks.map((b) => mode === "phone" ? `${b.key}:${b.width ?? ""}` : `${b.key}:${b.width ?? ""}:${Math.round(b.x)}:${Math.round(b.y)}:${Math.round(b.w)}:${Math.round(b.h)}`).join("|");
|
|
1384
1478
|
const payload = useMemo2(() => layoutSignature ? {
|
|
1385
1479
|
savedDay: localDay(),
|
|
1386
1480
|
blocks: layoutSignature.split("|").map((seg) => {
|
|
1387
1481
|
const [key, width, x, y, w, h] = seg.split(":");
|
|
1388
|
-
|
|
1482
|
+
const base = { key, ...width ? { width } : {} };
|
|
1483
|
+
return x === void 0 ? base : { ...base, x: Number(x), y: Number(y), w: Number(w), h: Number(h) };
|
|
1389
1484
|
})
|
|
1390
1485
|
} : null, [layoutSignature]);
|
|
1391
1486
|
const payloadRef = useRef6(payload);
|
|
@@ -1467,8 +1562,10 @@ function CanvasProvider({
|
|
|
1467
1562
|
setInteracting,
|
|
1468
1563
|
activeId,
|
|
1469
1564
|
setActiveId,
|
|
1470
|
-
boardWidth
|
|
1471
|
-
|
|
1565
|
+
boardWidth,
|
|
1566
|
+
mode,
|
|
1567
|
+
maxBlocks: maxWindowsFor(mode, limits)
|
|
1568
|
+
}), [blocks, addBlock, removeBlock, resizeBlock, snapTo, snapPreview, previewSnap, clearSnapPreview, snapGhost, moveTo, sizeTo, setRect, resizeRow, bringToFront, resolveCollisions, toggleLock, clear, interacting, activeId, boardWidth, mode, limits]);
|
|
1472
1569
|
return /* @__PURE__ */ jsx11(CanvasContext.Provider, { value: api, children });
|
|
1473
1570
|
}
|
|
1474
1571
|
|
|
@@ -1696,11 +1793,14 @@ export {
|
|
|
1696
1793
|
CanvasProvider,
|
|
1697
1794
|
CanvasTransition,
|
|
1698
1795
|
DEFAULT_H,
|
|
1796
|
+
DUO_FOCUS_SHARE,
|
|
1699
1797
|
GAP,
|
|
1700
1798
|
GenerativeView,
|
|
1701
1799
|
HolographicShine,
|
|
1702
1800
|
LiveCard,
|
|
1703
1801
|
LiveStat,
|
|
1802
|
+
MAX_WINDOWS,
|
|
1803
|
+
MAX_WINDOWS_PHONE,
|
|
1704
1804
|
MIN_H,
|
|
1705
1805
|
MIN_W,
|
|
1706
1806
|
PageTransition,
|
|
@@ -1709,6 +1809,8 @@ export {
|
|
|
1709
1809
|
ShiftCard,
|
|
1710
1810
|
TiltCard,
|
|
1711
1811
|
WIDTH_SPAN,
|
|
1812
|
+
detectMode,
|
|
1813
|
+
maxWindowsFor,
|
|
1712
1814
|
tallH,
|
|
1713
1815
|
useCanvas,
|
|
1714
1816
|
useGenerate,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@allshift/ui",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"description": "Shift family shared FEEL layer — glass morphism, 3D tilt physics, holographic shine, scroll-triggered generation, and the living-canvas engine (LiveCard, LiveStat, CanvasTransition, CanvasLauncher). Brand-neutral by design.",
|
|
5
5
|
"license": "UNLICENSED",
|
|
6
6
|
"private": false,
|
|
@@ -27,7 +27,8 @@
|
|
|
27
27
|
"scripts": {
|
|
28
28
|
"build": "tsup",
|
|
29
29
|
"dev": "tsup --watch",
|
|
30
|
-
"type-check": "tsc --noEmit"
|
|
30
|
+
"type-check": "tsc --noEmit",
|
|
31
|
+
"test": "vitest run"
|
|
31
32
|
},
|
|
32
33
|
"peerDependencies": {
|
|
33
34
|
"framer-motion": ">=10",
|
|
@@ -36,8 +37,12 @@
|
|
|
36
37
|
"next": ">=14"
|
|
37
38
|
},
|
|
38
39
|
"peerDependenciesMeta": {
|
|
39
|
-
"lucide-react": {
|
|
40
|
-
|
|
40
|
+
"lucide-react": {
|
|
41
|
+
"optional": true
|
|
42
|
+
},
|
|
43
|
+
"next": {
|
|
44
|
+
"optional": true
|
|
45
|
+
}
|
|
41
46
|
},
|
|
42
47
|
"devDependencies": {
|
|
43
48
|
"@types/react": "^19.2.17",
|
|
@@ -46,7 +51,8 @@
|
|
|
46
51
|
"next": "^16.2.7",
|
|
47
52
|
"react": "^19.2.7",
|
|
48
53
|
"tsup": "^8.5.1",
|
|
49
|
-
"typescript": "^6.0.3"
|
|
54
|
+
"typescript": "^6.0.3",
|
|
55
|
+
"vitest": "^4.1.11"
|
|
50
56
|
},
|
|
51
57
|
"publishConfig": {
|
|
52
58
|
"access": "public"
|