contactsheet 0.1.2 → 0.1.4

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.
@@ -1296,220 +1296,210 @@ ${board.entry.kind} \xB7 ${board.entry.id}`);
1296
1296
  return viewport.classList.contains("is-panning");
1297
1297
  }
1298
1298
 
1299
- // src/canvas/modes.ts
1300
- var viewport2;
1301
- function boardOf(target) {
1302
- const el = target?.closest?.(".cs-board");
1303
- if (!el?.dataset.id) return null;
1304
- return state.boards.get(el.dataset.id) ?? null;
1299
+ // src/canvas/sidebar.ts
1300
+ var root;
1301
+ var listEl;
1302
+ var filter = "";
1303
+ var searchCollapsed = null;
1304
+ var uid = 0;
1305
+ function initSidebar() {
1306
+ root = qs("#cs-sidebar");
1307
+ root.textContent = "";
1308
+ const head = h("div", "cs-sb-head");
1309
+ const search = document.createElement("input");
1310
+ search.className = "cs-sb-search";
1311
+ search.type = "search";
1312
+ search.placeholder = "\u641C\u7D22\u753B\u677F\u2026";
1313
+ search.addEventListener("input", () => {
1314
+ filter = search.value.trim().toLowerCase();
1315
+ searchCollapsed = filter ? /* @__PURE__ */ new Set() : null;
1316
+ renderSidebar();
1317
+ });
1318
+ search.addEventListener("keydown", (e) => {
1319
+ e.stopPropagation();
1320
+ if (e.key === "Escape") {
1321
+ search.value = "";
1322
+ filter = "";
1323
+ searchCollapsed = null;
1324
+ renderSidebar();
1325
+ search.blur();
1326
+ }
1327
+ });
1328
+ head.append(h("span", "cs-sb-title", "\u753B\u677F"), search);
1329
+ listEl = h("div", "cs-sb-list");
1330
+ listEl.addEventListener("keydown", (e) => {
1331
+ if ((e.key === "Enter" || e.key === " ") && e.target.closest("button")) {
1332
+ e.stopPropagation();
1333
+ }
1334
+ });
1335
+ root.append(head, listEl);
1336
+ const toggle = qs("#cs-sb-toggle");
1337
+ toggle.addEventListener("click", () => {
1338
+ const closed = document.body.dataset.sidebar === "off";
1339
+ document.body.dataset.sidebar = closed ? "on" : "off";
1340
+ toggle.title = closed ? "\u6536\u8D77\u5217\u8868" : "\u5C55\u5F00\u5217\u8868";
1341
+ });
1342
+ renderSidebar();
1305
1343
  }
1306
- function setActive(id) {
1307
- if (state.activeId === id) return;
1308
- state.activeId = id;
1309
- for (const b of state.boards.values()) b.el.classList.toggle("is-active", b.entry.id === id);
1344
+ function match(text) {
1345
+ return !filter || text.toLowerCase().includes(filter);
1310
1346
  }
1311
- function setMode(mode, boardId) {
1312
- if (mode !== "browse" && boardId) setActive(boardId);
1313
- if (mode === "review" && !state.activeId) return;
1314
- const from = state.mode;
1315
- state.mode = mode;
1316
- hideHover();
1317
- if (mode !== "browse") clearSelection();
1318
- syncHud();
1319
- if (mode === "review" && from !== "review") {
1320
- state.modeBeforeReview = from;
1321
- state.viewBeforeReview = { scale: state.scale, tx: state.tx, ty: state.ty };
1322
- closeArgsPanel();
1323
- fitActiveBoard();
1324
- } else if (mode !== "review" && from === "review" && state.viewBeforeReview) {
1325
- const v = state.viewBeforeReview;
1326
- state.viewBeforeReview = null;
1327
- setView(v.tx, v.ty, v.scale);
1347
+ function collapseKey() {
1348
+ return `cs-collapsed:${state.info?.projectRoot ?? "unknown"}`;
1349
+ }
1350
+ function collapsedSet() {
1351
+ try {
1352
+ return new Set(JSON.parse(localStorage.getItem(collapseKey()) ?? "[]"));
1353
+ } catch {
1354
+ return /* @__PURE__ */ new Set();
1328
1355
  }
1329
- setProbe(
1330
- mode === "interact" ? "\u4EA4\u4E92\u6A21\u5F0F:\u76F4\u63A5\u64CD\u4F5C\u8FD9\u5757\u753B\u677F,Esc \u56DE\u6D4F\u89C8" : mode === "review" ? "\u8D70\u67E5\u6A21\u5F0F:Esc \u9000\u51FA" : "\u79FB\u5230\u753B\u677F\u4E0A\u53CD\u67E5\u5143\u7D20"
1331
- );
1332
1356
  }
1333
- function fitActiveBoard() {
1334
- const board = state.activeId ? state.boards.get(state.activeId) : null;
1335
- if (!board) return;
1336
- const r = board.frameEl.getBoundingClientRect();
1337
- const worldX = (r.left - state.tx) / state.scale;
1338
- const worldY = (r.top - state.ty) / state.scale;
1339
- const vw = window.innerWidth;
1340
- const vh = window.innerHeight;
1341
- const s = clamp(Math.min(vw / board.width, vh / board.height), MIN_SCALE, MAX_SCALE);
1342
- setView((vw - board.width * s) / 2 - worldX * s, (vh - board.height * s) / 2 - worldY * s, s);
1357
+ function isCollapsed(key) {
1358
+ return searchCollapsed ? searchCollapsed.has(key) : collapsedSet().has(key);
1343
1359
  }
1344
- function escape() {
1345
- if (isComposing()) {
1346
- closeComposer();
1347
- exitPinMode();
1348
- return;
1349
- }
1350
- if (state.pinPending) {
1351
- exitPinMode();
1352
- return;
1353
- }
1354
- if (selectedPin()) {
1355
- selectPin(null);
1356
- return;
1357
- }
1358
- if (state.mode === "review") {
1359
- setMode(state.modeBeforeReview === "review" ? "browse" : state.modeBeforeReview);
1360
+ function toggleCollapsed(key) {
1361
+ if (searchCollapsed) {
1362
+ if (!searchCollapsed.delete(key)) searchCollapsed.add(key);
1360
1363
  return;
1361
1364
  }
1362
- if (state.mode === "interact") {
1363
- setMode("browse");
1364
- return;
1365
+ const s = collapsedSet();
1366
+ if (s.has(key)) s.delete(key);
1367
+ else s.add(key);
1368
+ try {
1369
+ localStorage.setItem(collapseKey(), JSON.stringify([...s]));
1370
+ } catch {
1365
1371
  }
1366
- clearSelection();
1367
- closeArgsPanel();
1368
- setProbe("\u79FB\u5230\u753B\u677F\u4E0A\u53CD\u67E5\u5143\u7D20");
1369
1372
  }
1370
- function isEditable(target) {
1371
- const el = target;
1372
- if (!el) return false;
1373
- const tag = el.tagName;
1374
- if (tag === "INPUT" || tag === "TEXTAREA" || tag === "SELECT" || el.isContentEditable === true) return true;
1375
- return el.closest("#cs-sidebar, #cs-args, .cs-picker, #cs-topbar") !== null;
1373
+ function bulkMode(ids) {
1374
+ const shown = ids.filter((id) => !isHidden(id)).length;
1375
+ return { mode: shown === ids.length ? "all" : shown === 0 ? "none" : "some", shown };
1376
1376
  }
1377
- function onKeyDown(e) {
1378
- if (e.key === "Escape") {
1379
- escape();
1380
- return;
1381
- }
1382
- if (isEditable(e.target) || e.metaKey || e.ctrlKey || e.altKey) return;
1383
- if (e.key === "Enter") {
1384
- if (selectedPin()) {
1385
- e.preventDefault();
1386
- editSelectedPin();
1387
- return;
1388
- }
1389
- if (state.mode !== "review" && state.activeId) {
1390
- e.preventDefault();
1391
- setMode("review", state.activeId);
1392
- }
1393
- return;
1377
+ function setGroupHidden(ids, hide) {
1378
+ setHidden(ids, hide);
1379
+ }
1380
+ function focusedKey() {
1381
+ const el = document.activeElement;
1382
+ return el && listEl.contains(el) ? el.dataset.fk ?? null : null;
1383
+ }
1384
+ function restoreFocus(key) {
1385
+ if (!key) return;
1386
+ const el = listEl.querySelector(`[data-fk="${CSS.escape(key)}"]`);
1387
+ el?.focus({ preventScroll: true });
1388
+ }
1389
+ function renderSidebar() {
1390
+ if (!listEl) return;
1391
+ const keepFocus = focusedKey();
1392
+ listEl.textContent = "";
1393
+ uid = 0;
1394
+ const screens = state.entries.filter((e) => e.kind === "screen");
1395
+ const comps = state.entries.filter((e) => e.kind !== "screen");
1396
+ const visScreens = screens.filter((e) => match(e.exportName) || match(e.url ?? ""));
1397
+ if (visScreens.length) {
1398
+ const g = group("sec:screen", "\u9875\u9762", visScreens.map((e) => e.id), "cs-sb-section");
1399
+ for (const e of visScreens) g.body.appendChild(row(e.id, e.exportName, e.url ?? "", "screen"));
1400
+ listEl.appendChild(g.el);
1394
1401
  }
1395
- if (e.key === "Backspace" || e.key === "Delete") {
1396
- if (selectedPin()) {
1397
- e.preventDefault();
1398
- deleteSelectedPin();
1399
- }
1400
- return;
1402
+ const byFile = /* @__PURE__ */ new Map();
1403
+ for (const e of comps) {
1404
+ if (!(match(e.exportName) || match(groupLabel(e.file)))) continue;
1405
+ const l = byFile.get(e.file);
1406
+ if (l) l.push(e);
1407
+ else byFile.set(e.file, [e]);
1401
1408
  }
1402
- if (e.key === "c" || e.key === "C") {
1403
- if (state.mode === "browse" && !state.pinPending) {
1404
- e.preventDefault();
1405
- enterPinMode();
1409
+ if (byFile.size) {
1410
+ const files = [...byFile.keys()].sort();
1411
+ const allIds = files.flatMap((f) => (byFile.get(f) ?? []).map((e) => e.id));
1412
+ const g = group("sec:component", "\u7EC4\u4EF6", allIds, "cs-sb-section");
1413
+ for (const file of files) {
1414
+ const list = byFile.get(file) ?? [];
1415
+ const fg = group(`file:${file}`, groupLabel(file), list.map((e) => e.id), "cs-sb-file");
1416
+ for (const e of list) fg.body.appendChild(row(e.id, e.exportName, "", "component"));
1417
+ g.body.appendChild(fg.el);
1406
1418
  }
1407
- return;
1408
- }
1409
- if (e.key === "y" || e.key === "Y") {
1410
- e.preventDefault();
1411
- void copyContext();
1412
- return;
1413
- }
1414
- if (e.key === "p") {
1415
- e.preventDefault();
1416
- void pushToClaudeCode();
1417
- return;
1418
- }
1419
- if (e.key === "1") {
1420
- e.preventDefault();
1421
- fitAll();
1422
- return;
1423
- }
1424
- if (e.key === "2") {
1425
- e.preventDefault();
1426
- fitBoard();
1427
- return;
1428
- }
1429
- if (e.key === "0") {
1430
- e.preventDefault();
1431
- zoomReset();
1432
- return;
1433
- }
1434
- if (e.key === "=" || e.key === "+") {
1435
- e.preventDefault();
1436
- zoomStep(1.25);
1437
- return;
1419
+ listEl.appendChild(g.el);
1438
1420
  }
1439
- if (e.key === "-" || e.key === "_") {
1440
- e.preventDefault();
1441
- zoomStep(0.8);
1421
+ if (!listEl.childElementCount) {
1422
+ listEl.appendChild(h("div", "cs-sb-empty", filter ? "\u6CA1\u6709\u5339\u914D\u7684\u753B\u677F" : "\u8FD8\u6CA1\u6709\u753B\u677F"));
1442
1423
  }
1424
+ syncActive();
1425
+ restoreFocus(keepFocus);
1443
1426
  }
1444
- function attachFrameKeys(doc) {
1445
- doc.addEventListener("keydown", onKeyDown);
1446
- }
1447
- function onMouseMove(e) {
1448
- if (isPanning()) return;
1449
- const overlay = e.target?.closest?.(".cs-ovl");
1450
- const board = overlay ? boardOf(e.target) : null;
1451
- if (!board) {
1452
- hideHover();
1453
- return;
1454
- }
1455
- if (state.mode !== "browse") return;
1456
- setActive(board.entry.id);
1457
- const el = elementAt(board, e.clientX, e.clientY);
1458
- if (!el) {
1459
- hideHover();
1460
- return;
1461
- }
1462
- showHover(board, el);
1463
- setProbe(`${board.entry.exportName} ${describe(el)}`);
1464
- }
1465
- function onClick(e) {
1466
- const target = e.target;
1467
- const title = target?.closest?.(".cs-board-title");
1468
- if (title) {
1469
- const board2 = boardOf(target);
1470
- if (!board2) return;
1471
- setActive(board2.entry.id);
1472
- if (isArgsOpen(board2.entry.id)) closeArgsPanel();
1473
- else openArgsPanel(board2);
1474
- return;
1475
- }
1476
- const overlay = target?.closest?.(".cs-ovl");
1477
- if (!overlay) return;
1478
- const board = boardOf(target);
1479
- if (!board) return;
1480
- if (state.mode === "interact") return;
1481
- setActive(board.entry.id);
1482
- const el = elementAt(board, e.clientX, e.clientY);
1483
- if (!el) return;
1484
- if (state.pinPending) {
1485
- openComposer(board, el, e.clientX, e.clientY);
1486
- return;
1487
- }
1488
- if (state.mode !== "browse") return;
1489
- const selector = buildSelector(el);
1490
- const rel = relPos(el, e.clientX, e.clientY, board);
1491
- showSelection(board, selector);
1492
- state.selection = { artboardId: board.entry.id, selector, x: rel.x, y: rel.y, ts: Date.now() };
1493
- setProbe(`\u5DF2\u9009\u4E2D ${board.entry.exportName} \u2192 ${selector}`);
1494
- postSelection(state.selection).catch((err) => setProbe(`selection \u53D1\u9001\u5931\u8D25:${String(err)}`));
1495
- }
1496
- function onDblClick(e) {
1497
- const board = boardOf(e.target);
1498
- if (!board || !e.target.closest(".cs-ovl")) return;
1499
- setMode("interact", board.entry.id);
1427
+ function group(key, label, ids, headCls) {
1428
+ const el = h("div", "cs-sb-group");
1429
+ if (headCls === "cs-sb-file") el.classList.add("is-file");
1430
+ const headEl = h("div", headCls);
1431
+ const body = h("div", "cs-sb-items");
1432
+ body.id = `cs-sb-g${++uid}`;
1433
+ const collapsed = isCollapsed(key);
1434
+ body.hidden = collapsed;
1435
+ const { mode, shown } = bulkMode(ids);
1436
+ const caret = h("button", "cs-sb-caret");
1437
+ caret.type = "button";
1438
+ caret.dataset.fk = `caret:${key}`;
1439
+ caret.setAttribute("aria-expanded", String(!collapsed));
1440
+ caret.setAttribute("aria-controls", body.id);
1441
+ caret.title = collapsed ? `\u5C55\u5F00\u300C${label}\u300D` : `\u6298\u53E0\u300C${label}\u300D`;
1442
+ const arrow = h("span", "cs-sb-arrow", "\u25B8");
1443
+ arrow.setAttribute("aria-hidden", "true");
1444
+ const count = h("span", "cs-sb-count", mode === "all" ? String(ids.length) : `${shown}/${ids.length}`);
1445
+ caret.append(arrow, h("span", "cs-sb-label", label), count);
1446
+ caret.addEventListener("click", () => {
1447
+ toggleCollapsed(key);
1448
+ renderSidebar();
1449
+ });
1450
+ const bulk = h("button", "cs-sb-bulk", mode === "all" ? "\u25CF" : mode === "none" ? "\u25CC" : "\u25D0");
1451
+ bulk.type = "button";
1452
+ bulk.dataset.fk = `bulk:${key}`;
1453
+ bulk.dataset.state = mode;
1454
+ bulk.setAttribute("aria-pressed", mode === "all" ? "true" : mode === "none" ? "false" : "mixed");
1455
+ const act = mode === "all" ? `\u9690\u85CF\u300C${label}\u300D\u4E0B\u5168\u90E8 ${ids.length} \u5757\u753B\u677F` : `\u663E\u793A\u300C${label}\u300D\u4E0B\u5168\u90E8 ${ids.length} \u5757\u753B\u677F`;
1456
+ bulk.setAttribute("aria-label", act);
1457
+ bulk.title = mode === "some" ? `${act}(\u5F53\u524D ${shown}/${ids.length} \u663E\u793A\u4E2D)` : act;
1458
+ bulk.addEventListener("click", (e) => {
1459
+ e.stopPropagation();
1460
+ setGroupHidden(ids, mode === "all");
1461
+ renderSidebar();
1462
+ });
1463
+ headEl.append(caret, bulk);
1464
+ el.append(headEl, body);
1465
+ return { el, body };
1500
1466
  }
1501
- function initModes() {
1502
- viewport2 = qs("#cs-viewport");
1503
- viewport2.addEventListener("mousemove", onMouseMove);
1504
- viewport2.addEventListener("mouseleave", hideHover);
1505
- viewport2.addEventListener("click", onClick);
1506
- viewport2.addEventListener("dblclick", onDblClick);
1507
- document.addEventListener("keydown", onKeyDown);
1508
- window.addEventListener("resize", () => {
1509
- if (state.mode === "review") fitActiveBoard();
1510
- else applyView();
1467
+ function row(id, name, sub, kind) {
1468
+ const el = h("div", "cs-sb-row");
1469
+ el.dataset.id = id;
1470
+ const go = h("button", "cs-sb-go");
1471
+ go.type = "button";
1472
+ go.dataset.fk = `go:${id}`;
1473
+ go.dataset.kind = kind;
1474
+ go.title = sub ? `\u805A\u7126 ${name} (${sub})` : `\u805A\u7126 ${name}`;
1475
+ go.append(h("span", "cs-sb-dot"), h("span", "cs-sb-name", name));
1476
+ if (sub) go.appendChild(h("span", "cs-sb-route", sub));
1477
+ go.addEventListener("click", () => {
1478
+ if (isHidden(id)) toggleHidden(id);
1479
+ fitBoard(id);
1480
+ state.activeId = id;
1481
+ syncActive();
1511
1482
  });
1512
- syncHud();
1483
+ const eye = h("button", "cs-sb-eye", isHidden(id) ? "\u25CC" : "\u25CF");
1484
+ eye.type = "button";
1485
+ eye.dataset.fk = `eye:${id}`;
1486
+ eye.title = isHidden(id) ? "\u663E\u793A\u8FD9\u5757\u753B\u677F" : "\u4ECE\u5899\u4E0A\u9690\u85CF(\u4E0D\u5220,\u968F\u65F6\u70B9\u56DE\u6765)";
1487
+ eye.setAttribute("aria-label", isHidden(id) ? `\u663E\u793A ${name}` : `\u9690\u85CF ${name}`);
1488
+ eye.setAttribute("aria-pressed", String(!isHidden(id)));
1489
+ eye.addEventListener("click", (e) => {
1490
+ e.stopPropagation();
1491
+ toggleHidden(id);
1492
+ renderSidebar();
1493
+ });
1494
+ el.append(go, eye);
1495
+ if (isHidden(id)) el.classList.add("is-hidden-board");
1496
+ return el;
1497
+ }
1498
+ function syncActive() {
1499
+ if (!listEl) return;
1500
+ for (const r of listEl.querySelectorAll(".cs-sb-row")) {
1501
+ r.classList.toggle("is-active", r.dataset.id === state.activeId);
1502
+ }
1513
1503
  }
1514
1504
 
1515
1505
  // src/canvas/wall.ts
@@ -1539,6 +1529,7 @@ ${board.entry.kind} \xB7 ${board.entry.id}`);
1539
1529
  if (state.scale < MOUNT_MIN_SCALE) continue;
