@youtyan/code-viewer 0.5.4 → 0.5.5

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.
Files changed (3) hide show
  1. package/package.json +1 -1
  2. package/web/app.js +59 -22
  3. package/web/style.css +106 -26
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@youtyan/code-viewer",
3
- "version": "0.5.4",
3
+ "version": "0.5.5",
4
4
  "description": "Local browser-based code and git diff viewer",
5
5
  "type": "module",
6
6
  "bin": {
package/web/app.js CHANGED
@@ -9637,14 +9637,10 @@ ${frontmatter.yaml}
9637
9637
  calculatingDiff: "Computing diff…",
9638
9638
  diffError: "Failed to compute diff",
9639
9639
  noChanges: "No changes detected.",
9640
- before: "Before",
9641
9640
  loading: "Loading…",
9642
9641
  loadError: "Failed to load",
9643
9642
  saveNote: "Save",
9644
9643
  noteInputPlaceholder: "Enter a note",
9645
- inserted: "Added",
9646
- updated: "Updated",
9647
- deleted: "Deleted",
9648
9644
  tableCount: (n2) => `${n2} tables`,
9649
9645
  rowCount: (n2) => ` (${n2})`,
9650
9646
  listTitle: (n2) => `Snapshots (${n2})`,
@@ -9926,14 +9922,10 @@ ${frontmatter.yaml}
9926
9922
  calculatingDiff: "差分を計算中...",
9927
9923
  diffError: "差分の計算に失敗しました",
9928
9924
  noChanges: "変更は検出されませんでした。",
9929
- before: "更新前",
9930
9925
  loading: "読み込み中...",
9931
9926
  loadError: "読み込みに失敗しました",
9932
9927
  saveNote: "保存",
9933
9928
  noteInputPlaceholder: "メモを入力",
9934
- inserted: "追加",
9935
- updated: "更新",
9936
- deleted: "削除",
9937
9929
  tableCount: (n2) => `${n2}テーブル`,
9938
9930
  rowCount: (n2) => ` (${n2}件)`,
9939
9931
  listTitle: (n2) => `スナップショット (${n2}件)`,
@@ -14551,10 +14543,8 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
14551
14543
  const getJson = (path) => trackLoad(fetch(path));
14552
14544
  const changeTypeLabel = (type) => {
14553
14545
  if (type === "inserted")
14554
- return text2().snapshot.inserted;
14555
- if (type === "updated")
14556
- return text2().snapshot.updated;
14557
- return text2().snapshot.deleted;
14546
+ return "+";
14547
+ return "";
14558
14548
  };
14559
14549
  const snapshotLabel = (s2) => {
14560
14550
  const date = new Date(s2.createdAt).toLocaleString();
@@ -15360,11 +15350,19 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
15360
15350
  allCols.add(k);
15361
15351
  }
15362
15352
  const columns = [...allCols];
15363
- const table2 = document.createElement("table");
15364
- table2.className = "db-snap-diff-grid";
15353
+ const colHeaderWrap = document.createElement("div");
15354
+ colHeaderWrap.className = "db-snapshot-diff-col-header-wrap";
15355
+ const colHeaderTable = document.createElement("table");
15356
+ colHeaderTable.className = "db-snap-diff-grid";
15357
+ const colHeaderColgroup = document.createElement("colgroup");
15358
+ for (let i2 = 0;i2 < columns.length + 1; i2++) {
15359
+ colHeaderColgroup.appendChild(document.createElement("col"));
15360
+ }
15361
+ colHeaderTable.appendChild(colHeaderColgroup);
15365
15362
  const thead = document.createElement("thead");
15366
15363
  const headRow = document.createElement("tr");
15367
15364
  const thType = document.createElement("th");
15365
+ thType.className = "snap-diff-type-cell";
15368
15366
  thType.textContent = "";
15369
15367
  headRow.appendChild(thType);
15370
15368
  for (const col of columns) {
@@ -15373,16 +15371,25 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
15373
15371
  headRow.appendChild(th);
15374
15372
  }
15375
15373
  thead.appendChild(headRow);
15376
- table2.appendChild(thead);
15374
+ colHeaderTable.appendChild(thead);
15375
+ colHeaderWrap.appendChild(colHeaderTable);
15376
+ const rowsViewport = document.createElement("div");
15377
+ rowsViewport.className = "db-snapshot-diff-rows-viewport";
15378
+ const bodyTable = document.createElement("table");
15379
+ bodyTable.className = "db-snap-diff-grid";
15380
+ const bodyColgroup = document.createElement("colgroup");
15381
+ for (let i2 = 0;i2 < columns.length + 1; i2++) {
15382
+ bodyColgroup.appendChild(document.createElement("col"));
15383
+ }
15384
+ bodyTable.appendChild(bodyColgroup);
15377
15385
  const tbody = document.createElement("tbody");
15378
15386
  for (const row of rows) {
15379
15387
  if (row.changeType === "updated" && row.beforeValues && row.afterValues) {
15380
15388
  const trBefore = document.createElement("tr");
15381
- trBefore.className = "snap-diff-del";
15389
+ trBefore.className = "snap-diff-del snap-diff-record-start";
15382
15390
  const tdTypeBefore = document.createElement("td");
15383
15391
  tdTypeBefore.className = "snap-diff-type-cell";
15384
- tdTypeBefore.textContent = text2().snapshot.before;
15385
- tdTypeBefore.rowSpan = 2;
15392
+ tdTypeBefore.textContent = "−";
15386
15393
  trBefore.appendChild(tdTypeBefore);
15387
15394
  for (const col of columns) {
15388
15395
  const td = document.createElement("td");
@@ -15395,7 +15402,11 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
15395
15402
  }
15396
15403
  tbody.appendChild(trBefore);
15397
15404
  const trAfter = document.createElement("tr");
15398
- trAfter.className = "snap-diff-add";
15405
+ trAfter.className = "snap-diff-add snap-diff-record-end";
15406
+ const tdTypeAfter = document.createElement("td");
15407
+ tdTypeAfter.className = "snap-diff-type-cell";
15408
+ tdTypeAfter.textContent = "+";
15409
+ trAfter.appendChild(tdTypeAfter);
15399
15410
  for (const col of columns) {
15400
15411
  const td = document.createElement("td");
15401
15412
  const bs = row.beforeValues[col] == null ? "NULL" : String(row.beforeValues[col]);
@@ -15408,7 +15419,8 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
15408
15419
  tbody.appendChild(trAfter);
15409
15420
  } else {
15410
15421
  const tr = document.createElement("tr");
15411
- tr.className = row.changeType === "inserted" ? "snap-diff-add" : "snap-diff-del";
15422
+ const sideClass = row.changeType === "inserted" ? "snap-diff-add" : "snap-diff-del";
15423
+ tr.className = `${sideClass} snap-diff-record-start snap-diff-record-end`;
15412
15424
  const tdType = document.createElement("td");
15413
15425
  tdType.className = "snap-diff-type-cell";
15414
15426
  tdType.textContent = changeTypeLabel(row.changeType);
@@ -15416,6 +15428,7 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
15416
15428
  const values = row.changeType === "inserted" ? row.afterValues : row.beforeValues;
15417
15429
  for (const col of columns) {
15418
15430
  const td = document.createElement("td");
15431
+ td.classList.add("snap-diff-changed-cell");
15419
15432
  const v = values?.[col];
15420
15433
  td.textContent = v == null ? "NULL" : String(v);
15421
15434
  tr.appendChild(td);
@@ -15423,8 +15436,32 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
15423
15436
  tbody.appendChild(tr);
15424
15437
  }
15425
15438
  }
15426
- table2.appendChild(tbody);
15427
- container.appendChild(table2);
15439
+ bodyTable.appendChild(tbody);
15440
+ rowsViewport.appendChild(bodyTable);
15441
+ container.append(colHeaderWrap, rowsViewport);
15442
+ rowsViewport.addEventListener("scroll", () => {
15443
+ colHeaderWrap.scrollLeft = rowsViewport.scrollLeft;
15444
+ });
15445
+ const syncColumnWidths = () => {
15446
+ if (disposed)
15447
+ return;
15448
+ const firstRow = tbody.querySelector("tr");
15449
+ if (!firstRow)
15450
+ return;
15451
+ const cells = firstRow.children;
15452
+ const bodyCols = bodyColgroup.children;
15453
+ const headerCols = colHeaderColgroup.children;
15454
+ for (let i2 = 0;i2 < cells.length; i2++) {
15455
+ const w = cells[i2].offsetWidth;
15456
+ if (bodyCols[i2])
15457
+ bodyCols[i2].style.width = `${w}px`;
15458
+ if (headerCols[i2])
15459
+ headerCols[i2].style.width = `${w}px`;
15460
+ }
15461
+ };
15462
+ requestAnimationFrame(syncColumnWidths);
15463
+ const ro = new ResizeObserver(syncColumnWidths);
15464
+ ro.observe(bodyTable);
15428
15465
  if (total > rows.length) {
15429
15466
  const more = document.createElement("div");
15430
15467
  more.className = "db-snapshot-diff-more";
package/web/style.css CHANGED
@@ -7885,6 +7885,11 @@ body.db-resizing {
7885
7885
  flex: 1;
7886
7886
  min-width: 0;
7887
7887
  overflow-y: auto;
7888
+ /* 横スクロールはペインで受けない。各テーブルの .db-snapshot-diff-rows-viewport
7889
+ * (overflow-x:auto) が body 行だけを横スクロールする。ペインで両軸 scroll に
7890
+ * すると目次バー / テーブル名バー / 列ヘッダの sticky が横スクロールで全部
7891
+ * 流れて積層がぐちゃぐちゃになるので明示的に hidden にする。 */
7892
+ overflow-x: hidden;
7888
7893
  border: 1px solid var(--border);
7889
7894
  border-radius: 6px;
7890
7895
  background: var(--bg);
@@ -7892,12 +7897,20 @@ body.db-resizing {
7892
7897
  * left/right padding を取らず、内部の .db-snapshot-diff-inline 側で
7893
7898
  * inset を持たせる。toc だけ pane の端まで広がる。 */
7894
7899
  padding: 0;
7895
- /* sticky な目次バーの高さぶん、scrollIntoView の停止位置を下げる。
7896
- * これがないと chip クリック時に section が toc 裏に隠れて「中途半端な
7897
- * 位置で止まる」状態になる。 */
7898
- scroll-padding-top: 68px;
7900
+ /* sticky 帯 (目次バー + テーブル名 + 列ヘッダ) ぶん、scrollIntoView
7901
+ * 停止位置を下げる。chip クリック時に section が sticky 裏に隠れて
7902
+ * 「中途半端な位置で止まる」のを防ぐ。 */
7903
+ scroll-padding-top: calc(
7904
+ var(--snap-diff-toc-h) + var(--snap-diff-table-header-h) +
7905
+ var(--snap-diff-col-header-h)
7906
+ );
7899
7907
  }
7900
7908
  .db-snapshot-diff-inline {
7909
+ /* sticky 3 段 (目次 / テーブル名 / 列ヘッダ) の高さを CSS 変数で持ち、
7910
+ * top 計算と scroll-padding-top を一箇所で管理する。 */
7911
+ --snap-diff-toc-h: 40px;
7912
+ --snap-diff-table-header-h: 34px;
7913
+ --snap-diff-col-header-h: 30px;
7901
7914
  padding: 0 12px 12px;
7902
7915
  }
7903
7916
  .db-snapshot-right-pane:empty::before {
@@ -7962,7 +7975,7 @@ body.db-resizing {
7962
7975
  .db-snapshot-diff-toc {
7963
7976
  position: sticky;
7964
7977
  top: 0;
7965
- z-index: 2;
7978
+ z-index: 30;
7966
7979
  display: flex;
7967
7980
  gap: 6px;
7968
7981
  padding: 8px 12px;
@@ -7974,6 +7987,8 @@ body.db-resizing {
7974
7987
  overflow-y: hidden;
7975
7988
  white-space: nowrap;
7976
7989
  scroll-padding: 0 12px;
7990
+ height: var(--snap-diff-toc-h);
7991
+ box-sizing: border-box;
7977
7992
  }
7978
7993
  .db-snapshot-diff-toc-chip {
7979
7994
  flex: 0 0 auto;
@@ -8474,10 +8489,12 @@ body.db-resizing {
8474
8489
  font-size: var(--db-font-base);
8475
8490
  }
8476
8491
  .db-snapshot-diff-table {
8492
+ /* 各テーブルは「テーブル名 (sticky)」「列ヘッダ (sticky, scrollLeft 同期)」
8493
+ * 「rows-viewport (overflow-x:auto)」の 3 層構造。
8494
+ * overflow:hidden は付けない (内部 sticky を殺す)。 */
8477
8495
  border: 1px solid var(--border);
8478
8496
  border-radius: 6px;
8479
8497
  margin-bottom: 8px;
8480
- overflow: hidden;
8481
8498
  }
8482
8499
  .db-snapshot-diff-table-header {
8483
8500
  display: flex;
@@ -8487,24 +8504,50 @@ body.db-resizing {
8487
8504
  background: var(--bg-soft);
8488
8505
  font-size: var(--db-font-base);
8489
8506
  cursor: pointer;
8507
+ /* 目次バーのすぐ下に隙間ゼロで張り付く。 */
8508
+ position: sticky;
8509
+ top: var(--snap-diff-toc-h);
8510
+ z-index: 20;
8511
+ height: var(--snap-diff-table-header-h);
8512
+ box-sizing: border-box;
8513
+ border-top-left-radius: 6px;
8514
+ border-top-right-radius: 6px;
8490
8515
  }
8491
8516
  .db-snapshot-diff-table-header:hover {
8492
8517
  background: var(--bg-mute);
8493
8518
  }
8519
+ .db-snapshot-diff-col-header-wrap {
8520
+ /* 列ヘッダ専用 wrap。rows-viewport (overflow-x:auto) の scrollLeft を JS で
8521
+ * 同期して、横スクロール時もデータ列と列名の位置が揃うようにする。
8522
+ * 自身は overflow:hidden で内部 table が右にはみ出ても scrollbar を出さない。
8523
+ * 上端は table-header (背景同色 var(--bg-soft)) と地続きに見せたいので
8524
+ * border-top は持たない。下端 1px だけが列ヘッダと body の境界を描く。 */
8525
+ position: sticky;
8526
+ top: calc(
8527
+ var(--snap-diff-toc-h) + var(--snap-diff-table-header-h) - 1px
8528
+ );
8529
+ z-index: 15;
8530
+ height: var(--snap-diff-col-header-h);
8531
+ box-sizing: border-box;
8532
+ overflow: hidden;
8533
+ background: var(--bg-soft);
8534
+ border-bottom: 1px solid var(--border);
8535
+ }
8536
+ .db-snapshot-diff-rows-viewport {
8537
+ /* body 行だけが横スクロール対象。縦は親 (.db-snapshot-right-pane) が受ける。 */
8538
+ overflow-x: auto;
8539
+ overflow-y: visible;
8540
+ }
8494
8541
  .db-snapshot-diff-table-name {
8495
8542
  font-weight: 600;
8496
8543
  }
8497
8544
  .db-snapshot-diff-table-stats {
8498
8545
  color: var(--fg-muted);
8499
8546
  }
8500
- .db-snapshot-diff-rows-container {
8501
- /* (.db-snapshot-right-pane) が縦スクロール責務を一手に持つので、
8502
- * ここでは縦方向は visible のまま。max-height + overflow-y を持っていた
8503
- * 旧実装は、マウスホイールが grid に乗ると親に伝播せず「親スクロール
8504
- * できない」操作不能の元だった (nested scroll trap)。
8505
- * 横に広い diff grid のためにヨコだけ auto を残す。 */
8506
- overflow-x: auto;
8507
- }
8547
+ /* (旧) .db-snapshot-diff-rows-container は廃止。新構造では
8548
+ * .db-snapshot-diff-rows-viewport (overflow-x:auto) が body 行の横スクロールを
8549
+ * 担い、列ヘッダは .db-snapshot-diff-col-header-wrap に分離されて
8550
+ * scrollLeft 同期で追従する。 */
8508
8551
  /* (deleted) 旧 row-based diff レイアウトの dead CSS を削除した。
8509
8552
  * - .db-snapshot-diff-row / .db-snapshot-diff-type / .db-snapshot-diff-key
8510
8553
  * - .db-snapshot-diff-changes / .db-snapshot-diff-values
@@ -8526,37 +8569,59 @@ body.db-resizing {
8526
8569
  font-size: var(--db-font-base);
8527
8570
  }
8528
8571
 
8529
- /* Diff grid table */
8572
+ /* Diff grid table
8573
+ * 構造: 各テーブルにつき 2 つの <table class="db-snap-diff-grid"> を作る。
8574
+ * - 列ヘッダ用 (thead のみ、.db-snapshot-diff-col-header-wrap 内)
8575
+ * - body 用 (tbody のみ、.db-snapshot-diff-rows-viewport 内)
8576
+ * 両 table に同じ <colgroup> を入れ、JS (ResizeObserver) で body 側の各 col
8577
+ * 実幅を測って header 側に同じ width を反映する。これで横スクロール時も
8578
+ * 列ヘッダとデータ列が完全に揃う。
8579
+ * border-collapse:separate にする理由: collapse だと隣接 border をセル側が
8580
+ * 描く仕様で、sticky 化やテーブル分割と相性が悪く subpixel 透けの原因になる。 */
8530
8581
  .db-snap-diff-grid {
8531
- width: 100%;
8532
- border-collapse: collapse;
8582
+ min-width: 100%;
8583
+ width: max-content;
8584
+ border-collapse: separate;
8585
+ border-spacing: 0;
8533
8586
  font-size: var(--db-font-base);
8534
8587
  table-layout: auto;
8535
8588
  }
8536
8589
  .db-snap-diff-grid th {
8537
- position: sticky;
8538
- top: 0;
8590
+ /* 列ヘッダの下端は col-header-wrap.border-bottom が描くので th 側では持たない。
8591
+ * 縦罫線は持たない (gutter と data 列の境界は type-cell.border-right が担う)。 */
8539
8592
  background: var(--bg-soft);
8540
- border: 1px solid var(--border);
8541
8593
  padding: 4px 8px;
8542
8594
  font-weight: 600;
8543
8595
  text-align: left;
8544
8596
  white-space: nowrap;
8545
- z-index: 1;
8546
8597
  }
8547
8598
  .db-snap-diff-grid td {
8548
- border: 1px solid var(--border);
8599
+ /* 縦罫線は持たない。横罫線のみで行を区切る (GitHub PR diff 同様)
8600
+ * gutter と data 列の境界は type-cell.border-right が担う。 */
8601
+ border-bottom: 1px solid var(--border);
8549
8602
  padding: 3px 8px;
8550
8603
  max-width: 300px;
8551
8604
  overflow: hidden;
8552
8605
  text-overflow: ellipsis;
8553
8606
  white-space: nowrap;
8554
8607
  }
8555
- .db-snap-diff-grid tr.snap-diff-add {
8556
- background: var(--diff-add-bg);
8608
+ .db-snap-diff-grid tbody tr:last-child td {
8609
+ border-bottom: 0;
8557
8610
  }
8558
- .db-snap-diff-grid tr.snap-diff-del {
8559
- background: var(--diff-del-bg);
8611
+ /* 1 レコード境界の視覚化:
8612
+ * - updated は trBefore(snap-diff-record-start) + trAfter(snap-diff-record-end) の 2 行
8613
+ * - inserted/deleted は 1 行に snap-diff-record-start snap-diff-record-end 両方
8614
+ * 行間の通常 border (1px var(--border)) よりはっきり濃く太くしないと、ペア内
8615
+ * の - と + の境目とレコード境界が区別できない。
8616
+ * - 横境界: 2px var(--fg-muted) (灰色の濃い線)
8617
+ * - gutter 縦印: inset 3px var(--fg-muted) で record の左に bracket 風
8618
+ * (実 border ではないので border-collapse / 列幅同期に影響しない) */
8619
+ .db-snap-diff-grid tr.snap-diff-record-end td {
8620
+ border-bottom: 2px solid var(--fg-muted);
8621
+ }
8622
+ .db-snap-diff-grid tr.snap-diff-record-start td:first-child,
8623
+ .db-snap-diff-grid tr.snap-diff-record-end td:first-child {
8624
+ box-shadow: inset 3px 0 0 var(--fg-muted);
8560
8625
  }
8561
8626
  .db-snap-diff-grid .snap-diff-type-cell {
8562
8627
  font-weight: 600;
@@ -8565,10 +8630,25 @@ body.db-resizing {
8565
8630
  background: var(--bg-soft);
8566
8631
  text-align: center;
8567
8632
  min-width: 50px;
8633
+ /* gutter と data 列の境界は唯一ここの border-right が描く。
8634
+ * 他のセルは縦罫線を持たない。 */
8635
+ border-right: 1px solid var(--border);
8636
+ }
8637
+ .db-snap-diff-grid tr.snap-diff-del .snap-diff-type-cell {
8638
+ color: var(--danger);
8639
+ }
8640
+ .db-snap-diff-grid tr.snap-diff-add .snap-diff-type-cell {
8641
+ color: var(--success);
8568
8642
  }
8569
8643
  .db-snap-diff-grid .snap-diff-changed-cell {
8570
8644
  font-weight: 600;
8571
8645
  }
8646
+ .db-snap-diff-grid tr.snap-diff-del .snap-diff-changed-cell {
8647
+ background: var(--diff-del-bg);
8648
+ }
8649
+ .db-snap-diff-grid tr.snap-diff-add .snap-diff-changed-cell {
8650
+ background: var(--diff-add-bg);
8651
+ }
8572
8652
 
8573
8653
  /* Inline dialog (replaces confirm/prompt) */
8574
8654
  .db-snapshot-inline-dialog {