histropediajs 1.6.2 → 1.7.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/index.d.ts CHANGED
@@ -19,8 +19,29 @@ export type DeepPartial<T> = T extends (...args: any) => any
19
19
  ? { [K in keyof T]?: DeepPartial<T[K]> }
20
20
  : T;
21
21
 
22
+ /** Remove an override and resolve its default/inherited value. */
23
+ export const UNSET: unique symbol;
24
+
25
+ /** Sparse setter patch; arrays remain replace-whole values. */
26
+ export type OptionPatch<T> = T extends (...args: any) => any
27
+ ? T
28
+ : T extends readonly any[]
29
+ ? T
30
+ : T extends object
31
+ ? { [K in keyof T]?: OptionPatch<T[K]> | typeof UNSET }
32
+ : T;
33
+
22
34
  /** Built-in card layouts stay in autocomplete while custom registered names still type-check. */
23
- export type CardLayoutName = 'portrait' | 'landscape' | (string & {});
35
+ export type CardLayoutName =
36
+ | 'portrait'
37
+ | 'landscape'
38
+ | 'title-only'
39
+ | 'title-subtitle'
40
+ | 'label'
41
+ | 'image-text'
42
+ | 'image-only'
43
+ | 'pill'
44
+ | (string & {});
24
45
 
25
46
  /** Runtime visibility state used by lanes and time bands. */
26
47
  export type VisibilityState = 'visible' | 'hidden';
@@ -358,18 +379,32 @@ export interface ArticleStyleText {
358
379
  baseline?: 'top' | 'hanging' | 'middle' | 'alphabetic' | 'bottom';
359
380
  margin?: number;
360
381
  lineHeight?: number;
382
+ /** Maximum rendered lines: header defaults to 2, subheader to 1. Zero disables text. */
361
383
  numberOfLines?: number;
362
384
  offsetY?: number;
363
385
  }
364
386
 
