@kud/gh-ink 0.16.0 → 0.16.2

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 (2) hide show
  1. package/dist/index.js +80 -28
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -1566,11 +1566,11 @@ var RepoHeaderRow = ({ repo, gap }) => {
1566
1566
  const fill = Math.max(4, 46 - label.length);
1567
1567
  return /* @__PURE__ */ jsx(Box, { marginTop: gap ? 1 : 0, children: /* @__PURE__ */ jsx(Text2, { dimColor: true, children: " " + label + "\u2500".repeat(fill) }) });
1568
1568
  };
1569
- var MERGED_HOLD_MS = 3e3;
1569
+ var MERGED_HOLD_MS = 5e3;
1570
1570
  var MERGED_FRAME_MS = 150;
1571
1571
  var MERGED_FRAMES = ["\u2726", "\u2727", "\u2736", "\u2727"];
1572
1572
  var MERGED_COLOUR = "#A371F7";
1573
- var TRANSIT_HOLD_MS = 2500;
1573
+ var TRANSIT_HOLD_MS = 7e3;
1574
1574
  var NO_TRANSIENTS = /* @__PURE__ */ new Map();
1575
1575
  var TRANSIT_OUT_FRAMES = ["\u25C9", "\u25CE", "\u25CB", "\xB7"];
1576
1576
  var TRANSIT_IN_FRAMES = ["\xB7", "\u25CB", "\u25CE", "\u25C9"];
@@ -1760,35 +1760,48 @@ var legendLayout = (cols, available) => {
1760
1760
  if (layoutWidth(stacked) <= available) return stacked;
1761
1761
  return [cols];
1762
1762
  };
