graphein 0.11.0 → 0.12.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/dist/index.js CHANGED
@@ -10159,7 +10159,7 @@ function buildTable(opts) {
10159
10159
  overflow: "auto",
10160
10160
  pointerEvents: "auto",
10161
10161
  boxSizing: "border-box",
10162
- border: `1px solid ${tokens.color.border}`,
10162
+ border: `1px solid ${opts.sketch ? "transparent" : tokens.color.border}`,
10163
10163
  borderRadius: opts.sketch ? SKETCH_BORDER_RADIUS2 : `${tokens.radius.sm}px`,
10164
10164
  background: tokens.color.background
10165
10165
  });
@@ -10190,14 +10190,23 @@ function paintTableCanvas(opts) {
10190
10190
  const available = Math.max(0, rect.width - 2);
10191
10191
  const widths = computeFittedWidths(opts.columns, available);
10192
10192
  opts.widths = widths;
10193
+ const pen = opts.sketch ? new RoughPen(ctx, opts.sketch) : null;
10194
+ const w = Math.max(0, rect.width);
10195
+ const h = Math.max(0, rect.height);
10193
10196
  ctx.save();
10194
10197
  ctx.beginPath();
10195
- roundedRect(ctx, rect.x, rect.y, Math.max(0, rect.width), Math.max(0, rect.height), tokens.radius.sm);
10196
- ctx.fillStyle = tokens.color.background;
10197
- ctx.fill();
10198
- ctx.lineWidth = 1;
10199
- ctx.strokeStyle = tokens.color.border;
10200
- ctx.stroke();
10198
+ if (pen) {
10199
+ ctx.rect(rect.x, rect.y, w, h);
10200
+ ctx.fillStyle = tokens.color.background;
10201
+ ctx.fill();
10202
+ } else {
10203
+ roundedRect(ctx, rect.x, rect.y, w, h, tokens.radius.sm);
10204
+ ctx.fillStyle = tokens.color.background;
10205
+ ctx.fill();
10206
+ ctx.lineWidth = 1;
10207
+ ctx.strokeStyle = tokens.color.border;
10208
+ ctx.stroke();
10209
+ }
10201
10210
  ctx.clip();
10202
10211
  const headerRows = normalizedHeaderRows(opts);
10203
10212
  const headerHeight = headerRows.length * metrics.headerHeight;
@@ -10209,17 +10218,18 @@ function paintTableCanvas(opts) {
10209
10218
  const clipped = visibleRows < opts.rowCount;
10210
10219
  const cueReserve = clipped && opts.clippedCue !== false && visibleRows > 0 ? metrics.rowHeight : 0;
10211
10220
  const rowsToDraw = Math.max(0, visibleRows - (cueReserve > 0 ? 1 : 0));
10212
- paintHeaderCanvas(opts, widths, headerRows, metrics);
10221
+ paintHeaderCanvas(opts, widths, headerRows, metrics, pen);
10213
10222
  for (let rowIndex = 0; rowIndex < rowsToDraw; rowIndex += 1) {
10214
- paintBodyRowCanvas(opts, widths, rowIndex, bodyTop + rowIndex * metrics.rowHeight, metrics);
10223
+ paintBodyRowCanvas(opts, widths, rowIndex, bodyTop + rowIndex * metrics.rowHeight, metrics, pen);
10215
10224
  }
10216
10225
  if (cueReserve > 0) {
10217
10226
  paintClippedCueCanvas(opts, bodyTop + rowsToDraw * metrics.rowHeight, metrics, opts.rowCount - rowsToDraw);
10218
10227
  }
10219
10228
  if (opts.footerRow) {
10220
- paintFooterCanvas(opts, widths, rect.y + rect.height - metrics.rowHeight, metrics);
10229
+ paintFooterCanvas(opts, widths, rect.y + rect.height - metrics.rowHeight, metrics, pen);
10221
10230
  }
10222
10231
  ctx.restore();
10232
+ if (pen) pen.rect(rect.x, rect.y, w, h, { stroke: tokens.color.border });
10223
10233
  }
10224
10234
  var FILL_EPS = 0.5;
10225
10235
  var cachedScrollbarWidth = null;
@@ -10312,14 +10322,14 @@ function makeHeaderCell(opts, title2, colIndex, align, rowIndex = 0) {
10312
10322
  th.textContent = title2;
10313
10323
  const isSorted = colIndex != null && opts.sortState?.col === colIndex;
10314
10324
  if (isSorted) th.setAttribute("aria-sort", opts.sortState?.dir === "desc" ? "descending" : "ascending");
10315
- setCellBaseStyles(th, opts.tokens, align ?? "left", opts.stickyHeader !== false, metrics.padding);
10325
+ setCellBaseStyles(th, opts.tokens, align ?? "left", opts.stickyHeader !== false, metrics.padding, gridBorderColor(opts));
10316
10326
  setStyles(th, {
10317
10327
  top: `${rowIndex * metrics.headerHeight}px`,
10318
10328
  zIndex: "4",
10319
10329
  background: opts.tokens.color.surface,
10320
10330
  color: opts.tokens.color.text,
10321
10331
  fontWeight: String(opts.tokens.font.weight.bold),
10322
- borderBottom: `1px solid ${opts.tokens.color.border}`,
10332
+ borderBottom: `1px solid ${gridBorderColor(opts)}`,
10323
10333
  userSelect: "none",
10324
10334
  height: `${metrics.headerHeight}px`
10325
10335
  });
@@ -10377,19 +10387,19 @@ function makeBodyRow(opts, rowIndex, rowHeight) {
10377
10387
  const td = document.createElement("td");
10378
10388
  const cell = opts.getCell(rowIndex, colIndex);
10379
10389
  const rowBg = rowBackground(opts, rowIndex, cls);
10380
- setCellBaseStyles(td, opts.tokens, column.align, false, metrics.padding);
10390
+ setCellBaseStyles(td, opts.tokens, column.align, false, metrics.padding, gridBorderColor(opts));
10381
10391
  setStyles(td, {
10382
10392
  position: colIndex < (opts.rowHeaderSpan ?? 0) ? "sticky" : "relative",
10383
10393
  background: rowBg,
10384
10394
  fontWeight: cls === "normal" ? String(opts.tokens.font.weight.normal) : String(opts.tokens.font.weight.bold),
10385
10395
  color: opts.tokens.color.text,
10386
- borderBottom: `1px solid ${opts.tokens.color.border}`,
10396
+ borderBottom: `1px solid ${gridBorderColor(opts)}`,
10387
10397
  height: rowHeight ? `${rowHeight}px` : `${metrics.rowHeight}px`,
10388
10398
  whiteSpace: column.wrap === true ? "normal" : "nowrap"
10389
10399
  });
10390
10400
  const indent = opts.cellIndent?.(rowIndex, colIndex) ?? 0;
10391
10401
  if (indent > 0) td.style.paddingLeft = `${metrics.paddingX + indent}px`;
10392
- applyConditionalFormatting(td, opts, column, colIndex, cell, rowBg);
10402
+ applyConditionalFormatting(td, opts, column, colIndex, cell, rowBg, rowIndex);
10393
10403
  if (td.childNodes.length === 0) {
10394
10404
  td.textContent = formatDisplayValue(cell.value, column);
10395
10405
  applyNegativeStyle(td, cell.value, column);
@@ -10407,7 +10417,7 @@ function buildFooter(opts) {
10407
10417
  opts.columns.forEach((column, colIndex) => {
10408
10418
  const td = document.createElement("td");
10409
10419
  const cell = opts.footerRow?.cells[colIndex] ?? { value: null};
10410
- setCellBaseStyles(td, opts.tokens, column.align, false, metrics.padding);
10420
+ setCellBaseStyles(td, opts.tokens, column.align, false, metrics.padding, gridBorderColor(opts));
10411
10421
  setStyles(td, {
10412
10422
  position: colIndex < (opts.rowHeaderSpan ?? 0) ? "sticky" : "sticky",
10413
10423
  bottom: "0",
@@ -10415,8 +10425,8 @@ function buildFooter(opts) {
10415
10425
  background: opts.tokens.color.surface,
10416
10426
  color: opts.tokens.color.text,
10417
10427
  fontWeight: String(opts.tokens.font.weight.bold),
10418
- borderTop: `1px solid ${opts.tokens.color.border}`,
10419
- borderBottom: `1px solid ${opts.tokens.color.border}`,
10428
+ borderTop: `1px solid ${gridBorderColor(opts)}`,
10429
+ borderBottom: `1px solid ${gridBorderColor(opts)}`,
10420
10430
  height: `${metrics.rowHeight}px`,
10421
10431
  whiteSpace: column.wrap === true ? "normal" : "nowrap"
10422
10432
  });
@@ -10442,7 +10452,7 @@ function makeSpacerRow(opts, height) {
10442
10452
  tr.appendChild(td);
10443
10453
  return tr;
10444
10454
  }
10445
- function applyConditionalFormatting(td, opts, column, colIndex, cell, rowBg) {
10455
+ function applyConditionalFormatting(td, opts, column, colIndex, cell, rowBg, rowIndex = 0) {
10446
10456
  const cf = column.conditionalFormat;
10447
10457
  const raw = cell.raw;
10448
10458
  const domain = opts.conditionalDomains?.[colIndex] ?? null;
@@ -10487,13 +10497,27 @@ function applyConditionalFormatting(td, opts, column, colIndex, cell, rowBg) {
10487
10497
  }
10488
10498
  if (!domain) return;
10489
10499
  const ratio = normalized(numeric, domain);
10490
- const bar = document.createElement("div");
10491
10500
  const diverging2 = cf.negativeColor !== void 0 || cf.baseline === "zero";
10492
10501
  const positive = numeric >= 0;
10493
10502
  const barColor = alphaColor(positive ? cf.color ?? opts.tokens.color.accent : cf.negativeColor ?? opts.tokens.color.negative, 0.85);
10494
10503
  const baseline = diverging2 ? normalized(0, domain) : 0;
10495
10504
  const left = diverging2 ? Math.min(baseline, normalized(numeric, domain)) : 0;
10496
10505
  const width = diverging2 ? Math.abs(normalized(numeric, domain) - baseline) : ratio;
10506
+ const text = document.createElement("span");
10507
+ text.textContent = cf.showValue === false ? "" : formatDisplayValue(cell.value, column);
10508
+ setStyles(text, overlayTextStyles(opts.tokens.color.text));
10509
+ td.style.background = rowBg;
10510
+ if (opts.sketch) {
10511
+ const colWidth = opts.widths?.[colIndex] ?? column.width ?? defaultColumnWidth(column);
10512
+ const rowHeight = densityMetrics(opts.density).rowHeight;
10513
+ const seed = (opts.sketch.seed ^ Math.imul(rowIndex + 1, 374761393) ^ Math.imul(colIndex + 1, 668265263)) >>> 0;
10514
+ const canvas = buildSketchDataBar(opts.sketch, barColor, colWidth, rowHeight, left, width, seed);
10515
+ if (canvas) td.appendChild(canvas);
10516
+ td.appendChild(text);
10517
+ applyNegativeStyle(text, cell.value, column);
10518
+ return;
10519
+ }
10520
+ const bar = document.createElement("div");
10497
10521
  setStyles(bar, {
10498
10522
  position: "absolute",
10499
10523
  left: `calc(${Math.round(left * 1e3) / 10}% + 4px)`,
@@ -10505,14 +10529,38 @@ function applyConditionalFormatting(td, opts, column, colIndex, cell, rowBg) {
10505
10529
  opacity: "0.9",
10506
10530
  pointerEvents: "none"
10507
10531
  });
10508
- const text = document.createElement("span");
10509
- text.textContent = cf.showValue === false ? "" : formatDisplayValue(cell.value, column);
10510
- setStyles(text, overlayTextStyles(opts.tokens.color.text));
10511
- td.style.background = rowBg;
10512
10532
  td.append(bar, text);
10513
10533
  applyNegativeStyle(text, cell.value, column);
10514
10534
  }
10515
- function setCellBaseStyles(cell, tokens, align, sticky, padding = "6px 10px") {
10535
+ function buildSketchDataBar(sketch, color, colWidth, rowHeight, leftRatio, widthRatio, seed) {
10536
+ const avail = Math.max(0, colWidth - 8);
10537
+ const barW = widthRatio * avail;
10538
+ const barH = Math.max(0, rowHeight - 10);
10539
+ if (barW <= 0.5 || barH <= 0) return null;
10540
+ const pad3 = 3;
10541
+ const leftPx = 4 + leftRatio * avail;
10542
+ const dpr = getDevicePixelRatio();
10543
+ const canvas = document.createElement("canvas");
10544
+ const cssW = barW + pad3 * 2;
10545
+ const cssH = barH + pad3 * 2;
10546
+ canvas.width = Math.max(1, Math.round(cssW * dpr));
10547
+ canvas.height = Math.max(1, Math.round(cssH * dpr));
10548
+ setStyles(canvas, {
10549
+ position: "absolute",
10550
+ left: `${leftPx - pad3}px`,
10551
+ top: `${5 - pad3}px`,
10552
+ width: `${cssW}px`,
10553
+ height: `${cssH}px`,
10554
+ pointerEvents: "none"
10555
+ });
10556
+ const ctx = canvas.getContext("2d");
10557
+ if (!ctx) return null;
10558
+ ctx.scale(dpr, dpr);
10559
+ const pen = new RoughPen(ctx, { ...sketch, seed });
10560
+ pen.rect(pad3, pad3, barW, barH, { stroke: color, fill: color, fillAlpha: 0.9, strokeAlpha: 0.9 });
10561
+ return canvas;
10562
+ }
10563
+ function setCellBaseStyles(cell, tokens, align, sticky, padding = "6px 10px", borderColor = tokens.color.border) {
10516
10564
  setStyles(cell, {
10517
10565
  boxSizing: "border-box",
10518
10566
  padding,
@@ -10520,11 +10568,14 @@ function setCellBaseStyles(cell, tokens, align, sticky, padding = "6px 10px") {
10520
10568
  whiteSpace: "nowrap",
10521
10569
  overflow: "hidden",
10522
10570
  textOverflow: "ellipsis",
10523
- borderRight: `1px solid ${tokens.color.border}`,
10571
+ borderRight: `1px solid ${borderColor}`,
10524
10572
  verticalAlign: "middle"
10525
10573
  });
10526
10574
  if (sticky) cell.style.position = "sticky";
10527
10575
  }
10576
+ function gridBorderColor(opts) {
10577
+ return opts.sketch ? "transparent" : opts.tokens.color.border;
10578
+ }
10528
10579
  function renderIconValue(td, icon, color, value, position) {
10529
10580
  const iconSpan = document.createElement("span");
10530
10581
  iconSpan.textContent = icon;
@@ -10578,7 +10629,7 @@ function normalizedHeaderRows(opts) {
10578
10629
  }))
10579
10630
  ];
10580
10631
  }
10581
- function paintHeaderCanvas(opts, widths, rows, metrics) {
10632
+ function paintHeaderCanvas(opts, widths, rows, metrics, pen) {
10582
10633
  rows.forEach((row, rowIndex) => {
10583
10634
  let cursor = 0;
10584
10635
  for (const cell of row) {
@@ -10589,7 +10640,7 @@ function paintHeaderCanvas(opts, widths, rows, metrics) {
10589
10640
  const y = opts.rect.y + rowIndex * metrics.headerHeight;
10590
10641
  const w = sumWidths(widths.slice(start, start + colSpan));
10591
10642
  const h = metrics.headerHeight * rowSpan;
10592
- paintCellBox(opts.ctx, x, y, w, h, opts.tokens.color.surface, opts.tokens.color.border);
10643
+ paintCellBox(opts.ctx, x, y, w, h, opts.tokens.color.surface, opts.tokens.color.border, false, pen);
10593
10644
  paintCellText(opts, {
10594
10645
  x,
10595
10646
  y,
@@ -10605,7 +10656,7 @@ function paintHeaderCanvas(opts, widths, rows, metrics) {
10605
10656
  }
10606
10657
  });
10607
10658
  }
10608
- function paintBodyRowCanvas(opts, widths, rowIndex, y, metrics) {
10659
+ function paintBodyRowCanvas(opts, widths, rowIndex, y, metrics, pen) {
10609
10660
  const cls = opts.rowClass?.(rowIndex) ?? "normal";
10610
10661
  let x = opts.rect.x;
10611
10662
  for (let colIndex = 0; colIndex < opts.columns.length; colIndex += 1) {
@@ -10615,8 +10666,8 @@ function paintBodyRowCanvas(opts, widths, rowIndex, y, metrics) {
10615
10666
  const baseBg = canvasRowBackground(opts, rowIndex, cls);
10616
10667
  const conditional = resolveCanvasConditional(opts, column, colIndex, cell, baseBg);
10617
10668
  const bg = conditional.background ?? baseBg;
10618
- paintCellBox(opts.ctx, x, y, width, metrics.rowHeight, bg, opts.tokens.color.border);
10619
- if (conditional.bar) paintDataBar(opts, x, y, width, metrics.rowHeight, conditional.bar);
10669
+ paintCellBox(opts.ctx, x, y, width, metrics.rowHeight, bg, opts.tokens.color.border, false, pen);
10670
+ if (conditional.bar) paintDataBar(opts, x, y, width, metrics.rowHeight, conditional.bar, pen);
10620
10671
  const indent = opts.cellIndent?.(rowIndex, colIndex) ?? 0;
10621
10672
  const text = conditional.showValue === false ? "" : formatDisplayValue(cell.value, column);
10622
10673
  const color = negativeTextColor(cell.value, column, conditional.color ?? opts.tokens.color.text);
@@ -10638,13 +10689,13 @@ function paintBodyRowCanvas(opts, widths, rowIndex, y, metrics) {
10638
10689
  x += width;
10639
10690
  }
10640
10691
  }
10641
- function paintFooterCanvas(opts, widths, y, metrics) {
10692
+ function paintFooterCanvas(opts, widths, y, metrics, pen) {
10642
10693
  let x = opts.rect.x;
10643
10694
  for (let colIndex = 0; colIndex < opts.columns.length; colIndex += 1) {
10644
10695
  const column = opts.columns[colIndex];
10645
10696
  const width = widths[colIndex] ?? 0;
10646
10697
  const cell = opts.footerRow?.cells[colIndex] ?? { value: null};
10647
- paintCellBox(opts.ctx, x, y, width, metrics.rowHeight, opts.tokens.color.surface, opts.tokens.color.border, true);
10698
+ paintCellBox(opts.ctx, x, y, width, metrics.rowHeight, opts.tokens.color.surface, opts.tokens.color.border, true, pen);
10648
10699
  paintCellText(opts, {
10649
10700
  x,
10650
10701
  y,
@@ -10673,10 +10724,25 @@ function paintClippedCueCanvas(opts, y, metrics, remaining) {
10673
10724
  paddingX: metrics.paddingX
10674
10725
  });
10675
10726
  }
10676
- function paintCellBox(ctx, x, y, width, height, background, border, topBorder = false) {
10727
+ function paintCellBox(ctx, x, y, width, height, background, border, topBorder = false, pen) {
10677
10728
  ctx.save();
10678
10729
  ctx.fillStyle = background;
10679
10730
  ctx.fillRect(x, y, width, height);
10731
+ if (pen) {
10732
+ pen.polyline(
10733
+ [
10734
+ { x: x + width, y },
10735
+ { x: x + width, y: y + height },
10736
+ { x, y: y + height }
10737
+ ],
10738
+ { stroke: border, strokeWidth: 1 }
10739
+ );
10740
+ if (topBorder) {
10741
+ pen.polyline([{ x, y }, { x: x + width, y }], { stroke: border, strokeWidth: 1 });
10742
+ }
10743
+ ctx.restore();
10744
+ return;
10745
+ }
10680
10746
  ctx.strokeStyle = border;
10681
10747
  ctx.lineWidth = 1;
10682
10748
  ctx.beginPath();
@@ -10787,9 +10853,18 @@ function resolveCanvasConditional(opts, column, colIndex, cell, rowBg) {
10787
10853
  const color = alphaColor(positive ? cf.color ?? opts.tokens.color.accent : cf.negativeColor ?? opts.tokens.color.negative, 0.85);
10788
10854
  return { background: rowBg, showValue: cf.showValue !== false, bar: { leftRatio, widthRatio, color } };
10789
10855
  }
10790
- function paintDataBar(opts, x, y, width, height, bar) {
10856
+ function paintDataBar(opts, x, y, width, height, bar, pen) {
10791
10857
  const left = x + 4 + bar.leftRatio * Math.max(0, width - 8);
10792
10858
  const barW = bar.widthRatio * Math.max(0, width - 8);
10859
+ if (pen) {
10860
+ pen.rect(left, y + 5, barW, Math.max(0, height - 10), {
10861
+ stroke: bar.color,
10862
+ fill: bar.color,
10863
+ fillAlpha: 0.9,
10864
+ strokeAlpha: 0.9
10865
+ });
10866
+ return;
10867
+ }
10793
10868
  opts.ctx.save();
10794
10869
  opts.ctx.globalAlpha *= 0.9;
10795
10870
  opts.ctx.beginPath();
@@ -10798,6 +10873,72 @@ function paintDataBar(opts, x, y, width, height, bar) {
10798
10873
  opts.ctx.fill();
10799
10874
  opts.ctx.restore();
10800
10875
  }
10876
+ function createSketchGridCanvas(rect) {
10877
+ const dpr = getDevicePixelRatio();
10878
+ const w = Math.max(0, rect.width);
10879
+ const h = Math.max(0, rect.height);
10880
+ const canvas = document.createElement("canvas");
10881
+ canvas.width = Math.max(1, Math.round(w * dpr));
10882
+ canvas.height = Math.max(1, Math.round(h * dpr));
10883
+ setStyles(canvas, {
10884
+ position: "absolute",
10885
+ left: `${rect.x}px`,
10886
+ top: `${rect.y}px`,
10887
+ width: `${w}px`,
10888
+ height: `${h}px`,
10889
+ pointerEvents: "none"
10890
+ });
10891
+ return { canvas, ctx: canvas.getContext("2d"), dpr };
10892
+ }
10893
+ function paintSketchGridOverlay(ctx, o) {
10894
+ const w = Math.max(0, o.rect.width);
10895
+ const h = Math.max(0, o.rect.height);
10896
+ ctx.save();
10897
+ ctx.setTransform(o.dpr, 0, 0, o.dpr, 0, 0);
10898
+ ctx.clearRect(0, 0, w, h);
10899
+ const metrics = densityMetrics(o.density);
10900
+ const headerHeight = o.headerRowCount * metrics.headerHeight;
10901
+ const footerHeight = o.hasFooter ? metrics.rowHeight : 0;
10902
+ const bodyTop = headerHeight;
10903
+ const bodyBottom = Math.max(bodyTop, h - footerHeight);
10904
+ const border = o.tokens.color.border;
10905
+ const base = o.sketch.seed >>> 0;
10906
+ const stroke = (x1, y1, x2, y2, seed) => {
10907
+ const pen = new RoughPen(ctx, { ...o.sketch, seed: seed >>> 0 });
10908
+ pen.polyline(
10909
+ [
10910
+ { x: x1, y: y1 },
10911
+ { x: x2, y: y2 }
10912
+ ],
10913
+ { stroke: border, strokeWidth: 1 }
10914
+ );
10915
+ };
10916
+ let cursor = 0;
10917
+ for (let i = 0; i < o.columns.length - 1; i += 1) {
10918
+ cursor += o.widths[i] ?? 0;
10919
+ const pinned = i < (o.rowHeaderSpan ?? 0);
10920
+ const x = pinned ? cursor : cursor - o.scrollLeft;
10921
+ if (x <= 0 || x >= w) continue;
10922
+ stroke(x, 0, x, h, base ^ Math.imul(i + 1, 2246822519));
10923
+ }
10924
+ ctx.save();
10925
+ ctx.beginPath();
10926
+ ctx.rect(0, bodyTop, w, Math.max(0, bodyBottom - bodyTop));
10927
+ ctx.clip();
10928
+ const firstRow = Math.max(0, Math.floor(o.scrollTop / metrics.rowHeight) - 1);
10929
+ for (let r = firstRow; r < o.rowCount; r += 1) {
10930
+ const y = bodyTop + (r + 1) * metrics.rowHeight - o.scrollTop;
10931
+ if (y <= bodyTop) continue;
10932
+ if (y >= bodyBottom) break;
10933
+ stroke(0, y, w, y, base ^ Math.imul(r + 1, 3266489917));
10934
+ }
10935
+ ctx.restore();
10936
+ if (headerHeight > 0 && headerHeight < h) stroke(0, headerHeight, w, headerHeight, (base ^ 2654435769) >>> 0);
10937
+ if (footerHeight > 0 && bodyBottom > bodyTop) stroke(0, bodyBottom, w, bodyBottom, (base ^ 2246822507) >>> 0);
10938
+ const framePen = new RoughPen(ctx, { ...o.sketch, seed: base });
10939
+ framePen.rect(1.5, 1.5, Math.max(0, w - 3), Math.max(0, h - 3), { stroke: border, strokeWidth: 1 });
10940
+ ctx.restore();
10941
+ }
10801
10942
  function canvasRowBackground(opts, rowIndex, cls) {
10802
10943
  if (cls !== "normal") return opts.tokens.color.surface;
10803
10944
  if (opts.striped === true && rowIndex % 2 === 1) return subtleStripe(opts.tokens);
@@ -10900,7 +11041,7 @@ function drawTableCanvas(surface, spec, tokens, size) {
10900
11041
  headerRows: buildTableHeaderRows(columns),
10901
11042
  footerRow: buildTotalsRow(sortedRows, columns, read, spec),
10902
11043
  density: spec.density,
10903
- sketch: resolveSketch(spec) != null
11044
+ sketch: resolveSketch(spec)
10904
11045
  });
10905
11046
  }
10906
11047
  function renderTable(surface, spec, tokens, size) {
@@ -10908,7 +11049,7 @@ function renderTable(surface, spec, tokens, size) {
10908
11049
  const data = spec.data ?? [];
10909
11050
  const columns = resolveColumns(data, spec);
10910
11051
  const read = columns.map((column) => accessor(column.key));
10911
- const sketch = resolveSketch(spec) != null;
11052
+ const sketch = resolveSketch(spec);
10912
11053
  let sortState = initialSortState(spec, columns);
10913
11054
  let sortedRows = sortRows(data, columns, read, sortState);
10914
11055
  const domains = columns.map(
@@ -10919,6 +11060,9 @@ function renderTable(surface, spec, tokens, size) {
10919
11060
  );
10920
11061
  const host = document.createElement("div");
10921
11062
  surface.overlay.appendChild(host);
11063
+ const headerRowCount = buildTableHeaderRows(columns)?.length ?? 1;
11064
+ const overlay = sketch ? createSketchGridCanvas(rect) : null;
11065
+ if (overlay) surface.overlay.appendChild(overlay.canvas);
10922
11066
  let frame = 0;
10923
11067
  const renderWindow = () => {
10924
11068
  const scrollTop = host.scrollTop || 0;
@@ -10926,7 +11070,8 @@ function renderTable(surface, spec, tokens, size) {
10926
11070
  const start = Math.max(0, Math.floor(scrollTop / rowHeight) - BUFFER_ROWS);
10927
11071
  const visible = Math.ceil(Math.max(rect.height, host.clientHeight || rect.height) / rowHeight) + BUFFER_ROWS * 2;
10928
11072
  const end = Math.min(sortedRows.length, start + visible);
10929
- buildTable({
11073
+ const footerRow = buildTotalsRow(sortedRows, columns, read, spec);
11074
+ const buildOpts = {
10930
11075
  container: host,
10931
11076
  tokens,
10932
11077
  rect,
@@ -10947,11 +11092,28 @@ function renderTable(surface, spec, tokens, size) {
10947
11092
  sortState: sortState ?? void 0,
10948
11093
  conditionalDomains: domains,
10949
11094
  headerRows: buildTableHeaderRows(columns),
10950
- footerRow: buildTotalsRow(sortedRows, columns, read, spec),
11095
+ footerRow,
10951
11096
  density: spec.density,
10952
11097
  visibleRange: { start, end, rowHeight },
10953
11098
  sketch
10954
- });
11099
+ };
11100
+ buildTable(buildOpts);
11101
+ if (sketch && overlay?.ctx) {
11102
+ paintSketchGridOverlay(overlay.ctx, {
11103
+ sketch,
11104
+ tokens,
11105
+ rect,
11106
+ dpr: overlay.dpr,
11107
+ columns,
11108
+ widths: buildOpts.widths ?? [],
11109
+ rowCount: sortedRows.length,
11110
+ headerRowCount,
11111
+ hasFooter: Boolean(footerRow),
11112
+ density: spec.density,
11113
+ scrollLeft: host.scrollLeft || 0,
11114
+ scrollTop
11115
+ });
11116
+ }
10955
11117
  };
10956
11118
  host.addEventListener("scroll", () => {
10957
11119
  if (frame !== 0) return;
@@ -11108,15 +11270,16 @@ function drawMatrixCanvas(surface, spec, tokens, size) {
11108
11270
  conditionalDomains: prepared.bodyDomains,
11109
11271
  headerRows: prepared.headerRows,
11110
11272
  density: spec.density,
11111
- sketch: resolveSketch(spec) != null
11273
+ sketch: resolveSketch(spec)
11112
11274
  });
11113
11275
  }
11114
11276
  function renderMatrix(surface, spec, tokens, size) {
11115
11277
  const rect = drawTitleBlock(surface, tokens, size, spec.title);
11116
11278
  const prepared = prepareMatrix(spec);
11279
+ const sketch = resolveSketch(spec);
11117
11280
  const host = document.createElement("div");
11118
11281
  surface.overlay.appendChild(host);
11119
- buildTable({
11282
+ const buildOpts = {
11120
11283
  container: host,
11121
11284
  tokens,
11122
11285
  rect,
@@ -11143,8 +11306,45 @@ function renderMatrix(surface, spec, tokens, size) {
11143
11306
  conditionalDomains: prepared.bodyDomains,
11144
11307
  headerRows: prepared.headerRows,
11145
11308
  density: spec.density,
11146
- sketch: resolveSketch(spec) != null
11147
- });
11309
+ sketch
11310
+ };
11311
+ buildTable(buildOpts);
11312
+ if (sketch) {
11313
+ const overlay = createSketchGridCanvas(rect);
11314
+ if (overlay.ctx) {
11315
+ surface.overlay.appendChild(overlay.canvas);
11316
+ const ctx = overlay.ctx;
11317
+ const draw = () => {
11318
+ paintSketchGridOverlay(ctx, {
11319
+ sketch,
11320
+ tokens,
11321
+ rect,
11322
+ dpr: overlay.dpr,
11323
+ columns: prepared.columns,
11324
+ widths: buildOpts.widths ?? [],
11325
+ rowCount: prepared.rows.length,
11326
+ headerRowCount: prepared.headerRows.length,
11327
+ hasFooter: false,
11328
+ rowHeaderSpan: 1,
11329
+ density: spec.density,
11330
+ scrollLeft: host.scrollLeft || 0,
11331
+ scrollTop: host.scrollTop || 0
11332
+ });
11333
+ };
11334
+ draw();
11335
+ let frame = 0;
11336
+ host.addEventListener("scroll", () => {
11337
+ if (frame !== 0) return;
11338
+ frame = typeof requestAnimationFrame === "function" ? requestAnimationFrame(() => {
11339
+ frame = 0;
11340
+ draw();
11341
+ }) : window.setTimeout(() => {
11342
+ frame = 0;
11343
+ draw();
11344
+ }, 16);
11345
+ });
11346
+ }
11347
+ }
11148
11348
  }
11149
11349
  function prepareMatrix(spec) {
11150
11350
  const data = spec.data ?? [];