1540
1530
  const id = r.target.dataset.id;
1541
1531
  const board = id ? state.boards.get(id) : null;
1532
+ if (board?.el.hidden) continue;
1542
1533
  if (board) requestMount(board);
1543
1534
  io.unobserve(r.target);
1544
1535
  }
@@ -1568,6 +1559,7 @@ ${board.entry.kind} \xB7 ${board.entry.id}`);
1568
1559
  heightWatchers.get(id)?.disconnect();
1569
1560
  heightWatchers.delete(id);
1570
1561
  }
1562
+ const defaultHid = markSeenAndDefaultHide(entries);
1571
1563
  for (const [file, list] of byFile) {
1572
1564
  const ids = list.map((e) => e.id);
1573
1565
  const kept = (state.order.get(file) ?? []).filter((id) => ids.includes(id));
@@ -1587,6 +1579,7 @@ ${board.entry.kind} \xB7 ${board.entry.id}`);
1587
1579
  applyPositions();
1588
1580
  if (n && !firstSync) toast(`\u65B0\u753B\u677F ${n} \u5757 \xB7 \u5DF2\u653E\u5230\u5899\u5E95\u90E8`, "info");
1589
1581
  }
1582
+ if (defaultHid) toast("\u9875\u9762\u753B\u677F\u9ED8\u8BA4\u6536\u8D77(\u6574\u9875 iframe \u592A\u91CD),\u5728\u5DE6\u4FA7\u300C\u9875\u9762\u300D\u5206\u533A\u6309\u9700\u6253\u5F00", "info");
1590
1583
  if (state.boards.size) firstSync = false;
