@vectojs/markdown 0.20.2 → 0.20.3

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
@@ -1327,6 +1327,70 @@ function highlightLine(line, lang, theme, carry = null) {
1327
1327
  flush(theme.codeColor);
1328
1328
  return { segments, carry: null };
1329
1329
  }
1330
+ var HSCROLL_HIT_H = 12;
1331
+ var HSCROLL_THUMB_H = 4;
1332
+ var HSCROLL_THUMB_INSET = 4;
1333
+ var HSCROLL_THUMB_MIN = 24;
1334
+ var HSCROLL_TRACK_COLOR = "rgba(128, 128, 128, 0.14)";
1335
+ var HSCROLL_THUMB_COLOR = "rgba(128, 128, 128, 0.45)";
1336
+ var HSCROLL_THUMB_ACTIVE_COLOR = "rgba(128, 128, 128, 0.75)";
1337
+ function hScrollMetrics(trackW, maxScrollX) {
1338
+ const content = trackW + maxScrollX;
1339
+ const proportional = content > 0 ? trackW / content * trackW : trackW;
1340
+ const thumbW = Math.min(trackW, Math.max(HSCROLL_THUMB_MIN, proportional));
1341
+ return { thumbW, range: Math.max(1, trackW - thumbW) };
1342
+ }
1343
+ var HScrollTrack = class extends import_ui.UIComponent {
1344
+ constructor(block) {
1345
+ super();
1346
+ this.block = block;
1347
+ this.on("pointerdown", (e) => {
1348
+ if (e.localX === void 0) return;
1349
+ const maxSX = this.block.maxScrollX;
1350
+ if (maxSX <= 0) return;
1351
+ const { thumbW, range } = hScrollMetrics(this.width, maxSX);
1352
+ const thumbX = this.block.scrollX / maxSX * range;
1353
+ if (e.localX < thumbX || e.localX > thumbX + thumbW) {
1354
+ this.block.setScrollX((e.localX - thumbW / 2) / range * maxSX);
1355
+ }
1356
+ this.dragging = true;
1357
+ this.dragAnchorX = e.localX;
1358
+ this.scrollAtDragStart = this.block.scrollX;
1359
+ this.scene?.markDirty();
1360
+ });
1361
+ this.on("pointermove", (e) => {
1362
+ if (!this.dragging || e.localX === void 0) return;
1363
+ const maxSX = this.block.maxScrollX;
1364
+ const { range } = hScrollMetrics(this.width, maxSX);
1365
+ const dx = e.localX - this.dragAnchorX;
1366
+ this.block.setScrollX(this.scrollAtDragStart + dx / range * maxSX);
1367
+ });
1368
+ const endDrag = () => {
1369
+ if (!this.dragging) return;
1370
+ this.dragging = false;
1371
+ this.scene?.markDirty();
1372
+ };
1373
+ this.on("pointerup", endDrag);
1374
+ this.on("pointerleave", endDrag);
1375
+ }
1376
+ block;
1377
+ /** True while a drag owns the thumb; read by the block's painter for the active tint. */
1378
+ dragging = false;
1379
+ dragAnchorX = 0;
1380
+ scrollAtDragStart = 0;
1381
+ getA11yAttributes() {
1382
+ return {
1383
+ role: "scrollbar",
1384
+ label: "Scroll code horizontally",
1385
+ value: String(Math.round(this.block.scrollX)),
1386
+ valuemin: "0",
1387
+ valuemax: String(Math.round(this.block.maxScrollX))
1388
+ };
1389
+ }
1390
+ /** Nothing to draw: the thumb is painted by the owning block, so both share one pass. */
1391
+ render() {
1392
+ }
1393
+ };
1330
1394
  var CodeBlock = class extends import_ui.UIComponent {
1331
1395
  lines;
1332
1396
  /**
@@ -1364,6 +1428,12 @@ var CodeBlock = class extends import_ui.UIComponent {
1364
1428
  /** Memoized widest prepared line, keyed by the grid identity it came from. */
1365
1429
  contentWidthGrid = null;
1366
1430
  contentWidthValue = 0;
1431
+ /**
1432
+ * The interactive horizontal scrollbar strip (#527), created lazily by
1433
+ * {@link syncScrollTrack} the first time the block overflows. Kept, and merely
1434
+ * disabled, when the overflow later disappears — see there for why.
1435
+ */
1436
+ hTrack = null;
1367
1437
  lang;
1368
1438
  theme;
1369
1439
  /**
@@ -1668,6 +1738,31 @@ var CodeBlock = class extends import_ui.UIComponent {
1668
1738
  }
1669
1739
  return this.grid;
1670
1740
  }
1741
+ /**
1742
+ * Fit the interactive scrollbar strip (#527) to the current overflow.
1743
+ *
1744
+ * Called from {@link render} rather than from `setWidth` or `buildLines`:
1745
+ * `setWidth` is documented as costing nothing and `maxScrollX` costs a grid
1746
+ * build, while `render` is about to pay for that grid anyway. Once created,
1747
+ * the child is KEPT and merely de-`interactive`d when the overflow disappears
1748
+ * — this runs inside the scene's tree walk, and removing a child mid-walk is
1749
+ * how siblings get skipped.
1750
+ */
1751
+ syncScrollTrack() {
1752
+ if (this.maxScrollX <= 0) {
1753
+ if (this.hTrack) this.hTrack.interactive = false;
1754
+ return;
1755
+ }
1756
+ if (!this.hTrack) {
1757
+ this.hTrack = new HScrollTrack(this);
1758
+ this.add(this.hTrack);
1759
+ }
1760
+ this.hTrack.interactive = true;
1761
+ this.hTrack.x = this.pad;
1762
+ this.hTrack.y = this.height - HSCROLL_HIT_H;
1763
+ this.hTrack.width = Math.max(0, this.width - this.pad * 2);
1764
+ this.hTrack.height = HSCROLL_HIT_H;
1765
+ }
1671
1766
  /**
1672
1767
  * Not hit-testable, and deliberately still not `interactive`, even though the
1673
1768
  * block now consumes wheel events to scroll.
@@ -1676,11 +1771,16 @@ var CodeBlock = class extends import_ui.UIComponent {
1676
1771
  * hit-testing, so no a11y shadow node is needed. Creating one would place a
1677
1772
  * `pointer-events: auto` element above the transparent text mirror and swallow
1678
1773
  * the mousedown that starts a native drag-selection.
1774
+ *
1775
+ * Pointer-driven scrolling therefore lives on the {@link HScrollTrack} CHILD,
1776
+ * whose shadow node covers only the bottom-padding strip below the last
1777
+ * selectable carrier — never the text itself.
1679
1778
  */
1680
1779
  isPointInside() {
1681
1780
  return false;
1682
1781
  }
1683
1782
  render(r) {
1783
+ this.syncScrollTrack();
1684
1784
  r.beginPath();
1685
1785
  r.roundRect(0, 0, this.width, this.height, this.theme.codeRadius);
1686
1786
  r.fill(this.theme.codeBgColor);
@@ -1750,6 +1850,24 @@ var CodeBlock = class extends import_ui.UIComponent {
1750
1850
  }
1751
1851
  }
1752
1852
  r.restore();
1853
+ const maxSX = this.maxScrollX;
1854
+ if (maxSX > 0) {
1855
+ const trackW = Math.max(0, this.width - this.pad * 2);
1856
+ const { thumbW, range } = hScrollMetrics(trackW, maxSX);
1857
+ const trackY = this.height - HSCROLL_THUMB_H - HSCROLL_THUMB_INSET;
1858
+ r.beginPath();
1859
+ r.roundRect(this.pad, trackY, trackW, HSCROLL_THUMB_H, HSCROLL_THUMB_H / 2);
1860
+ r.fill(HSCROLL_TRACK_COLOR);
1861
+ r.beginPath();
1862
+ r.roundRect(
1863
+ this.pad + this.scrollX / maxSX * range,
1864
+ trackY,
1865
+ thumbW,
1866
+ HSCROLL_THUMB_H,
1867
+ HSCROLL_THUMB_H / 2
1868
+ );
1869
+ r.fill(this.hTrack?.dragging ? HSCROLL_THUMB_ACTIVE_COLOR : HSCROLL_THUMB_COLOR);
1870
+ }
1753
1871
  }
