lynx-console 0.5.0 → 0.6.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/README.md CHANGED
@@ -182,6 +182,7 @@ You can also integrate it with a back press handler so that the console closes w
182
182
  | `theme` | `"light" \| "dark"` | `"light"` | Console UI theme |
183
183
  | `safeAreaInsetBottom` | `string` | `"50px"` | Bottom safe area inset |
184
184
  | `customTabs` | `CustomTab[]` | `undefined` | Additional custom tabs to display in the console |
185
+ | `initialPosition` | `{ top?: number; left?: number; right?: number; bottom?: number }` | `{ right: 16, bottom: 84 }` | Initial position (px) of the floating button. Each side is independent, so you can anchor it to any corner (e.g. `{ top: 50, left: 16 }`). When both `top` and `bottom` (or both `left` and `right`) are set, `top`/`left` win. Once the user drags the button, the saved position takes precedence. |
185
186
 
186
187
  ### `CustomTab`
187
188
 
package/dist/index.cjs CHANGED
@@ -222,6 +222,35 @@ const useConsole = () => {
222
222
  };
223
223
  };
224
224
 
225
+ //#endregion
226
+ //#region src/hooks/useLatestFcp.ts
227
+ const pickFcp = (entry) => {
228
+ if (entry.entryType !== "metric" || entry.name !== "fcp") return void 0;
229
+ const raw = entry.rawEntry;
230
+ if (raw?.totalFcp?.duration !== void 0) return raw.totalFcp;
231
+ if (raw?.lynxFcp?.duration !== void 0) return raw.lynxFcp;
232
+ };
233
+ const useLatestFcp = () => {
234
+ const [fcp, setFcp] = (0, _lynx_js_react.useState)(() => {
235
+ const performances = globalThis.__LYNX_CONSOLE__?.state?.performances ?? [];
236
+ for (let i = performances.length - 1; i >= 0; i--) {
237
+ const entry = performances[i];
238
+ if (!entry) continue;
239
+ const found = pickFcp(entry);
240
+ if (found) return found;
241
+ }
242
+ });
243
+ (0, _lynx_js_react.useEffect)(() => {
244
+ const state = globalThis.__LYNX_CONSOLE__?.state;
245
+ if (!state?.subscribePerformance) return;
246
+ return state.subscribePerformance((entry) => {
247
+ const found = pickFcp(entry);
248
+ if (found) setFcp(found);
249
+ });
250
+ }, []);
251
+ return fcp;
252
+ };
253
+
225
254
  //#endregion
226
255
  //#region src/hooks/useNetwork.ts
227
256
  const useNetwork = () => {
@@ -484,7 +513,8 @@ const LogPanel = ({ logs, clearLogs }) => {
484
513
  params: {
485
514
  position: logsRef.current.length - 1,
486
515
  smooth
487
- }
516
+ },
517
+ fail: () => {}
488
518
  }).exec();
489
519
  };