1591
1584
  renderSidebar();
1592
1585
  }
@@ -1647,6 +1640,7 @@ ${board.entry.kind} \xB7 ${board.entry.id}`);
1647
1640
  }
1648
1641
  attachResizeHandles(board);
1649
1642
  attachBoardDrag(board);
1643
+ el.hidden = isHidden(entry.id);
1650
1644
  applySize(board);
1651
1645
  updateBoard(board, entry);
1652
1646
  io.observe(el);
@@ -1729,6 +1723,34 @@ ${board.entry.kind} \xB7 ${board.entry.id}`);
1729
1723
  function hiddenKey() {
1730
1724
  return `cs-hidden:${state.info?.projectRoot ?? "unknown"}`;
1731
1725
  }
1726
+ function seenKey() {
1727
+ return `cs-seen:${state.info?.projectRoot ?? "unknown"}`;
1728
+ }
1729
+ function markSeenAndDefaultHide(entries) {
1730
+ let seen;
1731
+ try {
1732
+ seen = new Set(JSON.parse(localStorage.getItem(seenKey()) ?? "[]"));
1733
+ } catch {
1734
+ seen = /* @__PURE__ */ new Set();
1735
+ }
1736
+ const fresh = entries.filter((e) => !seen.has(e.id));
1737
+ if (fresh.length === 0) return false;
1738
+ const hid = hiddenSet();
1739
+ let hidSomething = false;
1740
+ for (const e of fresh) {
1741
+ seen.add(e.id);
1742
+ if (e.kind === "screen") {
1743
+ hid.add(e.id);
1744
+ hidSomething = true;
1745
+ }
1746
+ }
1747
+ try {
1748
+ localStorage.setItem(seenKey(), JSON.stringify([...seen]));
1749
+ if (hidSomething) localStorage.setItem(hiddenKey(), JSON.stringify([...hid]));
1750
+ } catch {
1751
+ }
1752
+ return hidSomething;
1753
+ }
1732
1754
  function hiddenSet() {
1733
1755
  try {
1734
1756
  return new Set(JSON.parse(localStorage.getItem(hiddenKey()) ?? "[]"));
@@ -1979,6 +2001,7 @@ ${board.entry.kind} \xB7 ${board.entry.id}`);
1979
2001
  }
1980
2002
  }
1981
2003
  function requestMount(board) {
2004
+ if (board.el.hidden) return;
1982
2005
  if (board.iframe || mountQueue.includes(board)) return;
1983
2006
  if (loadingCount >= MAX_LOADING) {
1984
2007
  mountQueue.push(board);
@@ -2360,210 +2383,243 @@ ${board.entry.kind} \xB7 ${board.entry.id}`);
2360
2383
  settleTimer = null;
2361
2384
  }
