histropediajs 1.5.1 → 1.6.1

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/README.md CHANGED
@@ -1,499 +1,620 @@
1
- # HistropediaJS
2
-
3
- An embeddable, high-performance, canvas-based timeline engine for rendering events (known as "articles" in the library) over timescales from days to billions of years. Offers smooth pan/zoom, stacking, card layouts, time bands, image loading, and a flexible API.
4
-
5
- - **Canvas-based**: fast rendering and smooth animations
6
- - **Huge ranges**: day -> billion-year scales, BCE support
7
- - **Customizable**: styles, layouts, stacking, density, branding
8
- - **Modular**: ESM default build + UMD for script tags
9
-
10
- ## Installation
11
-
12
- ```bash
13
- npm install histropediajs
14
- ```
15
-
16
- ### Documentation
17
-
18
- For comprehensive guides, options reference, and API docs, see the hosted documentation: [HistropediaJS Documentation](https://js.histropedia.com/documentation).
19
-
20
- ### Importing
21
-
22
- `histropediajs` is published as an ESM package. Use `import` in bundlers or ESM-capable runtimes. `require('histropediajs')` is not supported. For non-module browser usage, load the UMD bundle with a `<script>` tag.
23
-
24
- - ESM (npm / bundlers, recommended):
25
-
26
- ```js
27
- import Histropedia from 'histropediajs';
28
- // or named exports when you only need part of the public API
29
- import { Timeline, Article, Chart, Dmy, Logger } from 'histropediajs';
30
- ```
31
-
32
- The package entry point is a bundled ESM file, so named imports are mainly for clarity and smaller application bundles when your bundler can remove unused exports. They do not expose separate per-module entry points.
33
-
34
- - UMD (script tag) via CDN or self-host:
35
- - CDN (jsDelivr):
36
-
37
- ```html
38
- <script src="https://cdn.jsdelivr.net/npm/histropediajs@1/dist/histropedia.umd.min.js"></script>
39
- <script>
40
- const { Timeline, Dmy } = Histropedia; // global
41
- // window.Histropedia is also available
42
- </script>
43
- ```
44
-
45
- - Self-host (copy dist file into your public assets during build):
46
-
47
- ```html
48
- <script src="/assets/vendor/histropedia.umd.js"></script>
49
- <script>
50
- const { Timeline, Dmy } = Histropedia;
51
- </script>
52
- ```
53
-
54
- ## Quick start
55
-
56
- ```html
57
- <div id="timeline"></div>
58
- <script type="module">
59
- import { Timeline } from 'histropediajs';
60
-
61
- const container = document.getElementById('timeline');
62
- const options = {
63
- width: 1000,
64
- height: 500,
65
- initialDate: { year: 1990, month: 1, day: 1 },
66
- zoom: { initial: 34 },
67
- };
68
-
69
- const timeline = new Timeline(container, options);
70
-
71
- timeline.load([
72
- {
73
- id: 'apollo11',
74
- title: 'Apollo 11',
75
- subtitle: 'First Moon landing',
76
- from: { year: 1969, month: 7, day: 16 },
77
- to: { year: 1969, month: 7, day: 24 },
78
- imageUrl: 'https://example.com/apollo11.jpg',
79
- },
80
- ]);
81
- </script>
82
- ```
83
-
84
- ## Data model
85
-
86
- Each article (event) uses the following minimal shape. Short property aliases are also supported internally for compact data, but prefer the long form in your code.
87
-
88
- ```js
89
- {
90
- id: string|number,
91
- title: string,
92
- subtitle?: string,
93
- lane?: string|number,
94
- from: { year: number, month?: number, day?: number, precision?: string },
95
- to?: { year: number, month?: number, day?: number, precision?: string },
96
- isToPresent?: boolean,
97
- rank?: number,
98
- starred?: boolean,
99
- hidePeriodLine?: boolean,
100
- hiddenByFilter?: boolean,
101
- imageUrl?: string,
102
- cardLayout?: 'portrait' | 'landscape',
103
- style?: object,
104
- hoverStyle?: object,
105
- activeStyle?: object
106
- }
107
- ```
108
-
109
- Notes:
110
-
111
- - BCE years are supported with negative year values; internally year 0 represents 1 BCE. Set `shiftBceDates: true` to shift year input by 1 so that -1 is 1BCE.
112
- - Precision accepts `day`, `month`, `year`, `decade`, `century`, `millennium`, `million-years`, and `billion-years`. The exported `Histropedia.PRECISION_*` constants resolve to these strings. Legacy numeric precision values remain supported but are deprecated.
113
- - If `to` is omitted and `isToPresent` is false, the article is treated as an instant event (uses `from`), but the `from.precision` determines the span of the period line.
114
- - Full precision boundary rules and examples: [docs/date-precision.md](docs/date-precision.md)
115
-
116
- ## Lanes
117
-
118
- Use `lane` when you need multiple horizontal event rows that share the same bottom axis. Articles, connectors, hit-testing, and period lines still render on the shared canvas, while lane headers, titles, and bodies render as DOM elements underneath the canvas so they can be styled independently. By default, period lines anchor to each lane bottom while lane layout is active; set `article.periodLine.position = 'axis'` to keep the legacy shared-axis behavior.
119
-
120
- ```js
121
- const timeline = new Timeline(container, {
122
- lane: {
123
- defaultClassName: 'timeline-lane',
124
- defaultHeaderClassName: 'timeline-lane-header',
125
- defaultBodyClassName: 'timeline-lane-body',
126
- defaultTitleClassName: 'timeline-lane-title',
127
- data: [
128
- { id: 'people', title: 'People', layout: { heightWeight: 2 } },
129
- { id: 'projects', title: 'Projects', layout: { heightWeight: 1 } },
130
- ],
131
- },
132
- });
133
-
134
- timeline.load([
135
- {
136
- id: 'ada',
137
- lane: 'people',
138
- title: 'Ada Lovelace',
139
- from: { year: 1815, month: 12, day: 10 },
140
- },
141
- ]);
142
-
143
- timeline.loadLaneArticles('projects', [
144
- {
145
- id: 'analytical-engine',
146
- title: 'Analytical Engine',
147
- from: { year: 1837 },
148
- },
149
- ]);
150
- ```
151
-
152
- - Lane geometry stays inline so the renderer can place each lane correctly, but decorative inline styles are only written when you explicitly configure `lane.defaultStyle` or `LaneData.style`.
153
- - Lane roots always include `histropedia-lane`, `histropedia-lane-index-N`, and a position class, where `N` follows the stable `timeline.lanes` order from the axis outward.
154
- - Lower lanes stack above upper lanes with inline `z-index` values, making stacked CSS effects easier to write.
155
- - `timeline.articles` stays flat across all lanes for backward compatibility.
156
- - `ArticleData.lane` is the primary way to place events; omitted values fall back to `options.lane.defaultId` (`'default'`).
157
- - `lane.topGap` reserves space above the topmost lane and reduces the height available for lane layout.
158
- - `lane.axisGap` controls the space between the painted top edge of the shared main line and the lane area. Set it to `0` if the nearest lane body should meet the main line.
159
- - `lane.layout.heightWeight` distributes the remaining automatic lane height across visible lanes only. Omit it to keep the default equal split, or use `lane.layout.height` for a fixed lane height.
160
- - `timeline.loadLanes(...)` adds or updates lane definitions, including implicit lanes created from article data.
161
- - Per-lane article defaults live under `lane.article` and merge over global `options.article` presentation defaults.
162
-
163
- ## API
164
-
165
- ### Default export
166
-
167
- ```js
168
- import Histropedia from 'histropediajs';
169
- // Histropedia = { Timeline, Article, Chart, Lane, TimeBand, Dmy, Logger, ...constants, ...sorters, setDebug, isDebugEnabled, enableDebug, disableDebug }
170
- ```
171
-
172
- ### Classes and utilities
173
-
174
- - **Timeline**: main canvas timeline. `new Timeline(containerOrCanvas, options)`
175
- - **Article**: article model used internally; available for advanced usage
176
- - **Dmy**: lightweight date helper (year, month, day, BCE support)
177
- - **Logger**: centralized console logger with `setEnabled(boolean)`
178
- - **Constants**: zoom levels, precisions, density controls, etc.
179
-
180
- ### Timeline constructor
181
-
182
- ```ts
183
- new Timeline(container: HTMLElement | HTMLCanvasElement, options?: TimelineOptions)
184
- ```
185
-
186
- If `container` is an element, the timeline creates and appends its own `<canvas>`. If you pass a `<canvas>`, it will use it directly and derive width from the element unless overridden.
187
-
188
- ### Core methods
189
-
190
- - `load(articles: ArticleInput[])`: load or append articles (deduplicates by `id`).
191
- - `loadLanes(lanes: LaneData[])`: add or update lane definitions.
192
- - `loadLaneArticles(laneId, articles)`: stamp a lane id onto a batch and forward to `load(...)`.
193
- - `loadTimeBands(...)`: add timeline-wide background bands.
194
- - `loadCharts(...)`: add time-series charts; duplicate ids are skipped with a warning.
195
- - `loadLaneCharts(laneId, charts)`: stamp a lane id onto a batch and forward to `loadCharts(...)`.
196
- - `setStartDate(date, options?)`: set the left edge date. `date` accepts a `Dmy`-compatible object or date string; use `options.padding` to place that date away from the left edge, and `options.animation` for animated movement.
197
- - `setCentreDate(date, pixelOffset?)`: center the given date.
198
- - `setSize(width: number, height: number)`: resize canvas and reflow.
199
- - `setZoom(level: number, pivotPixel?: number, offsetX?: number)`: programmatic zoom.
200
- - `on(event: string, handler)` / `off(event, handler)` / `trigger(event, ...args)`: event system.
201
- - `select(articleId)`: bring to the front and trigger the `article-select` event.
202
-
203
- ### Events
204
-
205
- Register handlers using `timeline.on(name, callback)` or pass `options.on = { [name]: handler }`.
206
-
207
- Full list of events and payloads: [docs/events.md](docs/events.md)
208
-
209
- Basic usage:
210
-
211
- ```js
212
- // Register a handler
213
- timeline.on('article-click', (article, evt) => {
214
- console.log('article-click', article, evt);
215
- });
216
-
217
- // Or via options
218
- const timeline = new Timeline(container, {
219
- on: {
220
- 'article-click': (article, evt) => console.log('article-click', article, evt),
221
- },
222
- });
223
-
224
- // Trigger an event programmatically (advanced)
225
- timeline.trigger('my-custom-event', { any: 'payload' });
226
- ```
227
-
228
- ## Debugging and logging
229
-
230
- HistropediaJS uses a centralized `Logger` service to control console output. Logging is **disabled by default** so it will not spam your console unless you explicitly turn it on.
231
-
232
- `Histropedia.setDebug()` and the other global debug helpers control logging only. They do not enable visual canvas diagnostics. Visual debug overlays are configured per timeline and are disabled by default:
233
-
234
- ```js
235
- const timeline = new Histropedia.Timeline(container, {
236
- debugOverlays: {
237
- densityRegions: true,
238
- },
239
- });
240
- ```
241
-
242
- `debugOverlays.densityRegions` draws non-interactive vertical guides over the timeline at the current article-density region boundaries. It does not change density filtering, stacking, interaction, selection, or saved timeline data.
243
-
244
- - **Enable / disable debug logging (default export helpers)**:
245
-
246
- ```js
247
- import Histropedia from 'histropediajs';
248
-
249
- // Turn on all HistropediaJS logging
250
- Histropedia.enableDebug();
251
-
252
- // Or explicitly set the flag
253
- Histropedia.setDebug(true);
254
-
255
- // Later, turn logging off again
256
- Histropedia.disableDebug();
257
- // Equivalent:
258
- Histropedia.setDebug(false);
259
-
260
- // Check current state
261
- const isOn = Histropedia.isDebugEnabled(); // boolean
262
- ```
263
-
264
- - **Using the `Logger` directly (ESM / bundlers)**:
265
-
266
- ```js
267
- import { Logger } from 'histropediajs';
268
-
269
- Logger.setEnabled(true); // enable logging
270
- Logger.debug('Timeline initialised');
271
- Logger.info('Loaded articles', articles);
272
- Logger.warn('Something looks odd');
273
- Logger.error('Something went wrong', err);
274
- ```
275
-
276
- - **UMD / script-tag usage**:
277
-
278
- ```html
279
- <script src="https://cdn.jsdelivr.net/npm/histropediajs@1/dist/histropedia.umd.min.js"></script>
280
- <script>
281
- // Enable debug logging
282
- Histropedia.enableDebug();
283
-
284
- // Or via the Logger instance
285
- Histropedia.Logger.setEnabled(true);
286
- Histropedia.Logger.debug('Timeline ready');
287
- </script>
288
- ```
289
-
290
- - **Changing the debug label (prefix / "suffix" tag)**:
291
-
292
- All log messages are prefixed with a label (by default `[Histropedia]`) so you can easily spot them in the console. You can change this to include your app name, environment, or any other marker.
293
-
294
- ```js
295
- import { Logger } from 'histropediajs';
296
-
297
- // Set a custom label that will appear in front of every log message
298
- Logger.setPrefix('[MyApp Timeline]');
299
-
300
- Logger.setEnabled(true);
301
- Logger.debug('Loading articles...');
302
- ```
303
-
304
- For script-tag/UMD usage:
305
-
306
- ```js
307
- Histropedia.Logger.setPrefix('[MyApp Timeline]');
308
- Histropedia.enableDebug();
309
- Histropedia.Logger.info('Timeline is ready');
310
- ```
311
-
312
- The prefix is applied to all standard console-style methods exposed by the logger (`log`, `info`, `warn`, `error`, `debug`, `group`, `groupCollapsed`, `groupEnd`).
313
-
314
- ## Options
315
-
316
- TimelineOptions tunes layout, interaction, article rendering, and image loading. Start with the defaults and override only what you need for your embed.
317
-
318
- - **Layout**: adjust `width`, `height`, and `verticalOffset` to size and position the canvas.
319
- - **Navigation**: set `initialDate`, `zoom.initial`, and `enableUserControl` to define how people explore the timeline.
320
- - **Articles**: control stacking and presentation with `article.autoStacking`, adaptive `defaultCardLayout`, `cardLayoutBreakpoints`, `distanceToBaseline`, and density settings.
321
- - **Lanes**: use `lane.defaultStyle`, `lane.data`, and per-lane `article` overrides for shared-axis multi-lane layouts.
322
- - **Charts**: use `chart.data` or `loadCharts(...)` for read-only line charts, with optional `lane` placement.
323
- - **Images**: use `image.sanitizer.allowedOrigins`, caching limits, or a custom `image.customSanitizer` to match your content policies.
324
- - **Events**: register callbacks via the `on` map or helpers such as `onArticleClick`.
325
-
326
- Find the full option reference, defaults, and advanced patterns in the hosted docs: https://js.histropedia.com/documentation.
327
-
328
- ### Card layouts
329
-
330
- Two built-in layouts are registered: `portrait` and `landscape`. By default the timeline falls back to `article.defaultCardLayout: 'portrait'`, while ordered `article.cardLayoutBreakpoints` can override that fallback using the currently available vertical space for article cards.
331
-
332
- ```js
333
- const timeline = new Timeline(container, {
334
- article: {
335
- distanceToBaseline: 350,
336
- defaultCardLayout: 'portrait',
337
- cardLayoutBreakpoints: [{ maxHeight: 220, layout: 'landscape' }],
338
- },
339
- });
340
- ```
341
-
342
- Explicit `article.cardLayout` values still win, and lane defaults can set `lane.article.defaultCardLayout` as that lane's fallback layout when no breakpoint matches.
343
-
344
- You can register custom layouts:
345
-
346
- ```js
347
- import { Timeline } from 'histropediajs';
348
-
349
- Timeline.registerCardLayout({
350
- name: 'my-card',
351
- draw(ctx) {
352
- // draw using this.position, this._getCurrentStyle(), this.image, etc.
353
- },
354
- defaultStyle: {
355
- /* optional style defaults */
356
- },
357
- defaultHoverStyle: {
358
- /* optional */
359
- },
360
- defaultActiveStyle: {
361
- /* optional */
362
- },
363
- });
364
-
365
- const timeline = new Timeline(container, {
366
- article: { defaultCardLayout: 'my-card' },
367
- });
368
- ```
369
-
370
- ## Images and security
371
-
372
- - Every article image is sanitized before load. By default the timeline calls `sanitizeImageUrl` (src/services/url-sanitizer.js), which trims the value, enforces the scheme allowlist (`http`, `https`, `data`, `blob`), and rejects non image `data:` payloads.
373
- - Origins are unrestricted unless you supply `image.sanitizer.allowedOrigins`. Add host patterns (full origins, `*.example.com`, `.example.com`, bare hostnames) to enforce an allowlist, or provide a custom sanitizer.
374
- - `blob:` and `data:` URLs skip origin checks but still require an allowed scheme. `data:` URLs must begin with `data:image/`.
375
- - If you provide `image.customSanitizer`, it receives the raw URL and your `image.sanitizer` options object (including any custom fields you add). Return a trusted URL string to allow the load or `null` to block it; when `null` is returned the article keeps rendering without an image.
376
-
377
- ```js
378
- const timeline = new Histropedia.Timeline(container, {
379
- image: {
380
- sanitizer: {
381
- // Configure the built-in sanitizer
382
- allowedSchemes: ['https', 'data'],
383
- allowedOrigins: [
384
- 'https://static.example-cdn.com',
385
- '*.imgix.net',
386
- '.example.com',
387
- 'media.example.com',
388
- ],
389
- },
390
- // Optional custom sanitizer (takes precedence)
391
- customSanitizer: (url, opts) => {
392
- try {
393
- const candidate = new URL(url, window.location.href);
394
- const isHttps = candidate.protocol === 'https:';
395
- const onCdn = candidate.hostname.endsWith('.example-cdn.com');
396
- return isHttps && onCdn ? candidate.href : null;
397
- } catch {
398
- return null;
399
- }
400
- },
401
- },
402
- });
403
- ```
404
-
405
- ### Image loader configuration
406
-
407
- The image loader queues work on demand, deduplicates concurrent requests, and evicts cached bitmaps when the byte budget is exceeded. Tune it via `options.image`:
408
-
409
- - `maxCacheBytes` (default `64 * 1024 * 1024`) caps the decoded raster cache. Set `0` to disable caching or `Infinity` to remove the cap.
410
- - `maxConcurrent` (default `6`) limits concurrent fetch/decode operations.
411
- - `requireCORS` (default `false`) sets `crossOrigin = 'anonymous'` so the canvas stays untainted when your CDN returns the correct CORS headers.
412
- - `decodeMode` selects the decode pipeline: 'auto', 'prefer-bitmap', 'bitmap', 'prefer-img', or 'img'.
413
-
414
- See `src/services/url-sanitizer.js` and `src/services/image-loader.js` for reference implementations and additional behaviour such as shape cropping and cache management.
415
-
416
- ## Accessibility
417
-
418
- - Pointer interactions support mouse, touch, and trackpads.
419
- - Consider keyboard affordances and focus management in your host app; the canvas itself is not focusable by default.
420
-
421
- ## Examples and demo
422
-
423
- A demo app with scenarios is included.
424
-
425
- - Run the demo locally:
426
-
427
- ```bash
428
- npm install
429
- npm run dev
430
- ```
431
-
432
- Then open the served URL (Vite dev server). Switch scenarios via the toolbar.
433
-
434
- ## Browser support / Build targets
435
-
436
- ### Build targets
437
-
438
- | Build | File | Target | Notes |
439
- | ----- | ----------------------------------------------- | ------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
440
- | ESM | `histropedia.esm.js` | ES2020 | Published npm entrypoint for bundlers and other ESM-capable runtimes. Uses native `async/await`, optional chaining (`?.`), nullish coalescing (`??`). |
441
- | UMD | `histropedia.umd.js` / `histropedia.umd.min.js` | ES2017 | For `<script>` tags, CDN usage, or self-hosted browser embeds. The minified UMD file is published for UNPKG/jsDelivr. This build is not a CommonJS `require()` entrypoint. |
442
-
443
- ### Web APIs used
444
-
445
- The library uses several Web APIs that may not exist in all browsers. Most are feature-detected with automatic fallbacks; a few require polyfills if you support older environments.
446
-
447
- #### Performance boosts (feature-detected)
448
-
449
- These APIs improve performance when available. The timeline works correctly without them, but may use more main-thread time or be slightly less efficient:
450
-
451
- | API | Used for | Without it |
452
- | --------------------- | ---------------------------------- | --------------------------------------------------------------------------- |
453
- | `createImageBitmap()` | Off-thread image decoding | Falls back to `HTMLImageElement` + canvas; images decode on the main thread |
454
- | `OffscreenCanvas` | Off-screen image cropping/resizing | Falls back to regular `<canvas>`; slightly more GC pressure |
455
- | `img.decode()` | Async decode before paint | Falls back to `onload`; may cause minor jank when large images appear |
456
- | `performance.now()` | High-resolution cache timestamps | Falls back to `Date.now()`; millisecond precision instead of sub-ms |
457
-
458
- #### Graceful degradation (feature-detected)
459
-
460
- These APIs provide enhanced functionality when available. The timeline remains fully functional without them, with minor behavioural differences:
461
-
462
- | API | Used for | Without it |
463
- | --------------------------------------- | ------------------------------------------ | ------------------------------------------------------------------ |
464
- | `AbortController` | Cancelling in-flight image fetches | Requests complete even if no longer needed; wastes some bandwidth |
465
- | `document.fonts` (CSS Font Loading API) | Detecting when custom fonts finish loading | Text may briefly render in fallback font before redraw |
466
- | Pointer Events | Unified mouse/touch/pen handling | Falls back to separate mouse + touch listeners; same end behaviour |
467
-
468
- #### APIs that may need polyfills
469
-
470
- These APIs are used directly without fallbacks. They're widely supported in modern browsers, but very old environments may need polyfills:
471
-
472
- | API | Used for |
473
- | ----------------------- | ---------------------------------------- |
474
- | `fetch()` | Network image loading |
475
- | `Promise` | Async operations throughout |
476
- | `Map` / `Set` | Caching, deduplication, pointer tracking |
477
- | `URL` constructor | Parsing and validating image URLs |
478
- | `requestAnimationFrame` | Smooth animations |
479
-
480
- ## Development
481
-
482
- See CONTRIBUTING.md for the build, test, and release workflow, including npm scripts, linting, and publishing guidance.
483
-
484
- ## TypeScript
485
-
486
- TypeScript definitions are included in the package via `index.d.ts`. The types are automatically picked up by TypeScript-aware editors and bundlers.
487
-
488
- ## Resources
489
-
490
- - Hosted documentation (guides, API reference, option matrix): https://js.histropedia.com/documentation
491
- - Event payload reference: docs/events.md
492
- - Contribution workflow: CONTRIBUTING.md
493
- - Security policy: SECURITY.md
494
-
495
- ## License
496
-
497
- This package is distributed under a custom HistropediaJS license. Please review the full terms at: [https://js.histropedia.com/licence](https://js.histropedia.com/licence).
498
-
499
- For repository consumers, a `LICENSE` stub is included pointing to the hosted license text. Commercial use or redistribution may require additional permissions - contact us if unsure.
1
+ # HistropediaJS
2
+
3
+ An embeddable, high-performance, canvas-based timeline engine for rendering events (known as "articles" in the library) over timescales from milliseconds to billions of years. Offers smooth pan/zoom, stacking, card layouts, time bands, image loading, and a flexible API.
4
+
5
+ - **Canvas-based**: fast rendering and smooth animations
6
+ - **Huge ranges**: millisecond -> billion-year scales, BCE support
7
+ - **Customizable**: styles, layouts, stacking, density, branding
8
+ - **Modular**: ESM default build + UMD for script tags
9
+
10
+ ## Installation
11
+
12
+ ```bash
13
+ npm install histropediajs
14
+ ```
15
+
16
+ ### Documentation
17
+
18
+ For comprehensive guides, options reference, and API docs, see the hosted documentation: [HistropediaJS Documentation](https://js.histropedia.com/documentation).
19
+
20
+ ### Importing
21
+
22
+ `histropediajs` is published as an ESM package. Use `import` in bundlers or ESM-capable runtimes. `require('histropediajs')` is not supported. For non-module browser usage, load the UMD bundle with a `<script>` tag.
23
+
24
+ - ESM (npm / bundlers, recommended):
25
+
26
+ ```js
27
+ import Histropedia from 'histropediajs';
28
+ // or named exports when you only need part of the public API
29
+ import { Timeline, Article, Chart, Dmy, Logger } from 'histropediajs';
30
+ ```
31
+
32
+ The package entry point is a bundled ESM file, so named imports are mainly for clarity and smaller application bundles when your bundler can remove unused exports. They do not expose separate per-module entry points.
33
+
34
+ - UMD (script tag) via CDN or self-host:
35
+ - CDN (jsDelivr):
36
+
37
+ ```html
38
+ <script src="https://cdn.jsdelivr.net/npm/histropediajs@1/dist/histropedia.umd.min.js"></script>
39
+ <script>
40
+ const { Timeline, Dmy } = Histropedia; // global
41
+ // window.Histropedia is also available
42
+ </script>
43
+ ```
44
+
45
+ - Self-host (copy dist file into your public assets during build):
46
+
47
+ ```html
48
+ <script src="/assets/vendor/histropedia.umd.js"></script>
49
+ <script>
50
+ const { Timeline, Dmy } = Histropedia;
51
+ </script>
52
+ ```
53
+
54
+ ## Quick start
55
+
56
+ ```html
57
+ <div id="timeline"></div>
58
+ <script type="module">
59
+ import { Timeline } from 'histropediajs';
60
+
61
+ const container = document.getElementById('timeline');
62
+ const options = {
63
+ width: 1000,
64
+ height: 500,
65
+ initialDate: { year: 1990, month: 1, day: 1 },
66
+ zoom: { initial: 34 },
67
+ };
68
+
69
+ const timeline = new Timeline(container, options);
70
+
71
+ timeline.load([
72
+ {
73
+ id: 'apollo11',
74
+ title: 'Apollo 11',
75
+ subtitle: 'First Moon landing',
76
+ from: { year: 1969, month: 7, day: 16 },
77
+ to: { year: 1969, month: 7, day: 24 },
78
+ imageUrl: 'https://example.com/apollo11.jpg',
79
+ },
80
+ ]);
81
+ </script>
82
+ ```
83
+
84
+ ## Data model
85
+
86
+ Each article (event) uses the following minimal shape. Short property aliases are also supported internally for compact data, but prefer the long form in your code.
87
+
88
+ ```js
89
+ {
90
+ id: string|number,
91
+ title: string,
92
+ subtitle?: string,
93
+ lane?: string|number,
94
+ from: {
95
+ year: number,
96
+ month?: number,
97
+ day?: number,
98
+ hour?: number,
99
+ minute?: number,
100
+ second?: number,
101
+ millisecond?: number,
102
+ precision?: string
103
+ },
104
+ to?: { /* same date/time fields as from */ },
105
+ isToPresent?: boolean,
106
+ rank?: number,
107
+ starred?: boolean,
108
+ hidePeriodLine?: boolean,
109
+ hiddenByFilter?: boolean,
110
+ imageUrl?: string,
111
+ cardLayout?: 'portrait' | 'landscape',
112
+ style?: object,
113
+ hoverStyle?: object,
114
+ activeStyle?: object
115
+ }
116
+ ```
117
+
118
+ Notes:
119
+
120
+ - BCE years are supported with negative year values; internally year 0 represents 1 BCE. Set `shiftBceDates: true` to shift year input by 1 so that -1 is 1BCE.
121
+ - When `precision` is omitted from a date-parts object, the smallest supplied time component determines precision (`millisecond`, `second`, `minute`, then `hour`), including zero values. Objects without time components still default to day precision, including year-only and year/month objects. Explicit `precision` always overrides inference.
122
+ - Precision accepts `millisecond`, `second`, `minute`, `hour`, `day`, `month`, `year`, `decade`, `century`, `millennium`, `million-years`, and `billion-years`. The exported `Histropedia.PRECISION_*` constants resolve to these strings. Legacy numeric precision values remain supported but are deprecated.
123
+ - If `to` is omitted and `isToPresent` is false, the article is treated as an instant event (uses `from`), but the `from.precision` determines the span of the period line.
124
+ - Full precision boundary rules and examples: [docs/date-precision.md](docs/date-precision.md)
125
+
126
+ ## Main line styling
127
+
128
+ The main line uses a flat neutral fill by default. Set `style.mainLine.color` to any CSS colour
129
+ string for a solid fill, or use a gradient object for a vertical top-to-bottom gradient. Article
130
+ date indicators are configured through `style.mainLine.dateIndicators`:
131
+
132
+ ```js
133
+ const timeline = new Timeline(container, {
134
+ style: {
135
+ mainLine: {
136
+ color: {
137
+ type: 'gradient',
138
+ topColor: '#808080',
139
+ bottomColor: '#ccc',
140
+ },
141
+ dateIndicators: {
142
+ visible: true,
143
+ shape: 'circle',
144
+ size: 6,
145
+ color: 'article',
146
+ hiddenStyle: {
147
+ opacity: 0.4,
148
+ },
149
+ activeStyle: {
150
+ color: 'article',
151
+ size: 8,
152
+ },
153
+ },
154
+ },
155
+ },
156
+ });
157
+ ```
158
+
159
+ The example above preserves the gradient colours used by earlier HistropediaJS versions. Use a
160
+ string such as `color: '#d9dde3'` to select solid rendering; no separate gradient switch is needed.
161
+ Date indicator shapes can be `rectangle` (the default) or `circle`; `size` is the rectangle width
162
+ or circle diameter. Rectangles span the main line's thickness, retaining the original date-tick
163
+ geometry. Use any CSS colour, or `color: 'article'` to use each article's normal card colour without
164
+ reacting to its hover style. `hiddenStyle` and `activeStyle` are sparse overrides used only for
165
+ density-hidden and active/selected articles respectively, so omitted properties continue to use
166
+ the base indicator settings. Within `activeStyle`, `color: 'article'` uses the card's resolved active
167
+ colour; the same value in the base or hidden style uses its resolved normal colour. Indicator
168
+ `opacity` accepts values from `0` to `1`.
169
+
170
+ ## Parent context date labels
171
+
172
+ Set `style.dateLabel.parent.visible` to `true` to add a persistent calendar-parent row beneath the
173
+ existing major labels. The row is disabled by default, supports declarative `'auto'`, `'all'`, or
174
+ explicit scale levels, and reserves its configured height at every scale while enabled. See
175
+ [docs/parent-date-labels.md](docs/parent-date-labels.md) for configuration and scale behaviour.
176
+
177
+ ## Lanes
178
+
179
+ Use `lane` when you need multiple horizontal event rows that share the same bottom axis. Articles, connectors, hit-testing, and period lines still render on the shared canvas, while lane headers, titles, and bodies render as DOM elements underneath the canvas so they can be styled independently. By default, period lines anchor to each lane bottom while lane layout is active; set `article.periodLine.position = 'axis'` to keep the legacy shared-axis behavior.
180
+
181
+ ```js
182
+ const timeline = new Timeline(container, {
183
+ lane: {
184
+ defaultClassName: 'timeline-lane',
185
+ defaultHeaderClassName: 'timeline-lane-header',
186
+ defaultBodyClassName: 'timeline-lane-body',
187
+ defaultTitleClassName: 'timeline-lane-title',
188
+ data: [
189
+ { id: 'people', title: 'People', layout: { heightWeight: 2 } },
190
+ { id: 'projects', title: 'Projects', layout: { heightWeight: 1 } },
191
+ ],
192
+ },
193
+ });
194
+
195
+ timeline.load([
196
+ {
197
+ id: 'ada',
198
+ lane: 'people',
199
+ title: 'Ada Lovelace',
200
+ from: { year: 1815, month: 12, day: 10 },
201
+ },
202
+ ]);
203
+
204
+ timeline.loadLaneArticles('projects', [
205
+ {
206
+ id: 'analytical-engine',
207
+ title: 'Analytical Engine',
208
+ from: { year: 1837 },
209
+ },
210
+ ]);
211
+ ```
212
+
213
+ - Lane geometry stays inline so the renderer can place each lane correctly, but decorative inline styles are only written when you explicitly configure `lane.defaultStyle` or `LaneData.style`.
214
+ - Lane roots always include `histropedia-lane`, `histropedia-lane-index-N`, and a position class, where `N` follows the stable `timeline.lanes` order from the axis outward.
215
+ - Lower lanes stack above upper lanes with inline `z-index` values, making stacked CSS effects easier to write.
216
+ - `timeline.articles` stays flat across all lanes for backward compatibility.
217
+ - `ArticleData.lane` is the primary way to place events; omitted values fall back to `options.lane.defaultId` (`'default'`).
218
+ - `lane.topGap` reserves space above the topmost lane and reduces the height available for lane layout.
219
+ - `lane.axisGap` controls the space between the painted top edge of the shared main line and the lane area. Set it to `0` if the nearest lane body should meet the main line.
220
+ - `lane.layout.heightWeight` distributes the remaining automatic lane height across visible lanes only. Omit it to keep the default equal split, or use `lane.layout.height` for a fixed lane height.
221
+ - `timeline.loadLanes(...)` adds or updates lane definitions, including implicit lanes created from article data.
222
+ - Per-lane article defaults live under `lane.article` and merge over global `options.article` presentation defaults.
223
+
224
+ ## API
225
+
226
+ ### Default export
227
+
228
+ ```js
229
+ import Histropedia from 'histropediajs';
230
+ // Histropedia = { Timeline, Article, Chart, Lane, TimeBand, Dmy, Logger, ...constants, ...sorters, setDebug, isDebugEnabled, enableDebug, disableDebug }
231
+ ```
232
+
233
+ ### Classes and utilities
234
+
235
+ - **Timeline**: main canvas timeline. `new Timeline(containerOrCanvas, options)`
236
+ - **Article**: article model used internally; available for advanced usage
237
+ - **Dmy**: lightweight date/time helper (millisecond precision and BCE support)
238
+ - **Logger**: centralized console logger with `setEnabled(boolean)`
239
+ - **Constants**: zoom levels, precisions, density controls, etc.
240
+
241
+ ### Timeline constructor
242
+
243
+ ```ts
244
+ new Timeline(container: HTMLElement | HTMLCanvasElement, options?: TimelineOptions)
245
+ ```
246
+
247
+ If `container` is an element, the timeline creates and appends its own `<canvas>`. If you pass a `<canvas>`, it will use it directly and derive width from the element unless overridden.
248
+
249
+ ### Core methods
250
+
251
+ - `load(articles: ArticleInput[])`: load or append articles (deduplicates by `id`).
252
+ - `loadLanes(lanes: LaneData[])`: add or update lane definitions.
253
+ - `loadLaneArticles(laneId, articles)`: stamp a lane id onto a batch and forward to `load(...)`.
254
+ - `loadTimeBands(...)`: add timeline-wide background bands.
255
+ - `loadCharts(...)`: add time-series charts; duplicate ids are skipped with a warning.
256
+ - `loadLaneCharts(laneId, charts)`: stamp a lane id onto a batch and forward to `loadCharts(...)`.
257
+ - `setStartDate(date, options?)`: set the left edge date. `date` accepts a `Dmy`-compatible object or date string; use `options.padding` to place that date away from the left edge, and `options.animation` for animated movement.
258
+ - `setCentreDate(date, pixelOffset?)`: center the given date.
259
+ - `fitDateRange(start, end, options?)`: fit object, `Dmy`, or canonical date-string inputs, including millisecond ranges.
260
+ - `setSize(width: number, height: number)`: resize canvas and reflow.
261
+ - `setZoom(level: number, pivotPixel?: number, offsetX?: number)`: programmatic zoom.
262
+ - `on(event: string, handler)` / `off(event, handler)` / `trigger(event, ...args)`: event system.
263
+ - `select(articleId)`: bring to the front and trigger the `article-select` event.
264
+
265
+ ### Events
266
+
267
+ Register handlers using `timeline.on(name, callback)` or pass `options.on = { [name]: handler }`.
268
+
269
+ Full list of events and payloads: [docs/events.md](docs/events.md)
270
+
271
+ Basic usage:
272
+
273
+ ```js
274
+ // Register a handler
275
+ timeline.on('article-click', (article, evt) => {
276
+ console.log('article-click', article, evt);
277
+ });
278
+
279
+ // Or via options
280
+ const timeline = new Timeline(container, {
281
+ on: {
282
+ 'article-click': (article, evt) => console.log('article-click', article, evt),
283
+ },
284
+ });
285
+
286
+ // Trigger an event programmatically (advanced)
287
+ timeline.trigger('my-custom-event', { any: 'payload' });
288
+ ```
289
+
290
+ ## Debugging and logging
291
+
292
+ HistropediaJS uses a centralized `Logger` service to control console output. Logging is **disabled by default** so it will not spam your console unless you explicitly turn it on.
293
+
294
+ `Histropedia.setDebug()` and the other global debug helpers control logging only. They do not enable visual canvas diagnostics. Visual debug overlays are configured per timeline and are disabled by default:
295
+
296
+ ```js
297
+ const timeline = new Histropedia.Timeline(container, {
298
+ debugOverlays: {
299
+ densityRegions: true,
300
+ },
301
+ });
302
+ ```
303
+
304
+ `debugOverlays.densityRegions` draws non-interactive vertical guides over the timeline at the current article-density region boundaries. It does not change density filtering, stacking, interaction, selection, or saved timeline data.
305
+
306
+ - **Enable / disable debug logging (default export helpers)**:
307
+
308
+ ```js
309
+ import Histropedia from 'histropediajs';
310
+
311
+ // Turn on all HistropediaJS logging
312
+ Histropedia.enableDebug();
313
+
314
+ // Or explicitly set the flag
315
+ Histropedia.setDebug(true);
316
+
317
+ // Later, turn logging off again
318
+ Histropedia.disableDebug();
319
+ // Equivalent:
320
+ Histropedia.setDebug(false);
321
+
322
+ // Check current state
323
+ const isOn = Histropedia.isDebugEnabled(); // boolean
324
+ ```
325
+
326
+ - **Using the `Logger` directly (ESM / bundlers)**:
327
+
328
+ ```js
329
+ import { Logger } from 'histropediajs';
330
+
331
+ Logger.setEnabled(true); // enable logging
332
+ Logger.debug('Timeline initialised');
333
+ Logger.info('Loaded articles', articles);
334
+ Logger.warn('Something looks odd');
335
+ Logger.error('Something went wrong', err);
336
+ ```
337
+
338
+ - **UMD / script-tag usage**:
339
+
340
+ ```html
341
+ <script src="https://cdn.jsdelivr.net/npm/histropediajs@1/dist/histropedia.umd.min.js"></script>
342
+ <script>
343
+ // Enable debug logging
344
+ Histropedia.enableDebug();
345
+
346
+ // Or via the Logger instance
347
+ Histropedia.Logger.setEnabled(true);
348
+ Histropedia.Logger.debug('Timeline ready');
349
+ </script>
350
+ ```
351
+
352
+ - **Changing the debug label (prefix / "suffix" tag)**:
353
+
354
+ All log messages are prefixed with a label (by default `[Histropedia]`) so you can easily spot them in the console. You can change this to include your app name, environment, or any other marker.
355
+
356
+ ```js
357
+ import { Logger } from 'histropediajs';
358
+
359
+ // Set a custom label that will appear in front of every log message
360
+ Logger.setPrefix('[MyApp Timeline]');
361
+
362
+ Logger.setEnabled(true);
363
+ Logger.debug('Loading articles...');
364
+ ```
365
+
366
+ For script-tag/UMD usage:
367
+
368
+ ```js
369
+ Histropedia.Logger.setPrefix('[MyApp Timeline]');
370
+ Histropedia.enableDebug();
371
+ Histropedia.Logger.info('Timeline is ready');
372
+ ```
373
+
374
+ The prefix is applied to all standard console-style methods exposed by the logger (`log`, `info`, `warn`, `error`, `debug`, `group`, `groupCollapsed`, `groupEnd`).
375
+
376
+ ## Options
377
+
378
+ TimelineOptions tunes layout, interaction, article rendering, and image loading. Start with the defaults and override only what you need for your embed.
379
+
380
+ - **Layout**: adjust `width`, `height`, and `verticalOffset` to size and position the canvas.
381
+ - **Navigation**: set `initialDate`, `zoom.initial`, and `enableUserControl` to define how people explore the timeline.
382
+ - **Articles**: control stacking and presentation with `article.autoStacking`, adaptive `defaultCardLayout`, `cardLayoutBreakpoints`, `distanceToBaseline`, and density settings.
383
+ - **Lanes**: use `lane.defaultStyle`, `lane.data`, and per-lane `article` overrides for shared-axis multi-lane layouts.
384
+ - **Charts**: use `chart.data` or `loadCharts(...)` for read-only line charts, with optional `lane` placement.
385
+ - **Images**: use `image.sanitizer.allowedOrigins`, caching limits, or a custom `image.customSanitizer` to match your content policies.
386
+ - **Events**: register callbacks via the `on` map or helpers such as `onArticleClick`.
387
+
388
+ Find the full option reference, defaults, and advanced patterns in the hosted docs: https://js.histropedia.com/documentation.
389
+
390
+ ### Sub-day zoom
391
+
392
+ Sub-day zoom is opt-in, so existing timelines keep exactly the same minimum scale. The default
393
+ `zoom.minimum` remains `0`, where one day is the finest timescale unit. Set it to a negative value
394
+ to allow continuous zoom through hours, minutes, seconds, and milliseconds:
395
+
396
+ ```js
397
+ const timeline = new Timeline(container, {
398
+ initialDate: {
399
+ year: 2026,
400
+ month: 8,
401
+ day: 13,
402
+ hour: 12,
403
+ minute: 30,
404
+ second: 15,
405
+ millisecond: 250,
406
+ },
407
+ zoom: {
408
+ initial: -80,
409
+ minimum: -82,
410
+ },
411
+ });
412
+ ```
413
+
414
+ With the default `zoom.ratio` and `zoom.unitSize` values, `-82` makes one millisecond roughly
415
+ 200 px wide. The scale remains continuous below that value if you choose a lower minimum; the
416
+ smallest representable time and tick unit remains one millisecond. Custom ratio or unit-size
417
+ settings change the corresponding numeric zoom values.
418
+
419
+ Sub-day markers use an independent visual grid: major intervals change only when they have at
420
+ least `zoom.unitSize.minimumMajor` pixels (default `120`). A reduced set of significant minor
421
+ subdivisions appears only when it meets `zoom.unitSize.showMinorLabels` (default `48`), or the
422
+ larger `zoom.unitSize.minimum` value if customized. Every adaptive subdivision can therefore show
423
+ unitless labels as soon as it appears, while major labels remain readable.
424
+
425
+ Below day scale, article density uses fixed time regions. The first sub-day density level divides
426
+ each day into two half-day regions; every subsequent density transition divides every existing
427
+ region exactly in half, down to millisecond input resolution. Region boundaries remain attached to
428
+ their date and time while panning and between density transitions.
429
+
430
+ Navigation, `initialDate`, and viewport bounds accept either date-part objects, `Dmy` instances, or
431
+ timezone-free Histropedia date strings. The canonical grammar is
432
+ `year[-month[-day]][T| ]hour[:minute[:second[.SSS]]]`; the time part is optional, years may be
433
+ negative or longer than four digits, and fractional seconds contain one to three digits:
434
+
435
+ ```js
436
+ timeline.setStartDate('2026-08-13T12:30:15.250', {
437
+ animation: { active: true, duration: 500 },
438
+ });
439
+
440
+ timeline.setCentreDate('-44-03-15 09:01');
441
+ timeline.fitDateRange('1000000000-06-15T12:00:00.000', '1000000000-06-15T12:00:00.001');
442
+ ```
443
+
444
+ These strings are calendar positions, not JavaScript `Date` instants: timezone suffixes such as
445
+ `Z` or `+01:00` are intentionally unsupported. Omitted month/day values default to `1`; for
446
+ `fitDateRange`, a time-free year-only or month-only input represents that complete calendar
447
+ period.
448
+
449
+ ### Card layouts
450
+
451
+ Two built-in layouts are registered: `portrait` and `landscape`. By default the timeline falls back to `article.defaultCardLayout: 'portrait'`, while ordered `article.cardLayoutBreakpoints` can override that fallback using the currently available vertical space for article cards.
452
+
453
+ ```js
454
+ const timeline = new Timeline(container, {
455
+ article: {
456
+ distanceToBaseline: { value: 350 },
457
+ defaultCardLayout: 'portrait',
458
+ cardLayoutBreakpoints: [{ maxHeight: 220, layout: 'landscape' }],
459
+ },
460
+ });
461
+ ```
462
+
463
+ Explicit `article.cardLayout` values still win, and lane defaults can set `lane.article.defaultCardLayout` as that lane's fallback layout when no breakpoint matches.
464
+
465
+ You can register custom layouts:
466
+
467
+ ```js
468
+ import { Timeline } from 'histropediajs';
469
+
470
+ Timeline.registerCardLayout({
471
+ name: 'my-card',
472
+ draw(ctx) {
473
+ // draw using this.position, this.getCurrentStyle(), this.image, etc.
474
+ },
475
+ defaultStyle: {
476
+ /* optional style defaults */
477
+ },
478
+ defaultHoverStyle: {
479
+ /* optional */
480
+ },
481
+ defaultActiveStyle: {
482
+ /* optional */
483
+ },
484
+ });
485
+
486
+ const timeline = new Timeline(container, {
487
+ article: { defaultCardLayout: 'my-card' },
488
+ });
489
+ ```
490
+
491
+ ## Images and security
492
+
493
+ - Every article image is sanitized before load. By default the timeline calls `sanitizeImageUrl` (src/services/url-sanitizer.js), which trims the value, enforces the scheme allowlist (`http`, `https`, `data`, `blob`), and rejects non image `data:` payloads.
494
+ - Origins are unrestricted unless you supply `image.sanitizer.allowedOrigins`. Add host patterns (full origins, `*.example.com`, `.example.com`, bare hostnames) to enforce an allowlist, or provide a custom sanitizer.
495
+ - `blob:` and `data:` URLs skip origin checks but still require an allowed scheme. `data:` URLs must begin with `data:image/`.
496
+ - If you provide `image.customSanitizer`, it receives the raw URL and your `image.sanitizer` options object (including any custom fields you add). Return a trusted URL string to allow the load or `null` to block it; when `null` is returned the article keeps rendering without an image.
497
+
498
+ ```js
499
+ const timeline = new Histropedia.Timeline(container, {
500
+ image: {
501
+ sanitizer: {
502
+ // Configure the built-in sanitizer
503
+ allowedSchemes: ['https', 'data'],
504
+ allowedOrigins: [
505
+ 'https://static.example-cdn.com',
506
+ '*.imgix.net',
507
+ '.example.com',
508
+ 'media.example.com',
509
+ ],
510
+ },
511
+ // Optional custom sanitizer (takes precedence)
512
+ customSanitizer: (url, opts) => {
513
+ try {
514
+ const candidate = new URL(url, window.location.href);
515
+ const isHttps = candidate.protocol === 'https:';
516
+ const onCdn = candidate.hostname.endsWith('.example-cdn.com');
517
+ return isHttps && onCdn ? candidate.href : null;
518
+ } catch {
519
+ return null;
520
+ }
521
+ },
522
+ },
523
+ });
524
+ ```
525
+
526
+ ### Image loader configuration
527
+
528
+ The image loader queues work on demand, deduplicates concurrent requests, and evicts cached bitmaps when the byte budget is exceeded. Tune it via `options.image`:
529
+
530
+ - `maxCacheBytes` (default `64 * 1024 * 1024`) caps the decoded raster cache. Set `0` to disable caching or `Infinity` to remove the cap.
531
+ - `maxConcurrent` (default `6`) limits concurrent fetch/decode operations.
532
+ - `requireCORS` (default `false`) sets `crossOrigin = 'anonymous'` so the canvas stays untainted when your CDN returns the correct CORS headers.
533
+ - `decodeMode` selects the decode pipeline: 'auto', 'prefer-bitmap', 'bitmap', 'prefer-img', or 'img'.
534
+
535
+ See `src/services/url-sanitizer.js` and `src/services/image-loader.js` for reference implementations and additional behaviour such as shape cropping and cache management.
536
+
537
+ ## Accessibility
538
+
539
+ - Pointer interactions support mouse, touch, and trackpads.
540
+ - Consider keyboard affordances and focus management in your host app; the canvas itself is not focusable by default.
541
+
542
+ ## Examples and demo
543
+
544
+ A demo app with scenarios is included.
545
+
546
+ - Run the demo locally:
547
+
548
+ ```bash
549
+ npm install
550
+ npm run dev
551
+ ```
552
+
553
+ Then open the served URL (Vite dev server). Switch scenarios via the toolbar.
554
+
555
+ ## Browser support / Build targets
556
+
557
+ ### Build targets
558
+
559
+ | Build | File | Target | Notes |
560
+ | ----- | ----------------------------------------------- | ------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
561
+ | ESM | `histropedia.esm.js` | ES2020 | Published npm entrypoint for bundlers and other ESM-capable runtimes. Uses native `async/await`, optional chaining (`?.`), nullish coalescing (`??`). |
562
+ | UMD | `histropedia.umd.js` / `histropedia.umd.min.js` | ES2017 | For `<script>` tags, CDN usage, or self-hosted browser embeds. The minified UMD file is published for UNPKG/jsDelivr. This build is not a CommonJS `require()` entrypoint. |
563
+
564
+ ### Web APIs used
565
+
566
+ The library uses several Web APIs that may not exist in all browsers. Most are feature-detected with automatic fallbacks; a few require polyfills if you support older environments.
567
+
568
+ #### Performance boosts (feature-detected)
569
+
570
+ These APIs improve performance when available. The timeline works correctly without them, but may use more main-thread time or be slightly less efficient:
571
+
572
+ | API | Used for | Without it |
573
+ | --------------------- | ---------------------------------- | --------------------------------------------------------------------------- |
574
+ | `createImageBitmap()` | Off-thread image decoding | Falls back to `HTMLImageElement` + canvas; images decode on the main thread |
575
+ | `OffscreenCanvas` | Off-screen image cropping/resizing | Falls back to regular `<canvas>`; slightly more GC pressure |
576
+ | `img.decode()` | Async decode before paint | Falls back to `onload`; may cause minor jank when large images appear |
577
+ | `performance.now()` | High-resolution cache timestamps | Falls back to `Date.now()`; millisecond precision instead of sub-ms |
578
+
579
+ #### Graceful degradation (feature-detected)
580
+
581
+ These APIs provide enhanced functionality when available. The timeline remains fully functional without them, with minor behavioural differences:
582
+
583
+ | API | Used for | Without it |
584
+ | --------------------------------------- | ------------------------------------------ | ------------------------------------------------------------------ |
585
+ | `AbortController` | Cancelling in-flight image fetches | Requests complete even if no longer needed; wastes some bandwidth |
586
+ | `document.fonts` (CSS Font Loading API) | Detecting when custom fonts finish loading | Text may briefly render in fallback font before redraw |
587
+ | Pointer Events | Unified mouse/touch/pen handling | Falls back to separate mouse + touch listeners; same end behaviour |
588
+
589
+ #### APIs that may need polyfills
590
+
591
+ These APIs are used directly without fallbacks. They're widely supported in modern browsers, but very old environments may need polyfills:
592
+
593
+ | API | Used for |
594
+ | ----------------------- | ---------------------------------------- |
595
+ | `fetch()` | Network image loading |
596
+ | `Promise` | Async operations throughout |
597
+ | `Map` / `Set` | Caching, deduplication, pointer tracking |
598
+ | `URL` constructor | Parsing and validating image URLs |
599
+ | `requestAnimationFrame` | Smooth animations |
600
+
601
+ ## Development
602
+
603
+ See CONTRIBUTING.md for the build, test, and release workflow, including npm scripts, linting, and publishing guidance.
604
+
605
+ ## TypeScript
606
+
607
+ TypeScript definitions are included in the package via `index.d.ts`. The types are automatically picked up by TypeScript-aware editors and bundlers.
608
+
609
+ ## Resources
610
+
611
+ - Hosted documentation (guides, API reference, option matrix): https://js.histropedia.com/documentation
612
+ - Event payload reference: docs/events.md
613
+ - Contribution workflow: CONTRIBUTING.md
614
+ - Security policy: SECURITY.md
615
+
616
+ ## License
617
+
618
+ This package is distributed under a custom HistropediaJS license. Please review the full terms at: [https://js.histropedia.com/licence](https://js.histropedia.com/licence).
619
+
620
+ For repository consumers, a `LICENSE` stub is included pointing to the hosted license text. Commercial use or redistribution may require additional permissions - contact us if unsure.