387
+ /** Block padding in non-negative CSS pixels. Each side defaults to zero. */
388
+ export interface ArticleStylePadding {
389
+ top?: number;
390
+ right?: number;
391
+ bottom?: number;
392
+ left?: number;
393
+ }
394
+
395
+ /** Image margins in non-negative CSS pixels. Unspecified sides resolve to zero. */
396
+ export type ArticleStyleMargin = ArticleStylePadding;
397
+
365
398
  export interface ArticleStyle {
366
- /** @defaultValue 150 */
367
- width?: number;
368
- /** Landscape card layout only. @defaultValue 70 */
369
- height?: number;
399
+ /** Fixed width in CSS pixels, or content/layout width capped by maxWidth. Image-only uses the image aspect ratio with fixed height, or its default width when both dimensions are auto. @defaultValue 150 */
400
+ width?: number | 'auto';
401
+ /** Optional finite, non-negative cap in CSS pixels. Only applies when width is 'auto'. */
402
+ maxWidth?: number;
403
+ /** Fixed card height or content height. Portrait defaults to 'auto'; landscape to 70. */
404
+ height?: number | 'auto';
370
405
  /** Card background fill color. @defaultValue '#e5e5e5' */
371
406
  color?: string;
372
- /** Inner card background. @defaultValue '#fff' */
407
+ /** Inner card background, also used for image-only placeholders while images are loading, missing or failed. @defaultValue '#fff' */
373
408
  backgroundColor?: string;
374
409
  /** Portrait card layout only. @defaultValue 3 */
375
410
  topRadius?: number;
@@ -383,25 +418,33 @@ export interface ArticleStyle {
383
418
  */
384
419
  maxImageHeight?: number;
385
420
  image?: {
421
+ /** Include the image and its spacing in card sizing and rendering. @defaultValue true */
422
+ visible?: boolean;
386
423
  /** @defaultValue 'natural' */
387
424
  shape?: 'natural' | 'square' | 'circle';
388
- /** @defaultValue 0 */
389
- margin?: number;
390
- /** @defaultValue 200 */
425
+ /** Spacing around the image, including its right edge beside landscape text blocks. A number applies to all sides. Portrait defaults to 0; landscape to 4. */
426
+ margin?: number | ArticleStyleMargin;
427
+ /** Maximum rendered image height for all shapes in both built-in layouts, with automatic or numeric card heights. Does not change an explicit card height. @defaultValue 200 */
391
428
  maxHeight?: number;
392
429
  /** @defaultValue 0 */
393
430
  borderRadius?: number;
394
431
  };
395
432
  header?: {
396
- /** Portrait card layout only. @defaultValue 50 */
397
- height?: number;
433
+ /** Explicit allocation, zero to disable, or rendered-line height. Portrait defaults to 50; landscape to 'auto'. */
434
+ height?: number | 'auto';
435
+ /** Block fill. Falls back to color in portrait; transparent by default in landscape. */
436
+ color?: string;
437
+ /** Included in block height; insets text in addition to text.margin. A number applies to all sides. */
438
+ padding?: number | ArticleStylePadding;
398
439
  text?: ArticleStyleText;
399
440
  };
400
441
  subheader?: {
401
- /** Portrait card layout only. @defaultValue 30 */
402
- height?: number;
403
- /** Portrait card layout only. @defaultValue '#555' */
442
+ /** Explicit allocation, zero to disable, or rendered-line height. Portrait defaults to 30; landscape to 'auto'. */
443
+ height?: number | 'auto';
444
+ /** Block fill. Defaults to '#555' in portrait and transparent in landscape. */
404
445
  color?: string;
446
+ /** Included in block height; insets text in addition to text.margin. A number applies to all sides. */
447
+ padding?: number | ArticleStylePadding;
405
448
  text?: ArticleStyleText;
406
449
  };
407
450
  shadow?: {
@@ -415,11 +458,22 @@ export interface ArticleStyle {
415
458
  /** @defaultValue 1 */ width?: number;
416
459
  };
417
460
  connectorLine?: {
418
- /** @defaultValue true */ visible?: boolean;
461
+ /** Show both the connector stroke and arrow. @defaultValue true */ visible?: boolean;
462
+ /** CSS colour or 'article' for the current state's article colour. @defaultValue 'article' */
463
+ color?: string;
464
+ /** Canvas dash lengths in CSS pixels; [] is solid. Applies only to the line stroke. @defaultValue [] */
465
+ dash?: number[];
466
+ /** Shape of the line stroke's ends. @defaultValue 'butt' */
467
+ lineCap?: 'butt' | 'round' | 'square';
419
468
  /** @defaultValue 18 */ offsetX?: number;
420
469
  /** @defaultValue -20 */ offsetY?: number;
421
- /** @defaultValue 1 */ thickness?: number;
470
+ /** Stroke width in CSS pixels; 0 hides only the stroke. Active default: 2. @defaultValue 1 */
471
+ thickness?: number;
422
472
  arrow?: {
473
+ /** Show the arrow without changing the line's attachment point. @defaultValue true */
474
+ visible?: boolean;
475
+ /** CSS colour, 'article' for the current article colour, or 'inherit' for the resolved line colour. @defaultValue 'inherit' */
476
+ color?: string;
423
477
  /** @defaultValue 16 */ width?: number;
424
478
  /** @defaultValue 45 */ height?: number;
425
479
  };
@@ -535,7 +589,9 @@ export interface TimelineStyleOptions {
535
589
  /** Enable and reserve space for the parent-context row at every scale. @defaultValue false */
536
590
  visible?: boolean;
537
591
  /** Current major-label levels at which to render parent text. @defaultValue 'all' */
538
- levels?: 'all' | Array<
592
+ levels?:
593
+ | 'all'
594
+ | Array<
539
595
  | 'millisecond'
540
596
  | 'second'
541
597
  | 'minute'
@@ -893,6 +949,8 @@ export interface TimelineZoomOptions {
893
949
  allowCtrlWheel?: boolean;
894
950
  /** 'auto' picks smooth vs discrete from device; 'discrete' forces stepped. @defaultValue 'auto' */
895
951
  wheelMode?: 'auto' | 'discrete';
952
+ /** Touchscreen pinch zoom momentum; wheel/trackpad events retain their existing behaviour. */
953
+ pinchMomentum?: PinchMomentumOptions;
896
954
  /** Animate discrete/notched wheel zoom steps. */
897
955
  discreteWheelAnimation?: {
898
956
  /** Enable animated wheel zoom. @defaultValue true */
@@ -918,8 +976,23 @@ export interface TimelineZoomOptions {
918
976
  };
919
977
  }
920
978
 
979
+ export interface PinchMomentumOptions {
980
+ /** Continue zooming after releasing a touchscreen pinch. @defaultValue true */
981
+ active?: boolean;
982
+ /** Recent input window in ms used to estimate first-lift velocity, retaining a sparse baseline. @defaultValue 50 */
983
+ sampleWindow?: number;
984
+ /** Minimum release velocity in zoom levels/ms. @defaultValue 0.001 */
985
+ minVelocity?: number;
986
+ /** Velocity in zoom levels/ms at which momentum stops. @defaultValue 0.0001 */
987
+ stopVelocity?: number;
988
+ /** Exponential friction coefficient per ms. Lower values travel farther. @defaultValue 0.005 */
989
+ friction?: number;
990
+ /** Maximum zoom momentum duration in ms. @defaultValue 1200 */
991
+ maxDuration?: number;
992
+ }
993
+
921
994
  export interface PanMomentumOptions {
922
- /** Continue timeline panning after a drag release. @defaultValue true */
995
+ /** Continue timeline panning after a drag or pinch release. @defaultValue true */
923
996
  active?: boolean;
924
997
  /** Pointer movement sample window in ms used to estimate release velocity. @defaultValue 100 */
925
998
  sampleWindow?: number;
@@ -1383,9 +1456,11 @@ export type CardLayoutArticleContext = Article;
1383
1456
  export interface RegisteredCardLayout {
1384
1457
  name: string;
1385
1458
  draw(this: Article, ctx: CanvasRenderingContext2D): void;
1459
+ /** Return a numeric width; with width: 'auto', measure content and layout slots, not image resolution. The size cache applies maxWidth. */
1386
1460
  getWidth?(this: Article): number;
1387
1461
  getHeight?(this: Article): number;
1388
1462
  getIconBox?(this: Article): CardLayoutIconBox | false;
1463
+ getConnectorEnd?(this: Article): CanvasPosition;
1389
1464
  defaultStyle?: ArticleStyle;
1390
1465
  defaultHoverStyle?: ArticleStyle;
1391
1466
  defaultActiveStyle?: ArticleStyle;
@@ -1395,10 +1470,27 @@ export interface RegisteredCardLayout {
1395
1470
  export interface CardLayoutAliasDefinition {
1396
1471
  name: string;
1397
1472
  aliasOf: CardLayoutName;
1473
+ extends?: never;
1474
+ }
1475
+
1476
+ /** Registration-time snapshot of an existing layout, with optional hook and style overrides. */
1477
+ export interface CardLayoutExtensionDefinition
1478
+ extends Partial<
1479
+ Omit<RegisteredCardLayout, 'name' | 'defaultStyle' | 'defaultHoverStyle' | 'defaultActiveStyle'>
1480
+ > {
1481
+ name: string;
1482
+ extends: CardLayoutName;
1483
+ aliasOf?: never;
1484
+ defaultStyle?: OptionPatch<ArticleStyle> | typeof UNSET;
1485
+ defaultHoverStyle?: OptionPatch<ArticleStyle> | typeof UNSET;
1486
+ defaultActiveStyle?: OptionPatch<ArticleStyle> | typeof UNSET;
1398
1487
  }
1399
1488
 
1400
1489
  /** Definition accepted by `Timeline.registerCardLayout(...)`. */
1401
- export type CardLayoutDefinition = RegisteredCardLayout | CardLayoutAliasDefinition;
1490
+ export type CardLayoutDefinition =
1491
+ | (RegisteredCardLayout & { aliasOf?: never; extends?: never })
1492
+ | CardLayoutAliasDefinition
1493
+ | CardLayoutExtensionDefinition;
1402
1494
 
1403
1495
  // ------------------------------------------------------------------------------------
1404
1496
  // Classes
@@ -1407,13 +1499,16 @@ export type CardLayoutDefinition = RegisteredCardLayout | CardLayoutAliasDefinit
1407
1499
  export interface AnimOptions {
1408
1500
  /** Duration in ms. @defaultValue 2000 */ duration?: number;
1409
1501
  /** Easing name. @defaultValue 'swing' */ easing?: EasingName;
1410
- /** Callback when animation completes. */ complete?: () => void;
1502
+ /** Asynchronous completion after the final viewport draw. Takes precedence over complete. */ onComplete?: () => void;
1503
+ /** Legacy alias for onComplete. */ complete?: () => void;
1411
1504
  /** Additional horizontal shift, used by pinch. */ offsetX?: number;
1412
1505
  }
1413
1506
 
1414
1507
  export type FitDateRangePadding = number | { left?: number; right?: number };
1415
1508
 
1416
1509
  export interface FitDateRangeAnimationOptions {
1510
+ /** Legacy completion alias; top-level onComplete takes precedence. */
1511
+ complete?: () => void;
1417
1512
  /** Enable simultaneous animated zooming and panning. @defaultValue false */
1418
1513
  active?: boolean;
1419
1514
  /** Duration in ms. Inherits the viewport animation default when omitted. */
@@ -1423,6 +1518,8 @@ export interface FitDateRangeAnimationOptions {
1423
1518
  }
1424
1519
 
1425
1520
  export interface FitDateRangeOptions {
1521
+ /** Runs asynchronously after the final viewport draw, including immediate/no-op requests. Cancelled requests do not complete. */
1522
+ onComplete?: () => void;
1426
1523
  /** Edge padding in px, or per-side padding for asymmetric fits. @defaultValue 0 */
1427
1524
  padding?: FitDateRangePadding;
1428
1525
  /** Optional animation settings for the fit. */
@@ -1430,6 +1527,8 @@ export interface FitDateRangeOptions {
1430
1527
  }
1431
1528
 
1432
1529
  export interface SetStartDateOptions {
1530
+ /** Runs asynchronously after the final viewport draw, including immediate/no-op requests. Cancelled requests do not complete. */
1531
+ onComplete?: () => void;
1433
1532
  /** Left padding in px for the supplied start date. This only affects the left side. @defaultValue 0 */
1434
1533
  padding?: number;
1435
1534
  /** Optional animation settings matching `fitDateRange(..., { animation })`. */
@@ -1437,6 +1536,8 @@ export interface SetStartDateOptions {
1437
1536
  }
1438
1537
 
1439
1538
  export interface FitArticleRangeOptions {
1539
+ /** Runs asynchronously after the final viewport draw, including immediate/no-op requests. Cancelled requests do not complete. */
1540
+ onComplete?: () => void;
1440
1541
  /** Edge padding in px, or per-side padding for asymmetric fits. @defaultValue 0 */
1441
1542
  padding?: FitDateRangePadding;
1442
1543
  /** Optional animation settings for the fit. */
@@ -1444,6 +1545,8 @@ export interface FitArticleRangeOptions {
1444
1545
  }
1445
1546
 
1446
1547
  export interface FitArticlesOptions {
1548
+ /** Runs asynchronously after the final viewport draw, including immediate/no-op requests. Cancelled requests do not complete. */
1549
+ onComplete?: () => void;
1447
1550
  /** Space in px between the outermost fitted article cards and the viewport edge. @defaultValue 40 */
1448
1551
  padding?: FitDateRangePadding;
1449
1552
  /** Optional article predicate. Defaults to fitting articles that are not `hiddenByFilter`. */
@@ -1549,7 +1652,9 @@ export class Timeline {
1549
1652
  fitArticleRange(article: Article, options?: FitArticleRangeOptions): boolean;
1550
1653
  /** Fit currently unfiltered article cards into the viewport using each article's start date, card width, and connector offset. */
1551
1654
  fitArticles(options?: FitArticlesOptions): boolean;
1552
- /** Animate to an absolute zoom value using the built-in tween. */
1655
+ /** Settle an existing date-bound overflow; false means no correction or callback. */
1656
+ settleDateBounds(options?: { animate?: boolean; onComplete?: () => void }): boolean;
1657
+ /** Animate to an absolute zoom value. Completion is asynchronous after the final draw. */
1553
1658
  zoomToAnim(newZoom: number, complete?: () => void): void;
1554
1659
 
1555
1660
  // Drawing
@@ -1585,7 +1690,7 @@ export class Timeline {
1585
1690
  // Options
1586
1691
  /** Get or set options via dot-path or partial object. */
1587
1692
  setOption(path: string, value: unknown): void;
1588
- setOption<T extends DeepPartial<TimelineOptions>>(patch: T): void;
1693
+ setOption<T extends OptionPatch<TimelineOptions>>(patch: T): void;
1589
1694
  setOption(path: string): unknown;
1590
1695
  /** Resize the canvas. */
1591
1696
  setSize(width: number, height: number): void;
@@ -1645,22 +1750,22 @@ export class Article {
1645
1750
 
1646
1751
  /** Update article data at runtime (dot-path or object). */
1647
1752
  setOption(path: string, value: unknown): void;
1648
- setOption(patch: Partial<ArticleData>): void;
1753
+ setOption(patch: OptionPatch<ArticleData>): void;
1649
1754
  setOption(path: string): unknown;
1650
1755
 
1651
1756
  /** Update normal style (dot-path or object). */
1652
1757
  setStyle(path: string, value: unknown): void;
1653
- setStyle(style: Partial<ArticleStyle>): void;
1758
+ setStyle(style: OptionPatch<ArticleStyle>): void;
1654
1759
  setStyle(path: string): unknown;
1655
1760
 
1656
1761
  /** Update hover style (dot-path or object). */
1657
1762
  setHoverStyle(path: string, value: unknown): void;
1658
- setHoverStyle(style: Partial<ArticleStyle>): void;
1763
+ setHoverStyle(style: OptionPatch<ArticleStyle>): void;
1659
1764
  setHoverStyle(path: string): unknown;
1660
1765
 
1661
1766
  /** Update active style (dot-path or object). */
1662
1767
  setActiveStyle(path: string, value: unknown): void;
1663
- setActiveStyle(style: Partial<ArticleStyle>): void;
1768
+ setActiveStyle(style: OptionPatch<ArticleStyle>): void;
1664
1769
  setActiveStyle(path: string): unknown;
1665
1770
 
1666
1771
  /** Move instantly to absolute canvas position. */
@@ -1696,12 +1801,12 @@ export class TimeBand {
1696
1801
 
1697
1802
  /** Update band data at runtime (dot-path or object). */
1698
1803
  setOption(path: string, value: unknown): void;
1699
- setOption(patch: Partial<TimeBandData>): void;
1804
+ setOption(patch: OptionPatch<TimeBandData>): void;
1700
1805
  setOption(path: string): unknown;
1701
1806
 
1702
1807
  /** Update band style (dot-path or object). */
1703
1808
  setStyle(path: string, value: unknown): void;
1704
- setStyle(style: Partial<TimeBandStyle>): void;
1809
+ setStyle(style: OptionPatch<TimeBandStyle>): void;
1705
1810
  setStyle(path: string): unknown;
1706
1811
  }
1707
1812
 
@@ -1742,12 +1847,12 @@ export class Chart {
1742
1847
 
1743
1848
  /** Update chart data at runtime (dot-path or object). */
1744
1849
  setOption(path: string, value: unknown): void;
1745
- setOption(patch: Partial<Omit<ChartData, 'id'>>): void;
1850
+ setOption(patch: OptionPatch<Omit<ChartData, 'id'>>): void;
1746
1851
  setOption(path: string): unknown;
1747
1852
 
1748
1853
  /** Update chart style (dot-path or object). */
1749
1854
  setStyle(path: string, value: unknown): void;
1750
- setStyle(style: Partial<ChartStyle>): void;
1855
+ setStyle(style: OptionPatch<ChartStyle>): void;
1751
1856
  setStyle(path: string): unknown;
1752
1857
 
1753
1858
  /** Resolve the active y-domain for the chart. */
@@ -1767,7 +1872,7 @@ export class Lane {
1767
1872
 
1768
1873
  /** Update lane data at runtime (dot-path or object). */
1769
1874
  setOption(path: string, value: unknown): void;
1770
- setOption(patch: Partial<LaneData>): void;
1875
+ setOption(patch: OptionPatch<LaneData>): void;
1771
1876
  setOption(path: string): unknown;
1772
1877
 
1773
1878
  /** Hide the lane by setting visibility to "hidden". */
@@ -1793,7 +1898,7 @@ export class Lane {
1793
1898
 
1794
1899
  /** Update lane style (dot-path or object). */
1795
1900
  setStyle(path: string, value: unknown): void;
1796
- setStyle(style: Partial<LaneStyle>): void;
1901
+ setStyle(style: OptionPatch<LaneStyle>): void;
1797
1902
  setStyle(path: string): unknown;
1798
1903
  }
1799
1904
 
@@ -1808,6 +1913,7 @@ export const ARTICLE_TO_SORTER: ArticleSorter;
1808
1913
  export const ARTICLE_TOP_SORTER: ArticleSorter;
1809
1914
  export const ARTICLE_ROW_SORTER: ArticleSorter;
1810
1915
  export const ARTICLE_RANK_SORTER: ArticleSorter;
1916
+ /** Selected first, then starred, then normal; descending rank within each tier. */
1811
1917
  export const ARTICLE_EFFECTIVE_RANK_SORTER: ArticleSorter;
1812
1918
  export const ARTICLE_POSITION_SORTER: ArticleSorter;
1813
1919
 
@@ -1816,6 +1922,7 @@ export const ARTICLE_POSITION_SORTER: ArticleSorter;
1816
1922
  // ------------------------------------------------------------------------------------
1817
1923
 
1818
1924
  export interface HistropediaApi {
1925
+ readonly UNSET: typeof UNSET;
1819
1926
  Timeline: typeof Timeline;
1820
1927
  Article: typeof Article;
1821
1928
  Chart: typeof Chart;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "histropediajs",
3
- "version": "1.6.2",
3
+ "version": "1.7.0",
4
4
  "description": "JavaScript library for rendering interactive zoomable timelines in HTML5 canvas.",
5
5
  "main": "./dist/histropedia.esm.js",
6
6
  "module": "./dist/histropedia.esm.js",
@@ -30,6 +30,9 @@
30
30
  },
31
31
  "scripts": {
32
32
  "dev": "vite serve ./demo --host",
33
+ "benchmark:build": "node scripts/benchmark-build.mjs",
34
+ "benchmark:last": "node scripts/benchmark-build.mjs --last",
35
+ "benchmark:clean": "node scripts/benchmark-build.mjs --clean",
33
36
  "build:es": "vite build --config vite.config.js",
34
37
  "build:umd": "vite build --config vite.legacy.config.js",
35
38
  "build:umd:min": "vite build --config vite.legacy.config.js --mode minified",
@@ -76,18 +79,18 @@
76
79
  "license": "SEE LICENSE IN LICENSE",
77
80
  "devDependencies": {
78
81
  "@eslint/js": "^9.34.0",
79
- "@vitest/coverage-v8": "^4.1.11",
82
+ "@vitest/coverage-v8": "^4.1.12",
80
83
  "eslint": "^9.34.0",
81
84
  "eslint-plugin-import": "^2.32.0",
82
85
  "eslint-plugin-simple-import-sort": "^12.1.1",
83
86
  "globals": "^14.0.0",
84
87
  "husky": "^9.1.7",
85
88
  "jsdom": "^25.0.1",
86
- "lint-staged": "^15.5.2",
89
+ "lint-staged": "^16.4.0",
87
90
  "prettier": "^3.6.2",
88
91
  "typescript": "6.0.3",
89
92
  "vite": "^7.0.4",
90
- "vitest": "^4.1.11"
93
+ "vitest": "^4.1.12"
91
94
  },
92
95
  "lint-staged": {
93
96
  "**/*.{js,mjs,cjs}": [