1754
1872
  };
1755
1873
  var codeAtlases = /* @__PURE__ */ new Map();
package/dist/index.mjs CHANGED
@@ -1273,6 +1273,70 @@ function highlightLine(line, lang, theme, carry = null) {
1273
1273
  flush(theme.codeColor);
1274
1274
  return { segments, carry: null };
1275
1275
  }
1276
+ var HSCROLL_HIT_H = 12;
1277
+ var HSCROLL_THUMB_H = 4;
1278
+ var HSCROLL_THUMB_INSET = 4;
1279
+ var HSCROLL_THUMB_MIN = 24;
1280
+ var HSCROLL_TRACK_COLOR = "rgba(128, 128, 128, 0.14)";
1281
+ var HSCROLL_THUMB_COLOR = "rgba(128, 128, 128, 0.45)";
1282
+ var HSCROLL_THUMB_ACTIVE_COLOR = "rgba(128, 128, 128, 0.75)";
1283
+ function hScrollMetrics(trackW, maxScrollX) {
1284
+ const content = trackW + maxScrollX;
1285
+ const proportional = content > 0 ? trackW / content * trackW : trackW;
1286
+ const thumbW = Math.min(trackW, Math.max(HSCROLL_THUMB_MIN, proportional));
1287
+ return { thumbW, range: Math.max(1, trackW - thumbW) };
1288
+ }
1289
+ var HScrollTrack = class extends UIComponent {
1290
+ constructor(block) {
1291
+ super();
1292
+ this.block = block;
1293
+ this.on("pointerdown", (e) => {
1294
+ if (e.localX === void 0) return;
1295
+ const maxSX = this.block.maxScrollX;
1296
+ if (maxSX <= 0) return;
1297
+ const { thumbW, range } = hScrollMetrics(this.width, maxSX);
1298
+ const thumbX = this.block.scrollX / maxSX * range;
1299
+ if (e.localX < thumbX || e.localX > thumbX + thumbW) {
1300
+ this.block.setScrollX((e.localX - thumbW / 2) / range * maxSX);
1301
+ }
1302
+ this.dragging = true;
1303
+ this.dragAnchorX = e.localX;
1304
+ this.scrollAtDragStart = this.block.scrollX;
1305
+ this.scene?.markDirty();
1306
+ });
1307
+ this.on("pointermove", (e) => {
1308
+ if (!this.dragging || e.localX === void 0) return;
1309
+ const maxSX = this.block.maxScrollX;
1310
+ const { range } = hScrollMetrics(this.width, maxSX);
1311
+ const dx = e.localX - this.dragAnchorX;
1312
+ this.block.setScrollX(this.scrollAtDragStart + dx / range * maxSX);
1313
+ });
1314
+ const endDrag = () => {
1315
+ if (!this.dragging) return;
1316
+ this.dragging = false;
1317
+ this.scene?.markDirty();
1318
+ };
1319
+ this.on("pointerup", endDrag);
1320
+ this.on("pointerleave", endDrag);
1321
+ }
1322
+ block;
1323
+ /** True while a drag owns the thumb; read by the block's painter for the active tint. */
1324
+ dragging = false;
1325
+ dragAnchorX = 0;
1326
+ scrollAtDragStart = 0;
1327
+ getA11yAttributes() {
1328
+ return {
1329
+ role: "scrollbar",
1330
+ label: "Scroll code horizontally",
1331
+ value: String(Math.round(this.block.scrollX)),
1332
+ valuemin: "0",
1333
+ valuemax: String(Math.round(this.block.maxScrollX))
1334
+ };
1335
+ }
1336
+ /** Nothing to draw: the thumb is painted by the owning block, so both share one pass. */
1337
+ render() {
1338
+ }
1339
+ };
1276
1340
  var CodeBlock = class extends UIComponent {
1277
1341
  lines;
1278
1342
  /**
@@ -1310,6 +1374,12 @@ var CodeBlock = class extends UIComponent {
1310
1374
  /** Memoized widest prepared line, keyed by the grid identity it came from. */
1311
1375
  contentWidthGrid = null;
1312
1376
  contentWidthValue = 0;
1377
+ /**
1378
+ * The interactive horizontal scrollbar strip (#527), created lazily by
1379
+ * {@link syncScrollTrack} the first time the block overflows. Kept, and merely
1380
+ * disabled, when the overflow later disappears — see there for why.
1381
+ */
1382
+ hTrack = null;
1313
1383
  lang;
1314
1384
  theme;
1315
1385
  /**
@@ -1614,6 +1684,31 @@ var CodeBlock = class extends UIComponent {
1614
1684
  }
1615
1685
  return this.grid;
1616
1686
  }
1687
+ /**
1688
+ * Fit the interactive scrollbar strip (#527) to the current overflow.
1689
+ *
1690
+ * Called from {@link render} rather than from `setWidth` or `buildLines`:
1691
+ * `setWidth` is documented as costing nothing and `maxScrollX` costs a grid
1692
+ * build, while `render` is about to pay for that grid anyway. Once created,
1693
+ * the child is KEPT and merely de-`interactive`d when the overflow disappears
1694
+ * — this runs inside the scene's tree walk, and removing a child mid-walk is
1695
+ * how siblings get skipped.
1696
+ */
1697
+ syncScrollTrack() {
1698
+ if (this.maxScrollX <= 0) {
1699
+ if (this.hTrack) this.hTrack.interactive = false;
1700
+ return;
1701
+ }
1702
+ if (!this.hTrack) {
1703
+ this.hTrack = new HScrollTrack(this);
1704
+ this.add(this.hTrack);
1705
+ }
1706
+ this.hTrack.interactive = true;
1707
+ this.hTrack.x = this.pad;
1708
+ this.hTrack.y = this.height - HSCROLL_HIT_H;
1709
+ this.hTrack.width = Math.max(0, this.width - this.pad * 2);
1710
+ this.hTrack.height = HSCROLL_HIT_H;
1711
+ }
1617
1712
  /**
1618
1713
  * Not hit-testable, and deliberately still not `interactive`, even though the
1619
1714
  * block now consumes wheel events to scroll.
@@ -1622,11 +1717,16 @@ var CodeBlock = class extends UIComponent {
1622
1717
  * hit-testing, so no a11y shadow node is needed. Creating one would place a
1623
1718
  * `pointer-events: auto` element above the transparent text mirror and swallow
1624
1719
  * the mousedown that starts a native drag-selection.
1720
+ *
1721
+ * Pointer-driven scrolling therefore lives on the {@link HScrollTrack} CHILD,
1722
+ * whose shadow node covers only the bottom-padding strip below the last
1723
+ * selectable carrier — never the text itself.
1625
1724
  */
1626
1725
  isPointInside() {
1627
1726
  return false;
1628
1727
  }
1629
1728
  render(r) {
1729
+ this.syncScrollTrack();
1630
1730
  r.beginPath();
1631
1731
  r.roundRect(0, 0, this.width, this.height, this.theme.codeRadius);
1632
1732
  r.fill(this.theme.codeBgColor);
@@ -1696,6 +1796,24 @@ var CodeBlock = class extends UIComponent {
1696
1796
  }
1697
1797
  }
1698
1798
  r.restore();
1799
+ const maxSX = this.maxScrollX;
1800
+ if (maxSX > 0) {
1801
+ const trackW = Math.max(0, this.width - this.pad * 2);
1802
+ const { thumbW, range } = hScrollMetrics(trackW, maxSX);
1803
+ const trackY = this.height - HSCROLL_THUMB_H - HSCROLL_THUMB_INSET;
1804
+ r.beginPath();
1805
+ r.roundRect(this.pad, trackY, trackW, HSCROLL_THUMB_H, HSCROLL_THUMB_H / 2);
1806
+ r.fill(HSCROLL_TRACK_COLOR);
1807
+ r.beginPath();
1808
+ r.roundRect(
1809
+ this.pad + this.scrollX / maxSX * range,
1810
+ trackY,
1811
+ thumbW,
1812
+ HSCROLL_THUMB_H,
1813
+ HSCROLL_THUMB_H / 2
1814
+ );
1815
+ r.fill(this.hTrack?.dragging ? HSCROLL_THUMB_ACTIVE_COLOR : HSCROLL_THUMB_COLOR);
1816
+ }
1699
1817
  }