490
520
  (0, _lynx_js_react.useEffect)(() => {
@@ -1368,22 +1398,42 @@ const LONG_PRESS_DURATION = 400;
1368
1398
  const MOVE_THRESHOLD = 5;
1369
1399
  const DEFAULT_RIGHT = 16;
1370
1400
  const DEFAULT_BOTTOM = 84;
1371
- let savedRight = DEFAULT_RIGHT;
1372
- let savedBottom = DEFAULT_BOTTOM;
1373
- function useLongPressDrag(onTap) {
1374
- const [right, setRight] = (0, _lynx_js_react.useState)(savedRight);
1375
- const [bottom, setBottom] = (0, _lynx_js_react.useState)(savedBottom);
1401
+ function resolveAnchors(initial) {
1402
+ const vertical = initial?.top !== void 0 ? "top" : "bottom";
1403
+ const horizontal = initial?.left !== void 0 ? "left" : "right";
1404
+ const y = vertical === "top" ? initial?.top ?? 0 : initial?.bottom ?? DEFAULT_BOTTOM;
1405
+ return {
1406
+ vertical,
1407
+ horizontal,
1408
+ x: horizontal === "left" ? initial?.left ?? 0 : initial?.right ?? DEFAULT_RIGHT,
1409
+ y
1410
+ };
1411
+ }
1412
+ let saved = null;
1413
+ function useLongPressDrag(onTap, options) {
1414
+ const anchors = resolveAnchors(options?.initialPosition);
1415
+ const snapshot = saved;
1416
+ let initX = anchors.x;
1417
+ let initY = anchors.y;
1418
+ if (snapshot !== null && snapshot.vertical === anchors.vertical && snapshot.horizontal === anchors.horizontal) {
1419
+ initX = snapshot.x;
1420
+ initY = snapshot.y;
1421
+ }
1422
+ const [x, setX] = (0, _lynx_js_react.useState)(initX);
1423
+ const [y, setY] = (0, _lynx_js_react.useState)(initY);
1376
1424
  const [phase, setPhase] = (0, _lynx_js_react.useState)("idle");
1377
- const [tempRight, setTempRight] = (0, _lynx_js_react.useState)(savedRight);
1378
- const [tempBottom, setTempBottom] = (0, _lynx_js_react.useState)(savedBottom);
1425
+ const [tempX, setTempX] = (0, _lynx_js_react.useState)(initX);
1426
+ const [tempY, setTempY] = (0, _lynx_js_react.useState)(initY);
1379
1427
  const timerRef = (0, _lynx_js_react.useRef)(null);
1380
1428
  const draggingRef = (0, _lynx_js_react.useRef)(false);
1381
1429
  const startRef = (0, _lynx_js_react.useRef)({
1382
1430
  x: 0,
1383
1431
  y: 0,
1384
- r: 0,
1385
- b: 0
1432
+ ax: 0,
1433
+ ay: 0
1386
1434
  });
1435
+ const xSign = anchors.horizontal === "right" ? -1 : 1;
1436
+ const ySign = anchors.vertical === "bottom" ? -1 : 1;
1387
1437
  const clearTimer = () => {
1388
1438
  if (timerRef.current) {
1389
1439
  clearTimeout(timerRef.current);
@@ -1394,15 +1444,15 @@ function useLongPressDrag(onTap) {
1394
1444
  startRef.current = {
1395
1445
  x: e.detail.x,
1396
1446
  y: e.detail.y,
1397
- r: right,
1398
- b: bottom
1447
+ ax: x,
1448
+ ay: y
1399
1449
  };
1400
1450
  draggingRef.current = false;
1401
1451
  timerRef.current = setTimeout(() => {
1402
1452
  draggingRef.current = true;
1403
1453
  setPhase("dragging");
1404
- setTempRight(right);
1405
- setTempBottom(bottom);
1454
+ setTempX(x);
1455
+ setTempY(y);
1406
1456
  }, LONG_PRESS_DURATION);
1407
1457
  };
1408
1458
  const handleTouchMove = (e) => {
@@ -1410,26 +1460,34 @@ function useLongPressDrag(onTap) {
1410
1460
  const dy = e.detail.y - startRef.current.y;
1411
1461
  if (!draggingRef.current && (Math.abs(dx) > MOVE_THRESHOLD || Math.abs(dy) > MOVE_THRESHOLD)) clearTimer();
1412
1462
  if (!draggingRef.current) return;
1413
- setTempRight(startRef.current.r - dx);
1414
- setTempBottom(startRef.current.b - dy);
1463
+ setTempX(startRef.current.ax + xSign * dx);
1464
+ setTempY(startRef.current.ay + ySign * dy);
1415
1465
  };
1416
1466
  const handleTouchEnd = () => {
1417
1467
  clearTimer();
1418
1468
  if (draggingRef.current) {
1419
- setRight(tempRight);
1420
- setBottom(tempBottom);
1421
- savedRight = tempRight;
1422
- savedBottom = tempBottom;
1469
+ setX(tempX);
1470
+ setY(tempY);
1471
+ saved = {
1472
+ vertical: anchors.vertical,
1473
+ horizontal: anchors.horizontal,
1474
+ x: tempX,
1475
+ y: tempY
1476
+ };
1423
1477
  setPhase("releasing");
1424
1478
  draggingRef.current = false;
1425
1479
  setTimeout(() => setPhase("idle"), 300);
1426
1480
  } else onTap();
1427
1481
  };
1428
1482
  const isDragging = phase === "dragging";
1483
+ const currentX = isDragging ? tempX : x;
1484
+ const currentY = isDragging ? tempY : y;
1429
1485
  return {
1430
1486
  phase,
1431
- right: isDragging ? tempRight : right,
1432
- bottom: isDragging ? tempBottom : bottom,
1487
+ positionStyle: {
1488
+ [anchors.horizontal]: `${currentX}px`,
1489
+ [anchors.vertical]: `${currentY}px`
1490
+ },
1433
1491
  clearTimer,
1434
1492
  handlers: {
1435
1493
  catchtouchstart: handleTouchStart,
@@ -1457,9 +1515,9 @@ const SHINE_STYLES = {
1457
1515
  transition: "opacity 300ms cubic-bezier(0.4, 0, 0.2, 1)"
1458
1516
  }
1459
1517
  };
1460
- const FloatingButton = ({ bindtap, children }) => {
1518
+ const FloatingButton = ({ bindtap, children, initialPosition }) => {
1461
1519
  const colors = useThemeColors();
1462
- const { phase, right, bottom, clearTimer, handlers } = useLongPressDrag(bindtap);
1520
+ const { phase, positionStyle, clearTimer, handlers } = useLongPressDrag(bindtap, { initialPosition });
1463
1521
  const handleReload = () => {
1464
1522
  try {
1465
1523
  lynx.reload({}, () => {
@@ -1470,9 +1528,8 @@ const FloatingButton = ({ bindtap, children }) => {
1470
1528
  }
1471
1529
  };
1472
1530
  const isDragging = phase === "dragging";
1473
- return <view className={"fb-wrapper"} consume-slide-event={[[-180, 180]]} style={{
1474
- right: `${right}px`,
1475
- bottom: `${bottom}px`,
1531
+ return <view className={"fb-wrapper"} style={{
1532
+ ...positionStyle,
1476
1533
  transform: isDragging ? "scale(1.05)" : "scale(1)",
1477
1534
  transition: `transform ${duration.d4} cubic-bezier(0.4, 0, 0.2, 1)`
1478
1535
  }} {...handlers}>
@@ -1490,21 +1547,11 @@ const FloatingButton = ({ bindtap, children }) => {
1490
1547
 
1491
1548
  //#endregion
1492
1549
  //#region src/index.tsx
1493
- const LynxConsole = (0, _lynx_js_react.forwardRef)(({ theme = "light", safeAreaInsetBottom = "50px", customTabs }, ref) => {
1550
+ const LynxConsole = (0, _lynx_js_react.forwardRef)(({ theme = "light", safeAreaInsetBottom = "50px", customTabs, initialPosition }, ref) => {
1494
1551
  const [isOpen, setIsOpen] = (0, _lynx_js_react.useState)(false);
1495
1552
  const [shouldClose, setShouldClose] = (0, _lynx_js_react.useState)(false);
1496
- const { performances } = usePerformance();
1553
+ const latestFcp = useLatestFcp();
1497
1554
  const colors = (0, _lynx_js_react.useMemo)(() => getColors(theme), [theme]);
1498
- const latestFcp = (0, _lynx_js_react.useMemo)(() => {
1499
- for (let i = performances.length - 1; i >= 0; i--) {
1500
- const perf = performances[i];
1501
- if (perf && perf.entryType === "metric" && perf.name === "fcp") {
1502
- const metricEntry = perf.rawEntry;
1503
- if (metricEntry?.totalFcp?.duration !== void 0) return metricEntry.totalFcp;
1504
- if (metricEntry?.lynxFcp?.duration !== void 0) return metricEntry.lynxFcp;
1505
- }
1506
- }
1507
- }, [performances]);
1508
1555
  (0, _lynx_js_react.useImperativeHandle)(ref, () => ({
1509
1556
  open: () => {
1510
1557
  setIsOpen(true);
@@ -1528,7 +1575,7 @@ const LynxConsole = (0, _lynx_js_react.forwardRef)(({ theme = "light", safeAreaI
1528
1575
  backgroundColor: colors.bg.layerDefault,
1529
1576
  color: colors.fg.neutral
1530
1577
  }}>
1531
- <FloatingButton bindtap={handleOpenBottomSheet}>
1578
+ <FloatingButton bindtap={handleOpenBottomSheet} initialPosition={initialPosition}>
1532
1579
  <text className="fb-title t4" style={{
1533
1580
  fontWeight: "400",
1534
1581
  color: colors.palette.staticWhite
package/dist/index.d.cts CHANGED
@@ -88,6 +88,12 @@ interface LynxConsoleProps {
88
88
  theme?: "light" | "dark";
89
89
  safeAreaInsetBottom?: string;
90
90
  customTabs?: CustomTab[];
91
+ initialPosition?: {
92
+ top?: number;
93
+ left?: number;
94
+ right?: number;
95
+ bottom?: number;
96
+ };
91
97
  }
92
98
  declare const LynxConsole: react.ForwardRefExoticComponent<LynxConsoleProps & react.RefAttributes<LynxConsoleHandle>>;
93
99
  //#endregion
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.cts","names":[],"sources":["../src/types.ts","../src/index.tsx"],"mappings":";;;;;UAIiB,SAAA;EACf,GAAA;EACA,KAAA;EACA,aAAA,QAAqB,SAAA;AAAA;AAAA,KAKX,QAAA;AAAA,UAEK,QAAA;EACf,EAAA;EACA,KAAA,EAAO,QAAA;EACP,OAAA;EACA,SAAA;EACA,IAAA;AAAA;AAAA,KAIU,aAAA;AAAA,UAEK,YAAA;EACf,EAAA;EACA,GAAA;EACA,MAAA;EACA,MAAA,EAAQ,aAAA;EACR,UAAA;EACA,UAAA;EACA,SAAA;EACA,OAAA;EACA,QAAA;EACA,cAAA,GAAiB,MAAA;EACjB,WAAA;EACA,eAAA,GAAkB,MAAA;EAClB,YAAA;EACA,KAAA;AAAA;AAAA,KAIU,oBAAA;AAAA,UAEK,iBAAA;EACf,IAAA;EACA,QAAA;EACA,kBAAA;EACA,cAAA;EACA,gBAAA;EACA,YAAA;AAAA;AAAA,UAGe,oBAAA;EACf,EAAA;EACA,SAAA,EAAW,oBAAA;EACX,IAAA;EACA,SAAA;EACA,OAAA,GAAU,iBAAA;EACV,QAAA;AAAA;AAAA,QAIM,MAAA;EAAA,IACF,aAAA;IACF,KAAA;IAAA,CACC,GAAA;EAAA;EAAA,IAGC,gBAAA;IAGE,eAAA;MACE,GAAA,MAAS,IAAA;MACT,IAAA,MAAU,IAAA;MACV,KAAA,MAAW,IAAA;MACX,IAAA,MAAU,IAAA;IAAA;IAIZ,KAAA;MACE,IAAA,GAAO,QAAA;MACP,YAAA,GAAe,GAAA,EAAK,KAAA,EAAO,QAAA;MAC3B,YAAA,IAAgB,QAAA,GAAW,KAAA,EAAO,QAAA;MAElC,QAAA,GAAW,YAAA;MACX,WAAA,GAAc,GAAA,SAAY,YAAA;MAC1B,gBAAA,GAAmB,GAAA,EAAK,KAAA,EAAO,YAAA;MAC/B,gBAAA,IACE,QAAA,GAAW,KAAA,EAAO,YAAA;MAGpB,YAAA,GAAe,oBAAA;MACf,oBAAA,GAAuB,GAAA,EAAK,KAAA,EAAO,oBAAA;MACnC,oBAAA,IACE,QAAA,GAAW,KAAA,EAAO,oBAAA;IAAA;IAItB,qBAAA;EAAA;AAAA;;;UCnFS,iBAAA;EACf,IAAA;EACA,KAAA;EACA,MAAA;AAAA;AAAA,UAGe,gBAAA;EACf,KAAA;EACA,mBAAA;EACA,UAAA,GAAa,SAAA;AAAA;AAAA,cAcT,WAAA,EAAW,KAAA,CAAA,yBAAA,CAAA,gBAAA,GAAA,KAAA,CAAA,aAAA,CAAA,iBAAA"}
1
+ {"version":3,"file":"index.d.cts","names":[],"sources":["../src/types.ts","../src/index.tsx"],"mappings":";;;;;UAIiB,SAAA;EACf,GAAA;EACA,KAAA;EACA,aAAA,QAAqB,SAAA;AAAA;AAAA,KAKX,QAAA;AAAA,UAEK,QAAA;EACf,EAAA;EACA,KAAA,EAAO,QAAA;EACP,OAAA;EACA,SAAA;EACA,IAAA;AAAA;AAAA,KAIU,aAAA;AAAA,UAEK,YAAA;EACf,EAAA;EACA,GAAA;EACA,MAAA;EACA,MAAA,EAAQ,aAAA;EACR,UAAA;EACA,UAAA;EACA,SAAA;EACA,OAAA;EACA,QAAA;EACA,cAAA,GAAiB,MAAA;EACjB,WAAA;EACA,eAAA,GAAkB,MAAA;EAClB,YAAA;EACA,KAAA;AAAA;AAAA,KAIU,oBAAA;AAAA,UAEK,iBAAA;EACf,IAAA;EACA,QAAA;EACA,kBAAA;EACA,cAAA;EACA,gBAAA;EACA,YAAA;AAAA;AAAA,UAGe,oBAAA;EACf,EAAA;EACA,SAAA,EAAW,oBAAA;EACX,IAAA;EACA,SAAA;EACA,OAAA,GAAU,iBAAA;EACV,QAAA;AAAA;AAAA,QAIM,MAAA;EAAA,IACF,aAAA;IACF,KAAA;IAAA,CACC,GAAA;EAAA;EAAA,IAGC,gBAAA;IAGE,eAAA;MACE,GAAA,MAAS,IAAA;MACT,IAAA,MAAU,IAAA;MACV,KAAA,MAAW,IAAA;MACX,IAAA,MAAU,IAAA;IAAA;IAIZ,KAAA;MACE,IAAA,GAAO,QAAA;MACP,YAAA,GAAe,GAAA,EAAK,KAAA,EAAO,QAAA;MAC3B,YAAA,IAAgB,QAAA,GAAW,KAAA,EAAO,QAAA;MAElC,QAAA,GAAW,YAAA;MACX,WAAA,GAAc,GAAA,SAAY,YAAA;MAC1B,gBAAA,GAAmB,GAAA,EAAK,KAAA,EAAO,YAAA;MAC/B,gBAAA,IACE,QAAA,GAAW,KAAA,EAAO,YAAA;MAGpB,YAAA,GAAe,oBAAA;MACf,oBAAA,GAAuB,GAAA,EAAK,KAAA,EAAO,oBAAA;MACnC,oBAAA,IACE,QAAA,GAAW,KAAA,EAAO,oBAAA;IAAA;IAItB,qBAAA;EAAA;AAAA;;;UCnFS,iBAAA;EACf,IAAA;EACA,KAAA;EACA,MAAA;AAAA;AAAA,UAGe,gBAAA;EACf,KAAA;EACA,mBAAA;EACA,UAAA,GAAa,SAAA;EACb,eAAA;IACE,GAAA;IACA,IAAA;IACA,KAAA;IACA,MAAA;EAAA;AAAA;AAAA,cAIE,WAAA,EAAW,KAAA,CAAA,yBAAA,CAAA,gBAAA,GAAA,KAAA,CAAA,aAAA,CAAA,iBAAA"}
package/dist/index.d.mts CHANGED
@@ -88,6 +88,12 @@ interface LynxConsoleProps {
88
88
  theme?: "light" | "dark";
89
89
  safeAreaInsetBottom?: string;
90
90
  customTabs?: CustomTab[];
91
+ initialPosition?: {
92
+ top?: number;
93
+ left?: number;
94
+ right?: number;
95
+ bottom?: number;
96
+ };
91
97
  }
92
98
  declare const LynxConsole: react.ForwardRefExoticComponent<LynxConsoleProps & react.RefAttributes<LynxConsoleHandle>>;
93
99
  //#endregion
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.mts","names":[],"sources":["../src/types.ts","../src/index.tsx"],"mappings":";;;;;UAIiB,SAAA;EACf,GAAA;EACA,KAAA;EACA,aAAA,QAAqB,SAAA;AAAA;AAAA,KAKX,QAAA;AAAA,UAEK,QAAA;EACf,EAAA;EACA,KAAA,EAAO,QAAA;EACP,OAAA;EACA,SAAA;EACA,IAAA;AAAA;AAAA,KAIU,aAAA;AAAA,UAEK,YAAA;EACf,EAAA;EACA,GAAA;EACA,MAAA;EACA,MAAA,EAAQ,aAAA;EACR,UAAA;EACA,UAAA;EACA,SAAA;EACA,OAAA;EACA,QAAA;EACA,cAAA,GAAiB,MAAA;EACjB,WAAA;EACA,eAAA,GAAkB,MAAA;EAClB,YAAA;EACA,KAAA;AAAA;AAAA,KAIU,oBAAA;AAAA,UAEK,iBAAA;EACf,IAAA;EACA,QAAA;EACA,kBAAA;EACA,cAAA;EACA,gBAAA;EACA,YAAA;AAAA;AAAA,UAGe,oBAAA;EACf,EAAA;EACA,SAAA,EAAW,oBAAA;EACX,IAAA;EACA,SAAA;EACA,OAAA,GAAU,iBAAA;EACV,QAAA;AAAA;AAAA,QAIM,MAAA;EAAA,IACF,aAAA;IACF,KAAA;IAAA,CACC,GAAA;EAAA;EAAA,IAGC,gBAAA;IAGE,eAAA;MACE,GAAA,MAAS,IAAA;MACT,IAAA,MAAU,IAAA;MACV,KAAA,MAAW,IAAA;MACX,IAAA,MAAU,IAAA;IAAA;IAIZ,KAAA;MACE,IAAA,GAAO,QAAA;MACP,YAAA,GAAe,GAAA,EAAK,KAAA,EAAO,QAAA;MAC3B,YAAA,IAAgB,QAAA,GAAW,KAAA,EAAO,QAAA;MAElC,QAAA,GAAW,YAAA;MACX,WAAA,GAAc,GAAA,SAAY,YAAA;MAC1B,gBAAA,GAAmB,GAAA,EAAK,KAAA,EAAO,YAAA;MAC/B,gBAAA,IACE,QAAA,GAAW,KAAA,EAAO,YAAA;MAGpB,YAAA,GAAe,oBAAA;MACf,oBAAA,GAAuB,GAAA,EAAK,KAAA,EAAO,oBAAA;MACnC,oBAAA,IACE,QAAA,GAAW,KAAA,EAAO,oBAAA;IAAA;IAItB,qBAAA;EAAA;AAAA;;;UCnFS,iBAAA;EACf,IAAA;EACA,KAAA;EACA,MAAA;AAAA;AAAA,UAGe,gBAAA;EACf,KAAA;EACA,mBAAA;EACA,UAAA,GAAa,SAAA;AAAA;AAAA,cAcT,WAAA,EAAW,KAAA,CAAA,yBAAA,CAAA,gBAAA,GAAA,KAAA,CAAA,aAAA,CAAA,iBAAA"}
1
+ {"version":3,"file":"index.d.mts","names":[],"sources":["../src/types.ts","../src/index.tsx"],"mappings":";;;;;UAIiB,SAAA;EACf,GAAA;EACA,KAAA;EACA,aAAA,QAAqB,SAAA;AAAA;AAAA,KAKX,QAAA;AAAA,UAEK,QAAA;EACf,EAAA;EACA,KAAA,EAAO,QAAA;EACP,OAAA;EACA,SAAA;EACA,IAAA;AAAA;AAAA,KAIU,aAAA;AAAA,UAEK,YAAA;EACf,EAAA;EACA,GAAA;EACA,MAAA;EACA,MAAA,EAAQ,aAAA;EACR,UAAA;EACA,UAAA;EACA,SAAA;EACA,OAAA;EACA,QAAA;EACA,cAAA,GAAiB,MAAA;EACjB,WAAA;EACA,eAAA,GAAkB,MAAA;EAClB,YAAA;EACA,KAAA;AAAA;AAAA,KAIU,oBAAA;AAAA,UAEK,iBAAA;EACf,IAAA;EACA,QAAA;EACA,kBAAA;EACA,cAAA;EACA,gBAAA;EACA,YAAA;AAAA;AAAA,UAGe,oBAAA;EACf,EAAA;EACA,SAAA,EAAW,oBAAA;EACX,IAAA;EACA,SAAA;EACA,OAAA,GAAU,iBAAA;EACV,QAAA;AAAA;AAAA,QAIM,MAAA;EAAA,IACF,aAAA;IACF,KAAA;IAAA,CACC,GAAA;EAAA;EAAA,IAGC,gBAAA;IAGE,eAAA;MACE,GAAA,MAAS,IAAA;MACT,IAAA,MAAU,IAAA;MACV,KAAA,MAAW,IAAA;MACX,IAAA,MAAU,IAAA;IAAA;IAIZ,KAAA;MACE,IAAA,GAAO,QAAA;MACP,YAAA,GAAe,GAAA,EAAK,KAAA,EAAO,QAAA;MAC3B,YAAA,IAAgB,QAAA,GAAW,KAAA,EAAO,QAAA;MAElC,QAAA,GAAW,YAAA;MACX,WAAA,GAAc,GAAA,SAAY,YAAA;MAC1B,gBAAA,GAAmB,GAAA,EAAK,KAAA,EAAO,YAAA;MAC/B,gBAAA,IACE,QAAA,GAAW,KAAA,EAAO,YAAA;MAGpB,YAAA,GAAe,oBAAA;MACf,oBAAA,GAAuB,GAAA,EAAK,KAAA,EAAO,oBAAA;MACnC,oBAAA,IACE,QAAA,GAAW,KAAA,EAAO,oBAAA;IAAA;IAItB,qBAAA;EAAA;AAAA;;;UCnFS,iBAAA;EACf,IAAA;EACA,KAAA;EACA,MAAA;AAAA;AAAA,UAGe,gBAAA;EACf,KAAA;EACA,mBAAA;EACA,UAAA,GAAa,SAAA;EACb,eAAA;IACE,GAAA;IACA,IAAA;IACA,KAAA;IACA,MAAA;EAAA;AAAA;AAAA,cAIE,WAAA,EAAW,KAAA,CAAA,yBAAA,CAAA,gBAAA,GAAA,KAAA,CAAA,aAAA,CAAA,iBAAA"}
package/dist/index.mjs CHANGED
@@ -222,6 +222,35 @@ const useConsole = () => {
222
222
  };
223
223
  };
224
224
 
225
+ //#endregion
226
+ //#region src/hooks/useLatestFcp.ts
227
+ const pickFcp = (entry) => {
228
+ if (entry.entryType !== "metric" || entry.name !== "fcp") return void 0;
229
+ const raw = entry.rawEntry;
230
+ if (raw?.totalFcp?.duration !== void 0) return raw.totalFcp;
231
+ if (raw?.lynxFcp?.duration !== void 0) return raw.lynxFcp;
232
+ };
233
+ const useLatestFcp = () => {
234
+ const [fcp, setFcp] = useState(() => {
235
+ const performances = globalThis.__LYNX_CONSOLE__?.state?.performances ?? [];
236
+ for (let i = performances.length - 1; i >= 0; i--) {
237
+ const entry = performances[i];
238
+ if (!entry) continue;
239
+ const found = pickFcp(entry);
240
+ if (found) return found;
241
+ }
242
+ });
243
+ useEffect(() => {
244
+ const state = globalThis.__LYNX_CONSOLE__?.state;
245
+ if (!state?.subscribePerformance) return;
246
+ return state.subscribePerformance((entry) => {
247
+ const found = pickFcp(entry);
248
+ if (found) setFcp(found);
249
+ });
250
+ }, []);
251
+ return fcp;
252
+ };
253
+
225
254
  //#endregion
226
255
  //#region src/hooks/useNetwork.ts
227
256
  const useNetwork = () => {
@@ -484,7 +513,8 @@ const LogPanel = ({ logs, clearLogs }) => {
484
513
  params: {
485
514
  position: logsRef.current.length - 1,
486
515
  smooth
487
- }
516
+ },
517
+ fail: () => {}
488
518
  }).exec();
489
519
  };
490
520
  useEffect(() => {
@@ -1368,22 +1398,42 @@ const LONG_PRESS_DURATION = 400;
1368
1398
  const MOVE_THRESHOLD = 5;
1369
1399
  const DEFAULT_RIGHT = 16;
1370
1400
  const DEFAULT_BOTTOM = 84;
1371
- let savedRight = DEFAULT_RIGHT;
1372
- let savedBottom = DEFAULT_BOTTOM;
1373
- function useLongPressDrag(onTap) {
1374
- const [right, setRight] = useState(savedRight);
1375
- const [bottom, setBottom] = useState(savedBottom);
1401
+ function resolveAnchors(initial) {
1402
+ const vertical = initial?.top !== void 0 ? "top" : "bottom";
1403
+ const horizontal = initial?.left !== void 0 ? "left" : "right";
1404
+ const y = vertical === "top" ? initial?.top ?? 0 : initial?.bottom ?? DEFAULT_BOTTOM;
1405
+ return {
1406
+ vertical,
1407
+ horizontal,
1408
+ x: horizontal === "left" ? initial?.left ?? 0 : initial?.right ?? DEFAULT_RIGHT,
1409
+ y
1410
+ };
1411
+ }
1412
+ let saved = null;
1413
+ function useLongPressDrag(onTap, options) {
1414
+ const anchors = resolveAnchors(options?.initialPosition);
1415
+ const snapshot = saved;
1416
+ let initX = anchors.x;
1417
+ let initY = anchors.y;
1418
+ if (snapshot !== null && snapshot.vertical === anchors.vertical && snapshot.horizontal === anchors.horizontal) {
1419
+ initX = snapshot.x;
1420
+ initY = snapshot.y;
1421
+ }
1422
+ const [x, setX] = useState(initX);
1423
+ const [y, setY] = useState(initY);
1376
1424
  const [phase, setPhase] = useState("idle");
1377
- const [tempRight, setTempRight] = useState(savedRight);
1378
- const [tempBottom, setTempBottom] = useState(savedBottom);
1425
+ const [tempX, setTempX] = useState(initX);
1426
+ const [tempY, setTempY] = useState(initY);
1379
1427
  const timerRef = useRef(null);
1380
1428
  const draggingRef = useRef(false);
1381
1429
  const startRef = useRef({
1382
1430
  x: 0,
1383
1431
  y: 0,
1384
- r: 0,
1385
- b: 0
1432
+ ax: 0,
1433
+ ay: 0
1386
1434
  });
1435
+ const xSign = anchors.horizontal === "right" ? -1 : 1;
1436
+ const ySign = anchors.vertical === "bottom" ? -1 : 1;
1387
1437
  const clearTimer = () => {
1388
1438
  if (timerRef.current) {
1389
1439
  clearTimeout(timerRef.current);
@@ -1394,15 +1444,15 @@ function useLongPressDrag(onTap) {
1394
1444
  startRef.current = {
1395
1445
  x: e.detail.x,
1396
1446
  y: e.detail.y,
1397
- r: right,
1398
- b: bottom
1447
+ ax: x,
1448
+ ay: y
1399
1449
  };
1400
1450
  draggingRef.current = false;
1401
1451
  timerRef.current = setTimeout(() => {
1402
1452
  draggingRef.current = true;
1403
1453
  setPhase("dragging");
1404
- setTempRight(right);
1405
- setTempBottom(bottom);
1454
+ setTempX(x);
1455
+ setTempY(y);
1406
1456
  }, LONG_PRESS_DURATION);
1407
1457
  };
1408
1458
  const handleTouchMove = (e) => {
@@ -1410,26 +1460,34 @@ function useLongPressDrag(onTap) {
1410
1460
  const dy = e.detail.y - startRef.current.y;
1411
1461
  if (!draggingRef.current && (Math.abs(dx) > MOVE_THRESHOLD || Math.abs(dy) > MOVE_THRESHOLD)) clearTimer();
1412
1462
  if (!draggingRef.current) return;
1413
- setTempRight(startRef.current.r - dx);
1414
- setTempBottom(startRef.current.b - dy);
1463
+ setTempX(startRef.current.ax + xSign * dx);
1464
+ setTempY(startRef.current.ay + ySign * dy);
1415
1465
  };
1416
1466
  const handleTouchEnd = () => {
1417
1467
  clearTimer();
1418
1468
  if (draggingRef.current) {
1419
- setRight(tempRight);
1420
- setBottom(tempBottom);
1421
- savedRight = tempRight;
1422
- savedBottom = tempBottom;
1469
+ setX(tempX);
1470
+ setY(tempY);
1471
+ saved = {
1472
+ vertical: anchors.vertical,
1473
+ horizontal: anchors.horizontal,
1474
+ x: tempX,
1475
+ y: tempY
1476
+ };
1423
1477
  setPhase("releasing");
1424
1478
  draggingRef.current = false;
1425
1479
  setTimeout(() => setPhase("idle"), 300);
1426
1480
  } else onTap();
1427
1481
  };
1428
1482
  const isDragging = phase === "dragging";
1483
+ const currentX = isDragging ? tempX : x;
1484
+ const currentY = isDragging ? tempY : y;
1429
1485
  return {
1430
1486
  phase,
1431
- right: isDragging ? tempRight : right,
1432
- bottom: isDragging ? tempBottom : bottom,
1487
+ positionStyle: {
1488
+ [anchors.horizontal]: `${currentX}px`,
1489
+ [anchors.vertical]: `${currentY}px`
1490
+ },
1433
1491
  clearTimer,
1434
1492
  handlers: {
1435
1493
  catchtouchstart: handleTouchStart,
@@ -1457,9 +1515,9 @@ const SHINE_STYLES = {
1457
1515
  transition: "opacity 300ms cubic-bezier(0.4, 0, 0.2, 1)"
1458
1516
  }
1459
1517
  };
1460
- const FloatingButton = ({ bindtap, children }) => {
1518
+ const FloatingButton = ({ bindtap, children, initialPosition }) => {
1461
1519
  const colors = useThemeColors();
1462
- const { phase, right, bottom, clearTimer, handlers } = useLongPressDrag(bindtap);
1520
+ const { phase, positionStyle, clearTimer, handlers } = useLongPressDrag(bindtap, { initialPosition });
1463
1521
  const handleReload = () => {
1464
1522
  try {
1465
1523
  lynx.reload({}, () => {
@@ -1470,9 +1528,8 @@ const FloatingButton = ({ bindtap, children }) => {
1470
1528
  }
1471
1529
  };
1472
1530
  const isDragging = phase === "dragging";
1473
- return <view className={"fb-wrapper"} consume-slide-event={[[-180, 180]]} style={{
1474
- right: `${right}px`,
1475
- bottom: `${bottom}px`,
1531
+ return <view className={"fb-wrapper"} style={{
1532
+ ...positionStyle,
1476
1533
  transform: isDragging ? "scale(1.05)" : "scale(1)",
1477
1534
  transition: `transform ${duration.d4} cubic-bezier(0.4, 0, 0.2, 1)`
1478
1535
  }} {...handlers}>
@@ -1490,21 +1547,11 @@ const FloatingButton = ({ bindtap, children }) => {
1490
1547
 
1491
1548
  //#endregion
1492
1549
  //#region src/index.tsx
1493
- const LynxConsole = forwardRef(({ theme = "light", safeAreaInsetBottom = "50px", customTabs }, ref) => {
1550
+ const LynxConsole = forwardRef(({ theme = "light", safeAreaInsetBottom = "50px", customTabs, initialPosition }, ref) => {
1494
1551
  const [isOpen, setIsOpen] = useState(false);
1495
1552
  const [shouldClose, setShouldClose] = useState(false);
1496
- const { performances } = usePerformance();
1553
+ const latestFcp = useLatestFcp();
1497
1554
  const colors = useMemo(() => getColors(theme), [theme]);
1498
- const latestFcp = useMemo(() => {
1499
- for (let i = performances.length - 1; i >= 0; i--) {
1500
- const perf = performances[i];
1501
- if (perf && perf.entryType === "metric" && perf.name === "fcp") {
1502
- const metricEntry = perf.rawEntry;
1503
- if (metricEntry?.totalFcp?.duration !== void 0) return metricEntry.totalFcp;
1504
- if (metricEntry?.lynxFcp?.duration !== void 0) return metricEntry.lynxFcp;
1505
- }
1506
- }
1507
- }, [performances]);
1508
1555
  useImperativeHandle(ref, () => ({
1509
1556
  open: () => {
1510
1557
  setIsOpen(true);
@@ -1528,7 +1575,7 @@ const LynxConsole = forwardRef(({ theme = "light", safeAreaInsetBottom = "50px",
1528
1575
  backgroundColor: colors.bg.layerDefault,
1529
1576
  color: colors.fg.neutral
1530
1577
  }}>
1531
- <FloatingButton bindtap={handleOpenBottomSheet}>
1578
+ <FloatingButton bindtap={handleOpenBottomSheet} initialPosition={initialPosition}>
1532
1579
  <text className="fb-title t4" style={{
1533
1580
  fontWeight: "400",
1534
1581
  color: colors.palette.staticWhite