2362
2385
 
2363
- // src/canvas/sidebar.ts
2364
- var root;
2365
- var listEl;
2366
- var filter = "";
2367
- var searchCollapsed = null;
2368
- var uid = 0;
2369
- function initSidebar() {
2370
- root = qs("#cs-sidebar");
2371
- root.textContent = "";
2372
- const head = h("div", "cs-sb-head");
2373
- const search = document.createElement("input");
2374
- search.className = "cs-sb-search";
2375
- search.type = "search";
2376
- search.placeholder = "\u641C\u7D22\u753B\u677F\u2026";
2377
- search.addEventListener("input", () => {
2378
- filter = search.value.trim().toLowerCase();
2379
- searchCollapsed = filter ? /* @__PURE__ */ new Set() : null;
2380
- renderSidebar();
2381
- });
2382
- search.addEventListener("keydown", (e) => {
2383
- e.stopPropagation();
2384
- if (e.key === "Escape") {
2385
- search.value = "";
2386
- filter = "";
2387
- searchCollapsed = null;
2388
- renderSidebar();
2389
- search.blur();
2390
- }
2391
- });
2392
- head.append(h("span", "cs-sb-title", "\u753B\u677F"), search);
2393
- listEl = h("div", "cs-sb-list");
2394
- listEl.addEventListener("keydown", (e) => {
2395
- if ((e.key === "Enter" || e.key === " ") && e.target.closest("button")) {
2396
- e.stopPropagation();
2397
- }
2398
- });
2399
- root.append(head, listEl);
2400
- const toggle = qs("#cs-sb-toggle");
2401
- toggle.addEventListener("click", () => {
2402
- const closed = document.body.dataset.sidebar === "off";
2403
- document.body.dataset.sidebar = closed ? "on" : "off";
2404
- toggle.title = closed ? "\u6536\u8D77\u5217\u8868" : "\u5C55\u5F00\u5217\u8868";
2405
- });
2406
- renderSidebar();
2407
- }
2408
- function match(text) {
2409
- return !filter || text.toLowerCase().includes(filter);
2386
+ // src/canvas/modes.ts
2387
+ var viewport2;
2388
+ function boardOf(target) {
2389
+ const el = target?.closest?.(".cs-board");
2390
+ if (!el?.dataset.id) return null;
2391
+ return state.boards.get(el.dataset.id) ?? null;
2410
2392
  }
2411
- function collapseKey() {
2412
- return `cs-collapsed:${state.info?.projectRoot ?? "unknown"}`;
2393
+ function setActive(id) {
2394
+ if (state.activeId === id) return;
2395
+ state.activeId = id;
2396
+ for (const b of state.boards.values()) b.el.classList.toggle("is-active", b.entry.id === id);
2413
2397
  }
2414
- function collapsedSet() {
2415
- try {
2416
- return new Set(JSON.parse(localStorage.getItem(collapseKey()) ?? "[]"));
2417
- } catch {
2418
- return /* @__PURE__ */ new Set();
2398
+ function setMode(mode, boardId) {
2399
+ if (mode !== "browse" && boardId) setActive(boardId);
2400
+ if (mode === "review" && !state.activeId) return;
2401
+ const from = state.mode;
2402
+ state.mode = mode;
2403
+ hideHover();
2404
+ if (mode !== "browse") clearSelection();
2405
+ syncHud();
2406
+ if (mode === "review" && from !== "review") {
2407
+ state.modeBeforeReview = from;
2408
+ state.viewBeforeReview = { scale: state.scale, tx: state.tx, ty: state.ty };
2409
+ closeArgsPanel();
2410
+ fitActiveBoard();
2411
+ } else if (mode !== "review" && from === "review" && state.viewBeforeReview) {
2412
+ const v = state.viewBeforeReview;
2413
+ state.viewBeforeReview = null;
2414
+ setView(v.tx, v.ty, v.scale);
2419
2415
  }
2416
+ setProbe(
2417
+ mode === "interact" ? "\u4EA4\u4E92\u6A21\u5F0F:\u76F4\u63A5\u64CD\u4F5C\u8FD9\u5757\u753B\u677F,Esc \u56DE\u6D4F\u89C8" : mode === "review" ? "\u8D70\u67E5\u6A21\u5F0F:Esc \u9000\u51FA" : "\u79FB\u5230\u753B\u677F\u4E0A\u53CD\u67E5\u5143\u7D20"
2418
+ );
2420
2419
  }
2421
- function isCollapsed(key) {
2422
- return searchCollapsed ? searchCollapsed.has(key) : collapsedSet().has(key);
2420
+ function fitActiveBoard() {
2421
+ const board = state.activeId ? state.boards.get(state.activeId) : null;
2422
+ if (!board) return;
2423
+ const r = board.frameEl.getBoundingClientRect();
2424
+ const worldX = (r.left - state.tx) / state.scale;
2425
+ const worldY = (r.top - state.ty) / state.scale;
2426
+ const vw = window.innerWidth;
2427
+ const vh = window.innerHeight;
2428
+ const s = clamp(Math.min(vw / board.width, vh / board.height), MIN_SCALE, MAX_SCALE);
2429
+ setView((vw - board.width * s) / 2 - worldX * s, (vh - board.height * s) / 2 - worldY * s, s);
2423
2430
  }
2424
- function toggleCollapsed(key) {
2425
- if (searchCollapsed) {
2426
- if (!searchCollapsed.delete(key)) searchCollapsed.add(key);
2431
+ function escape() {
2432
+ if (isComposing()) {
2433
+ closeComposer();
2434
+ exitPinMode();
2427
2435
  return;
2428
2436
  }
2429
- const s = collapsedSet();
2430
- if (s.has(key)) s.delete(key);
2431
- else s.add(key);
2432
- try {
2433
- localStorage.setItem(collapseKey(), JSON.stringify([...s]));
2434
- } catch {
2437
+ if (state.pinPending) {
2438
+ exitPinMode();
2439
+ return;
2435
2440
  }
2441
+ if (selectedPin()) {
2442
+ selectPin(null);
2443
+ return;
2444
+ }
2445
+ if (state.mode === "review") {
2446
+ setMode(state.modeBeforeReview === "review" ? "browse" : state.modeBeforeReview);
2447
+ return;
2448
+ }
2449
+ if (state.mode === "interact") {
2450
+ setMode("browse");
2451
+ return;
2452
+ }
2453
+ clearSelection();
2454
+ closeArgsPanel();
2455
+ setProbe("\u79FB\u5230\u753B\u677F\u4E0A\u53CD\u67E5\u5143\u7D20");
2436
2456
  }
2437
- function bulkMode(ids) {
2438
- const shown = ids.filter((id) => !isHidden(id)).length;
2439
- return { mode: shown === ids.length ? "all" : shown === 0 ? "none" : "some", shown };
2440
- }
2441
- function setGroupHidden(ids, hide) {
2442
- setHidden(ids, hide);
2443
- }
2444
- function focusedKey() {
2445
- const el = document.activeElement;
2446
- return el && listEl.contains(el) ? el.dataset.fk ?? null : null;
2447
- }
2448
- function restoreFocus(key) {
2449
- if (!key) return;
2450
- const el = listEl.querySelector(`[data-fk="${CSS.escape(key)}"]`);
2451
- el?.focus({ preventScroll: true });
2457
+ function isEditable(target) {
2458
+ const el = target;
2459
+ if (!el) return false;
2460
+ const tag = el.tagName;
2461
+ if (tag === "INPUT" || tag === "TEXTAREA" || tag === "SELECT" || el.isContentEditable === true) return true;
2462
+ return el.closest("#cs-sidebar, #cs-args, .cs-picker, #cs-topbar") !== null;
2452
2463
  }
2453
- function renderSidebar() {
2454
- if (!listEl) return;
2455
- const keepFocus = focusedKey();
2456
- listEl.textContent = "";
2457
- uid = 0;
2458
- const screens = state.entries.filter((e) => e.kind === "screen");
2459
- const comps = state.entries.filter((e) => e.kind !== "screen");
2460
- const visScreens = screens.filter((e) => match(e.exportName) || match(e.url ?? ""));
2461
- if (visScreens.length) {
2462
- const g = group("sec:screen", "\u9875\u9762", visScreens.map((e) => e.id), "cs-sb-section");
2463
- for (const e of visScreens) g.body.appendChild(row(e.id, e.exportName, e.url ?? "", "screen"));
2464
- listEl.appendChild(g.el);
2464
+ function onKeyDown(e) {
2465
+ if (e.key === "Escape") {
2466
+ escape();
2467
+ return;
2465
2468
  }
2466
- const byFile = /* @__PURE__ */ new Map();
2467
- for (const e of comps) {
2468
- if (!(match(e.exportName) || match(groupLabel(e.file)))) continue;
2469
- const l = byFile.get(e.file);
2470
- if (l) l.push(e);
2471
- else byFile.set(e.file, [e]);
2469
+ if (isEditable(e.target) || e.metaKey || e.ctrlKey || e.altKey) return;
2470
+ if (e.key === "Enter") {
2471
+ if (selectedPin()) {
2472
+ e.preventDefault();
2473
+ editSelectedPin();
2474
+ return;
2475
+ }
2476
+ if (state.mode !== "review" && state.activeId) {
2477
+ e.preventDefault();
2478
+ setMode("review", state.activeId);
2479
+ }
2480
+ return;
2472
2481
  }
2473
- if (byFile.size) {
2474
- const files = [...byFile.keys()].sort();
2475
- const allIds = files.flatMap((f) => (byFile.get(f) ?? []).map((e) => e.id));
2476
- const g = group("sec:component", "\u7EC4\u4EF6", allIds, "cs-sb-section");
2477
- for (const file of files) {
2478
- const list = byFile.get(file) ?? [];
2479
- const fg = group(`file:${file}`, groupLabel(file), list.map((e) => e.id), "cs-sb-file");
2480
- for (const e of list) fg.body.appendChild(row(e.id, e.exportName, "", "component"));
2481
- g.body.appendChild(fg.el);
2482
+ if (e.key === "Backspace" || e.key === "Delete") {
2483
+ if (selectedPin()) {
2484
+ e.preventDefault();
2485
+ deleteSelectedPin();
2486
+ return;
2482
2487
  }
2483
- listEl.appendChild(g.el);
2488
+ if (state.mode === "browse" && state.activeId) {
2489
+ e.preventDefault();
2490
+ const b = state.boards.get(state.activeId);
2491
+ setHidden([state.activeId], true);
2492
+ clearSelection();
2493
+ setActive(null);
2494
+ toast(`\u5DF2\u6536\u8D77\u300C${b?.entry.exportName ?? state.activeId}\u300D\xB7 \u5DE6\u4FA7\u5217\u8868\u968F\u65F6\u70B9\u56DE\u6765`, "info");
2495
+ }
2496
+ return;
2484
2497
  }
2485
- if (!listEl.childElementCount) {
2486
- listEl.appendChild(h("div", "cs-sb-empty", filter ? "\u6CA1\u6709\u5339\u914D\u7684\u753B\u677F" : "\u8FD8\u6CA1\u6709\u753B\u677F"));
2498
+ if (e.key === "c" || e.key === "C") {
2499
+ if (state.mode === "browse" && !state.pinPending) {
2500
+ e.preventDefault();
2501
+ enterPinMode();
2502
+ }
2503
+ return;
2504
+ }
2505
+ if (e.key === "y" || e.key === "Y") {
2506
+ e.preventDefault();
2507
+ void copyContext();
2508
+ return;
2509
+ }
2510
+ if (e.key === "p") {
2511
+ e.preventDefault();
2512
+ void pushToClaudeCode();
2513
+ return;
2514
+ }
2515
+ if (e.key === "1") {
2516
+ e.preventDefault();
2517
+ fitAll();
2518
+ return;
2519
+ }
2520
+ if (e.key === "2") {
2521
+ e.preventDefault();
2522
+ fitBoard();
2523
+ return;
2524
+ }
2525
+ if (e.key === "0") {
2526
+ e.preventDefault();
2527
+ zoomReset();
2528
+ return;
2529
+ }
2530
+ if (e.key === "=" || e.key === "+") {
2531
+ e.preventDefault();
2532
+ zoomStep(1.25);
2533
+ return;
2534
+ }
2535
+ if (e.key === "-" || e.key === "_") {
2536
+ e.preventDefault();
2537
+ zoomStep(0.8);
2487
2538
  }
2488
- syncActive();
2489
- restoreFocus(keepFocus);
2490
2539
  }
2491
- function group(key, label, ids, headCls) {
2492
- const el = h("div", "cs-sb-group");
2493
- if (headCls === "cs-sb-file") el.classList.add("is-file");
2494
- const headEl = h("div", headCls);
2495
- const body = h("div", "cs-sb-items");
2496
- body.id = `cs-sb-g${++uid}`;
2497
- const collapsed = isCollapsed(key);
2498
- body.hidden = collapsed;
2499
- const { mode, shown } = bulkMode(ids);
2500
- const caret = h("button", "cs-sb-caret");
2501
- caret.type = "button";
2502
- caret.dataset.fk = `caret:${key}`;
2503
- caret.setAttribute("aria-expanded", String(!collapsed));
2504
- caret.setAttribute("aria-controls", body.id);
2505
- caret.title = collapsed ? `\u5C55\u5F00\u300C${label}\u300D` : `\u6298\u53E0\u300C${label}\u300D`;
2506
- const arrow = h("span", "cs-sb-arrow", "\u25B8");
2507
- arrow.setAttribute("aria-hidden", "true");
2508
- const count = h("span", "cs-sb-count", mode === "all" ? String(ids.length) : `${shown}/${ids.length}`);
2509
- caret.append(arrow, h("span", "cs-sb-label", label), count);
2510
- caret.addEventListener("click", () => {
2511
- toggleCollapsed(key);
2512
- renderSidebar();
2513
- });
2514
- const bulk = h("button", "cs-sb-bulk", mode === "all" ? "\u25CF" : mode === "none" ? "\u25CC" : "\u25D0");
2515
- bulk.type = "button";
2516
- bulk.dataset.fk = `bulk:${key}`;
2517
- bulk.dataset.state = mode;
2518
- bulk.setAttribute("aria-pressed", mode === "all" ? "true" : mode === "none" ? "false" : "mixed");
2519
- const act = mode === "all" ? `\u9690\u85CF\u300C${label}\u300D\u4E0B\u5168\u90E8 ${ids.length} \u5757\u753B\u677F` : `\u663E\u793A\u300C${label}\u300D\u4E0B\u5168\u90E8 ${ids.length} \u5757\u753B\u677F`;
2520
- bulk.setAttribute("aria-label", act);
2521
- bulk.title = mode === "some" ? `${act}(\u5F53\u524D ${shown}/${ids.length} \u663E\u793A\u4E2D)` : act;
2522
- bulk.addEventListener("click", (e) => {
2523
- e.stopPropagation();
2524
- setGroupHidden(ids, mode === "all");
2525
- renderSidebar();
2526
- });
2527
- headEl.append(caret, bulk);
2528
- el.append(headEl, body);
2529
- return { el, body };
2540
+ function attachFrameKeys(doc) {
2541
+ doc.addEventListener("keydown", onKeyDown);
2530
2542
  }
2531
- function row(id, name, sub, kind) {
2532
- const el = h("div", "cs-sb-row");
2533
- el.dataset.id = id;
2534
- const go = h("button", "cs-sb-go");
2535
- go.type = "button";
2536
- go.dataset.fk = `go:${id}`;
2537
- go.dataset.kind = kind;
2538
- go.title = sub ? `\u805A\u7126 ${name} (${sub})` : `\u805A\u7126 ${name}`;
2539
- go.append(h("span", "cs-sb-dot"), h("span", "cs-sb-name", name));
2540
- if (sub) go.appendChild(h("span", "cs-sb-route", sub));
2541
- go.addEventListener("click", () => {
2542
- if (isHidden(id)) toggleHidden(id);
2543
- fitBoard(id);
2544
- state.activeId = id;
2545
- syncActive();
2546
- });
2547
- const eye = h("button", "cs-sb-eye", isHidden(id) ? "\u25CC" : "\u25CF");
2548
- eye.type = "button";
2549
- eye.dataset.fk = `eye:${id}`;
2550
- eye.title = isHidden(id) ? "\u663E\u793A\u8FD9\u5757\u753B\u677F" : "\u4ECE\u5899\u4E0A\u9690\u85CF(\u4E0D\u5220,\u968F\u65F6\u70B9\u56DE\u6765)";
2551
- eye.setAttribute("aria-label", isHidden(id) ? `\u663E\u793A ${name}` : `\u9690\u85CF ${name}`);
2552
- eye.setAttribute("aria-pressed", String(!isHidden(id)));
2553
- eye.addEventListener("click", (e) => {
2554
- e.stopPropagation();
2555
- toggleHidden(id);
2556
- renderSidebar();
2557
- });
2558
- el.append(go, eye);
2559
- if (isHidden(id)) el.classList.add("is-hidden-board");
2560
- return el;
2543
+ function onMouseMove(e) {
2544
+ if (isPanning()) return;
2545
+ const overlay = e.target?.closest?.(".cs-ovl");
2546
+ const board = overlay ? boardOf(e.target) : null;
2547
+ if (!board) {
2548
+ hideHover();
2549
+ return;
2550
+ }
2551
+ if (state.mode !== "browse") return;
2552
+ setActive(board.entry.id);
2553
+ const el = elementAt(board, e.clientX, e.clientY);
2554
+ if (!el) {
2555
+ hideHover();
2556
+ return;
2557
+ }
2558
+ showHover(board, el);
2559
+ setProbe(`${board.entry.exportName} ${describe(el)}`);
2561
2560
  }
2562
- function syncActive() {
2563
- if (!listEl) return;
2564
- for (const r of listEl.querySelectorAll(".cs-sb-row")) {
2565
- r.classList.toggle("is-active", r.dataset.id === state.activeId);
2561
+ function onClick(e) {
2562
+ const target = e.target;
2563
+ const title = target?.closest?.(".cs-board-title");
2564
+ if (title) {
2565
+ const board2 = boardOf(target);
2566
+ if (!board2) return;
2567
+ setActive(board2.entry.id);
2568
+ if (isArgsOpen(board2.entry.id)) closeArgsPanel();
2569
+ else openArgsPanel(board2);
2570
+ return;
2571
+ }
2572
+ const overlay = target?.closest?.(".cs-ovl");
2573
+ if (!overlay) return;
2574
+ const board = boardOf(target);
2575
+ if (!board) return;
2576
+ if (state.mode === "interact") {
2577
+ if (board.entry.id !== state.activeId) hintInactive(board);
2578
+ return;
2566
2579
  }
2580
+ setActive(board.entry.id);
2581
+ const el = elementAt(board, e.clientX, e.clientY);
2582
+ if (!el) return;
2583
+ if (state.pinPending) {
2584
+ openComposer(board, el, e.clientX, e.clientY);
2585
+ return;
2586
+ }
2587
+ if (state.mode !== "browse") return;
2588
+ const selector = buildSelector(el);
2589
+ const rel = relPos(el, e.clientX, e.clientY, board);
2590
+ showSelection(board, selector);
2591
+ state.selection = { artboardId: board.entry.id, selector, x: rel.x, y: rel.y, ts: Date.now() };
2592
+ setProbe(`\u5DF2\u9009\u4E2D ${board.entry.exportName} \u2192 ${selector}`);
2593
+ postSelection(state.selection).catch((err) => setProbe(`selection \u53D1\u9001\u5931\u8D25:${String(err)}`));
2594
+ }
2595
+ var lastHint = "";
2596
+ var lastHintAt = 0;
2597
+ function hintInactive(board) {
2598
+ const cur = state.boards.get(state.activeId ?? "")?.entry.exportName ?? "?";
2599
+ setProbe(`\u4EA4\u4E92\u4E2D\u7684\u662F\u300C${cur}\u300D\u2014\u2014 \u8981\u4EA4\u4E92\u8FD9\u5757\u8BF7\u53CC\u51FB\u5B83,Esc \u9000\u51FA\u4EA4\u4E92`);
2600
+ const now = Date.now();
2601
+ if (lastHint === board.entry.id && now - lastHintAt < 4e3) return;
2602
+ lastHint = board.entry.id;
2603
+ lastHintAt = now;
2604
+ toast(`\u300C${board.entry.exportName}\u300D\u672A\u6FC0\u6D3B \xB7 \u53CC\u51FB\u5B83\u5207\u6362\u4EA4\u4E92\u76EE\u6807`, "info");
2605
+ }
2606
+ function onDblClick(e) {
2607
+ const board = boardOf(e.target);
2608
+ if (!board || !e.target.closest(".cs-ovl")) return;
2609
+ setMode("interact", board.entry.id);
2610
+ }
2611
+ function initModes() {
2612
+ viewport2 = qs("#cs-viewport");
2613
+ viewport2.addEventListener("mousemove", onMouseMove);
2614
+ viewport2.addEventListener("mouseleave", hideHover);
2615
+ viewport2.addEventListener("click", onClick);
2616
+ viewport2.addEventListener("dblclick", onDblClick);
2617
+ document.addEventListener("keydown", onKeyDown);
2618
+ window.addEventListener("resize", () => {
2619
+ if (state.mode === "review") fitActiveBoard();
2620
+ else applyView();
2621
+ });
2622
+ syncHud();
2567
2623
  }
2568
2624
 
2569
2625
  // src/canvas/app.ts