1700
1818
  };
1701
1819
  var codeAtlases = /* @__PURE__ */ new Map();
@@ -71,6 +71,12 @@ export declare class CodeBlock extends UIComponent {
71
71
  /** Memoized widest prepared line, keyed by the grid identity it came from. */
72
72
  private contentWidthGrid;
73
73
  private contentWidthValue;
74
+ /**
75
+ * The interactive horizontal scrollbar strip (#527), created lazily by
76
+ * {@link syncScrollTrack} the first time the block overflows. Kept, and merely
77
+ * disabled, when the overflow later disappears — see there for why.
78
+ */
79
+ private hTrack;
74
80
  private lang;
75
81
  private theme;
76
82
  /**
@@ -203,6 +209,17 @@ export declare class CodeBlock extends UIComponent {
203
209
  */
204
210
  private buildLines;
205
211
  private ensureGrid;
212
+ /**
213
+ * Fit the interactive scrollbar strip (#527) to the current overflow.
214
+ *
215
+ * Called from {@link render} rather than from `setWidth` or `buildLines`:
216
+ * `setWidth` is documented as costing nothing and `maxScrollX` costs a grid
217
+ * build, while `render` is about to pay for that grid anyway. Once created,
218
+ * the child is KEPT and merely de-`interactive`d when the overflow disappears
219
+ * — this runs inside the scene's tree walk, and removing a child mid-walk is
220
+ * how siblings get skipped.
221
+ */
222
+ private syncScrollTrack;
206
223
  /**
207
224
  * Not hit-testable, and deliberately still not `interactive`, even though the
208
225
  * block now consumes wheel events to scroll.
@@ -211,6 +228,10 @@ export declare class CodeBlock extends UIComponent {
211
228
  * hit-testing, so no a11y shadow node is needed. Creating one would place a
212
229
  * `pointer-events: auto` element above the transparent text mirror and swallow
213
230
  * the mousedown that starts a native drag-selection.
231
+ *
232
+ * Pointer-driven scrolling therefore lives on the {@link HScrollTrack} CHILD,
233
+ * whose shadow node covers only the bottom-padding strip below the last
234
+ * selectable carrier — never the text itself.
214
235
  */
215
236
  isPointInside(): boolean;
216
237
  render(r: IRenderer): void;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vectojs/markdown",
3
- "version": "0.20.2",
3
+ "version": "0.20.3",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },