histropediajs 1.6.1 → 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';
@@ -212,6 +233,9 @@ export interface DmyConstructor {
212
233
  new (value: Dmy): DmyInstance;
213
234
  readonly prototype: DmyInstance;
214
235
  getByDayOfYear(year: number, dayOfYear: number): DmyInstance;
236
+ /** Period helpers take internal astronomical years (0 = 1 BCE), after any input shift.
237
+ * Decades group historical tens digits; boundary buckets are 9 through 1 BCE and 1 through 9 CE.
238
+ */
215
239
  CreateAsStartOfPeriod(date: Dmy, precision?: Precision): DmyInstance;
216
240
  CreateAsEndOfPeriod(date: Dmy, precision?: Precision): DmyInstance;
217
241
  CreateAsExclusiveEndOfPeriod(date: Dmy, precision?: Precision): DmyInstance;
@@ -355,18 +379,32 @@ export interface ArticleStyleText {
355
379
  baseline?: 'top' | 'hanging' | 'middle' | 'alphabetic' | 'bottom';
356
380
  margin?: number;
357
381
  lineHeight?: number;
382
+ /** Maximum rendered lines: header defaults to 2, subheader to 1. Zero disables text. */
358
383
  numberOfLines?: number;
359
384
  offsetY?: number;
360
385
  }
361
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
+
362
398
  export interface ArticleStyle {
363
- /** @defaultValue 150 */
364
- width?: number;
365
- /** Landscape card layout only. @defaultValue 70 */
366
- 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';
367
405
  /** Card background fill color. @defaultValue '#e5e5e5' */
368
406
  color?: string;
369
- /** Inner card background. @defaultValue '#fff' */
407
+ /** Inner card background, also used for image-only placeholders while images are loading, missing or failed. @defaultValue '#fff' */
370
408
  backgroundColor?: string;
371
409
  /** Portrait card layout only. @defaultValue 3 */
372
410
  topRadius?: number;
@@ -380,25 +418,33 @@ export interface ArticleStyle {
380
418
  */
381
419
  maxImageHeight?: number;
382
420
  image?: {
421
+ /** Include the image and its spacing in card sizing and rendering. @defaultValue true */
422
+ visible?: boolean;
383
423
  /** @defaultValue 'natural' */
384
424
  shape?: 'natural' | 'square' | 'circle';
385
- /** @defaultValue 0 */
386
- margin?: number;
387
- /** @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 */
388
428
  maxHeight?: number;
389
429
  /** @defaultValue 0 */
390
430
  borderRadius?: number;
391
431
  };
392
432
  header?: {
393
- /** Portrait card layout only. @defaultValue 50 */
394
- 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;
395
439
  text?: ArticleStyleText;
396
440
  };
397
441
  subheader?: {
398
- /** Portrait card layout only. @defaultValue 30 */
399
- height?: number;
400
- /** 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. */
401
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;
402
448
  text?: ArticleStyleText;
403
449
  };
404
450
  shadow?: {
@@ -412,11 +458,22 @@ export interface ArticleStyle {
412
458
  /** @defaultValue 1 */ width?: number;
413
459
  };
414
460
  connectorLine?: {
415
- /** @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';
416
468
  /** @defaultValue 18 */ offsetX?: number;
417
469
  /** @defaultValue -20 */ offsetY?: number;
418
- /** @defaultValue 1 */ thickness?: number;
470
+ /** Stroke width in CSS pixels; 0 hides only the stroke. Active default: 2. @defaultValue 1 */
471
+ thickness?: number;
419
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;
420
477
  /** @defaultValue 16 */ width?: number;
421
478
  /** @defaultValue 45 */ height?: number;
422
479
  };
@@ -532,7 +589,9 @@ export interface TimelineStyleOptions {
532
589
  /** Enable and reserve space for the parent-context row at every scale. @defaultValue false */
533
590
  visible?: boolean;
534
591
  /** Current major-label levels at which to render parent text. @defaultValue 'all' */
535
- levels?: 'all' | Array<
592
+ levels?:
593
+ | 'all'
594
+ | Array<
536
595
  | 'millisecond'
537
596
  | 'second'
538
597
  | 'minute'
@@ -890,6 +949,8 @@ export interface TimelineZoomOptions {
890
949
  allowCtrlWheel?: boolean;
891
950
  /** 'auto' picks smooth vs discrete from device; 'discrete' forces stepped. @defaultValue 'auto' */
892
951
  wheelMode?: 'auto' | 'discrete';
952
+ /** Touchscreen pinch zoom momentum; wheel/trackpad events retain their existing behaviour. */
953
+ pinchMomentum?: PinchMomentumOptions;
893
954
  /** Animate discrete/notched wheel zoom steps. */
894
955
  discreteWheelAnimation?: {
895
956
  /** Enable animated wheel zoom. @defaultValue true */
@@ -915,8 +976,23 @@ export interface TimelineZoomOptions {
915
976
  };
916
977
  }
917
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
+
918
994
  export interface PanMomentumOptions {
919
- /** Continue timeline panning after a drag release. @defaultValue true */
995
+ /** Continue timeline panning after a drag or pinch release. @defaultValue true */
920
996
  active?: boolean;
921
997
  /** Pointer movement sample window in ms used to estimate release velocity. @defaultValue 100 */
922
998
  sampleWindow?: number;
@@ -1380,9 +1456,11 @@ export type CardLayoutArticleContext = Article;
1380
1456
  export interface RegisteredCardLayout {
1381
1457
  name: string;
1382
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. */
1383
1460
  getWidth?(this: Article): number;
1384
1461
  getHeight?(this: Article): number;
1385
1462
  getIconBox?(this: Article): CardLayoutIconBox | false;
1463
+ getConnectorEnd?(this: Article): CanvasPosition;
1386
1464
  defaultStyle?: ArticleStyle;
1387
1465
  defaultHoverStyle?: ArticleStyle;
1388
1466
  defaultActiveStyle?: ArticleStyle;
@@ -1392,10 +1470,27 @@ export interface RegisteredCardLayout {
1392
1470
  export interface CardLayoutAliasDefinition {
1393
1471
  name: string;
1394
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;
1395
1487
  }
1396
1488
 
1397
1489
  /** Definition accepted by `Timeline.registerCardLayout(...)`. */
1398
- export type CardLayoutDefinition = RegisteredCardLayout | CardLayoutAliasDefinition;
1490
+ export type CardLayoutDefinition =
1491
+ | (RegisteredCardLayout & { aliasOf?: never; extends?: never })
1492
+ | CardLayoutAliasDefinition
1493
+ | CardLayoutExtensionDefinition;
1399
1494
 
1400
1495
  // ------------------------------------------------------------------------------------
1401
1496
  // Classes
@@ -1404,13 +1499,16 @@ export type CardLayoutDefinition = RegisteredCardLayout | CardLayoutAliasDefinit
1404
1499
  export interface AnimOptions {
1405
1500
  /** Duration in ms. @defaultValue 2000 */ duration?: number;
1406
1501
  /** Easing name. @defaultValue 'swing' */ easing?: EasingName;
1407
- /** 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;
1408
1504
  /** Additional horizontal shift, used by pinch. */ offsetX?: number;
1409
1505
  }
1410
1506
 
1411
1507
  export type FitDateRangePadding = number | { left?: number; right?: number };
1412
1508
 
1413
1509
  export interface FitDateRangeAnimationOptions {
1510
+ /** Legacy completion alias; top-level onComplete takes precedence. */
1511
+ complete?: () => void;
1414
1512
  /** Enable simultaneous animated zooming and panning. @defaultValue false */
1415
1513
  active?: boolean;
1416
1514
  /** Duration in ms. Inherits the viewport animation default when omitted. */
@@ -1420,6 +1518,8 @@ export interface FitDateRangeAnimationOptions {
1420
1518
  }
1421
1519
 
1422
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;
1423
1523
  /** Edge padding in px, or per-side padding for asymmetric fits. @defaultValue 0 */
1424
1524
  padding?: FitDateRangePadding;
1425
1525
  /** Optional animation settings for the fit. */
@@ -1427,6 +1527,8 @@ export interface FitDateRangeOptions {
1427
1527
  }
1428
1528
 
1429
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;
1430
1532
  /** Left padding in px for the supplied start date. This only affects the left side. @defaultValue 0 */
1431
1533
  padding?: number;
1432
1534
  /** Optional animation settings matching `fitDateRange(..., { animation })`. */
@@ -1434,6 +1536,8 @@ export interface SetStartDateOptions {
1434
1536
  }
1435
1537
 
1436
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;
1437
1541
  /** Edge padding in px, or per-side padding for asymmetric fits. @defaultValue 0 */
1438
1542
  padding?: FitDateRangePadding;
1439
1543
  /** Optional animation settings for the fit. */
@@ -1441,6 +1545,8 @@ export interface FitArticleRangeOptions {
1441
1545
  }
1442
1546
 
1443
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;
1444
1550
  /** Space in px between the outermost fitted article cards and the viewport edge. @defaultValue 40 */
1445
1551
  padding?: FitDateRangePadding;
1446
1552
  /** Optional article predicate. Defaults to fitting articles that are not `hiddenByFilter`. */
@@ -1546,7 +1652,9 @@ export class Timeline {
1546
1652
  fitArticleRange(article: Article, options?: FitArticleRangeOptions): boolean;
1547
1653
  /** Fit currently unfiltered article cards into the viewport using each article's start date, card width, and connector offset. */
1548
1654
  fitArticles(options?: FitArticlesOptions): boolean;
1549
- /** 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. */
1550
1658
  zoomToAnim(newZoom: number, complete?: () => void): void;
1551
1659
 
1552
1660
  // Drawing
@@ -1582,7 +1690,7 @@ export class Timeline {
1582
1690
  // Options
1583
1691
  /** Get or set options via dot-path or partial object. */
1584
1692
  setOption(path: string, value: unknown): void;
1585
- setOption<T extends DeepPartial<TimelineOptions>>(patch: T): void;
1693
+ setOption<T extends OptionPatch<TimelineOptions>>(patch: T): void;
1586
1694
  setOption(path: string): unknown;
1587
1695
  /** Resize the canvas. */
1588
1696
  setSize(width: number, height: number): void;
@@ -1642,22 +1750,22 @@ export class Article {
1642
1750
 
1643
1751
  /** Update article data at runtime (dot-path or object). */
1644
1752
  setOption(path: string, value: unknown): void;
1645
- setOption(patch: Partial<ArticleData>): void;
1753
+ setOption(patch: OptionPatch<ArticleData>): void;
1646
1754
  setOption(path: string): unknown;
1647
1755
 
1648
1756
  /** Update normal style (dot-path or object). */
1649
1757
  setStyle(path: string, value: unknown): void;
1650
- setStyle(style: Partial<ArticleStyle>): void;
1758
+ setStyle(style: OptionPatch<ArticleStyle>): void;
1651
1759
  setStyle(path: string): unknown;
1652
1760
 
1653
1761
  /** Update hover style (dot-path or object). */
1654
1762
  setHoverStyle(path: string, value: unknown): void;
1655
- setHoverStyle(style: Partial<ArticleStyle>): void;
1763
+ setHoverStyle(style: OptionPatch<ArticleStyle>): void;
1656
1764
  setHoverStyle(path: string): unknown;
1657
1765
 
1658
1766
  /** Update active style (dot-path or object). */
1659
1767
  setActiveStyle(path: string, value: unknown): void;
1660
- setActiveStyle(style: Partial<ArticleStyle>): void;
1768
+ setActiveStyle(style: OptionPatch<ArticleStyle>): void;
1661
1769
  setActiveStyle(path: string): unknown;
1662
1770
 
1663
1771
  /** Move instantly to absolute canvas position. */
@@ -1693,12 +1801,12 @@ export class TimeBand {
1693
1801
 
1694
1802
  /** Update band data at runtime (dot-path or object). */
1695
1803
  setOption(path: string, value: unknown): void;
1696
- setOption(patch: Partial<TimeBandData>): void;
1804
+ setOption(patch: OptionPatch<TimeBandData>): void;
1697
1805
  setOption(path: string): unknown;
1698
1806
 
1699
1807
  /** Update band style (dot-path or object). */
1700
1808
  setStyle(path: string, value: unknown): void;
1701
- setStyle(style: Partial<TimeBandStyle>): void;
1809
+ setStyle(style: OptionPatch<TimeBandStyle>): void;
1702
1810
  setStyle(path: string): unknown;
1703
1811
  }
1704
1812
 
@@ -1739,12 +1847,12 @@ export class Chart {
1739
1847
 
1740
1848
  /** Update chart data at runtime (dot-path or object). */
1741
1849
  setOption(path: string, value: unknown): void;
1742
- setOption(patch: Partial<Omit<ChartData, 'id'>>): void;
1850
+ setOption(patch: OptionPatch<Omit<ChartData, 'id'>>): void;
1743
1851
  setOption(path: string): unknown;
1744
1852
 
1745
1853
  /** Update chart style (dot-path or object). */
1746
1854
  setStyle(path: string, value: unknown): void;
1747
- setStyle(style: Partial<ChartStyle>): void;
1855
+ setStyle(style: OptionPatch<ChartStyle>): void;
1748
1856
  setStyle(path: string): unknown;
1749
1857
 
1750
1858
  /** Resolve the active y-domain for the chart. */
@@ -1764,7 +1872,7 @@ export class Lane {
1764
1872
 
1765
1873
  /** Update lane data at runtime (dot-path or object). */
1766
1874
  setOption(path: string, value: unknown): void;
1767
- setOption(patch: Partial<LaneData>): void;
1875
+ setOption(patch: OptionPatch<LaneData>): void;
1768
1876
  setOption(path: string): unknown;
1769
1877
 
1770
1878
  /** Hide the lane by setting visibility to "hidden". */
@@ -1790,7 +1898,7 @@ export class Lane {
1790
1898
 
1791
1899
  /** Update lane style (dot-path or object). */
1792
1900
  setStyle(path: string, value: unknown): void;
1793
- setStyle(style: Partial<LaneStyle>): void;
1901
+ setStyle(style: OptionPatch<LaneStyle>): void;
1794
1902
  setStyle(path: string): unknown;
1795
1903
  }
1796
1904
 
@@ -1805,6 +1913,7 @@ export const ARTICLE_TO_SORTER: ArticleSorter;
1805
1913
  export const ARTICLE_TOP_SORTER: ArticleSorter;
1806
1914
  export const ARTICLE_ROW_SORTER: ArticleSorter;
1807
1915
  export const ARTICLE_RANK_SORTER: ArticleSorter;
1916
+ /** Selected first, then starred, then normal; descending rank within each tier. */
1808
1917
  export const ARTICLE_EFFECTIVE_RANK_SORTER: ArticleSorter;
1809
1918
  export const ARTICLE_POSITION_SORTER: ArticleSorter;
1810
1919
 
@@ -1813,6 +1922,7 @@ export const ARTICLE_POSITION_SORTER: ArticleSorter;
1813
1922
  // ------------------------------------------------------------------------------------
1814
1923
 
1815
1924
  export interface HistropediaApi {
1925
+ readonly UNSET: typeof UNSET;
1816
1926
  Timeline: typeof Timeline;
1817
1927
  Article: typeof Article;
1818
1928
  Chart: typeof Chart;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "histropediajs",
3
- "version": "1.6.1",
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": "^3.2.4",
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": "^3.2.4"
93
+ "vitest": "^4.1.12"
91
94
  },
92
95
  "lint-staged": {
93
96
  "**/*.{js,mjs,cjs}": [