@develemit/web-hooks 0.3.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +16 -0
- package/README.md +64 -0
- package/dist/{chunk-UJ366T3Y.js → chunk-R2PFNE3U.js} +12 -4
- package/dist/chunk-TE2FRIPF.js +616 -0
- package/dist/{chunk-JG3GPTMY.js → chunk-UVJBJETY.js} +26 -6
- package/dist/index.cjs +677 -17
- package/dist/index.d.cts +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +44 -2
- package/dist/push.cjs +12 -4
- package/dist/push.d.cts +2 -1
- package/dist/push.d.ts +2 -1
- package/dist/push.js +1 -1
- package/dist/service-worker.cjs +26 -6
- package/dist/service-worker.d.cts +2 -1
- package/dist/service-worker.d.ts +2 -1
- package/dist/service-worker.js +1 -1
- package/dist/terminal-output.cjs +660 -0
- package/dist/terminal-output.d.cts +186 -0
- package/dist/terminal-output.d.ts +186 -0
- package/dist/terminal-output.js +45 -0
- package/package.json +7 -1
package/dist/index.cjs
CHANGED
|
@@ -25,19 +25,29 @@ __export(src_exports, {
|
|
|
25
25
|
CLOSE_DISTANCE_PX: () => CLOSE_DISTANCE_PX,
|
|
26
26
|
COARSE_POINTER_QUERY: () => COARSE_POINTER_QUERY,
|
|
27
27
|
DEFAULT_IGNORE_SELECTOR: () => DEFAULT_IGNORE_SELECTOR,
|
|
28
|
+
DEFAULT_TAIL_THRESHOLD: () => DEFAULT_TAIL_THRESHOLD,
|
|
29
|
+
FAILURE_PATTERNS: () => FAILURE_PATTERNS,
|
|
28
30
|
FOCUSABLE_SELECTOR: () => FOCUSABLE_SELECTOR,
|
|
29
31
|
FORWARD_ZONE_PX: () => FORWARD_ZONE_PX,
|
|
30
32
|
MAX_SWIPE_DURATION_MS: () => MAX_SWIPE_DURATION_MS,
|
|
33
|
+
NOISE_PATTERNS: () => NOISE_PATTERNS,
|
|
31
34
|
OPEN_DISTANCE_PX: () => OPEN_DISTANCE_PX,
|
|
32
35
|
REDUCED_MOTION_QUERY: () => REDUCED_MOTION_QUERY,
|
|
33
36
|
VERTICAL_CANCEL_PX: () => VERTICAL_CANCEL_PX,
|
|
34
37
|
canShareFiles: () => canShareFiles,
|
|
35
38
|
computeKeyboardInset: () => computeKeyboardInset,
|
|
36
39
|
copyText: () => copyText,
|
|
40
|
+
createAnsiParser: () => createAnsiParser,
|
|
41
|
+
createAnsiStream: () => createAnsiStream,
|
|
37
42
|
createDebouncer: () => createDebouncer,
|
|
43
|
+
createLineBuffer: () => createLineBuffer,
|
|
38
44
|
deriveUpdateState: () => deriveUpdateState,
|
|
39
45
|
detectApple: () => detectApple,
|
|
40
46
|
detectPlatform: () => detectPlatform,
|
|
47
|
+
distanceFromEnd: () => distanceFromEnd,
|
|
48
|
+
filterLines: () => filterLines,
|
|
49
|
+
findFailures: () => findFailures,
|
|
50
|
+
firstFailureIndex: () => firstFailureIndex,
|
|
41
51
|
focusableWithin: () => focusableWithin,
|
|
42
52
|
getPushSupport: () => getPushSupport,
|
|
43
53
|
handleBeforeUnload: () => handleBeforeUnload,
|
|
@@ -48,23 +58,32 @@ __export(src_exports, {
|
|
|
48
58
|
isInsideHorizontalScroller: () => isInsideHorizontalScroller,
|
|
49
59
|
isIos: () => isIos,
|
|
50
60
|
isIosSafari: () => isIosSafari,
|
|
61
|
+
isNearEnd: () => isNearEnd,
|
|
62
|
+
isNoise: () => isNoise,
|
|
51
63
|
isOutside: () => isOutside,
|
|
52
64
|
isPrimaryPress: () => isPrimaryPress,
|
|
53
65
|
isShareCancel: () => isShareCancel,
|
|
54
66
|
isStandalone: () => isStandalone,
|
|
67
|
+
isUserScrollUp: () => isUserScrollUp,
|
|
55
68
|
lockScroll: () => lockScroll,
|
|
56
69
|
matchHotkey: () => matchHotkey,
|
|
57
70
|
nextDelay: () => nextDelay,
|
|
58
71
|
nextFocusTarget: () => nextFocusTarget,
|
|
59
72
|
nextHeight: () => nextHeight,
|
|
73
|
+
parseAnsi: () => parseAnsi,
|
|
60
74
|
parseHotkey: () => parseHotkey,
|
|
61
75
|
readText: () => readText,
|
|
62
76
|
resolveSwipeIntent: () => resolveSwipeIntent,
|
|
63
77
|
shareOrCopy: () => shareOrCopy,
|
|
64
78
|
shareTextFor: () => shareTextFor,
|
|
65
79
|
shouldResubscribe: () => shouldResubscribe,
|
|
80
|
+
shouldStick: () => shouldStick,
|
|
81
|
+
splitByMatch: () => splitByMatch,
|
|
66
82
|
startInstallPromptCapture: () => startInstallPromptCapture,
|
|
83
|
+
stripAnsi: () => stripAnsi,
|
|
84
|
+
tailMeaningful: () => tailMeaningful,
|
|
67
85
|
unlockScroll: () => unlockScroll,
|
|
86
|
+
unreadDelta: () => unreadDelta,
|
|
68
87
|
unregisterAll: () => unregisterAll,
|
|
69
88
|
urlBase64ToUint8Array: () => urlBase64ToUint8Array,
|
|
70
89
|
useAutoGrowTextarea: () => useAutoGrowTextarea,
|
|
@@ -75,6 +94,7 @@ __export(src_exports, {
|
|
|
75
94
|
useDrawerSwipe: () => useDrawerSwipe,
|
|
76
95
|
useEscape: () => useEscape,
|
|
77
96
|
useFocusTrap: () => useFocusTrap,
|
|
97
|
+
useFollowTail: () => useFollowTail,
|
|
78
98
|
useHotkey: () => useHotkey,
|
|
79
99
|
useInstallPrompt: () => useInstallPrompt,
|
|
80
100
|
useIsStandalone: () => useIsStandalone,
|
|
@@ -560,9 +580,9 @@ function useVisibilityPolling(fn, intervalMs, { enabled = true, immediate = true
|
|
|
560
580
|
function schedule() {
|
|
561
581
|
clearTimer();
|
|
562
582
|
if (!active || isHidden()) return;
|
|
563
|
-
timer = setTimeout(
|
|
583
|
+
timer = setTimeout(run2, nextDelay(intervalMs, optionsRef.current.jitter, Math.random));
|
|
564
584
|
}
|
|
565
|
-
async function
|
|
585
|
+
async function run2() {
|
|
566
586
|
timer = null;
|
|
567
587
|
if (!active || runningRef.current || isHidden()) return;
|
|
568
588
|
runningRef.current = true;
|
|
@@ -579,13 +599,13 @@ function useVisibilityPolling(fn, intervalMs, { enabled = true, immediate = true
|
|
|
579
599
|
clearTimer();
|
|
580
600
|
} else if (optionsRef.current.runOnVisible) {
|
|
581
601
|
clearTimer();
|
|
582
|
-
void
|
|
602
|
+
void run2();
|
|
583
603
|
} else if (!runningRef.current) {
|
|
584
604
|
schedule();
|
|
585
605
|
}
|
|
586
606
|
}
|
|
587
607
|
scheduleRef.current = schedule;
|
|
588
|
-
if (optionsRef.current.immediate && !isHidden()) void
|
|
608
|
+
if (optionsRef.current.immediate && !isHidden()) void run2();
|
|
589
609
|
else schedule();
|
|
590
610
|
document.addEventListener("visibilitychange", onVisibilityChange);
|
|
591
611
|
return () => {
|
|
@@ -606,7 +626,7 @@ function createDebouncer(getFn, getMs) {
|
|
|
606
626
|
timer = void 0;
|
|
607
627
|
pending = void 0;
|
|
608
628
|
}
|
|
609
|
-
function
|
|
629
|
+
function run2() {
|
|
610
630
|
const args = pending;
|
|
611
631
|
clear();
|
|
612
632
|
if (args) getFn()(...args);
|
|
@@ -615,11 +635,11 @@ function createDebouncer(getFn, getMs) {
|
|
|
615
635
|
call(...args) {
|
|
616
636
|
if (timer !== void 0) clearTimeout(timer);
|
|
617
637
|
pending = args;
|
|
618
|
-
timer = setTimeout(
|
|
638
|
+
timer = setTimeout(run2, getMs());
|
|
619
639
|
},
|
|
620
640
|
cancel: clear,
|
|
621
641
|
flush() {
|
|
622
|
-
if (pending)
|
|
642
|
+
if (pending) run2();
|
|
623
643
|
},
|
|
624
644
|
isPending: () => pending !== void 0
|
|
625
645
|
};
|
|
@@ -1089,17 +1109,24 @@ async function unregisterAll() {
|
|
|
1089
1109
|
} catch {
|
|
1090
1110
|
}
|
|
1091
1111
|
}
|
|
1092
|
-
function registerServiceWorker({ url, scope, onUpdateAvailable }, { reload = () => window.location.reload() } = {}) {
|
|
1112
|
+
function registerServiceWorker({ url, scope, onUpdateAvailable, onError }, { reload = () => window.location.reload() } = {}) {
|
|
1093
1113
|
let registration;
|
|
1094
1114
|
let disposed = false;
|
|
1095
1115
|
let applying = false;
|
|
1096
1116
|
const cleanups = [];
|
|
1097
1117
|
const controlledAtStart = hasController();
|
|
1098
|
-
|
|
1099
|
-
|
|
1118
|
+
let activatedUnasked = false;
|
|
1119
|
+
const reloadOnce = () => {
|
|
1120
|
+
if (reloaded) return;
|
|
1100
1121
|
reloaded = true;
|
|
1101
1122
|
reload();
|
|
1102
1123
|
};
|
|
1124
|
+
const onControllerChange = () => {
|
|
1125
|
+
if (applying) return reloadOnce();
|
|
1126
|
+
if (!controlledAtStart || activatedUnasked) return;
|
|
1127
|
+
activatedUnasked = true;
|
|
1128
|
+
onUpdateAvailable();
|
|
1129
|
+
};
|
|
1103
1130
|
navigator.serviceWorker.addEventListener("controllerchange", onControllerChange);
|
|
1104
1131
|
cleanups.push(() => navigator.serviceWorker.removeEventListener("controllerchange", onControllerChange));
|
|
1105
1132
|
const watchInstalling = (reg) => {
|
|
@@ -1122,10 +1149,16 @@ function registerServiceWorker({ url, scope, onUpdateAvailable }, { reload = ()
|
|
|
1122
1149
|
const onUpdateFound = () => watchInstalling(reg);
|
|
1123
1150
|
reg.addEventListener("updatefound", onUpdateFound);
|
|
1124
1151
|
cleanups.push(() => reg.removeEventListener("updatefound", onUpdateFound));
|
|
1125
|
-
}).catch(() => {
|
|
1152
|
+
}).catch((error) => {
|
|
1153
|
+
if (disposed) return;
|
|
1154
|
+
try {
|
|
1155
|
+
onError?.(error);
|
|
1156
|
+
} catch {
|
|
1157
|
+
}
|
|
1126
1158
|
});
|
|
1127
1159
|
return {
|
|
1128
1160
|
applyUpdate() {
|
|
1161
|
+
if (activatedUnasked) return reloadOnce();
|
|
1129
1162
|
const waiting = registration?.waiting;
|
|
1130
1163
|
if (!waiting) return;
|
|
1131
1164
|
applying = true;
|
|
@@ -1139,9 +1172,11 @@ function registerServiceWorker({ url, scope, onUpdateAvailable }, { reload = ()
|
|
|
1139
1172
|
}
|
|
1140
1173
|
|
|
1141
1174
|
// src/service-worker/use-service-worker.ts
|
|
1142
|
-
function useServiceWorker({ url, scope, enabled = true }) {
|
|
1175
|
+
function useServiceWorker({ url, scope, enabled = true, onError }) {
|
|
1143
1176
|
const [supported, setSupported] = (0, import_react18.useState)(true);
|
|
1144
1177
|
const [updateAvailable, setUpdateAvailable] = (0, import_react18.useState)(false);
|
|
1178
|
+
const onErrorRef = (0, import_react18.useRef)(onError);
|
|
1179
|
+
onErrorRef.current = onError;
|
|
1145
1180
|
const handle = (0, import_react18.useRef)(void 0);
|
|
1146
1181
|
(0, import_react18.useEffect)(() => {
|
|
1147
1182
|
if (!enabled) return;
|
|
@@ -1149,7 +1184,12 @@ function useServiceWorker({ url, scope, enabled = true }) {
|
|
|
1149
1184
|
setSupported(false);
|
|
1150
1185
|
return;
|
|
1151
1186
|
}
|
|
1152
|
-
const registered = registerServiceWorker({
|
|
1187
|
+
const registered = registerServiceWorker({
|
|
1188
|
+
url,
|
|
1189
|
+
scope,
|
|
1190
|
+
onUpdateAvailable: () => setUpdateAvailable(true),
|
|
1191
|
+
onError: (error) => onErrorRef.current?.(error)
|
|
1192
|
+
});
|
|
1153
1193
|
handle.current = registered;
|
|
1154
1194
|
return () => {
|
|
1155
1195
|
registered.dispose();
|
|
@@ -1324,8 +1364,11 @@ function readPermission() {
|
|
|
1324
1364
|
if (typeof window === "undefined" || !("Notification" in window)) return "unsupported";
|
|
1325
1365
|
return Notification.permission;
|
|
1326
1366
|
}
|
|
1327
|
-
function
|
|
1328
|
-
return
|
|
1367
|
+
function isKeyReady(key) {
|
|
1368
|
+
return key.trim().length > 0;
|
|
1369
|
+
}
|
|
1370
|
+
function shouldResubscribe({ permission, hasSubscription, resubscribeOnLoad, hasKey = true }) {
|
|
1371
|
+
return hasKey && resubscribeOnLoad && permission === "granted" && !hasSubscription;
|
|
1329
1372
|
}
|
|
1330
1373
|
function isGone(error) {
|
|
1331
1374
|
return typeof error === "object" && error !== null && error.status === 410;
|
|
@@ -1371,6 +1414,7 @@ function usePushSubscription(options) {
|
|
|
1371
1414
|
const [subscribed, setSubscribed] = (0, import_react21.useState)(false);
|
|
1372
1415
|
const [busy, setBusy] = (0, import_react21.useState)(false);
|
|
1373
1416
|
const [error, setError] = (0, import_react21.useState)(null);
|
|
1417
|
+
const hasKey = isKeyReady(options.vapidPublicKey);
|
|
1374
1418
|
const latest = (0, import_react21.useRef)(options);
|
|
1375
1419
|
latest.current = options;
|
|
1376
1420
|
(0, import_react21.useEffect)(() => {
|
|
@@ -1387,7 +1431,7 @@ function usePushSubscription(options) {
|
|
|
1387
1431
|
const permissionNow = readPermission();
|
|
1388
1432
|
setPermission(permissionNow);
|
|
1389
1433
|
if (existing) return setSubscribed(true);
|
|
1390
|
-
if (!shouldResubscribe({ permission: permissionNow, hasSubscription: false, resubscribeOnLoad })) return;
|
|
1434
|
+
if (!shouldResubscribe({ permission: permissionNow, hasSubscription: false, resubscribeOnLoad, hasKey })) return;
|
|
1391
1435
|
await ensureSubscribed(reg, latest.current);
|
|
1392
1436
|
if (!cancelled) setSubscribed(true);
|
|
1393
1437
|
} catch {
|
|
@@ -1396,9 +1440,13 @@ function usePushSubscription(options) {
|
|
|
1396
1440
|
return () => {
|
|
1397
1441
|
cancelled = true;
|
|
1398
1442
|
};
|
|
1399
|
-
}, [resubscribeOnLoad]);
|
|
1443
|
+
}, [resubscribeOnLoad, hasKey]);
|
|
1400
1444
|
const subscribe3 = (0, import_react21.useCallback)(async () => {
|
|
1401
1445
|
if (getPushSupport() !== "supported") return;
|
|
1446
|
+
if (!isKeyReady(latest.current.vapidPublicKey)) {
|
|
1447
|
+
setError("push key not ready");
|
|
1448
|
+
return;
|
|
1449
|
+
}
|
|
1402
1450
|
setError(null);
|
|
1403
1451
|
setBusy(true);
|
|
1404
1452
|
try {
|
|
@@ -1434,25 +1482,627 @@ function usePushSubscription(options) {
|
|
|
1434
1482
|
}, []);
|
|
1435
1483
|
return { support, permission, subscribed, busy, error, subscribe: subscribe3, unsubscribe };
|
|
1436
1484
|
}
|
|
1485
|
+
|
|
1486
|
+
// src/terminal-output/ansi-sgr.ts
|
|
1487
|
+
var FLAG_ON = {
|
|
1488
|
+
1: "bold",
|
|
1489
|
+
2: "dim",
|
|
1490
|
+
3: "italic",
|
|
1491
|
+
4: "underline",
|
|
1492
|
+
7: "inverse"
|
|
1493
|
+
};
|
|
1494
|
+
var FLAG_OFF = {
|
|
1495
|
+
22: ["bold", "dim"],
|
|
1496
|
+
23: ["italic"],
|
|
1497
|
+
24: ["underline"],
|
|
1498
|
+
27: ["inverse"]
|
|
1499
|
+
};
|
|
1500
|
+
function byte(n) {
|
|
1501
|
+
return Math.max(0, Math.min(255, n));
|
|
1502
|
+
}
|
|
1503
|
+
function extendedColor(args) {
|
|
1504
|
+
if (args[0] === 5 && args.length >= 2) return { kind: "ansi", index: byte(args[1] ?? 0) };
|
|
1505
|
+
if (args[0] === 2 && args.length >= 4) {
|
|
1506
|
+
const [r = 0, g = 0, b = 0] = args.slice(-3);
|
|
1507
|
+
return { kind: "rgb", r: byte(r), g: byte(g), b: byte(b) };
|
|
1508
|
+
}
|
|
1509
|
+
return null;
|
|
1510
|
+
}
|
|
1511
|
+
function setKey(style, key, value) {
|
|
1512
|
+
if (value === void 0 || value === false) delete style[key];
|
|
1513
|
+
else style[key] = value;
|
|
1514
|
+
}
|
|
1515
|
+
function applySgr(style, params) {
|
|
1516
|
+
const next = { ...style };
|
|
1517
|
+
const tokens = params === "" ? ["0"] : params.split(";");
|
|
1518
|
+
for (let i = 0; i < tokens.length; i++) {
|
|
1519
|
+
const parts = (tokens[i] ?? "").split(":").map((p) => p === "" ? 0 : Number(p));
|
|
1520
|
+
const code = parts[0] ?? 0;
|
|
1521
|
+
if (Number.isNaN(code)) continue;
|
|
1522
|
+
if (code === 38 || code === 48) {
|
|
1523
|
+
const key = code === 38 ? "fg" : "bg";
|
|
1524
|
+
let args = parts.slice(1);
|
|
1525
|
+
if (parts.length === 1) {
|
|
1526
|
+
args = tokens.slice(i + 1, i + 5).map(Number);
|
|
1527
|
+
const used = args[0] === 5 ? 2 : args[0] === 2 ? 4 : 0;
|
|
1528
|
+
i += used;
|
|
1529
|
+
args = args.slice(0, used);
|
|
1530
|
+
}
|
|
1531
|
+
const color = extendedColor(args);
|
|
1532
|
+
if (color) setKey(next, key, color);
|
|
1533
|
+
continue;
|
|
1534
|
+
}
|
|
1535
|
+
if (code === 0) {
|
|
1536
|
+
for (const k of Object.keys(next)) delete next[k];
|
|
1537
|
+
} else if (FLAG_ON[code]) setKey(next, FLAG_ON[code], true);
|
|
1538
|
+
else if (FLAG_OFF[code]) (FLAG_OFF[code] ?? []).forEach((k) => setKey(next, k, void 0));
|
|
1539
|
+
else if (code >= 30 && code <= 37) setKey(next, "fg", { kind: "ansi", index: code - 30 });
|
|
1540
|
+
else if (code === 39) setKey(next, "fg", void 0);
|
|
1541
|
+
else if (code >= 40 && code <= 47) setKey(next, "bg", { kind: "ansi", index: code - 40 });
|
|
1542
|
+
else if (code === 49) setKey(next, "bg", void 0);
|
|
1543
|
+
else if (code >= 90 && code <= 97) setKey(next, "fg", { kind: "ansi", index: code - 90 + 8 });
|
|
1544
|
+
else if (code >= 100 && code <= 107) setKey(next, "bg", { kind: "ansi", index: code - 100 + 8 });
|
|
1545
|
+
}
|
|
1546
|
+
return next;
|
|
1547
|
+
}
|
|
1548
|
+
function colorEquals(a, b) {
|
|
1549
|
+
if (a === b) return true;
|
|
1550
|
+
if (!a || !b || a.kind !== b.kind) return false;
|
|
1551
|
+
if (a.kind === "ansi") return a.index === b.index;
|
|
1552
|
+
const o = b;
|
|
1553
|
+
return a.r === o.r && a.g === o.g && a.b === o.b;
|
|
1554
|
+
}
|
|
1555
|
+
function styleEquals(a, b) {
|
|
1556
|
+
return a === b || colorEquals(a.fg, b.fg) && colorEquals(a.bg, b.bg) && !!a.bold === !!b.bold && !!a.dim === !!b.dim && !!a.italic === !!b.italic && !!a.underline === !!b.underline && !!a.inverse === !!b.inverse;
|
|
1557
|
+
}
|
|
1558
|
+
|
|
1559
|
+
// src/terminal-output/ansi-screen.ts
|
|
1560
|
+
var BLANK = {};
|
|
1561
|
+
var newRow = () => ({ cells: [], shared: false });
|
|
1562
|
+
var Screen = class _Screen {
|
|
1563
|
+
rows = [newRow()];
|
|
1564
|
+
row = 0;
|
|
1565
|
+
col = 0;
|
|
1566
|
+
line() {
|
|
1567
|
+
const row = this.rows[this.row] ??= newRow();
|
|
1568
|
+
if (row.shared) {
|
|
1569
|
+
row.cells = row.cells.slice();
|
|
1570
|
+
row.shared = false;
|
|
1571
|
+
}
|
|
1572
|
+
row.segments = void 0;
|
|
1573
|
+
return row.cells;
|
|
1574
|
+
}
|
|
1575
|
+
write(ch, style, href) {
|
|
1576
|
+
const line = this.line();
|
|
1577
|
+
while (line.length < this.col) line.push({ ch: " ", style: BLANK });
|
|
1578
|
+
line[this.col] = { ch, style, href };
|
|
1579
|
+
this.col++;
|
|
1580
|
+
}
|
|
1581
|
+
newline() {
|
|
1582
|
+
this.moveRow(this.row + 1);
|
|
1583
|
+
this.col = 0;
|
|
1584
|
+
}
|
|
1585
|
+
carriageReturn() {
|
|
1586
|
+
this.col = 0;
|
|
1587
|
+
}
|
|
1588
|
+
backspace() {
|
|
1589
|
+
this.col = Math.max(0, this.col - 1);
|
|
1590
|
+
}
|
|
1591
|
+
moveRow(row) {
|
|
1592
|
+
this.row = Math.max(0, row);
|
|
1593
|
+
while (this.rows.length <= this.row) this.rows.push(newRow());
|
|
1594
|
+
}
|
|
1595
|
+
moveRowBy(delta) {
|
|
1596
|
+
this.moveRow(this.row + delta);
|
|
1597
|
+
}
|
|
1598
|
+
setColumn(col) {
|
|
1599
|
+
this.col = Math.max(0, col);
|
|
1600
|
+
}
|
|
1601
|
+
/** `ESC[nK`: 0 cursor to end, 1 start to cursor, 2 whole line. */
|
|
1602
|
+
eraseLine(mode) {
|
|
1603
|
+
const line = this.line();
|
|
1604
|
+
if (mode === 2) this.rows[this.row] = newRow();
|
|
1605
|
+
else if (mode === 1) {
|
|
1606
|
+
for (let i = 0; i < Math.min(this.col + 1, line.length); i++) line[i] = { ch: " ", style: BLANK };
|
|
1607
|
+
} else if (this.col < line.length) line.length = this.col;
|
|
1608
|
+
}
|
|
1609
|
+
/** `ESC[0J`: erase from the cursor to the end of the screen. */
|
|
1610
|
+
eraseBelow() {
|
|
1611
|
+
this.eraseLine(0);
|
|
1612
|
+
this.rows.length = this.row + 1;
|
|
1613
|
+
}
|
|
1614
|
+
lines() {
|
|
1615
|
+
return this.rows.map((row) => row.segments ??= toSegments(row.cells));
|
|
1616
|
+
}
|
|
1617
|
+
get rowCount() {
|
|
1618
|
+
return this.rows.length;
|
|
1619
|
+
}
|
|
1620
|
+
get cursorRow() {
|
|
1621
|
+
return this.row;
|
|
1622
|
+
}
|
|
1623
|
+
/** Forgets the first `count` rows (never the cursor's row) and returns how many went. */
|
|
1624
|
+
dropTop(count2) {
|
|
1625
|
+
const n = Math.max(0, Math.min(count2, this.row));
|
|
1626
|
+
this.rows.splice(0, n);
|
|
1627
|
+
this.row -= n;
|
|
1628
|
+
return n;
|
|
1629
|
+
}
|
|
1630
|
+
/** An independent copy, so a partial line can be rendered without committing it. */
|
|
1631
|
+
clone() {
|
|
1632
|
+
const copy = new _Screen();
|
|
1633
|
+
for (const row of this.rows) row.shared = true;
|
|
1634
|
+
copy.rows = this.rows.map((r) => ({ ...r }));
|
|
1635
|
+
copy.row = this.row;
|
|
1636
|
+
copy.col = this.col;
|
|
1637
|
+
return copy;
|
|
1638
|
+
}
|
|
1639
|
+
};
|
|
1640
|
+
function toSegments(cells) {
|
|
1641
|
+
const out = [];
|
|
1642
|
+
let current = null;
|
|
1643
|
+
let currentStyle = null;
|
|
1644
|
+
for (const cell of cells) {
|
|
1645
|
+
if (current && currentStyle && current.href === cell.href && styleEquals(currentStyle, cell.style)) {
|
|
1646
|
+
current.text += cell.ch;
|
|
1647
|
+
continue;
|
|
1648
|
+
}
|
|
1649
|
+
current = { text: cell.ch, ...cell.style };
|
|
1650
|
+
if (cell.href) current.href = cell.href;
|
|
1651
|
+
currentStyle = cell.style;
|
|
1652
|
+
out.push(current);
|
|
1653
|
+
}
|
|
1654
|
+
return out;
|
|
1655
|
+
}
|
|
1656
|
+
|
|
1657
|
+
// src/terminal-output/ansi.ts
|
|
1658
|
+
var ESCAPE_SOURCE = "\\x1b(?:\\[([0-?]*)([ -/]*)([@-~])|\\]([^\\x07\\x1b]*)(?:\\x07|\\x1b\\\\)|[ -/]+[0-~]|[0-~])";
|
|
1659
|
+
var DEFAULT_SCHEMES = ["http", "https", "file"];
|
|
1660
|
+
function stripAnsi(text) {
|
|
1661
|
+
return text.replace(new RegExp(ESCAPE_SOURCE, "g"), "");
|
|
1662
|
+
}
|
|
1663
|
+
function allowedHref(uri, schemes) {
|
|
1664
|
+
const match = /^([a-z][a-z0-9+.-]*):/i.exec(uri);
|
|
1665
|
+
return match && schemes.includes((match[1] ?? "").toLowerCase()) ? uri : void 0;
|
|
1666
|
+
}
|
|
1667
|
+
function writeText(screen, state2, text) {
|
|
1668
|
+
for (const ch of text) {
|
|
1669
|
+
if (ch === "\n") screen.newline();
|
|
1670
|
+
else if (ch === "\r") screen.carriageReturn();
|
|
1671
|
+
else if (ch === "\b") screen.backspace();
|
|
1672
|
+
else if (ch === " " || ch >= " " && ch !== "\x7F") screen.write(ch, state2.style, state2.href);
|
|
1673
|
+
}
|
|
1674
|
+
}
|
|
1675
|
+
function applyCsi(screen, state2, params, inter, final) {
|
|
1676
|
+
if (inter !== "") return;
|
|
1677
|
+
if (final === "m") {
|
|
1678
|
+
if (/^[0-9;:]*$/.test(params)) state2.style = applySgr(state2.style, params);
|
|
1679
|
+
return;
|
|
1680
|
+
}
|
|
1681
|
+
if (!/^[0-9;]*$/.test(params)) return;
|
|
1682
|
+
const n = params === "" ? NaN : Number(params.split(";")[0]);
|
|
1683
|
+
const count2 = Number.isNaN(n) || n === 0 ? 1 : n;
|
|
1684
|
+
const mode = Number.isNaN(n) ? 0 : n;
|
|
1685
|
+
if (final === "A") screen.moveRowBy(-count2);
|
|
1686
|
+
else if (final === "B") screen.moveRowBy(count2);
|
|
1687
|
+
else if (final === "G") screen.setColumn(count2 - 1);
|
|
1688
|
+
else if (final === "K") screen.eraseLine(mode);
|
|
1689
|
+
else if (final === "J" && mode === 0) screen.eraseBelow();
|
|
1690
|
+
}
|
|
1691
|
+
function applyOsc(state2, payload, schemes) {
|
|
1692
|
+
if (!payload.startsWith("8;")) return;
|
|
1693
|
+
const split = payload.indexOf(";", 2);
|
|
1694
|
+
state2.href = split === -1 ? void 0 : allowedHref(payload.slice(split + 1), schemes);
|
|
1695
|
+
}
|
|
1696
|
+
function feedAnsi(screen, state2, text, options) {
|
|
1697
|
+
const schemes = options.linkSchemes ?? DEFAULT_SCHEMES;
|
|
1698
|
+
const re = new RegExp(ESCAPE_SOURCE, "g");
|
|
1699
|
+
let last = 0;
|
|
1700
|
+
for (let m = re.exec(text); m; m = re.exec(text)) {
|
|
1701
|
+
writeText(screen, state2, text.slice(last, m.index));
|
|
1702
|
+
last = m.index + m[0].length;
|
|
1703
|
+
if (m[3] !== void 0) applyCsi(screen, state2, m[1] ?? "", m[2] ?? "", m[3]);
|
|
1704
|
+
else if (m[4] !== void 0) applyOsc(state2, m[4], schemes);
|
|
1705
|
+
}
|
|
1706
|
+
writeText(screen, state2, text.slice(last));
|
|
1707
|
+
}
|
|
1708
|
+
function run(text, state2, options) {
|
|
1709
|
+
const screen = new Screen();
|
|
1710
|
+
feedAnsi(screen, state2, text, options);
|
|
1711
|
+
return screen.lines();
|
|
1712
|
+
}
|
|
1713
|
+
function parseAnsi(text, options = {}) {
|
|
1714
|
+
return run(text, { style: {} }, options);
|
|
1715
|
+
}
|
|
1716
|
+
function createAnsiParser(options = {}) {
|
|
1717
|
+
const state2 = { style: {} };
|
|
1718
|
+
return {
|
|
1719
|
+
parse: (chunk) => run(chunk, state2, options),
|
|
1720
|
+
reset: () => {
|
|
1721
|
+
state2.style = {};
|
|
1722
|
+
state2.href = void 0;
|
|
1723
|
+
}
|
|
1724
|
+
};
|
|
1725
|
+
}
|
|
1726
|
+
|
|
1727
|
+
// src/terminal-output/line-split.ts
|
|
1728
|
+
var INCOMPLETE_ESCAPE = /\x1b(?:\[[0-?]*[ -/]*|\][^\x07\x1b]*\x1b?|[ -/]*)$/;
|
|
1729
|
+
function splitIncompleteEscape(text) {
|
|
1730
|
+
const match = INCOMPLETE_ESCAPE.exec(text);
|
|
1731
|
+
if (!match) return { complete: text, held: "" };
|
|
1732
|
+
return { complete: text.slice(0, match.index), held: match[0] };
|
|
1733
|
+
}
|
|
1734
|
+
function splitLines(text) {
|
|
1735
|
+
const pieces = text.split("\n");
|
|
1736
|
+
const rest = pieces.pop() ?? "";
|
|
1737
|
+
const lines = pieces.map((line) => line.endsWith("\r") ? line.slice(0, -1) : line);
|
|
1738
|
+
return { lines, rest };
|
|
1739
|
+
}
|
|
1740
|
+
|
|
1741
|
+
// src/terminal-output/line-buffer.ts
|
|
1742
|
+
function createLineBuffer({ maxLines }) {
|
|
1743
|
+
let decoder = new TextDecoder("utf-8");
|
|
1744
|
+
let lines = [];
|
|
1745
|
+
let pending = "";
|
|
1746
|
+
let cursor;
|
|
1747
|
+
let dropped = 0;
|
|
1748
|
+
function keep(added) {
|
|
1749
|
+
lines.push(...added);
|
|
1750
|
+
const over = Math.max(0, lines.length - maxLines);
|
|
1751
|
+
if (over > 0) lines.splice(0, over);
|
|
1752
|
+
dropped += over;
|
|
1753
|
+
return over;
|
|
1754
|
+
}
|
|
1755
|
+
function update(added, over) {
|
|
1756
|
+
return { lines: added, partial: splitIncompleteEscape(pending).complete, dropped: over };
|
|
1757
|
+
}
|
|
1758
|
+
return {
|
|
1759
|
+
push(chunk, options) {
|
|
1760
|
+
const text = typeof chunk === "string" ? chunk : decoder.decode(chunk, { stream: true });
|
|
1761
|
+
if (options?.cursor !== void 0) cursor = options.cursor;
|
|
1762
|
+
const { lines: added, rest } = splitLines(pending + text);
|
|
1763
|
+
pending = rest;
|
|
1764
|
+
return update(added, keep(added));
|
|
1765
|
+
},
|
|
1766
|
+
flush() {
|
|
1767
|
+
const tail = pending + decoder.decode();
|
|
1768
|
+
pending = "";
|
|
1769
|
+
const added = tail === "" ? [] : [tail];
|
|
1770
|
+
return update(added, keep(added));
|
|
1771
|
+
},
|
|
1772
|
+
reset() {
|
|
1773
|
+
decoder = new TextDecoder("utf-8");
|
|
1774
|
+
lines = [];
|
|
1775
|
+
pending = "";
|
|
1776
|
+
cursor = void 0;
|
|
1777
|
+
dropped = 0;
|
|
1778
|
+
},
|
|
1779
|
+
get lines() {
|
|
1780
|
+
return lines;
|
|
1781
|
+
},
|
|
1782
|
+
get partial() {
|
|
1783
|
+
return splitIncompleteEscape(pending).complete;
|
|
1784
|
+
},
|
|
1785
|
+
get cursor() {
|
|
1786
|
+
return cursor;
|
|
1787
|
+
},
|
|
1788
|
+
get dropped() {
|
|
1789
|
+
return dropped;
|
|
1790
|
+
}
|
|
1791
|
+
};
|
|
1792
|
+
}
|
|
1793
|
+
|
|
1794
|
+
// src/terminal-output/ansi-stream.ts
|
|
1795
|
+
var DEFAULT_TAIL = 50;
|
|
1796
|
+
var CURSOR_UP = /\x1b\[(\d*)A/g;
|
|
1797
|
+
function largestCursorUp(text) {
|
|
1798
|
+
let largest = 0;
|
|
1799
|
+
for (const m of text.matchAll(CURSOR_UP)) largest = Math.max(largest, Number(m[1] || 1));
|
|
1800
|
+
return largest;
|
|
1801
|
+
}
|
|
1802
|
+
function sameLine(a, b) {
|
|
1803
|
+
return a !== void 0 && (a === b || JSON.stringify(a) === JSON.stringify(b));
|
|
1804
|
+
}
|
|
1805
|
+
function createAnsiStream(options = {}) {
|
|
1806
|
+
let tail = options.tailLines ?? DEFAULT_TAIL;
|
|
1807
|
+
let screen = new Screen();
|
|
1808
|
+
let state2 = { style: {} };
|
|
1809
|
+
let frozen = 0;
|
|
1810
|
+
let shown = [];
|
|
1811
|
+
let lastPartial = "";
|
|
1812
|
+
let finished = false;
|
|
1813
|
+
function render(partial) {
|
|
1814
|
+
tail = Math.max(tail, largestCursorUp(partial));
|
|
1815
|
+
screen.lines();
|
|
1816
|
+
const draft = screen.clone();
|
|
1817
|
+
feedAnsi(draft, { ...state2 }, partial, options);
|
|
1818
|
+
const rows = draft.lines();
|
|
1819
|
+
const first = rows.findIndex((row, i) => !sameLine(shown[i], row));
|
|
1820
|
+
const from = first === -1 ? rows.length < shown.length ? rows.length : -1 : first;
|
|
1821
|
+
shown = rows;
|
|
1822
|
+
const update = from === -1 ? {} : { replaced: { from: frozen + from, lines: rows.slice(from) } };
|
|
1823
|
+
const freeze = screen.cursorRow - tail;
|
|
1824
|
+
if (freeze > 0) {
|
|
1825
|
+
const dropped = screen.dropTop(freeze);
|
|
1826
|
+
shown = shown.slice(dropped);
|
|
1827
|
+
frozen += dropped;
|
|
1828
|
+
}
|
|
1829
|
+
return update;
|
|
1830
|
+
}
|
|
1831
|
+
return {
|
|
1832
|
+
push(lines, partial = "") {
|
|
1833
|
+
if (finished) return {};
|
|
1834
|
+
for (const line of lines) {
|
|
1835
|
+
tail = Math.max(tail, largestCursorUp(line));
|
|
1836
|
+
feedAnsi(screen, state2, `${line}
|
|
1837
|
+
`, options);
|
|
1838
|
+
}
|
|
1839
|
+
lastPartial = partial;
|
|
1840
|
+
return render(partial);
|
|
1841
|
+
},
|
|
1842
|
+
finish(partial = lastPartial) {
|
|
1843
|
+
if (finished) return {};
|
|
1844
|
+
const update = render(partial);
|
|
1845
|
+
finished = true;
|
|
1846
|
+
return update;
|
|
1847
|
+
},
|
|
1848
|
+
reset() {
|
|
1849
|
+
finished = false;
|
|
1850
|
+
lastPartial = "";
|
|
1851
|
+
screen = new Screen();
|
|
1852
|
+
state2 = { style: {} };
|
|
1853
|
+
frozen = 0;
|
|
1854
|
+
shown = [];
|
|
1855
|
+
tail = options.tailLines ?? DEFAULT_TAIL;
|
|
1856
|
+
}
|
|
1857
|
+
};
|
|
1858
|
+
}
|
|
1859
|
+
|
|
1860
|
+
// src/terminal-output/log-helpers.ts
|
|
1861
|
+
var escapeRegExp = (text) => text.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
1862
|
+
function queryMatcher(query) {
|
|
1863
|
+
const q = query.trim();
|
|
1864
|
+
return q ? new RegExp(escapeRegExp(q), "giu") : null;
|
|
1865
|
+
}
|
|
1866
|
+
function filterLines(lines, query) {
|
|
1867
|
+
const matcher = queryMatcher(query);
|
|
1868
|
+
const indexes = [];
|
|
1869
|
+
lines.forEach((line, i) => {
|
|
1870
|
+
if (!matcher) indexes.push(i);
|
|
1871
|
+
else {
|
|
1872
|
+
matcher.lastIndex = 0;
|
|
1873
|
+
if (matcher.test(line)) indexes.push(i);
|
|
1874
|
+
}
|
|
1875
|
+
});
|
|
1876
|
+
return indexes;
|
|
1877
|
+
}
|
|
1878
|
+
function splitByMatch(line, query) {
|
|
1879
|
+
const matcher = queryMatcher(query);
|
|
1880
|
+
if (!matcher) return [{ text: line, match: false }];
|
|
1881
|
+
const segments = [];
|
|
1882
|
+
let last = 0;
|
|
1883
|
+
for (const m of line.matchAll(matcher)) {
|
|
1884
|
+
const start = m.index ?? 0;
|
|
1885
|
+
if (start > last) segments.push({ text: line.slice(last, start), match: false });
|
|
1886
|
+
segments.push({ text: m[0], match: true });
|
|
1887
|
+
last = start + m[0].length;
|
|
1888
|
+
}
|
|
1889
|
+
if (last < line.length || segments.length === 0) {
|
|
1890
|
+
segments.push({ text: line.slice(last), match: false });
|
|
1891
|
+
}
|
|
1892
|
+
return segments;
|
|
1893
|
+
}
|
|
1894
|
+
var FAILURE_PATTERNS = [
|
|
1895
|
+
{ tool: "tsc", kind: "error", pattern: /\berror TS\d+/ },
|
|
1896
|
+
{ tool: "pnpm", kind: "error", pattern: /\bERR_PNPM_[A-Z_]+/ },
|
|
1897
|
+
{ tool: "npm", kind: "error", pattern: /\bnpm (?:ERR!|error\b)/ },
|
|
1898
|
+
{ tool: "nx", kind: "failed tasks", pattern: /targets failed, including|Failed tasks/ },
|
|
1899
|
+
{ tool: "nx", kind: "task failed", pattern: /^\s*✖\s+nx run / },
|
|
1900
|
+
{ tool: "nx", kind: "failed", pattern: /^\s*✖\s+\d+\/\d+ failed/ },
|
|
1901
|
+
{ tool: "vitest", kind: "fail", pattern: /^\s*FAIL\s/ },
|
|
1902
|
+
{ tool: "vitest", kind: "test", pattern: /^\s*[×✗]\s/ },
|
|
1903
|
+
{ tool: "docker", kind: "error", pattern: /^\s*(?:Error response from daemon|ERROR: failed to solve|failed to solve|pull access denied)/ },
|
|
1904
|
+
{ tool: "docker", kind: "exit", pattern: /^\s*Container \S+\s+(?:Error|exited with code [1-9]\d*)/ },
|
|
1905
|
+
{ tool: "generic", kind: "error", pattern: /\b\w*Error\b(?: \[\w+\])?:/ }
|
|
1906
|
+
];
|
|
1907
|
+
var STACK_FRAME = /^\s*at\s/;
|
|
1908
|
+
function findFailures(lines, patterns = FAILURE_PATTERNS) {
|
|
1909
|
+
const failures = [];
|
|
1910
|
+
lines.forEach((line, index) => {
|
|
1911
|
+
if (STACK_FRAME.test(line)) return;
|
|
1912
|
+
const hit = patterns.find((p) => p.pattern.test(line));
|
|
1913
|
+
if (hit) failures.push({ index, tool: hit.tool, kind: hit.kind });
|
|
1914
|
+
});
|
|
1915
|
+
return failures;
|
|
1916
|
+
}
|
|
1917
|
+
function firstFailureIndex(lines, patterns = FAILURE_PATTERNS) {
|
|
1918
|
+
return findFailures(lines, patterns)[0]?.index ?? -1;
|
|
1919
|
+
}
|
|
1920
|
+
var NOISE_PATTERNS = [
|
|
1921
|
+
/^\s*[0-9a-f]{12}: (?:Pulling fs layer|Waiting|Downloading|Download complete|Verifying Checksum|Extracting|Pull complete|Already exists)/,
|
|
1922
|
+
/^\s*[0-9a-f]{12} (?:Pulling fs layer|Waiting|Downloading|Download complete|Verifying|Extracting|Pull complete|Already exists)/,
|
|
1923
|
+
/^\s*Image \S+\s+(?:Pulling|Pulled)\s*$/,
|
|
1924
|
+
/^\s*Container \S+\s+(?:Creating|Created|Starting|Started|Running|Waiting|Healthy|Stopping|Stopped|Removing|Removed)\s*$/,
|
|
1925
|
+
/^\s*Nx read the output from the cache/
|
|
1926
|
+
];
|
|
1927
|
+
function isNoise(line, patterns = NOISE_PATTERNS) {
|
|
1928
|
+
return patterns.some((p) => p.test(line));
|
|
1929
|
+
}
|
|
1930
|
+
function tailMeaningful(lines, n, patterns = NOISE_PATTERNS) {
|
|
1931
|
+
const out = [];
|
|
1932
|
+
for (let i = lines.length - 1; i >= 0 && out.length < n; i--) {
|
|
1933
|
+
const line = (lines[i] ?? "").trimEnd();
|
|
1934
|
+
if (line.trim() && !isNoise(line, patterns)) out.unshift(line);
|
|
1935
|
+
}
|
|
1936
|
+
return out;
|
|
1937
|
+
}
|
|
1938
|
+
|
|
1939
|
+
// src/terminal-output/follow-tail.ts
|
|
1940
|
+
var DEFAULT_TAIL_THRESHOLD = 24;
|
|
1941
|
+
function distanceFromEnd({ scrollTop, scrollHeight, clientHeight }) {
|
|
1942
|
+
return scrollHeight - scrollTop - clientHeight;
|
|
1943
|
+
}
|
|
1944
|
+
function isNearEnd(metrics, threshold = DEFAULT_TAIL_THRESHOLD) {
|
|
1945
|
+
return distanceFromEnd(metrics) <= threshold;
|
|
1946
|
+
}
|
|
1947
|
+
function shouldStick(prevAtBottom, metrics, userInitiated, threshold = DEFAULT_TAIL_THRESHOLD) {
|
|
1948
|
+
if (!userInitiated) return prevAtBottom;
|
|
1949
|
+
return isNearEnd(metrics, threshold);
|
|
1950
|
+
}
|
|
1951
|
+
function isUserScrollUp({ kind, delta = 0, lastTarget, scrollTop }, threshold = DEFAULT_TAIL_THRESHOLD) {
|
|
1952
|
+
if (kind === "wheel") return delta < 0;
|
|
1953
|
+
if (kind === "touch") return delta > 0;
|
|
1954
|
+
if (kind === "gutter") return true;
|
|
1955
|
+
return lastTarget !== null && scrollTop < lastTarget - threshold;
|
|
1956
|
+
}
|
|
1957
|
+
function unreadDelta(prevCount, nextCount, attached2) {
|
|
1958
|
+
if (attached2) return 0;
|
|
1959
|
+
return Math.max(0, nextCount - prevCount);
|
|
1960
|
+
}
|
|
1961
|
+
|
|
1962
|
+
// src/terminal-output/use-follow-tail.ts
|
|
1963
|
+
var import_react22 = require("react");
|
|
1964
|
+
var useIsomorphicLayoutEffect2 = typeof window === "undefined" ? import_react22.useEffect : import_react22.useLayoutEffect;
|
|
1965
|
+
function useFollowTail({
|
|
1966
|
+
scrollRef,
|
|
1967
|
+
itemCount,
|
|
1968
|
+
scrollToEnd,
|
|
1969
|
+
resetKey,
|
|
1970
|
+
threshold = DEFAULT_TAIL_THRESHOLD
|
|
1971
|
+
}) {
|
|
1972
|
+
const [atBottom, setAtBottom] = (0, import_react22.useState)(true);
|
|
1973
|
+
const [unread, setUnread] = (0, import_react22.useState)(0);
|
|
1974
|
+
const attached2 = (0, import_react22.useRef)(true);
|
|
1975
|
+
const programmatic = (0, import_react22.useRef)(false);
|
|
1976
|
+
const lastTarget = (0, import_react22.useRef)(null);
|
|
1977
|
+
const lastTop = (0, import_react22.useRef)(0);
|
|
1978
|
+
const frame = (0, import_react22.useRef)(0);
|
|
1979
|
+
const prevCount = (0, import_react22.useRef)(itemCount);
|
|
1980
|
+
const prevReset = (0, import_react22.useRef)(resetKey);
|
|
1981
|
+
const latest = (0, import_react22.useRef)({ scrollToEnd, threshold });
|
|
1982
|
+
latest.current = { scrollToEnd, threshold };
|
|
1983
|
+
const attach = (0, import_react22.useCallback)((value) => {
|
|
1984
|
+
attached2.current = value;
|
|
1985
|
+
setAtBottom(value);
|
|
1986
|
+
if (value) setUnread(0);
|
|
1987
|
+
}, []);
|
|
1988
|
+
const followEnd = (0, import_react22.useCallback)(() => {
|
|
1989
|
+
const el = scrollRef.current;
|
|
1990
|
+
if (!el) return;
|
|
1991
|
+
programmatic.current = true;
|
|
1992
|
+
if (latest.current.scrollToEnd) latest.current.scrollToEnd();
|
|
1993
|
+
else el.scrollTop = el.scrollHeight;
|
|
1994
|
+
lastTarget.current = el.scrollHeight - el.clientHeight;
|
|
1995
|
+
if (typeof requestAnimationFrame === "function") {
|
|
1996
|
+
cancelAnimationFrame(frame.current);
|
|
1997
|
+
frame.current = requestAnimationFrame(() => {
|
|
1998
|
+
programmatic.current = false;
|
|
1999
|
+
});
|
|
2000
|
+
} else {
|
|
2001
|
+
programmatic.current = false;
|
|
2002
|
+
}
|
|
2003
|
+
}, [scrollRef]);
|
|
2004
|
+
useIsomorphicLayoutEffect2(() => {
|
|
2005
|
+
const count2 = prevCount.current;
|
|
2006
|
+
prevCount.current = itemCount;
|
|
2007
|
+
if (prevReset.current !== resetKey) {
|
|
2008
|
+
prevReset.current = resetKey;
|
|
2009
|
+
attach(true);
|
|
2010
|
+
followEnd();
|
|
2011
|
+
return;
|
|
2012
|
+
}
|
|
2013
|
+
if (attached2.current) {
|
|
2014
|
+
if (itemCount > 0) followEnd();
|
|
2015
|
+
return;
|
|
2016
|
+
}
|
|
2017
|
+
const added = unreadDelta(count2, itemCount, false);
|
|
2018
|
+
if (added > 0) setUnread((n) => n + added);
|
|
2019
|
+
}, [itemCount, resetKey, attach, followEnd]);
|
|
2020
|
+
(0, import_react22.useEffect)(() => {
|
|
2021
|
+
const el = scrollRef.current;
|
|
2022
|
+
if (!el) return;
|
|
2023
|
+
const takeOver = () => {
|
|
2024
|
+
programmatic.current = false;
|
|
2025
|
+
lastTarget.current = null;
|
|
2026
|
+
if (attached2.current) attach(false);
|
|
2027
|
+
};
|
|
2028
|
+
const onScroll = () => {
|
|
2029
|
+
const { threshold: limit } = latest.current;
|
|
2030
|
+
const movedUp = el.scrollTop < lastTop.current;
|
|
2031
|
+
lastTop.current = el.scrollTop;
|
|
2032
|
+
const info = { kind: "scroll", lastTarget: movedUp ? lastTarget.current : null, scrollTop: el.scrollTop };
|
|
2033
|
+
const user = isUserScrollUp(info, limit);
|
|
2034
|
+
if (user) takeOver();
|
|
2035
|
+
const next = shouldStick(attached2.current, el, user || !programmatic.current, limit);
|
|
2036
|
+
if (next !== attached2.current) attach(next);
|
|
2037
|
+
};
|
|
2038
|
+
const canScrollUp = () => el.scrollHeight > el.clientHeight && el.scrollTop > 0;
|
|
2039
|
+
const onWheel = (e) => {
|
|
2040
|
+
if (canScrollUp() && isUserScrollUp({ kind: "wheel", delta: e.deltaY, lastTarget: null, scrollTop: el.scrollTop })) takeOver();
|
|
2041
|
+
};
|
|
2042
|
+
let touchY = 0;
|
|
2043
|
+
const onTouchStart = (e) => {
|
|
2044
|
+
touchY = e.touches[0]?.clientY ?? 0;
|
|
2045
|
+
};
|
|
2046
|
+
const onTouchMove = (e) => {
|
|
2047
|
+
const delta = (e.touches[0]?.clientY ?? touchY) - touchY;
|
|
2048
|
+
if (canScrollUp() && isUserScrollUp({ kind: "touch", delta, lastTarget: null, scrollTop: el.scrollTop })) takeOver();
|
|
2049
|
+
};
|
|
2050
|
+
const onPointerDown = (e) => {
|
|
2051
|
+
const gutter = el.offsetWidth - el.clientWidth;
|
|
2052
|
+
if (e.target !== el || gutter <= 0) return;
|
|
2053
|
+
if (e.clientX >= el.getBoundingClientRect().left + el.clientWidth) {
|
|
2054
|
+
if (isUserScrollUp({ kind: "gutter", lastTarget: null, scrollTop: el.scrollTop })) takeOver();
|
|
2055
|
+
}
|
|
2056
|
+
};
|
|
2057
|
+
el.addEventListener("scroll", onScroll, { passive: true });
|
|
2058
|
+
el.addEventListener("wheel", onWheel, { passive: true });
|
|
2059
|
+
el.addEventListener("touchstart", onTouchStart, { passive: true });
|
|
2060
|
+
el.addEventListener("touchmove", onTouchMove, { passive: true });
|
|
2061
|
+
el.addEventListener("pointerdown", onPointerDown, { passive: true });
|
|
2062
|
+
return () => {
|
|
2063
|
+
el.removeEventListener("scroll", onScroll);
|
|
2064
|
+
el.removeEventListener("wheel", onWheel);
|
|
2065
|
+
el.removeEventListener("touchstart", onTouchStart);
|
|
2066
|
+
el.removeEventListener("touchmove", onTouchMove);
|
|
2067
|
+
el.removeEventListener("pointerdown", onPointerDown);
|
|
2068
|
+
if (typeof cancelAnimationFrame === "function") cancelAnimationFrame(frame.current);
|
|
2069
|
+
};
|
|
2070
|
+
}, [scrollRef, attach]);
|
|
2071
|
+
const jumpToBottom = (0, import_react22.useCallback)(() => {
|
|
2072
|
+
attach(true);
|
|
2073
|
+
followEnd();
|
|
2074
|
+
}, [attach, followEnd]);
|
|
2075
|
+
return { atBottom, unread, jumpToBottom };
|
|
2076
|
+
}
|
|
1437
2077
|
// Annotate the CommonJS export names for ESM import in node:
|
|
1438
2078
|
0 && (module.exports = {
|
|
1439
2079
|
BACK_ZONE_PX,
|
|
1440
2080
|
CLOSE_DISTANCE_PX,
|
|
1441
2081
|
COARSE_POINTER_QUERY,
|
|
1442
2082
|
DEFAULT_IGNORE_SELECTOR,
|
|
2083
|
+
DEFAULT_TAIL_THRESHOLD,
|
|
2084
|
+
FAILURE_PATTERNS,
|
|
1443
2085
|
FOCUSABLE_SELECTOR,
|
|
1444
2086
|
FORWARD_ZONE_PX,
|
|
1445
2087
|
MAX_SWIPE_DURATION_MS,
|
|
2088
|
+
NOISE_PATTERNS,
|
|
1446
2089
|
OPEN_DISTANCE_PX,
|
|
1447
2090
|
REDUCED_MOTION_QUERY,
|
|
1448
2091
|
VERTICAL_CANCEL_PX,
|
|
1449
2092
|
canShareFiles,
|
|
1450
2093
|
computeKeyboardInset,
|
|
1451
2094
|
copyText,
|
|
2095
|
+
createAnsiParser,
|
|
2096
|
+
createAnsiStream,
|
|
1452
2097
|
createDebouncer,
|
|
2098
|
+
createLineBuffer,
|
|
1453
2099
|
deriveUpdateState,
|
|
1454
2100
|
detectApple,
|
|
1455
2101
|
detectPlatform,
|
|
2102
|
+
distanceFromEnd,
|
|
2103
|
+
filterLines,
|
|
2104
|
+
findFailures,
|
|
2105
|
+
firstFailureIndex,
|
|
1456
2106
|
focusableWithin,
|
|
1457
2107
|
getPushSupport,
|
|
1458
2108
|
handleBeforeUnload,
|
|
@@ -1463,23 +2113,32 @@ function usePushSubscription(options) {
|
|
|
1463
2113
|
isInsideHorizontalScroller,
|
|
1464
2114
|
isIos,
|
|
1465
2115
|
isIosSafari,
|
|
2116
|
+
isNearEnd,
|
|
2117
|
+
isNoise,
|
|
1466
2118
|
isOutside,
|
|
1467
2119
|
isPrimaryPress,
|
|
1468
2120
|
isShareCancel,
|
|
1469
2121
|
isStandalone,
|
|
2122
|
+
isUserScrollUp,
|
|
1470
2123
|
lockScroll,
|
|
1471
2124
|
matchHotkey,
|
|
1472
2125
|
nextDelay,
|
|
1473
2126
|
nextFocusTarget,
|
|
1474
2127
|
nextHeight,
|
|
2128
|
+
parseAnsi,
|
|
1475
2129
|
parseHotkey,
|
|
1476
2130
|
readText,
|
|
1477
2131
|
resolveSwipeIntent,
|
|
1478
2132
|
shareOrCopy,
|
|
1479
2133
|
shareTextFor,
|
|
1480
2134
|
shouldResubscribe,
|
|
2135
|
+
shouldStick,
|
|
2136
|
+
splitByMatch,
|
|
1481
2137
|
startInstallPromptCapture,
|
|
2138
|
+
stripAnsi,
|
|
2139
|
+
tailMeaningful,
|
|
1482
2140
|
unlockScroll,
|
|
2141
|
+
unreadDelta,
|
|
1483
2142
|
unregisterAll,
|
|
1484
2143
|
urlBase64ToUint8Array,
|
|
1485
2144
|
useAutoGrowTextarea,
|
|
@@ -1490,6 +2149,7 @@ function usePushSubscription(options) {
|
|
|
1490
2149
|
useDrawerSwipe,
|
|
1491
2150
|
useEscape,
|
|
1492
2151
|
useFocusTrap,
|
|
2152
|
+
useFollowTail,
|
|
1493
2153
|
useHotkey,
|
|
1494
2154
|
useInstallPrompt,
|
|
1495
2155
|
useIsStandalone,
|