1763
+ var groupLines = (group) => group.flatMap((col, index) => {
1764
+ const cellWidth = cellWidthOf(col);
1765
+ const head = index === 0 ? [] : [{ kind: "gap" }];
1766
+ return [
1767
+ ...head,
1768
+ { kind: "title", text: col.title },
1769
+ ...col.rows.map((row) => ({ kind: "row", row, cellWidth }))
1770
+ ];
1771
+ });
1772
+ var scrolledPastTitle = (lines, offset) => {
1773
+ let seen = null;
1774
+ for (let i = 0; i <= offset && i < lines.length; i++) {
1775
+ const line = lines[i];
1776
+ if (line.kind === "title") seen = i === offset ? null : line.text;
1777
+ }
1778
+ return seen;
1779
+ };
1763
1780
  var LegendGroup = ({
1764
- group,
1781
+ lines,
1782
+ pinned,
1765
1783
  width,
1766
1784
  marginRight
1767
- }) => /* @__PURE__ */ jsx(Box, { flexDirection: "column", width, marginRight, children: group.map((col, index) => {
1768
- const cellWidth = cellWidthOf(col);
1769
- return /* @__PURE__ */ jsxs(
1770
- Box,
1771
- {
1772
- flexDirection: "column",
1773
- marginTop: index === 0 ? 0 : 1,
1774
- children: [
1775
- /* @__PURE__ */ jsx(Text2, { bold: true, dimColor: true, children: col.title }),
1776
- col.rows.map((row) => /* @__PURE__ */ jsxs(Box, { children: [
1777
- /* @__PURE__ */ jsx(Text2, { color: row.color, bold: row.bold, children: row.cell.padEnd(cellWidth) }),
1778
- /* @__PURE__ */ jsx(Text2, { wrap: "truncate-end", children: row.label })
1779
- ] }, col.title + row.cell + row.label))
1780
- ]
1781
- },
1782
- col.title
1783
- );
1784
- }) });
1785
+ }) => /* @__PURE__ */ jsxs(Box, { flexDirection: "column", width, marginRight, children: [
1786
+ pinned !== void 0 ? /* @__PURE__ */ jsx(Text2, { bold: true, dimColor: true, children: pinned ?? " " }) : null,
1787
+ lines.map(
1788
+ (line, index) => line.kind === "gap" ? /* @__PURE__ */ jsx(Text2, { children: " " }, index) : line.kind === "title" ? /* @__PURE__ */ jsx(Text2, { bold: true, dimColor: true, children: line.text }, index) : /* @__PURE__ */ jsxs(Box, { children: [
1789
+ /* @__PURE__ */ jsx(Text2, { color: line.row.color, bold: line.row.bold, children: line.row.cell.padEnd(line.cellWidth) }),
1790
+ /* @__PURE__ */ jsx(Text2, { wrap: "truncate-end", children: line.row.label })
1791
+ ] }, index)
1792
+ )
1793
+ ] });
1794
+ var LEGEND_FOOTNOTE = "Each item lands in the FIRST tab that claims it, so counts are residuals rather than totals.";
1785
1795
  var HelpModal = ({
1786
1796
  workToggle,
1787
1797
  extensions,
1788
1798
  hasJira,
1789
- tabHelp
1799
+ tabHelp,
1800
+ maxRows,
1801
+ scroll = 0,
1802
+ onScrollRange
1790
1803
  }) => {
1791
- const { columns } = useWindowSize();
1804
+ const { columns, rows } = useWindowSize();
1792
1805
  const available = Math.max(
1793
1806
  20,
1794
1807
  columns - FRAME_CHROME_COLS - MODAL_CHROME_COLS
@@ -1857,6 +1870,28 @@ var HelpModal = ({
1857
1870
  const groups = legendLayout(cols, available);
1858
1871
  const widths = groups.map((g) => Math.min(groupWidth(g), available));
1859
1872
  const contentWidth = Math.min(available, layoutWidth(groups));
1873
+ const lines = groups.map(groupLines);
1874
+ const totalRows = Math.max(0, ...lines.map((l) => l.length));
1875
+ const footnoteRows = tabHelp ? Math.ceil(LEGEND_FOOTNOTE.length / Math.max(1, contentWidth)) : 0;
1876
+ const chromeRows = 2 + 1 + 1 + 1;
1877
+ const fitWithin = (extra) => {
1878
+ const body = Math.max(3, (maxRows ?? rows) - chromeRows - extra);
1879
+ return { body, max: Math.max(0, totalRows - body) };
1880
+ };
1881
+ const loose = fitWithin(footnoteRows);
1882
+ const { body: bodyRows, max: maxScroll } = loose.max > 0 ? fitWithin(1) : loose;
1883
+ const offset = Math.min(scroll, maxScroll);
1884
+ const scrollable = maxScroll > 0;
1885
+ const headAligned = scrollable && lines.every((l) => l[offset]?.kind === "title");
1886
+ const pinnedOf = (l) => {
1887
+ const at = l[offset];
1888
+ return headAligned && at?.kind === "title" ? at.text : scrolledPastTitle(l, offset);
1889
+ };
1890
+ const bodyStart = offset + (headAligned ? 1 : 0);
1891
+ const shown = Math.min(totalRows - offset, bodyRows + (headAligned ? 1 : 0));
1892
+ useEffect(() => {
1893
+ onScrollRange?.(maxScroll, bodyRows);
1894
+ }, [maxScroll, bodyRows]);
1860
1895
  return /* @__PURE__ */ jsxs(
1861
1896
  Box,
1862
1897
  {
@@ -1867,18 +1902,22 @@ var HelpModal = ({
1867
1902
  paddingX: 1,
1868
1903
  width: contentWidth + MODAL_CHROME_COLS,
1869
1904
  children: [
1870
- /* @__PURE__ */ jsx(Text2, { color: "cyan", bold: true, children: "Legend" }),
1905
+ /* @__PURE__ */ jsxs(Box, { children: [
1906
+ /* @__PURE__ */ jsx(Text2, { color: "cyan", bold: true, children: "Legend" }),
1907
+ scrollable ? /* @__PURE__ */ jsx(Text2, { dimColor: true, children: ` ${offset + 1}\u2013${offset + shown} of ${totalRows}` }) : null
1908
+ ] }),
1871
1909
  /* @__PURE__ */ jsx(Box, { marginTop: 1, width: contentWidth, children: groups.map((group, index) => /* @__PURE__ */ jsx(
1872
1910
  LegendGroup,
1873
1911
  {
1874
- group,
1912
+ lines: lines[index].slice(bodyStart, bodyStart + bodyRows),
1913
+ pinned: scrollable ? pinnedOf(lines[index]) : void 0,
1875
1914
  width: widths[index],
1876
1915
  marginRight: index === groups.length - 1 ? 0 : COL_GAP
1877
1916
  },
1878
1917
  group[0]?.title ?? index
1879
1918
  )) }),
1880
- tabHelp ? /* @__PURE__ */ jsx(Text2, { dimColor: true, children: "Each item lands in the FIRST tab that claims it, so counts are residuals rather than totals." }) : null,
1881
- /* @__PURE__ */ jsx(Text2, { dimColor: true, children: "esc \xB7 ? close" })
1919
+ tabHelp && !scrollable ? /* @__PURE__ */ jsx(Text2, { dimColor: true, children: LEGEND_FOOTNOTE }) : null,
1920
+ /* @__PURE__ */ jsx(Text2, { dimColor: true, children: scrollable ? "\u2191\u2193 scroll \xB7 esc \xB7 ? close" : "esc \xB7 ? close" })
1882
1921
  ]
1883
1922
  }
1884
1923
  );
@@ -2000,6 +2039,8 @@ var BrowseScreen = ({
2000
2039
  const [repoFilter, setRepoFilter] = useState(/* @__PURE__ */ new Set());
2001
2040
  const [repoPicker, setRepoPicker] = useState(false);
2002
2041
  const [help, setHelp] = useState(false);
2042
+ const [helpScroll, setHelpScroll] = useState(0);
2043
+ const [helpRange, setHelpRange] = useState({ max: 0, page: 1 });
2003
2044
  const [explain, setExplain] = useState(false);
2004
2045
  const filterActive = search != null || repoFilter.size > 0;
2005
2046
  const reserveCiRow = ciStatusState != null;
@@ -2118,10 +2159,18 @@ var BrowseScreen = ({
2118
2159
  if (hidden) return;
2119
2160
  if (key.ctrl || key.meta) return;
2120
2161
  if (help) {
2162
+ if (helpRange.max > 0) {
2163
+ const step = (n) => setHelpScroll((s) => Math.min(helpRange.max, Math.max(0, s + n)));
2164
+ if (key.upArrow) return step(-1);
2165
+ if (key.downArrow) return step(1);
2166
+ if (key.pageUp) return step(-helpRange.page);
2167
+ if (key.pageDown) return step(helpRange.page);
2168
+ }
2121
2169
  setHelp(false);
2122
2170
  return;
2123
2171
  }
2124
2172
  if (input === "?") {
2173
+ setHelpScroll(0);
2125
2174
  setHelp(true);
2126
2175
  return;
2127
2176
  }
@@ -2384,7 +2433,10 @@ var BrowseScreen = ({
2384
2433
  workToggle,
2385
2434
  extensions,
2386
2435
  hasJira: !!jiraBase,
2387
- tabHelp
2436
+ tabHelp,
2437
+ maxRows: listHeight,
2438
+ scroll: helpScroll,
2439
+ onScrollRange: (max, page) => setHelpRange({ max, page })
2388
2440
  }
2389
2441
  ) : explain && activeItem && (activeItem.kind === "pr" || activeItem.kind === "issue") ? /* @__PURE__ */ jsx(ExplainModal, { item: activeItem, login }) : repoPicker ? /* @__PURE__ */ jsx(
2390
2442
  RepoPicker,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kud/gh-ink",
3
- "version": "0.16.0",
3
+ "version": "0.16.2",
4
4
  "description": "Ink components for rendering GitHub PR review comments and health — controlled, presentation-only, built on @kud/ink-ui and fed by @kud/gh.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",