@wicklib/layouts 0.12.0 → 0.13.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/README.md CHANGED
@@ -44,13 +44,46 @@ Time-range and crosshair synchronization are enabled by default.
44
44
  ```ts
45
45
  const layout = createChartLayout(container, {
46
46
  columns: 2,
47
- syncTimeRange: true,
48
- syncCrosshair: true,
47
+ sync: {
48
+ timeRange: true,
49
+ crosshair: "time",
50
+ },
49
51
  });
50
52
  ```
51
53
 
52
- Time-range sync uses the chart `visible-range` event. Crosshair sync mirrors the vertical
53
- time line through chart overlays; the hovered chart keeps its native crosshair.
54
+ Crosshair modes:
55
+
56
+ - `"off"` — no crosshair sync.
57
+ - `"time"` — mirrors the vertical time line.
58
+ - `"full"` — also mirrors a horizontal line at the same relative pane height.
59
+
60
+ The hovered chart keeps its native crosshair. Other charts receive an overlay line.
61
+
62
+ Change synchronization without recreating charts:
63
+
64
+ ```ts
65
+ layout.setSyncOptions({
66
+ timeRange: false,
67
+ crosshair: "full",
68
+ });
69
+
70
+ layout.getSyncOptions();
71
+ ```
72
+
73
+ Boolean crosshair values remain accepted: `true` maps to `"time"` and `false` to `"off"`.
74
+
75
+ ## Sync groups
76
+
77
+ Cells only synchronize with cells in the same group. The default group is `"default"`.
78
+
79
+ ```ts
80
+ layout.addChart({ id: "btc", syncGroup: "crypto" });
81
+ layout.addChart({ id: "eth", syncGroup: "crypto" });
82
+ layout.addChart({ id: "spx", syncGroup: "equities" });
83
+ ```
84
+
85
+ `btc` and `eth` share ranges and crosshairs; `spx` stays independent. `syncNow()` also respects
86
+ the active chart's group.
54
87
 
55
88
  ## Persistence
56
89
 
@@ -65,15 +98,20 @@ const saved = layout.serialize();
65
98
  "version": 1,
66
99
  "columns": 2,
67
100
  "gap": 8,
101
+ "sync": {
102
+ "timeRange": true,
103
+ "crosshair": "time"
104
+ },
68
105
  "slots": [
69
- { "id": "primary", "rowSpan": 1, "columnSpan": 1 },
70
- { "id": "comparison", "rowSpan": 1, "columnSpan": 1 }
106
+ { "id": "primary", "rowSpan": 1, "columnSpan": 1, "syncGroup": "default" },
107
+ { "id": "comparison", "rowSpan": 1, "columnSpan": 1, "syncGroup": "default" }
71
108
  ]
72
109
  }
73
110
  ```
74
111
 
75
112
  Persist chart state, symbol metadata, datafeed identity, drawings, and layout structure in
76
- the host. Drawings can be read with `chart.serializeDrawings()`.
113
+ the host. Old snapshots that omit `sync` or `syncGroup` should fall back to
114
+ `{ timeRange: true, crosshair: "time" }` and `"default"` respectively. Drawings can be read with `chart.serializeDrawings()`.
77
115
 
78
116
  ## Styling
79
117
 
package/dist/index.cjs CHANGED
@@ -5,6 +5,7 @@ var core = require('@wicklib/core');
5
5
  // src/index.ts
6
6
  var DEFAULT_COLUMNS = 2;
7
7
  var DEFAULT_GAP = 1;
8
+ var DEFAULT_SYNC_GROUP = "default";
8
9
  var MAX_COLUMNS = 12;
9
10
  var DEFAULT_CROSSHAIR_COLOR = "rgba(139, 149, 167, 0.6)";
10
11
  function createChartLayout(container, options = {}) {
@@ -16,11 +17,10 @@ var ChartLayout = class {
16
17
  activeHandlers = /* @__PURE__ */ new Set();
17
18
  crosshairOverlays = /* @__PURE__ */ new Map();
18
19
  chartOptions;
19
- syncTimeRange;
20
- syncCrosshair;
21
20
  crosshairColor;
22
21
  columns;
23
22
  gap;
23
+ sync;
24
24
  activeId = null;
25
25
  counter = 0;
26
26
  applyingRange = false;
@@ -33,8 +33,7 @@ var ChartLayout = class {
33
33
  this.columns = validateColumns(options.columns ?? DEFAULT_COLUMNS);
34
34
  this.gap = validateGap(options.gap ?? DEFAULT_GAP);
35
35
  this.chartOptions = options.chartOptions;
36
- this.syncTimeRange = options.syncTimeRange ?? true;
37
- this.syncCrosshair = options.syncCrosshair ?? true;
36
+ this.sync = resolveSyncOptions(options);
38
37
  this.crosshairColor = options.crosshairColor ?? DEFAULT_CROSSHAIR_COLOR;
39
38
  if (options.onActiveChange) {
40
39
  this.activeHandlers.add(options.onActiveChange);
@@ -61,6 +60,7 @@ var ChartLayout = class {
61
60
  }
62
61
  const rowSpan = validateSpan(options.rowSpan ?? 1, "rowSpan");
63
62
  const columnSpan = validateSpan(options.columnSpan ?? 1, "columnSpan");
63
+ const syncGroup = validateSyncGroup(options.syncGroup ?? DEFAULT_SYNC_GROUP);
64
64
  const element = document.createElement("div");
65
65
  element.className = "wicklib-chart-layout__cell";
66
66
  element.dataset.chartId = id;
@@ -88,50 +88,23 @@ var ChartLayout = class {
88
88
  chart.on("visible-range", (event) => this.handleVisibleRange(id, event)),
89
89
  chart.on("crosshair", (event) => this.handleCrosshair(id, event))
90
90
  ];
91
- let overlay = null;
92
- if (this.syncCrosshair) {
93
- overlay = chart.addOverlay({
94
- id: "wicklib-layout-crosshair",
95
- draw: (context, frame) => {
96
- const current = this.crosshair;
97
- if (!current || current.sourceId === id) {
98
- return;
99
- }
100
- const x = frame.timeToX(current.time);
101
- if (x === null || x < frame.plot.left || x > frame.plot.right) {
102
- return;
103
- }
104
- const crispX = Math.round(x) + 0.5;
105
- context.save();
106
- context.beginPath();
107
- context.rect(frame.plot.left, frame.plot.top, frame.plot.width, frame.plot.height);
108
- context.clip();
109
- context.strokeStyle = this.crosshairColor;
110
- context.lineWidth = 1;
111
- context.setLineDash([3, 4]);
112
- context.beginPath();
113
- context.moveTo(crispX, frame.plot.top);
114
- context.lineTo(crispX, frame.plot.bottom);
115
- context.stroke();
116
- context.restore();
117
- }
118
- });
119
- this.crosshairOverlays.set(id, overlay);
120
- }
121
91
  const slot = {
122
92
  id,
123
93
  chart,
124
94
  element,
125
95
  rowSpan,
126
- columnSpan
96
+ columnSpan,
97
+ syncGroup
127
98
  };
128
- this.slots.push({
99
+ const entry = {
129
100
  slot,
130
101
  unsubscribe: unsubscribes,
131
- overlay,
102
+ overlay: null,
132
103
  onPointerDown,
133
104
  onFocusIn
134
- });
105
+ };
106
+ this.slots.push(entry);
107
+ this.ensureCrosshairOverlay(entry);
135
108
  if (this.activeId === null) {
136
109
  this.setActiveChart(id);
137
110
  }
@@ -176,6 +149,28 @@ var ChartLayout = class {
176
149
  this.gap = validateGap(gap);
177
150
  this.element.style.gap = `${this.gap}px`;
178
151
  }
152
+ setSyncOptions(options) {
153
+ this.assertAlive();
154
+ const next = {
155
+ timeRange: options.timeRange ?? this.sync.timeRange,
156
+ crosshair: options.crosshair === void 0 ? this.sync.crosshair : normalizeCrosshair(options.crosshair)
157
+ };
158
+ if (next.timeRange === this.sync.timeRange && next.crosshair === this.sync.crosshair) {
159
+ return;
160
+ }
161
+ this.sync = next;
162
+ for (const entry of this.slots) {
163
+ this.ensureCrosshairOverlay(entry);
164
+ }
165
+ if (next.crosshair === "off" && this.crosshair !== null) {
166
+ this.crosshair = null;
167
+ }
168
+ this.requestCrosshairRender();
169
+ }
170
+ getSyncOptions() {
171
+ this.assertAlive();
172
+ return { ...this.sync };
173
+ }
179
174
  setActiveChart(id) {
180
175
  this.assertAlive();
181
176
  if (id !== null && !this.findSlot(id)) {
@@ -215,7 +210,11 @@ var ChartLayout = class {
215
210
  if (!source) {
216
211
  return;
217
212
  }
218
- this.applyRange(source.slot.id, source.slot.chart.timeScale().getVisibleRange());
213
+ this.applyRange(
214
+ source.slot.id,
215
+ source.slot.syncGroup,
216
+ source.slot.chart.timeScale().getVisibleRange()
217
+ );
219
218
  }
220
219
  serialize() {
221
220
  this.assertAlive();
@@ -223,10 +222,12 @@ var ChartLayout = class {
223
222
  version: 1,
224
223
  columns: this.columns,
225
224
  gap: this.gap,
225
+ sync: { ...this.sync },
226
226
  slots: this.slots.map((entry) => ({
227
227
  id: entry.slot.id,
228
228
  rowSpan: entry.slot.rowSpan,
229
- columnSpan: entry.slot.columnSpan
229
+ columnSpan: entry.slot.columnSpan,
230
+ syncGroup: entry.slot.syncGroup
230
231
  }))
231
232
  };
232
233
  }
@@ -253,19 +254,23 @@ var ChartLayout = class {
253
254
  return this.slots.find((entry) => entry.slot.id === id);
254
255
  }
255
256
  handleVisibleRange(sourceId, event) {
256
- if (!this.syncTimeRange || this.applyingRange) {
257
+ if (!this.sync.timeRange || this.applyingRange) {
257
258
  return;
258
259
  }
259
- this.applyRange(sourceId, { from: event.from, to: event.to });
260
+ const source = this.findSlot(sourceId);
261
+ if (!source) {
262
+ return;
263
+ }
264
+ this.applyRange(sourceId, source.slot.syncGroup, { from: event.from, to: event.to });
260
265
  }
261
- applyRange(sourceId, range) {
266
+ applyRange(sourceId, syncGroup, range) {
262
267
  if (!Number.isFinite(range.from) || !Number.isFinite(range.to) || range.to <= range.from) {
263
268
  return;
264
269
  }
265
270
  this.applyingRange = true;
266
271
  try {
267
272
  for (const entry of this.slots) {
268
- if (entry.slot.id === sourceId) {
273
+ if (entry.slot.id === sourceId || entry.slot.syncGroup !== syncGroup) {
269
274
  continue;
270
275
  }
271
276
  try {
@@ -278,7 +283,7 @@ var ChartLayout = class {
278
283
  }
279
284
  }
280
285
  handleCrosshair(sourceId, event) {
281
- if (!this.syncCrosshair) {
286
+ if (this.sync.crosshair === "off") {
282
287
  return;
283
288
  }
284
289
  if (event.time === null || event.point === null) {
@@ -288,7 +293,18 @@ var ChartLayout = class {
288
293
  }
289
294
  return;
290
295
  }
291
- this.crosshair = { sourceId, time: event.time };
296
+ const source = this.findSlot(sourceId);
297
+ if (!source) {
298
+ return;
299
+ }
300
+ const height = source.slot.element.clientHeight;
301
+ const yRatio = height > 0 ? Math.min(1, Math.max(0, event.point.y / height)) : null;
302
+ this.crosshair = {
303
+ sourceId,
304
+ sourceGroup: source.slot.syncGroup,
305
+ time: event.time,
306
+ yRatio
307
+ };
292
308
  this.requestCrosshairRender();
293
309
  }
294
310
  requestCrosshairRender() {
@@ -296,6 +312,57 @@ var ChartLayout = class {
296
312
  overlay.requestRender();
297
313
  }
298
314
  }
315
+ ensureCrosshairOverlay(entry) {
316
+ if (this.sync.crosshair === "off") {
317
+ if (entry.overlay) {
318
+ entry.overlay.remove();
319
+ entry.overlay = null;
320
+ this.crosshairOverlays.delete(entry.slot.id);
321
+ }
322
+ return;
323
+ }
324
+ if (entry.overlay) {
325
+ return;
326
+ }
327
+ const id = entry.slot.id;
328
+ const group = entry.slot.syncGroup;
329
+ entry.overlay = entry.slot.chart.addOverlay({
330
+ id: "wicklib-layout-crosshair",
331
+ draw: (context, frame) => this.drawCrosshair(context, frame, id, group)
332
+ });
333
+ this.crosshairOverlays.set(id, entry.overlay);
334
+ }
335
+ drawCrosshair(context, frame, id, group) {
336
+ const current = this.crosshair;
337
+ if (!current || current.sourceId === id || current.sourceGroup !== group || this.sync.crosshair === "off") {
338
+ return;
339
+ }
340
+ const x = frame.timeToX(current.time);
341
+ if (x === null || x < frame.plot.left || x > frame.plot.right) {
342
+ return;
343
+ }
344
+ context.save();
345
+ context.beginPath();
346
+ context.rect(frame.plot.left, frame.plot.top, frame.plot.width, frame.plot.height);
347
+ context.clip();
348
+ context.strokeStyle = this.crosshairColor;
349
+ context.lineWidth = 1;
350
+ context.setLineDash([3, 4]);
351
+ const crispX = Math.round(x) + 0.5;
352
+ context.beginPath();
353
+ context.moveTo(crispX, frame.plot.top);
354
+ context.lineTo(crispX, frame.plot.bottom);
355
+ context.stroke();
356
+ if (this.sync.crosshair === "full" && current.yRatio !== null) {
357
+ const y = frame.plot.top + current.yRatio * frame.plot.height;
358
+ const crispY = Math.round(y) + 0.5;
359
+ context.beginPath();
360
+ context.moveTo(frame.plot.left, crispY);
361
+ context.lineTo(frame.plot.right, crispY);
362
+ context.stroke();
363
+ }
364
+ context.restore();
365
+ }
299
366
  disposeSlot(entry) {
300
367
  entry.slot.element.removeEventListener("pointerdown", entry.onPointerDown, true);
301
368
  entry.slot.element.removeEventListener("focusin", entry.onFocusIn);
@@ -303,6 +370,7 @@ var ChartLayout = class {
303
370
  unsubscribe();
304
371
  }
305
372
  entry.overlay?.remove();
373
+ entry.overlay = null;
306
374
  this.crosshairOverlays.delete(entry.slot.id);
307
375
  entry.slot.chart.destroy();
308
376
  entry.slot.element.remove();
@@ -313,6 +381,24 @@ var ChartLayout = class {
313
381
  }
314
382
  }
315
383
  };
384
+ function resolveSyncOptions(options) {
385
+ return {
386
+ timeRange: options.sync?.timeRange ?? options.syncTimeRange ?? true,
387
+ crosshair: normalizeCrosshair(options.sync?.crosshair ?? options.syncCrosshair ?? "time")
388
+ };
389
+ }
390
+ function normalizeCrosshair(value) {
391
+ if (value === void 0 || value === true || value === "time") {
392
+ return "time";
393
+ }
394
+ if (value === false || value === "off") {
395
+ return "off";
396
+ }
397
+ if (value === "full") {
398
+ return "full";
399
+ }
400
+ throw new core.WickError("INVALID_ARGUMENT", "crosshair must be off, time, or full");
401
+ }
316
402
  function mergeChartOptions(base, override) {
317
403
  if (!base) {
318
404
  return override;
@@ -340,6 +426,12 @@ function validateSpan(value, name) {
340
426
  }
341
427
  return value;
342
428
  }
429
+ function validateSyncGroup(value) {
430
+ if (typeof value !== "string" || value.trim() === "") {
431
+ throw new core.WickError("INVALID_ARGUMENT", "syncGroup must be a non-empty string");
432
+ }
433
+ return value;
434
+ }
343
435
 
344
436
  exports.createChartLayout = createChartLayout;
345
437
  //# sourceMappingURL=index.cjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts"],"names":["WickError","createChart","entry"],"mappings":"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","file":"index.cjs","sourcesContent":["/**\n * Free multi-chart layout primitives for Wicklib.\n */\n\nimport {\n WickError,\n createChart,\n type ChartApi,\n type ChartOptions,\n type ChartOverlayApi,\n type CrosshairEvent,\n type Unsubscribe,\n type VisibleRangeEvent,\n} from \"@wicklib/core\";\n\n/**\n * Options for a layout chart cell.\n */\nexport interface ChartLayoutSlotOptions {\n /** Stable slot id. Generated when omitted. */\n id?: string;\n /** Chart options for this slot. Merged over the layout defaults. */\n chartOptions?: ChartOptions;\n /** Number of grid rows covered by the cell. Defaults to 1. */\n rowSpan?: number;\n /** Number of grid columns covered by the cell. Defaults to 1. */\n columnSpan?: number;\n}\n\n/**\n * One chart cell in a layout.\n */\nexport interface ChartLayoutSlot {\n readonly id: string;\n readonly chart: ChartApi;\n readonly element: HTMLElement;\n readonly rowSpan: number;\n readonly columnSpan: number;\n}\n\n/**\n * Serializable structural layout state. Chart state is owned by the host.\n */\nexport interface ChartLayoutSnapshot {\n version: 1;\n columns: number;\n gap: number;\n slots: ReadonlyArray<{\n id: string;\n rowSpan: number;\n columnSpan: number;\n }>;\n}\n\n/**\n * Options for a multi-chart layout.\n */\nexport interface ChartLayoutOptions {\n /** Number of grid columns. Defaults to 2. */\n columns?: number;\n /** Gap between cells in CSS pixels. Defaults to 1. */\n gap?: number;\n /** Default chart options applied to every slot. */\n chartOptions?: ChartOptions;\n /** Synchronize visible time ranges. Defaults to true. */\n syncTimeRange?: boolean;\n /** Synchronize the vertical crosshair time line. Defaults to true. */\n syncCrosshair?: boolean;\n /** Crosshair line colour. */\n crosshairColor?: string;\n /** Extra class name on the layout root. */\n className?: string;\n /** Called when the active chart changes. */\n onActiveChange?: (chartId: string | null) => void;\n}\n\n/**\n * A live multi-chart layout.\n */\nexport interface ChartLayoutApi {\n /** Layout root inserted into the host container. */\n readonly element: HTMLElement;\n /** Adds a chart cell and returns its live slot. */\n addChart(options?: ChartLayoutSlotOptions): ChartLayoutSlot;\n /** Destroys and removes one chart cell. */\n removeChart(id: string): void;\n /** Returns a live chart, or null when the id is unknown. */\n getChart(id: string): ChartApi | null;\n /** Returns the current slots in DOM order. */\n getSlots(): readonly ChartLayoutSlot[];\n /** Returns slot ids in DOM order. */\n chartIds(): readonly string[];\n /** Changes the grid column count. */\n setColumns(columns: number): void;\n /** Changes the cell gap in CSS pixels. */\n setGap(gap: number): void;\n /** Marks one chart as active. Pass null to clear. */\n setActiveChart(id: string | null): void;\n /** Returns the active chart id, or null. */\n activeChartId(): string | null;\n /** Subscribes to active chart changes. */\n onActiveChange(handler: (chartId: string | null) => void): Unsubscribe;\n /** Applies the active chart's visible range to every other chart. */\n syncNow(): void;\n /** Returns structural layout state for host persistence. */\n serialize(): ChartLayoutSnapshot;\n /** Destroys every chart and removes the layout root. */\n destroy(): void;\n}\n\ninterface InternalSlot {\n readonly slot: ChartLayoutSlot;\n readonly unsubscribe: readonly Unsubscribe[];\n readonly overlay: ChartOverlayApi | null;\n readonly onPointerDown: () => void;\n readonly onFocusIn: () => void;\n}\n\nconst DEFAULT_COLUMNS = 2;\nconst DEFAULT_GAP = 1;\nconst MAX_COLUMNS = 12;\nconst DEFAULT_CROSSHAIR_COLOR = \"rgba(139, 149, 167, 0.6)\";\n\n/**\n * Creates a framework-agnostic grid of Wicklib charts.\n *\n * The layout owns chart creation, grid placement, time-range synchronization, and a\n * mirrored crosshair line. It does not own data, symbol state, accounts, or persistence.\n */\nexport function createChartLayout(\n container: HTMLElement,\n options: ChartLayoutOptions = {},\n): ChartLayoutApi {\n return new ChartLayout(container, options);\n}\n\nclass ChartLayout implements ChartLayoutApi {\n readonly element: HTMLElement;\n\n private readonly slots: InternalSlot[] = [];\n private readonly activeHandlers = new Set<(chartId: string | null) => void>();\n private readonly crosshairOverlays = new Map<string, ChartOverlayApi>();\n private readonly chartOptions: ChartOptions | undefined;\n private readonly syncTimeRange: boolean;\n private readonly syncCrosshair: boolean;\n private readonly crosshairColor: string;\n private columns: number;\n private gap: number;\n private activeId: string | null = null;\n private counter = 0;\n private applyingRange = false;\n private crosshair: { sourceId: string; time: number } | null = null;\n private destroyed = false;\n\n constructor(container: HTMLElement, options: ChartLayoutOptions) {\n if (!container || typeof container.appendChild !== \"function\") {\n throw new WickError(\"INVALID_ARGUMENT\", \"createChartLayout requires a DOM container\");\n }\n\n this.columns = validateColumns(options.columns ?? DEFAULT_COLUMNS);\n this.gap = validateGap(options.gap ?? DEFAULT_GAP);\n this.chartOptions = options.chartOptions;\n this.syncTimeRange = options.syncTimeRange ?? true;\n this.syncCrosshair = options.syncCrosshair ?? true;\n this.crosshairColor = options.crosshairColor ?? DEFAULT_CROSSHAIR_COLOR;\n if (options.onActiveChange) {\n this.activeHandlers.add(options.onActiveChange);\n }\n\n this.element = document.createElement(\"div\");\n this.element.className = [\"wicklib-chart-layout\", options.className].filter(Boolean).join(\" \");\n Object.assign(this.element.style, {\n display: \"grid\",\n width: \"100%\",\n height: \"100%\",\n minWidth: \"0\",\n minHeight: \"0\",\n gridAutoRows: \"minmax(0, 1fr)\",\n gap: `${this.gap}px`,\n });\n this.applyGrid();\n container.appendChild(this.element);\n }\n\n addChart(options: ChartLayoutSlotOptions = {}): ChartLayoutSlot {\n this.assertAlive();\n const id = options.id ?? `chart-${++this.counter}`;\n if (this.findSlot(id)) {\n throw new WickError(\"INVALID_ARGUMENT\", `Duplicate chart layout id: ${id}`);\n }\n const rowSpan = validateSpan(options.rowSpan ?? 1, \"rowSpan\");\n const columnSpan = validateSpan(options.columnSpan ?? 1, \"columnSpan\");\n\n const element = document.createElement(\"div\");\n element.className = \"wicklib-chart-layout__cell\";\n element.dataset.chartId = id;\n Object.assign(element.style, {\n position: \"relative\",\n minWidth: \"0\",\n minHeight: \"0\",\n overflow: \"hidden\",\n gridRow: `span ${rowSpan}`,\n gridColumn: `span ${columnSpan}`,\n });\n this.element.appendChild(element);\n\n let chart: ChartApi;\n try {\n chart = createChart(element, mergeChartOptions(this.chartOptions, options.chartOptions));\n } catch (error) {\n element.remove();\n throw error;\n }\n\n const onPointerDown = (): void => this.setActiveChart(id);\n const onFocusIn = (): void => this.setActiveChart(id);\n element.addEventListener(\"pointerdown\", onPointerDown, true);\n element.addEventListener(\"focusin\", onFocusIn);\n\n const unsubscribes: Unsubscribe[] = [\n chart.on(\"visible-range\", (event) => this.handleVisibleRange(id, event)),\n chart.on(\"crosshair\", (event) => this.handleCrosshair(id, event)),\n ];\n\n let overlay: ChartOverlayApi | null = null;\n if (this.syncCrosshair) {\n overlay = chart.addOverlay({\n id: \"wicklib-layout-crosshair\",\n draw: (context, frame) => {\n const current = this.crosshair;\n if (!current || current.sourceId === id) {\n return;\n }\n const x = frame.timeToX(current.time);\n if (x === null || x < frame.plot.left || x > frame.plot.right) {\n return;\n }\n const crispX = Math.round(x) + 0.5;\n context.save();\n context.beginPath();\n context.rect(frame.plot.left, frame.plot.top, frame.plot.width, frame.plot.height);\n context.clip();\n context.strokeStyle = this.crosshairColor;\n context.lineWidth = 1;\n context.setLineDash([3, 4]);\n context.beginPath();\n context.moveTo(crispX, frame.plot.top);\n context.lineTo(crispX, frame.plot.bottom);\n context.stroke();\n context.restore();\n },\n });\n this.crosshairOverlays.set(id, overlay);\n }\n\n const slot: ChartLayoutSlot = {\n id,\n chart,\n element,\n rowSpan,\n columnSpan,\n };\n this.slots.push({\n slot,\n unsubscribe: unsubscribes,\n overlay,\n onPointerDown,\n onFocusIn,\n });\n\n if (this.activeId === null) {\n this.setActiveChart(id);\n }\n\n return slot;\n }\n\n removeChart(id: string): void {\n this.assertAlive();\n const index = this.slots.findIndex((entry) => entry.slot.id === id);\n if (index < 0) {\n return;\n }\n const entry = this.slots[index]!;\n this.disposeSlot(entry);\n this.slots.splice(index, 1);\n if (this.activeId === id) {\n this.setActiveChart(null);\n }\n if (this.crosshair?.sourceId === id) {\n this.crosshair = null;\n this.requestCrosshairRender();\n }\n }\n\n getChart(id: string): ChartApi | null {\n this.assertAlive();\n return this.findSlot(id)?.slot.chart ?? null;\n }\n\n getSlots(): readonly ChartLayoutSlot[] {\n this.assertAlive();\n return this.slots.map((entry) => entry.slot);\n }\n\n chartIds(): readonly string[] {\n this.assertAlive();\n return this.slots.map((entry) => entry.slot.id);\n }\n\n setColumns(columns: number): void {\n this.assertAlive();\n this.columns = validateColumns(columns);\n this.applyGrid();\n }\n\n setGap(gap: number): void {\n this.assertAlive();\n this.gap = validateGap(gap);\n this.element.style.gap = `${this.gap}px`;\n }\n\n setActiveChart(id: string | null): void {\n this.assertAlive();\n if (id !== null && !this.findSlot(id)) {\n throw new WickError(\"INVALID_ARGUMENT\", `Unknown chart layout id: ${id}`);\n }\n if (this.activeId === id) {\n return;\n }\n const previous = this.activeId;\n this.activeId = id;\n for (const entry of this.slots) {\n if (entry.slot.id === previous) {\n delete entry.slot.element.dataset.active;\n }\n if (entry.slot.id === id) {\n entry.slot.element.dataset.active = \"true\";\n }\n }\n for (const handler of this.activeHandlers) {\n handler(id);\n }\n }\n\n activeChartId(): string | null {\n this.assertAlive();\n return this.activeId;\n }\n\n onActiveChange(handler: (chartId: string | null) => void): Unsubscribe {\n this.assertAlive();\n this.activeHandlers.add(handler);\n return () => {\n this.activeHandlers.delete(handler);\n };\n }\n\n syncNow(): void {\n this.assertAlive();\n const source = this.findSlot(this.activeId ?? this.slots[0]?.slot.id ?? \"\");\n if (!source) {\n return;\n }\n this.applyRange(source.slot.id, source.slot.chart.timeScale().getVisibleRange());\n }\n\n serialize(): ChartLayoutSnapshot {\n this.assertAlive();\n return {\n version: 1,\n columns: this.columns,\n gap: this.gap,\n slots: this.slots.map((entry) => ({\n id: entry.slot.id,\n rowSpan: entry.slot.rowSpan,\n columnSpan: entry.slot.columnSpan,\n })),\n };\n }\n\n destroy(): void {\n if (this.destroyed) {\n return;\n }\n this.destroyed = true;\n for (const entry of this.slots) {\n this.disposeSlot(entry);\n }\n this.slots.length = 0;\n this.activeHandlers.clear();\n this.crosshairOverlays.clear();\n this.element.remove();\n }\n\n private applyGrid(): void {\n this.element.style.gridTemplateColumns = `repeat(${this.columns}, minmax(0, 1fr))`;\n }\n\n private findSlot(id: string | null): InternalSlot | undefined {\n if (id === null) {\n return undefined;\n }\n return this.slots.find((entry) => entry.slot.id === id);\n }\n\n private handleVisibleRange(sourceId: string, event: VisibleRangeEvent): void {\n if (!this.syncTimeRange || this.applyingRange) {\n return;\n }\n this.applyRange(sourceId, { from: event.from, to: event.to });\n }\n\n private applyRange(sourceId: string, range: { from: number; to: number }): void {\n if (!Number.isFinite(range.from) || !Number.isFinite(range.to) || range.to <= range.from) {\n return;\n }\n this.applyingRange = true;\n try {\n for (const entry of this.slots) {\n if (entry.slot.id === sourceId) {\n continue;\n }\n try {\n entry.slot.chart.timeScale().setVisibleRange(range);\n } catch {\n // A chart with incompatible data can reject the range; leave it unchanged.\n }\n }\n } finally {\n this.applyingRange = false;\n }\n }\n\n private handleCrosshair(sourceId: string, event: CrosshairEvent): void {\n if (!this.syncCrosshair) {\n return;\n }\n if (event.time === null || event.point === null) {\n if (this.crosshair !== null) {\n this.crosshair = null;\n this.requestCrosshairRender();\n }\n return;\n }\n this.crosshair = { sourceId, time: event.time };\n this.requestCrosshairRender();\n }\n\n private requestCrosshairRender(): void {\n for (const overlay of this.crosshairOverlays.values()) {\n overlay.requestRender();\n }\n }\n\n private disposeSlot(entry: InternalSlot): void {\n entry.slot.element.removeEventListener(\"pointerdown\", entry.onPointerDown, true);\n entry.slot.element.removeEventListener(\"focusin\", entry.onFocusIn);\n for (const unsubscribe of entry.unsubscribe) {\n unsubscribe();\n }\n entry.overlay?.remove();\n this.crosshairOverlays.delete(entry.slot.id);\n entry.slot.chart.destroy();\n entry.slot.element.remove();\n }\n\n private assertAlive(): void {\n if (this.destroyed) {\n throw new WickError(\"DISPOSED\", \"Chart layout is disposed\");\n }\n }\n}\n\nfunction mergeChartOptions(\n base: ChartOptions | undefined,\n override: ChartOptions | undefined,\n): ChartOptions | undefined {\n if (!base) {\n return override;\n }\n if (!override) {\n return base;\n }\n return { ...base, ...override };\n}\n\nfunction validateColumns(columns: number): number {\n if (!Number.isInteger(columns) || columns < 1 || columns > MAX_COLUMNS) {\n throw new WickError(\"INVALID_ARGUMENT\", `columns must be an integer from 1 to ${MAX_COLUMNS}`);\n }\n return columns;\n}\n\nfunction validateGap(gap: number): number {\n if (!Number.isFinite(gap) || gap < 0) {\n throw new WickError(\"INVALID_ARGUMENT\", \"gap must be a non-negative finite number\");\n }\n return gap;\n}\n\nfunction validateSpan(value: number, name: string): number {\n if (!Number.isInteger(value) || value < 1) {\n throw new WickError(\"INVALID_ARGUMENT\", `${name} must be a positive integer`);\n }\n return value;\n}\n"]}
1
+ {"version":3,"sources":["../src/index.ts"],"names":["WickError","createChart","entry"],"mappings":"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","file":"index.cjs","sourcesContent":["/**\n * Free multi-chart layout primitives for Wicklib.\n */\n\nimport {\n WickError,\n createChart,\n type ChartApi,\n type ChartOptions,\n type ChartOverlayApi,\n type CrosshairEvent,\n type OverlayRenderFrame,\n type Unsubscribe,\n type VisibleRangeEvent,\n} from \"@wicklib/core\";\n\n/**\n * Crosshair synchronization mode.\n *\n * `time` mirrors the vertical time line. `full` also mirrors a horizontal line at the same\n * relative pane height.\n */\nexport type ChartLayoutCrosshairMode = \"off\" | \"time\" | \"full\";\n\n/**\n * Resolved synchronization behavior for a layout.\n */\nexport interface ChartLayoutSyncOptions {\n /** Synchronize visible time ranges. */\n timeRange: boolean;\n /** Crosshair synchronization mode. */\n crosshair: ChartLayoutCrosshairMode;\n}\n\n/**\n * Partial synchronization update. Boolean crosshair values map to `time` and `off`.\n */\nexport interface ChartLayoutSyncUpdate {\n /** Synchronize visible time ranges. */\n timeRange?: boolean;\n /** Crosshair synchronization mode. */\n crosshair?: boolean | ChartLayoutCrosshairMode;\n}\n\n/**\n * Options for a layout chart cell.\n */\nexport interface ChartLayoutSlotOptions {\n /** Stable slot id. Generated when omitted. */\n id?: string;\n /** Chart options for this slot. Merged over the layout defaults. */\n chartOptions?: ChartOptions;\n /** Number of grid rows covered by the cell. Defaults to 1. */\n rowSpan?: number;\n /** Number of grid columns covered by the cell. Defaults to 1. */\n columnSpan?: number;\n /** Synchronization group. Slots only sync with slots in the same group. */\n syncGroup?: string;\n}\n\n/**\n * One chart cell in a layout.\n */\nexport interface ChartLayoutSlot {\n readonly id: string;\n readonly chart: ChartApi;\n readonly element: HTMLElement;\n readonly rowSpan: number;\n readonly columnSpan: number;\n readonly syncGroup: string;\n}\n\n/**\n * Serializable structural layout state. Chart state is owned by the host.\n */\nexport interface ChartLayoutSnapshot {\n version: 1;\n columns: number;\n gap: number;\n sync: ChartLayoutSyncOptions;\n slots: ReadonlyArray<{\n id: string;\n rowSpan: number;\n columnSpan: number;\n syncGroup: string;\n }>;\n}\n\n/**\n * Options for a multi-chart layout.\n */\nexport interface ChartLayoutOptions {\n /** Number of grid columns. Defaults to 2. */\n columns?: number;\n /** Gap between cells in CSS pixels. Defaults to 1. */\n gap?: number;\n /** Default chart options applied to every slot. */\n chartOptions?: ChartOptions;\n /** Synchronization options. */\n sync?: ChartLayoutSyncUpdate;\n /** Synchronize visible time ranges. Defaults to true. */\n syncTimeRange?: boolean;\n /** Crosshair synchronization. Defaults to `time`. */\n syncCrosshair?: boolean | ChartLayoutCrosshairMode;\n /** Crosshair line colour. */\n crosshairColor?: string;\n /** Extra class name on the layout root. */\n className?: string;\n /** Called when the active chart changes. */\n onActiveChange?: (chartId: string | null) => void;\n}\n\n/**\n * A live multi-chart layout.\n */\nexport interface ChartLayoutApi {\n /** Layout root inserted into the host container. */\n readonly element: HTMLElement;\n /** Adds a chart cell and returns its live slot. */\n addChart(options?: ChartLayoutSlotOptions): ChartLayoutSlot;\n /** Destroys and removes one chart cell. */\n removeChart(id: string): void;\n /** Returns a live chart, or null when the id is unknown. */\n getChart(id: string): ChartApi | null;\n /** Returns the current slots in DOM order. */\n getSlots(): readonly ChartLayoutSlot[];\n /** Returns slot ids in DOM order. */\n chartIds(): readonly string[];\n /** Changes the grid column count. */\n setColumns(columns: number): void;\n /** Changes the cell gap in CSS pixels. */\n setGap(gap: number): void;\n /** Changes synchronization behavior without recreating charts. */\n setSyncOptions(options: ChartLayoutSyncUpdate): void;\n /** Returns the resolved synchronization behavior. */\n getSyncOptions(): Readonly<ChartLayoutSyncOptions>;\n /** Marks one chart as active. Pass null to clear. */\n setActiveChart(id: string | null): void;\n /** Returns the active chart id, or null. */\n activeChartId(): string | null;\n /** Subscribes to active chart changes. */\n onActiveChange(handler: (chartId: string | null) => void): Unsubscribe;\n /** Applies the active chart's visible range to every chart in its sync group. */\n syncNow(): void;\n /** Returns structural layout state for host persistence. */\n serialize(): ChartLayoutSnapshot;\n /** Destroys every chart and removes the layout root. */\n destroy(): void;\n}\n\ninterface InternalSlot {\n readonly slot: ChartLayoutSlot;\n readonly unsubscribe: readonly Unsubscribe[];\n overlay: ChartOverlayApi | null;\n readonly onPointerDown: () => void;\n readonly onFocusIn: () => void;\n}\n\ninterface CrosshairState {\n sourceId: string;\n sourceGroup: string;\n time: number;\n yRatio: number | null;\n}\n\nconst DEFAULT_COLUMNS = 2;\nconst DEFAULT_GAP = 1;\nconst DEFAULT_SYNC_GROUP = \"default\";\nconst MAX_COLUMNS = 12;\nconst DEFAULT_CROSSHAIR_COLOR = \"rgba(139, 149, 167, 0.6)\";\n\n/**\n * Creates a framework-agnostic grid of Wicklib charts.\n *\n * The layout owns chart creation, grid placement, time-range synchronization, and a\n * mirrored crosshair. It does not own data, symbol state, accounts, or persistence.\n */\nexport function createChartLayout(\n container: HTMLElement,\n options: ChartLayoutOptions = {},\n): ChartLayoutApi {\n return new ChartLayout(container, options);\n}\n\nclass ChartLayout implements ChartLayoutApi {\n readonly element: HTMLElement;\n\n private readonly slots: InternalSlot[] = [];\n private readonly activeHandlers = new Set<(chartId: string | null) => void>();\n private readonly crosshairOverlays = new Map<string, ChartOverlayApi>();\n private readonly chartOptions: ChartOptions | undefined;\n private readonly crosshairColor: string;\n private columns: number;\n private gap: number;\n private sync: ChartLayoutSyncOptions;\n private activeId: string | null = null;\n private counter = 0;\n private applyingRange = false;\n private crosshair: CrosshairState | null = null;\n private destroyed = false;\n\n constructor(container: HTMLElement, options: ChartLayoutOptions) {\n if (!container || typeof container.appendChild !== \"function\") {\n throw new WickError(\"INVALID_ARGUMENT\", \"createChartLayout requires a DOM container\");\n }\n\n this.columns = validateColumns(options.columns ?? DEFAULT_COLUMNS);\n this.gap = validateGap(options.gap ?? DEFAULT_GAP);\n this.chartOptions = options.chartOptions;\n this.sync = resolveSyncOptions(options);\n this.crosshairColor = options.crosshairColor ?? DEFAULT_CROSSHAIR_COLOR;\n if (options.onActiveChange) {\n this.activeHandlers.add(options.onActiveChange);\n }\n\n this.element = document.createElement(\"div\");\n this.element.className = [\"wicklib-chart-layout\", options.className].filter(Boolean).join(\" \");\n Object.assign(this.element.style, {\n display: \"grid\",\n width: \"100%\",\n height: \"100%\",\n minWidth: \"0\",\n minHeight: \"0\",\n gridAutoRows: \"minmax(0, 1fr)\",\n gap: `${this.gap}px`,\n });\n this.applyGrid();\n container.appendChild(this.element);\n }\n\n addChart(options: ChartLayoutSlotOptions = {}): ChartLayoutSlot {\n this.assertAlive();\n const id = options.id ?? `chart-${++this.counter}`;\n if (this.findSlot(id)) {\n throw new WickError(\"INVALID_ARGUMENT\", `Duplicate chart layout id: ${id}`);\n }\n const rowSpan = validateSpan(options.rowSpan ?? 1, \"rowSpan\");\n const columnSpan = validateSpan(options.columnSpan ?? 1, \"columnSpan\");\n const syncGroup = validateSyncGroup(options.syncGroup ?? DEFAULT_SYNC_GROUP);\n\n const element = document.createElement(\"div\");\n element.className = \"wicklib-chart-layout__cell\";\n element.dataset.chartId = id;\n Object.assign(element.style, {\n position: \"relative\",\n minWidth: \"0\",\n minHeight: \"0\",\n overflow: \"hidden\",\n gridRow: `span ${rowSpan}`,\n gridColumn: `span ${columnSpan}`,\n });\n this.element.appendChild(element);\n\n let chart: ChartApi;\n try {\n chart = createChart(element, mergeChartOptions(this.chartOptions, options.chartOptions));\n } catch (error) {\n element.remove();\n throw error;\n }\n\n const onPointerDown = (): void => this.setActiveChart(id);\n const onFocusIn = (): void => this.setActiveChart(id);\n element.addEventListener(\"pointerdown\", onPointerDown, true);\n element.addEventListener(\"focusin\", onFocusIn);\n\n const unsubscribes: Unsubscribe[] = [\n chart.on(\"visible-range\", (event) => this.handleVisibleRange(id, event)),\n chart.on(\"crosshair\", (event) => this.handleCrosshair(id, event)),\n ];\n\n const slot: ChartLayoutSlot = {\n id,\n chart,\n element,\n rowSpan,\n columnSpan,\n syncGroup,\n };\n const entry: InternalSlot = {\n slot,\n unsubscribe: unsubscribes,\n overlay: null,\n onPointerDown,\n onFocusIn,\n };\n this.slots.push(entry);\n this.ensureCrosshairOverlay(entry);\n\n if (this.activeId === null) {\n this.setActiveChart(id);\n }\n\n return slot;\n }\n\n removeChart(id: string): void {\n this.assertAlive();\n const index = this.slots.findIndex((entry) => entry.slot.id === id);\n if (index < 0) {\n return;\n }\n const entry = this.slots[index]!;\n this.disposeSlot(entry);\n this.slots.splice(index, 1);\n if (this.activeId === id) {\n this.setActiveChart(null);\n }\n if (this.crosshair?.sourceId === id) {\n this.crosshair = null;\n this.requestCrosshairRender();\n }\n }\n\n getChart(id: string): ChartApi | null {\n this.assertAlive();\n return this.findSlot(id)?.slot.chart ?? null;\n }\n\n getSlots(): readonly ChartLayoutSlot[] {\n this.assertAlive();\n return this.slots.map((entry) => entry.slot);\n }\n\n chartIds(): readonly string[] {\n this.assertAlive();\n return this.slots.map((entry) => entry.slot.id);\n }\n\n setColumns(columns: number): void {\n this.assertAlive();\n this.columns = validateColumns(columns);\n this.applyGrid();\n }\n\n setGap(gap: number): void {\n this.assertAlive();\n this.gap = validateGap(gap);\n this.element.style.gap = `${this.gap}px`;\n }\n\n setSyncOptions(options: ChartLayoutSyncUpdate): void {\n this.assertAlive();\n const next: ChartLayoutSyncOptions = {\n timeRange: options.timeRange ?? this.sync.timeRange,\n crosshair:\n options.crosshair === undefined\n ? this.sync.crosshair\n : normalizeCrosshair(options.crosshair),\n };\n if (next.timeRange === this.sync.timeRange && next.crosshair === this.sync.crosshair) {\n return;\n }\n this.sync = next;\n for (const entry of this.slots) {\n this.ensureCrosshairOverlay(entry);\n }\n if (next.crosshair === \"off\" && this.crosshair !== null) {\n this.crosshair = null;\n }\n this.requestCrosshairRender();\n }\n\n getSyncOptions(): Readonly<ChartLayoutSyncOptions> {\n this.assertAlive();\n return { ...this.sync };\n }\n\n setActiveChart(id: string | null): void {\n this.assertAlive();\n if (id !== null && !this.findSlot(id)) {\n throw new WickError(\"INVALID_ARGUMENT\", `Unknown chart layout id: ${id}`);\n }\n if (this.activeId === id) {\n return;\n }\n const previous = this.activeId;\n this.activeId = id;\n for (const entry of this.slots) {\n if (entry.slot.id === previous) {\n delete entry.slot.element.dataset.active;\n }\n if (entry.slot.id === id) {\n entry.slot.element.dataset.active = \"true\";\n }\n }\n for (const handler of this.activeHandlers) {\n handler(id);\n }\n }\n\n activeChartId(): string | null {\n this.assertAlive();\n return this.activeId;\n }\n\n onActiveChange(handler: (chartId: string | null) => void): Unsubscribe {\n this.assertAlive();\n this.activeHandlers.add(handler);\n return () => {\n this.activeHandlers.delete(handler);\n };\n }\n\n syncNow(): void {\n this.assertAlive();\n const source = this.findSlot(this.activeId ?? this.slots[0]?.slot.id ?? \"\");\n if (!source) {\n return;\n }\n this.applyRange(\n source.slot.id,\n source.slot.syncGroup,\n source.slot.chart.timeScale().getVisibleRange(),\n );\n }\n\n serialize(): ChartLayoutSnapshot {\n this.assertAlive();\n return {\n version: 1,\n columns: this.columns,\n gap: this.gap,\n sync: { ...this.sync },\n slots: this.slots.map((entry) => ({\n id: entry.slot.id,\n rowSpan: entry.slot.rowSpan,\n columnSpan: entry.slot.columnSpan,\n syncGroup: entry.slot.syncGroup,\n })),\n };\n }\n\n destroy(): void {\n if (this.destroyed) {\n return;\n }\n this.destroyed = true;\n for (const entry of this.slots) {\n this.disposeSlot(entry);\n }\n this.slots.length = 0;\n this.activeHandlers.clear();\n this.crosshairOverlays.clear();\n this.element.remove();\n }\n\n private applyGrid(): void {\n this.element.style.gridTemplateColumns = `repeat(${this.columns}, minmax(0, 1fr))`;\n }\n\n private findSlot(id: string | null): InternalSlot | undefined {\n if (id === null) {\n return undefined;\n }\n return this.slots.find((entry) => entry.slot.id === id);\n }\n\n private handleVisibleRange(sourceId: string, event: VisibleRangeEvent): void {\n if (!this.sync.timeRange || this.applyingRange) {\n return;\n }\n const source = this.findSlot(sourceId);\n if (!source) {\n return;\n }\n this.applyRange(sourceId, source.slot.syncGroup, { from: event.from, to: event.to });\n }\n\n private applyRange(\n sourceId: string,\n syncGroup: string,\n range: { from: number; to: number },\n ): void {\n if (!Number.isFinite(range.from) || !Number.isFinite(range.to) || range.to <= range.from) {\n return;\n }\n this.applyingRange = true;\n try {\n for (const entry of this.slots) {\n if (entry.slot.id === sourceId || entry.slot.syncGroup !== syncGroup) {\n continue;\n }\n try {\n entry.slot.chart.timeScale().setVisibleRange(range);\n } catch {\n // A chart with incompatible data can reject the range; leave it unchanged.\n }\n }\n } finally {\n this.applyingRange = false;\n }\n }\n\n private handleCrosshair(sourceId: string, event: CrosshairEvent): void {\n if (this.sync.crosshair === \"off\") {\n return;\n }\n if (event.time === null || event.point === null) {\n if (this.crosshair !== null) {\n this.crosshair = null;\n this.requestCrosshairRender();\n }\n return;\n }\n const source = this.findSlot(sourceId);\n if (!source) {\n return;\n }\n const height = source.slot.element.clientHeight;\n const yRatio = height > 0 ? Math.min(1, Math.max(0, event.point.y / height)) : null;\n this.crosshair = {\n sourceId,\n sourceGroup: source.slot.syncGroup,\n time: event.time,\n yRatio,\n };\n this.requestCrosshairRender();\n }\n\n private requestCrosshairRender(): void {\n for (const overlay of this.crosshairOverlays.values()) {\n overlay.requestRender();\n }\n }\n\n private ensureCrosshairOverlay(entry: InternalSlot): void {\n if (this.sync.crosshair === \"off\") {\n if (entry.overlay) {\n entry.overlay.remove();\n entry.overlay = null;\n this.crosshairOverlays.delete(entry.slot.id);\n }\n return;\n }\n if (entry.overlay) {\n return;\n }\n const id = entry.slot.id;\n const group = entry.slot.syncGroup;\n entry.overlay = entry.slot.chart.addOverlay({\n id: \"wicklib-layout-crosshair\",\n draw: (context, frame) => this.drawCrosshair(context, frame, id, group),\n });\n this.crosshairOverlays.set(id, entry.overlay);\n }\n\n private drawCrosshair(\n context: CanvasRenderingContext2D,\n frame: OverlayRenderFrame,\n id: string,\n group: string,\n ): void {\n const current = this.crosshair;\n if (\n !current ||\n current.sourceId === id ||\n current.sourceGroup !== group ||\n this.sync.crosshair === \"off\"\n ) {\n return;\n }\n const x = frame.timeToX(current.time);\n if (x === null || x < frame.plot.left || x > frame.plot.right) {\n return;\n }\n context.save();\n context.beginPath();\n context.rect(frame.plot.left, frame.plot.top, frame.plot.width, frame.plot.height);\n context.clip();\n context.strokeStyle = this.crosshairColor;\n context.lineWidth = 1;\n context.setLineDash([3, 4]);\n\n const crispX = Math.round(x) + 0.5;\n context.beginPath();\n context.moveTo(crispX, frame.plot.top);\n context.lineTo(crispX, frame.plot.bottom);\n context.stroke();\n\n if (this.sync.crosshair === \"full\" && current.yRatio !== null) {\n const y = frame.plot.top + current.yRatio * frame.plot.height;\n const crispY = Math.round(y) + 0.5;\n context.beginPath();\n context.moveTo(frame.plot.left, crispY);\n context.lineTo(frame.plot.right, crispY);\n context.stroke();\n }\n\n context.restore();\n }\n\n private disposeSlot(entry: InternalSlot): void {\n entry.slot.element.removeEventListener(\"pointerdown\", entry.onPointerDown, true);\n entry.slot.element.removeEventListener(\"focusin\", entry.onFocusIn);\n for (const unsubscribe of entry.unsubscribe) {\n unsubscribe();\n }\n entry.overlay?.remove();\n entry.overlay = null;\n this.crosshairOverlays.delete(entry.slot.id);\n entry.slot.chart.destroy();\n entry.slot.element.remove();\n }\n\n private assertAlive(): void {\n if (this.destroyed) {\n throw new WickError(\"DISPOSED\", \"Chart layout is disposed\");\n }\n }\n}\n\nfunction resolveSyncOptions(options: ChartLayoutOptions): ChartLayoutSyncOptions {\n return {\n timeRange: options.sync?.timeRange ?? options.syncTimeRange ?? true,\n crosshair: normalizeCrosshair(options.sync?.crosshair ?? options.syncCrosshair ?? \"time\"),\n };\n}\n\nfunction normalizeCrosshair(\n value: boolean | ChartLayoutCrosshairMode | undefined,\n): ChartLayoutCrosshairMode {\n if (value === undefined || value === true || value === \"time\") {\n return \"time\";\n }\n if (value === false || value === \"off\") {\n return \"off\";\n }\n if (value === \"full\") {\n return \"full\";\n }\n throw new WickError(\"INVALID_ARGUMENT\", \"crosshair must be off, time, or full\");\n}\n\nfunction mergeChartOptions(\n base: ChartOptions | undefined,\n override: ChartOptions | undefined,\n): ChartOptions | undefined {\n if (!base) {\n return override;\n }\n if (!override) {\n return base;\n }\n return { ...base, ...override };\n}\n\nfunction validateColumns(columns: number): number {\n if (!Number.isInteger(columns) || columns < 1 || columns > MAX_COLUMNS) {\n throw new WickError(\"INVALID_ARGUMENT\", `columns must be an integer from 1 to ${MAX_COLUMNS}`);\n }\n return columns;\n}\n\nfunction validateGap(gap: number): number {\n if (!Number.isFinite(gap) || gap < 0) {\n throw new WickError(\"INVALID_ARGUMENT\", \"gap must be a non-negative finite number\");\n }\n return gap;\n}\n\nfunction validateSpan(value: number, name: string): number {\n if (!Number.isInteger(value) || value < 1) {\n throw new WickError(\"INVALID_ARGUMENT\", `${name} must be a positive integer`);\n }\n return value;\n}\n\nfunction validateSyncGroup(value: string): string {\n if (typeof value !== \"string\" || value.trim() === \"\") {\n throw new WickError(\"INVALID_ARGUMENT\", \"syncGroup must be a non-empty string\");\n }\n return value;\n}\n"]}
package/dist/index.d.cts CHANGED
@@ -4,6 +4,31 @@ import { ChartOptions, ChartApi, Unsubscribe } from '@wicklib/core';
4
4
  * Free multi-chart layout primitives for Wicklib.
5
5
  */
6
6
 
7
+ /**
8
+ * Crosshair synchronization mode.
9
+ *
10
+ * `time` mirrors the vertical time line. `full` also mirrors a horizontal line at the same
11
+ * relative pane height.
12
+ */
13
+ type ChartLayoutCrosshairMode = "off" | "time" | "full";
14
+ /**
15
+ * Resolved synchronization behavior for a layout.
16
+ */
17
+ interface ChartLayoutSyncOptions {
18
+ /** Synchronize visible time ranges. */
19
+ timeRange: boolean;
20
+ /** Crosshair synchronization mode. */
21
+ crosshair: ChartLayoutCrosshairMode;
22
+ }
23
+ /**
24
+ * Partial synchronization update. Boolean crosshair values map to `time` and `off`.
25
+ */
26
+ interface ChartLayoutSyncUpdate {
27
+ /** Synchronize visible time ranges. */
28
+ timeRange?: boolean;
29
+ /** Crosshair synchronization mode. */
30
+ crosshair?: boolean | ChartLayoutCrosshairMode;
31
+ }
7
32
  /**
8
33
  * Options for a layout chart cell.
9
34
  */
@@ -16,6 +41,8 @@ interface ChartLayoutSlotOptions {
16
41
  rowSpan?: number;
17
42
  /** Number of grid columns covered by the cell. Defaults to 1. */
18
43
  columnSpan?: number;
44
+ /** Synchronization group. Slots only sync with slots in the same group. */
45
+ syncGroup?: string;
19
46
  }
20
47
  /**
21
48
  * One chart cell in a layout.
@@ -26,6 +53,7 @@ interface ChartLayoutSlot {
26
53
  readonly element: HTMLElement;
27
54
  readonly rowSpan: number;
28
55
  readonly columnSpan: number;
56
+ readonly syncGroup: string;
29
57
  }
30
58
  /**
31
59
  * Serializable structural layout state. Chart state is owned by the host.
@@ -34,10 +62,12 @@ interface ChartLayoutSnapshot {
34
62
  version: 1;
35
63
  columns: number;
36
64
  gap: number;
65
+ sync: ChartLayoutSyncOptions;
37
66
  slots: ReadonlyArray<{
38
67
  id: string;
39
68
  rowSpan: number;
40
69
  columnSpan: number;
70
+ syncGroup: string;
41
71
  }>;
42
72
  }
43
73
  /**
@@ -50,10 +80,12 @@ interface ChartLayoutOptions {
50
80
  gap?: number;
51
81
  /** Default chart options applied to every slot. */
52
82
  chartOptions?: ChartOptions;
83
+ /** Synchronization options. */
84
+ sync?: ChartLayoutSyncUpdate;
53
85
  /** Synchronize visible time ranges. Defaults to true. */
54
86
  syncTimeRange?: boolean;
55
- /** Synchronize the vertical crosshair time line. Defaults to true. */
56
- syncCrosshair?: boolean;
87
+ /** Crosshair synchronization. Defaults to `time`. */
88
+ syncCrosshair?: boolean | ChartLayoutCrosshairMode;
57
89
  /** Crosshair line colour. */
58
90
  crosshairColor?: string;
59
91
  /** Extra class name on the layout root. */
@@ -81,13 +113,17 @@ interface ChartLayoutApi {
81
113
  setColumns(columns: number): void;
82
114
  /** Changes the cell gap in CSS pixels. */
83
115
  setGap(gap: number): void;
116
+ /** Changes synchronization behavior without recreating charts. */
117
+ setSyncOptions(options: ChartLayoutSyncUpdate): void;
118
+ /** Returns the resolved synchronization behavior. */
119
+ getSyncOptions(): Readonly<ChartLayoutSyncOptions>;
84
120
  /** Marks one chart as active. Pass null to clear. */
85
121
  setActiveChart(id: string | null): void;
86
122
  /** Returns the active chart id, or null. */
87
123
  activeChartId(): string | null;
88
124
  /** Subscribes to active chart changes. */
89
125
  onActiveChange(handler: (chartId: string | null) => void): Unsubscribe;
90
- /** Applies the active chart's visible range to every other chart. */
126
+ /** Applies the active chart's visible range to every chart in its sync group. */
91
127
  syncNow(): void;
92
128
  /** Returns structural layout state for host persistence. */
93
129
  serialize(): ChartLayoutSnapshot;
@@ -98,9 +134,9 @@ interface ChartLayoutApi {
98
134
  * Creates a framework-agnostic grid of Wicklib charts.
99
135
  *
100
136
  * The layout owns chart creation, grid placement, time-range synchronization, and a
101
- * mirrored crosshair line. It does not own data, symbol state, accounts, or persistence.
137
+ * mirrored crosshair. It does not own data, symbol state, accounts, or persistence.
102
138
  */
103
139
  declare function createChartLayout(container: HTMLElement, options?: ChartLayoutOptions): ChartLayoutApi;
104
140
 
105
141
  export { createChartLayout };
106
- export type { ChartLayoutApi, ChartLayoutOptions, ChartLayoutSlot, ChartLayoutSlotOptions, ChartLayoutSnapshot };
142
+ export type { ChartLayoutApi, ChartLayoutCrosshairMode, ChartLayoutOptions, ChartLayoutSlot, ChartLayoutSlotOptions, ChartLayoutSnapshot, ChartLayoutSyncOptions, ChartLayoutSyncUpdate };
package/dist/index.d.ts CHANGED
@@ -4,6 +4,31 @@ import { ChartOptions, ChartApi, Unsubscribe } from '@wicklib/core';
4
4
  * Free multi-chart layout primitives for Wicklib.
5
5
  */
6
6
 
7
+ /**
8
+ * Crosshair synchronization mode.
9
+ *
10
+ * `time` mirrors the vertical time line. `full` also mirrors a horizontal line at the same
11
+ * relative pane height.
12
+ */
13
+ type ChartLayoutCrosshairMode = "off" | "time" | "full";
14
+ /**
15
+ * Resolved synchronization behavior for a layout.
16
+ */
17
+ interface ChartLayoutSyncOptions {
18
+ /** Synchronize visible time ranges. */
19
+ timeRange: boolean;
20
+ /** Crosshair synchronization mode. */
21
+ crosshair: ChartLayoutCrosshairMode;
22
+ }
23
+ /**
24
+ * Partial synchronization update. Boolean crosshair values map to `time` and `off`.
25
+ */
26
+ interface ChartLayoutSyncUpdate {
27
+ /** Synchronize visible time ranges. */
28
+ timeRange?: boolean;
29
+ /** Crosshair synchronization mode. */
30
+ crosshair?: boolean | ChartLayoutCrosshairMode;
31
+ }
7
32
  /**
8
33
  * Options for a layout chart cell.
9
34
  */
@@ -16,6 +41,8 @@ interface ChartLayoutSlotOptions {
16
41
  rowSpan?: number;
17
42
  /** Number of grid columns covered by the cell. Defaults to 1. */
18
43
  columnSpan?: number;
44
+ /** Synchronization group. Slots only sync with slots in the same group. */
45
+ syncGroup?: string;
19
46
  }
20
47
  /**
21
48
  * One chart cell in a layout.
@@ -26,6 +53,7 @@ interface ChartLayoutSlot {
26
53
  readonly element: HTMLElement;
27
54
  readonly rowSpan: number;
28
55
  readonly columnSpan: number;
56
+ readonly syncGroup: string;
29
57
  }
30
58
  /**
31
59
  * Serializable structural layout state. Chart state is owned by the host.
@@ -34,10 +62,12 @@ interface ChartLayoutSnapshot {
34
62
  version: 1;
35
63
  columns: number;
36
64
  gap: number;
65
+ sync: ChartLayoutSyncOptions;
37
66
  slots: ReadonlyArray<{
38
67
  id: string;
39
68
  rowSpan: number;
40
69
  columnSpan: number;
70
+ syncGroup: string;
41
71
  }>;
42
72
  }
43
73
  /**
@@ -50,10 +80,12 @@ interface ChartLayoutOptions {
50
80
  gap?: number;
51
81
  /** Default chart options applied to every slot. */
52
82
  chartOptions?: ChartOptions;
83
+ /** Synchronization options. */
84
+ sync?: ChartLayoutSyncUpdate;
53
85
  /** Synchronize visible time ranges. Defaults to true. */
54
86
  syncTimeRange?: boolean;
55
- /** Synchronize the vertical crosshair time line. Defaults to true. */
56
- syncCrosshair?: boolean;
87
+ /** Crosshair synchronization. Defaults to `time`. */
88
+ syncCrosshair?: boolean | ChartLayoutCrosshairMode;
57
89
  /** Crosshair line colour. */
58
90
  crosshairColor?: string;
59
91
  /** Extra class name on the layout root. */
@@ -81,13 +113,17 @@ interface ChartLayoutApi {
81
113
  setColumns(columns: number): void;
82
114
  /** Changes the cell gap in CSS pixels. */
83
115
  setGap(gap: number): void;
116
+ /** Changes synchronization behavior without recreating charts. */
117
+ setSyncOptions(options: ChartLayoutSyncUpdate): void;
118
+ /** Returns the resolved synchronization behavior. */
119
+ getSyncOptions(): Readonly<ChartLayoutSyncOptions>;
84
120
  /** Marks one chart as active. Pass null to clear. */
85
121
  setActiveChart(id: string | null): void;
86
122
  /** Returns the active chart id, or null. */
87
123
  activeChartId(): string | null;
88
124
  /** Subscribes to active chart changes. */
89
125
  onActiveChange(handler: (chartId: string | null) => void): Unsubscribe;
90
- /** Applies the active chart's visible range to every other chart. */
126
+ /** Applies the active chart's visible range to every chart in its sync group. */
91
127
  syncNow(): void;
92
128
  /** Returns structural layout state for host persistence. */
93
129
  serialize(): ChartLayoutSnapshot;
@@ -98,9 +134,9 @@ interface ChartLayoutApi {
98
134
  * Creates a framework-agnostic grid of Wicklib charts.
99
135
  *
100
136
  * The layout owns chart creation, grid placement, time-range synchronization, and a
101
- * mirrored crosshair line. It does not own data, symbol state, accounts, or persistence.
137
+ * mirrored crosshair. It does not own data, symbol state, accounts, or persistence.
102
138
  */
103
139
  declare function createChartLayout(container: HTMLElement, options?: ChartLayoutOptions): ChartLayoutApi;
104
140
 
105
141
  export { createChartLayout };
106
- export type { ChartLayoutApi, ChartLayoutOptions, ChartLayoutSlot, ChartLayoutSlotOptions, ChartLayoutSnapshot };
142
+ export type { ChartLayoutApi, ChartLayoutCrosshairMode, ChartLayoutOptions, ChartLayoutSlot, ChartLayoutSlotOptions, ChartLayoutSnapshot, ChartLayoutSyncOptions, ChartLayoutSyncUpdate };
package/dist/index.js CHANGED
@@ -3,6 +3,7 @@ import { WickError, createChart } from '@wicklib/core';
3
3
  // src/index.ts
4
4
  var DEFAULT_COLUMNS = 2;
5
5
  var DEFAULT_GAP = 1;
6
+ var DEFAULT_SYNC_GROUP = "default";
6
7
  var MAX_COLUMNS = 12;
7
8
  var DEFAULT_CROSSHAIR_COLOR = "rgba(139, 149, 167, 0.6)";
8
9
  function createChartLayout(container, options = {}) {
@@ -14,11 +15,10 @@ var ChartLayout = class {
14
15
  activeHandlers = /* @__PURE__ */ new Set();
15
16
  crosshairOverlays = /* @__PURE__ */ new Map();
16
17
  chartOptions;
17
- syncTimeRange;
18
- syncCrosshair;
19
18
  crosshairColor;
20
19
  columns;
21
20
  gap;
21
+ sync;
22
22
  activeId = null;
23
23
  counter = 0;
24
24
  applyingRange = false;
@@ -31,8 +31,7 @@ var ChartLayout = class {
31
31
  this.columns = validateColumns(options.columns ?? DEFAULT_COLUMNS);
32
32
  this.gap = validateGap(options.gap ?? DEFAULT_GAP);
33
33
  this.chartOptions = options.chartOptions;
34
- this.syncTimeRange = options.syncTimeRange ?? true;
35
- this.syncCrosshair = options.syncCrosshair ?? true;
34
+ this.sync = resolveSyncOptions(options);
36
35
  this.crosshairColor = options.crosshairColor ?? DEFAULT_CROSSHAIR_COLOR;
37
36
  if (options.onActiveChange) {
38
37
  this.activeHandlers.add(options.onActiveChange);
@@ -59,6 +58,7 @@ var ChartLayout = class {
59
58
  }
60
59
  const rowSpan = validateSpan(options.rowSpan ?? 1, "rowSpan");
61
60
  const columnSpan = validateSpan(options.columnSpan ?? 1, "columnSpan");
61
+ const syncGroup = validateSyncGroup(options.syncGroup ?? DEFAULT_SYNC_GROUP);
62
62
  const element = document.createElement("div");
63
63
  element.className = "wicklib-chart-layout__cell";
64
64
  element.dataset.chartId = id;
@@ -86,50 +86,23 @@ var ChartLayout = class {
86
86
  chart.on("visible-range", (event) => this.handleVisibleRange(id, event)),
87
87
  chart.on("crosshair", (event) => this.handleCrosshair(id, event))
88
88
  ];
89
- let overlay = null;
90
- if (this.syncCrosshair) {
91
- overlay = chart.addOverlay({
92
- id: "wicklib-layout-crosshair",
93
- draw: (context, frame) => {
94
- const current = this.crosshair;
95
- if (!current || current.sourceId === id) {
96
- return;
97
- }
98
- const x = frame.timeToX(current.time);
99
- if (x === null || x < frame.plot.left || x > frame.plot.right) {
100
- return;
101
- }
102
- const crispX = Math.round(x) + 0.5;
103
- context.save();
104
- context.beginPath();
105
- context.rect(frame.plot.left, frame.plot.top, frame.plot.width, frame.plot.height);
106
- context.clip();
107
- context.strokeStyle = this.crosshairColor;
108
- context.lineWidth = 1;
109
- context.setLineDash([3, 4]);
110
- context.beginPath();
111
- context.moveTo(crispX, frame.plot.top);
112
- context.lineTo(crispX, frame.plot.bottom);
113
- context.stroke();
114
- context.restore();
115
- }
116
- });
117
- this.crosshairOverlays.set(id, overlay);
118
- }
119
89
  const slot = {
120
90
  id,
121
91
  chart,
122
92
  element,
123
93
  rowSpan,
124
- columnSpan
94
+ columnSpan,
95
+ syncGroup
125
96
  };
126
- this.slots.push({
97
+ const entry = {
127
98
  slot,
128
99
  unsubscribe: unsubscribes,
129
- overlay,
100
+ overlay: null,
130
101
  onPointerDown,
131
102
  onFocusIn
132
- });
103
+ };
104
+ this.slots.push(entry);
105
+ this.ensureCrosshairOverlay(entry);
133
106
  if (this.activeId === null) {
134
107
  this.setActiveChart(id);
135
108
  }
@@ -174,6 +147,28 @@ var ChartLayout = class {
174
147
  this.gap = validateGap(gap);
175
148
  this.element.style.gap = `${this.gap}px`;
176
149
  }
150
+ setSyncOptions(options) {
151
+ this.assertAlive();
152
+ const next = {
153
+ timeRange: options.timeRange ?? this.sync.timeRange,
154
+ crosshair: options.crosshair === void 0 ? this.sync.crosshair : normalizeCrosshair(options.crosshair)
155
+ };
156
+ if (next.timeRange === this.sync.timeRange && next.crosshair === this.sync.crosshair) {
157
+ return;
158
+ }
159
+ this.sync = next;
160
+ for (const entry of this.slots) {
161
+ this.ensureCrosshairOverlay(entry);
162
+ }
163
+ if (next.crosshair === "off" && this.crosshair !== null) {
164
+ this.crosshair = null;
165
+ }
166
+ this.requestCrosshairRender();
167
+ }
168
+ getSyncOptions() {
169
+ this.assertAlive();
170
+ return { ...this.sync };
171
+ }
177
172
  setActiveChart(id) {
178
173
  this.assertAlive();
179
174
  if (id !== null && !this.findSlot(id)) {
@@ -213,7 +208,11 @@ var ChartLayout = class {
213
208
  if (!source) {
214
209
  return;
215
210
  }
216
- this.applyRange(source.slot.id, source.slot.chart.timeScale().getVisibleRange());
211
+ this.applyRange(
212
+ source.slot.id,
213
+ source.slot.syncGroup,
214
+ source.slot.chart.timeScale().getVisibleRange()
215
+ );
217
216
  }
218
217
  serialize() {
219
218
  this.assertAlive();
@@ -221,10 +220,12 @@ var ChartLayout = class {
221
220
  version: 1,
222
221
  columns: this.columns,
223
222
  gap: this.gap,
223
+ sync: { ...this.sync },
224
224
  slots: this.slots.map((entry) => ({
225
225
  id: entry.slot.id,
226
226
  rowSpan: entry.slot.rowSpan,
227
- columnSpan: entry.slot.columnSpan
227
+ columnSpan: entry.slot.columnSpan,
228
+ syncGroup: entry.slot.syncGroup
228
229
  }))
229
230
  };
230
231
  }
@@ -251,19 +252,23 @@ var ChartLayout = class {
251
252
  return this.slots.find((entry) => entry.slot.id === id);
252
253
  }
253
254
  handleVisibleRange(sourceId, event) {
254
- if (!this.syncTimeRange || this.applyingRange) {
255
+ if (!this.sync.timeRange || this.applyingRange) {
255
256
  return;
256
257
  }
257
- this.applyRange(sourceId, { from: event.from, to: event.to });
258
+ const source = this.findSlot(sourceId);
259
+ if (!source) {
260
+ return;
261
+ }
262
+ this.applyRange(sourceId, source.slot.syncGroup, { from: event.from, to: event.to });
258
263
  }
259
- applyRange(sourceId, range) {
264
+ applyRange(sourceId, syncGroup, range) {
260
265
  if (!Number.isFinite(range.from) || !Number.isFinite(range.to) || range.to <= range.from) {
261
266
  return;
262
267
  }
263
268
  this.applyingRange = true;
264
269
  try {
265
270
  for (const entry of this.slots) {
266
- if (entry.slot.id === sourceId) {
271
+ if (entry.slot.id === sourceId || entry.slot.syncGroup !== syncGroup) {
267
272
  continue;
268
273
  }
269
274
  try {
@@ -276,7 +281,7 @@ var ChartLayout = class {
276
281
  }
277
282
  }
278
283
  handleCrosshair(sourceId, event) {
279
- if (!this.syncCrosshair) {
284
+ if (this.sync.crosshair === "off") {
280
285
  return;
281
286
  }
282
287
  if (event.time === null || event.point === null) {
@@ -286,7 +291,18 @@ var ChartLayout = class {
286
291
  }
287
292
  return;
288
293
  }
289
- this.crosshair = { sourceId, time: event.time };
294
+ const source = this.findSlot(sourceId);
295
+ if (!source) {
296
+ return;
297
+ }
298
+ const height = source.slot.element.clientHeight;
299
+ const yRatio = height > 0 ? Math.min(1, Math.max(0, event.point.y / height)) : null;
300
+ this.crosshair = {
301
+ sourceId,
302
+ sourceGroup: source.slot.syncGroup,
303
+ time: event.time,
304
+ yRatio
305
+ };
290
306
  this.requestCrosshairRender();
291
307
  }
292
308
  requestCrosshairRender() {
@@ -294,6 +310,57 @@ var ChartLayout = class {
294
310
  overlay.requestRender();
295
311
  }
296
312
  }
313
+ ensureCrosshairOverlay(entry) {
314
+ if (this.sync.crosshair === "off") {
315
+ if (entry.overlay) {
316
+ entry.overlay.remove();
317
+ entry.overlay = null;
318
+ this.crosshairOverlays.delete(entry.slot.id);
319
+ }
320
+ return;
321
+ }
322
+ if (entry.overlay) {
323
+ return;
324
+ }
325
+ const id = entry.slot.id;
326
+ const group = entry.slot.syncGroup;
327
+ entry.overlay = entry.slot.chart.addOverlay({
328
+ id: "wicklib-layout-crosshair",
329
+ draw: (context, frame) => this.drawCrosshair(context, frame, id, group)
330
+ });
331
+ this.crosshairOverlays.set(id, entry.overlay);
332
+ }
333
+ drawCrosshair(context, frame, id, group) {
334
+ const current = this.crosshair;
335
+ if (!current || current.sourceId === id || current.sourceGroup !== group || this.sync.crosshair === "off") {
336
+ return;
337
+ }
338
+ const x = frame.timeToX(current.time);
339
+ if (x === null || x < frame.plot.left || x > frame.plot.right) {
340
+ return;
341
+ }
342
+ context.save();
343
+ context.beginPath();
344
+ context.rect(frame.plot.left, frame.plot.top, frame.plot.width, frame.plot.height);
345
+ context.clip();
346
+ context.strokeStyle = this.crosshairColor;
347
+ context.lineWidth = 1;
348
+ context.setLineDash([3, 4]);
349
+ const crispX = Math.round(x) + 0.5;
350
+ context.beginPath();
351
+ context.moveTo(crispX, frame.plot.top);
352
+ context.lineTo(crispX, frame.plot.bottom);
353
+ context.stroke();
354
+ if (this.sync.crosshair === "full" && current.yRatio !== null) {
355
+ const y = frame.plot.top + current.yRatio * frame.plot.height;
356
+ const crispY = Math.round(y) + 0.5;
357
+ context.beginPath();
358
+ context.moveTo(frame.plot.left, crispY);
359
+ context.lineTo(frame.plot.right, crispY);
360
+ context.stroke();
361
+ }
362
+ context.restore();
363
+ }
297
364
  disposeSlot(entry) {
298
365
  entry.slot.element.removeEventListener("pointerdown", entry.onPointerDown, true);
299
366
  entry.slot.element.removeEventListener("focusin", entry.onFocusIn);
@@ -301,6 +368,7 @@ var ChartLayout = class {
301
368
  unsubscribe();
302
369
  }
303
370
  entry.overlay?.remove();
371
+ entry.overlay = null;
304
372
  this.crosshairOverlays.delete(entry.slot.id);
305
373
  entry.slot.chart.destroy();
306
374
  entry.slot.element.remove();
@@ -311,6 +379,24 @@ var ChartLayout = class {
311
379
  }
312
380
  }
313
381
  };
382
+ function resolveSyncOptions(options) {
383
+ return {
384
+ timeRange: options.sync?.timeRange ?? options.syncTimeRange ?? true,
385
+ crosshair: normalizeCrosshair(options.sync?.crosshair ?? options.syncCrosshair ?? "time")
386
+ };
387
+ }
388
+ function normalizeCrosshair(value) {
389
+ if (value === void 0 || value === true || value === "time") {
390
+ return "time";
391
+ }
392
+ if (value === false || value === "off") {
393
+ return "off";
394
+ }
395
+ if (value === "full") {
396
+ return "full";
397
+ }
398
+ throw new WickError("INVALID_ARGUMENT", "crosshair must be off, time, or full");
399
+ }
314
400
  function mergeChartOptions(base, override) {
315
401
  if (!base) {
316
402
  return override;
@@ -338,6 +424,12 @@ function validateSpan(value, name) {
338
424
  }
339
425
  return value;
340
426
  }
427
+ function validateSyncGroup(value) {
428
+ if (typeof value !== "string" || value.trim() === "") {
429
+ throw new WickError("INVALID_ARGUMENT", "syncGroup must be a non-empty string");
430
+ }
431
+ return value;
432
+ }
341
433
 
342
434
  export { createChartLayout };
343
435
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts"],"names":["entry"],"mappings":"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","file":"index.js","sourcesContent":["/**\n * Free multi-chart layout primitives for Wicklib.\n */\n\nimport {\n WickError,\n createChart,\n type ChartApi,\n type ChartOptions,\n type ChartOverlayApi,\n type CrosshairEvent,\n type Unsubscribe,\n type VisibleRangeEvent,\n} from \"@wicklib/core\";\n\n/**\n * Options for a layout chart cell.\n */\nexport interface ChartLayoutSlotOptions {\n /** Stable slot id. Generated when omitted. */\n id?: string;\n /** Chart options for this slot. Merged over the layout defaults. */\n chartOptions?: ChartOptions;\n /** Number of grid rows covered by the cell. Defaults to 1. */\n rowSpan?: number;\n /** Number of grid columns covered by the cell. Defaults to 1. */\n columnSpan?: number;\n}\n\n/**\n * One chart cell in a layout.\n */\nexport interface ChartLayoutSlot {\n readonly id: string;\n readonly chart: ChartApi;\n readonly element: HTMLElement;\n readonly rowSpan: number;\n readonly columnSpan: number;\n}\n\n/**\n * Serializable structural layout state. Chart state is owned by the host.\n */\nexport interface ChartLayoutSnapshot {\n version: 1;\n columns: number;\n gap: number;\n slots: ReadonlyArray<{\n id: string;\n rowSpan: number;\n columnSpan: number;\n }>;\n}\n\n/**\n * Options for a multi-chart layout.\n */\nexport interface ChartLayoutOptions {\n /** Number of grid columns. Defaults to 2. */\n columns?: number;\n /** Gap between cells in CSS pixels. Defaults to 1. */\n gap?: number;\n /** Default chart options applied to every slot. */\n chartOptions?: ChartOptions;\n /** Synchronize visible time ranges. Defaults to true. */\n syncTimeRange?: boolean;\n /** Synchronize the vertical crosshair time line. Defaults to true. */\n syncCrosshair?: boolean;\n /** Crosshair line colour. */\n crosshairColor?: string;\n /** Extra class name on the layout root. */\n className?: string;\n /** Called when the active chart changes. */\n onActiveChange?: (chartId: string | null) => void;\n}\n\n/**\n * A live multi-chart layout.\n */\nexport interface ChartLayoutApi {\n /** Layout root inserted into the host container. */\n readonly element: HTMLElement;\n /** Adds a chart cell and returns its live slot. */\n addChart(options?: ChartLayoutSlotOptions): ChartLayoutSlot;\n /** Destroys and removes one chart cell. */\n removeChart(id: string): void;\n /** Returns a live chart, or null when the id is unknown. */\n getChart(id: string): ChartApi | null;\n /** Returns the current slots in DOM order. */\n getSlots(): readonly ChartLayoutSlot[];\n /** Returns slot ids in DOM order. */\n chartIds(): readonly string[];\n /** Changes the grid column count. */\n setColumns(columns: number): void;\n /** Changes the cell gap in CSS pixels. */\n setGap(gap: number): void;\n /** Marks one chart as active. Pass null to clear. */\n setActiveChart(id: string | null): void;\n /** Returns the active chart id, or null. */\n activeChartId(): string | null;\n /** Subscribes to active chart changes. */\n onActiveChange(handler: (chartId: string | null) => void): Unsubscribe;\n /** Applies the active chart's visible range to every other chart. */\n syncNow(): void;\n /** Returns structural layout state for host persistence. */\n serialize(): ChartLayoutSnapshot;\n /** Destroys every chart and removes the layout root. */\n destroy(): void;\n}\n\ninterface InternalSlot {\n readonly slot: ChartLayoutSlot;\n readonly unsubscribe: readonly Unsubscribe[];\n readonly overlay: ChartOverlayApi | null;\n readonly onPointerDown: () => void;\n readonly onFocusIn: () => void;\n}\n\nconst DEFAULT_COLUMNS = 2;\nconst DEFAULT_GAP = 1;\nconst MAX_COLUMNS = 12;\nconst DEFAULT_CROSSHAIR_COLOR = \"rgba(139, 149, 167, 0.6)\";\n\n/**\n * Creates a framework-agnostic grid of Wicklib charts.\n *\n * The layout owns chart creation, grid placement, time-range synchronization, and a\n * mirrored crosshair line. It does not own data, symbol state, accounts, or persistence.\n */\nexport function createChartLayout(\n container: HTMLElement,\n options: ChartLayoutOptions = {},\n): ChartLayoutApi {\n return new ChartLayout(container, options);\n}\n\nclass ChartLayout implements ChartLayoutApi {\n readonly element: HTMLElement;\n\n private readonly slots: InternalSlot[] = [];\n private readonly activeHandlers = new Set<(chartId: string | null) => void>();\n private readonly crosshairOverlays = new Map<string, ChartOverlayApi>();\n private readonly chartOptions: ChartOptions | undefined;\n private readonly syncTimeRange: boolean;\n private readonly syncCrosshair: boolean;\n private readonly crosshairColor: string;\n private columns: number;\n private gap: number;\n private activeId: string | null = null;\n private counter = 0;\n private applyingRange = false;\n private crosshair: { sourceId: string; time: number } | null = null;\n private destroyed = false;\n\n constructor(container: HTMLElement, options: ChartLayoutOptions) {\n if (!container || typeof container.appendChild !== \"function\") {\n throw new WickError(\"INVALID_ARGUMENT\", \"createChartLayout requires a DOM container\");\n }\n\n this.columns = validateColumns(options.columns ?? DEFAULT_COLUMNS);\n this.gap = validateGap(options.gap ?? DEFAULT_GAP);\n this.chartOptions = options.chartOptions;\n this.syncTimeRange = options.syncTimeRange ?? true;\n this.syncCrosshair = options.syncCrosshair ?? true;\n this.crosshairColor = options.crosshairColor ?? DEFAULT_CROSSHAIR_COLOR;\n if (options.onActiveChange) {\n this.activeHandlers.add(options.onActiveChange);\n }\n\n this.element = document.createElement(\"div\");\n this.element.className = [\"wicklib-chart-layout\", options.className].filter(Boolean).join(\" \");\n Object.assign(this.element.style, {\n display: \"grid\",\n width: \"100%\",\n height: \"100%\",\n minWidth: \"0\",\n minHeight: \"0\",\n gridAutoRows: \"minmax(0, 1fr)\",\n gap: `${this.gap}px`,\n });\n this.applyGrid();\n container.appendChild(this.element);\n }\n\n addChart(options: ChartLayoutSlotOptions = {}): ChartLayoutSlot {\n this.assertAlive();\n const id = options.id ?? `chart-${++this.counter}`;\n if (this.findSlot(id)) {\n throw new WickError(\"INVALID_ARGUMENT\", `Duplicate chart layout id: ${id}`);\n }\n const rowSpan = validateSpan(options.rowSpan ?? 1, \"rowSpan\");\n const columnSpan = validateSpan(options.columnSpan ?? 1, \"columnSpan\");\n\n const element = document.createElement(\"div\");\n element.className = \"wicklib-chart-layout__cell\";\n element.dataset.chartId = id;\n Object.assign(element.style, {\n position: \"relative\",\n minWidth: \"0\",\n minHeight: \"0\",\n overflow: \"hidden\",\n gridRow: `span ${rowSpan}`,\n gridColumn: `span ${columnSpan}`,\n });\n this.element.appendChild(element);\n\n let chart: ChartApi;\n try {\n chart = createChart(element, mergeChartOptions(this.chartOptions, options.chartOptions));\n } catch (error) {\n element.remove();\n throw error;\n }\n\n const onPointerDown = (): void => this.setActiveChart(id);\n const onFocusIn = (): void => this.setActiveChart(id);\n element.addEventListener(\"pointerdown\", onPointerDown, true);\n element.addEventListener(\"focusin\", onFocusIn);\n\n const unsubscribes: Unsubscribe[] = [\n chart.on(\"visible-range\", (event) => this.handleVisibleRange(id, event)),\n chart.on(\"crosshair\", (event) => this.handleCrosshair(id, event)),\n ];\n\n let overlay: ChartOverlayApi | null = null;\n if (this.syncCrosshair) {\n overlay = chart.addOverlay({\n id: \"wicklib-layout-crosshair\",\n draw: (context, frame) => {\n const current = this.crosshair;\n if (!current || current.sourceId === id) {\n return;\n }\n const x = frame.timeToX(current.time);\n if (x === null || x < frame.plot.left || x > frame.plot.right) {\n return;\n }\n const crispX = Math.round(x) + 0.5;\n context.save();\n context.beginPath();\n context.rect(frame.plot.left, frame.plot.top, frame.plot.width, frame.plot.height);\n context.clip();\n context.strokeStyle = this.crosshairColor;\n context.lineWidth = 1;\n context.setLineDash([3, 4]);\n context.beginPath();\n context.moveTo(crispX, frame.plot.top);\n context.lineTo(crispX, frame.plot.bottom);\n context.stroke();\n context.restore();\n },\n });\n this.crosshairOverlays.set(id, overlay);\n }\n\n const slot: ChartLayoutSlot = {\n id,\n chart,\n element,\n rowSpan,\n columnSpan,\n };\n this.slots.push({\n slot,\n unsubscribe: unsubscribes,\n overlay,\n onPointerDown,\n onFocusIn,\n });\n\n if (this.activeId === null) {\n this.setActiveChart(id);\n }\n\n return slot;\n }\n\n removeChart(id: string): void {\n this.assertAlive();\n const index = this.slots.findIndex((entry) => entry.slot.id === id);\n if (index < 0) {\n return;\n }\n const entry = this.slots[index]!;\n this.disposeSlot(entry);\n this.slots.splice(index, 1);\n if (this.activeId === id) {\n this.setActiveChart(null);\n }\n if (this.crosshair?.sourceId === id) {\n this.crosshair = null;\n this.requestCrosshairRender();\n }\n }\n\n getChart(id: string): ChartApi | null {\n this.assertAlive();\n return this.findSlot(id)?.slot.chart ?? null;\n }\n\n getSlots(): readonly ChartLayoutSlot[] {\n this.assertAlive();\n return this.slots.map((entry) => entry.slot);\n }\n\n chartIds(): readonly string[] {\n this.assertAlive();\n return this.slots.map((entry) => entry.slot.id);\n }\n\n setColumns(columns: number): void {\n this.assertAlive();\n this.columns = validateColumns(columns);\n this.applyGrid();\n }\n\n setGap(gap: number): void {\n this.assertAlive();\n this.gap = validateGap(gap);\n this.element.style.gap = `${this.gap}px`;\n }\n\n setActiveChart(id: string | null): void {\n this.assertAlive();\n if (id !== null && !this.findSlot(id)) {\n throw new WickError(\"INVALID_ARGUMENT\", `Unknown chart layout id: ${id}`);\n }\n if (this.activeId === id) {\n return;\n }\n const previous = this.activeId;\n this.activeId = id;\n for (const entry of this.slots) {\n if (entry.slot.id === previous) {\n delete entry.slot.element.dataset.active;\n }\n if (entry.slot.id === id) {\n entry.slot.element.dataset.active = \"true\";\n }\n }\n for (const handler of this.activeHandlers) {\n handler(id);\n }\n }\n\n activeChartId(): string | null {\n this.assertAlive();\n return this.activeId;\n }\n\n onActiveChange(handler: (chartId: string | null) => void): Unsubscribe {\n this.assertAlive();\n this.activeHandlers.add(handler);\n return () => {\n this.activeHandlers.delete(handler);\n };\n }\n\n syncNow(): void {\n this.assertAlive();\n const source = this.findSlot(this.activeId ?? this.slots[0]?.slot.id ?? \"\");\n if (!source) {\n return;\n }\n this.applyRange(source.slot.id, source.slot.chart.timeScale().getVisibleRange());\n }\n\n serialize(): ChartLayoutSnapshot {\n this.assertAlive();\n return {\n version: 1,\n columns: this.columns,\n gap: this.gap,\n slots: this.slots.map((entry) => ({\n id: entry.slot.id,\n rowSpan: entry.slot.rowSpan,\n columnSpan: entry.slot.columnSpan,\n })),\n };\n }\n\n destroy(): void {\n if (this.destroyed) {\n return;\n }\n this.destroyed = true;\n for (const entry of this.slots) {\n this.disposeSlot(entry);\n }\n this.slots.length = 0;\n this.activeHandlers.clear();\n this.crosshairOverlays.clear();\n this.element.remove();\n }\n\n private applyGrid(): void {\n this.element.style.gridTemplateColumns = `repeat(${this.columns}, minmax(0, 1fr))`;\n }\n\n private findSlot(id: string | null): InternalSlot | undefined {\n if (id === null) {\n return undefined;\n }\n return this.slots.find((entry) => entry.slot.id === id);\n }\n\n private handleVisibleRange(sourceId: string, event: VisibleRangeEvent): void {\n if (!this.syncTimeRange || this.applyingRange) {\n return;\n }\n this.applyRange(sourceId, { from: event.from, to: event.to });\n }\n\n private applyRange(sourceId: string, range: { from: number; to: number }): void {\n if (!Number.isFinite(range.from) || !Number.isFinite(range.to) || range.to <= range.from) {\n return;\n }\n this.applyingRange = true;\n try {\n for (const entry of this.slots) {\n if (entry.slot.id === sourceId) {\n continue;\n }\n try {\n entry.slot.chart.timeScale().setVisibleRange(range);\n } catch {\n // A chart with incompatible data can reject the range; leave it unchanged.\n }\n }\n } finally {\n this.applyingRange = false;\n }\n }\n\n private handleCrosshair(sourceId: string, event: CrosshairEvent): void {\n if (!this.syncCrosshair) {\n return;\n }\n if (event.time === null || event.point === null) {\n if (this.crosshair !== null) {\n this.crosshair = null;\n this.requestCrosshairRender();\n }\n return;\n }\n this.crosshair = { sourceId, time: event.time };\n this.requestCrosshairRender();\n }\n\n private requestCrosshairRender(): void {\n for (const overlay of this.crosshairOverlays.values()) {\n overlay.requestRender();\n }\n }\n\n private disposeSlot(entry: InternalSlot): void {\n entry.slot.element.removeEventListener(\"pointerdown\", entry.onPointerDown, true);\n entry.slot.element.removeEventListener(\"focusin\", entry.onFocusIn);\n for (const unsubscribe of entry.unsubscribe) {\n unsubscribe();\n }\n entry.overlay?.remove();\n this.crosshairOverlays.delete(entry.slot.id);\n entry.slot.chart.destroy();\n entry.slot.element.remove();\n }\n\n private assertAlive(): void {\n if (this.destroyed) {\n throw new WickError(\"DISPOSED\", \"Chart layout is disposed\");\n }\n }\n}\n\nfunction mergeChartOptions(\n base: ChartOptions | undefined,\n override: ChartOptions | undefined,\n): ChartOptions | undefined {\n if (!base) {\n return override;\n }\n if (!override) {\n return base;\n }\n return { ...base, ...override };\n}\n\nfunction validateColumns(columns: number): number {\n if (!Number.isInteger(columns) || columns < 1 || columns > MAX_COLUMNS) {\n throw new WickError(\"INVALID_ARGUMENT\", `columns must be an integer from 1 to ${MAX_COLUMNS}`);\n }\n return columns;\n}\n\nfunction validateGap(gap: number): number {\n if (!Number.isFinite(gap) || gap < 0) {\n throw new WickError(\"INVALID_ARGUMENT\", \"gap must be a non-negative finite number\");\n }\n return gap;\n}\n\nfunction validateSpan(value: number, name: string): number {\n if (!Number.isInteger(value) || value < 1) {\n throw new WickError(\"INVALID_ARGUMENT\", `${name} must be a positive integer`);\n }\n return value;\n}\n"]}
1
+ {"version":3,"sources":["../src/index.ts"],"names":["entry"],"mappings":"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","file":"index.js","sourcesContent":["/**\n * Free multi-chart layout primitives for Wicklib.\n */\n\nimport {\n WickError,\n createChart,\n type ChartApi,\n type ChartOptions,\n type ChartOverlayApi,\n type CrosshairEvent,\n type OverlayRenderFrame,\n type Unsubscribe,\n type VisibleRangeEvent,\n} from \"@wicklib/core\";\n\n/**\n * Crosshair synchronization mode.\n *\n * `time` mirrors the vertical time line. `full` also mirrors a horizontal line at the same\n * relative pane height.\n */\nexport type ChartLayoutCrosshairMode = \"off\" | \"time\" | \"full\";\n\n/**\n * Resolved synchronization behavior for a layout.\n */\nexport interface ChartLayoutSyncOptions {\n /** Synchronize visible time ranges. */\n timeRange: boolean;\n /** Crosshair synchronization mode. */\n crosshair: ChartLayoutCrosshairMode;\n}\n\n/**\n * Partial synchronization update. Boolean crosshair values map to `time` and `off`.\n */\nexport interface ChartLayoutSyncUpdate {\n /** Synchronize visible time ranges. */\n timeRange?: boolean;\n /** Crosshair synchronization mode. */\n crosshair?: boolean | ChartLayoutCrosshairMode;\n}\n\n/**\n * Options for a layout chart cell.\n */\nexport interface ChartLayoutSlotOptions {\n /** Stable slot id. Generated when omitted. */\n id?: string;\n /** Chart options for this slot. Merged over the layout defaults. */\n chartOptions?: ChartOptions;\n /** Number of grid rows covered by the cell. Defaults to 1. */\n rowSpan?: number;\n /** Number of grid columns covered by the cell. Defaults to 1. */\n columnSpan?: number;\n /** Synchronization group. Slots only sync with slots in the same group. */\n syncGroup?: string;\n}\n\n/**\n * One chart cell in a layout.\n */\nexport interface ChartLayoutSlot {\n readonly id: string;\n readonly chart: ChartApi;\n readonly element: HTMLElement;\n readonly rowSpan: number;\n readonly columnSpan: number;\n readonly syncGroup: string;\n}\n\n/**\n * Serializable structural layout state. Chart state is owned by the host.\n */\nexport interface ChartLayoutSnapshot {\n version: 1;\n columns: number;\n gap: number;\n sync: ChartLayoutSyncOptions;\n slots: ReadonlyArray<{\n id: string;\n rowSpan: number;\n columnSpan: number;\n syncGroup: string;\n }>;\n}\n\n/**\n * Options for a multi-chart layout.\n */\nexport interface ChartLayoutOptions {\n /** Number of grid columns. Defaults to 2. */\n columns?: number;\n /** Gap between cells in CSS pixels. Defaults to 1. */\n gap?: number;\n /** Default chart options applied to every slot. */\n chartOptions?: ChartOptions;\n /** Synchronization options. */\n sync?: ChartLayoutSyncUpdate;\n /** Synchronize visible time ranges. Defaults to true. */\n syncTimeRange?: boolean;\n /** Crosshair synchronization. Defaults to `time`. */\n syncCrosshair?: boolean | ChartLayoutCrosshairMode;\n /** Crosshair line colour. */\n crosshairColor?: string;\n /** Extra class name on the layout root. */\n className?: string;\n /** Called when the active chart changes. */\n onActiveChange?: (chartId: string | null) => void;\n}\n\n/**\n * A live multi-chart layout.\n */\nexport interface ChartLayoutApi {\n /** Layout root inserted into the host container. */\n readonly element: HTMLElement;\n /** Adds a chart cell and returns its live slot. */\n addChart(options?: ChartLayoutSlotOptions): ChartLayoutSlot;\n /** Destroys and removes one chart cell. */\n removeChart(id: string): void;\n /** Returns a live chart, or null when the id is unknown. */\n getChart(id: string): ChartApi | null;\n /** Returns the current slots in DOM order. */\n getSlots(): readonly ChartLayoutSlot[];\n /** Returns slot ids in DOM order. */\n chartIds(): readonly string[];\n /** Changes the grid column count. */\n setColumns(columns: number): void;\n /** Changes the cell gap in CSS pixels. */\n setGap(gap: number): void;\n /** Changes synchronization behavior without recreating charts. */\n setSyncOptions(options: ChartLayoutSyncUpdate): void;\n /** Returns the resolved synchronization behavior. */\n getSyncOptions(): Readonly<ChartLayoutSyncOptions>;\n /** Marks one chart as active. Pass null to clear. */\n setActiveChart(id: string | null): void;\n /** Returns the active chart id, or null. */\n activeChartId(): string | null;\n /** Subscribes to active chart changes. */\n onActiveChange(handler: (chartId: string | null) => void): Unsubscribe;\n /** Applies the active chart's visible range to every chart in its sync group. */\n syncNow(): void;\n /** Returns structural layout state for host persistence. */\n serialize(): ChartLayoutSnapshot;\n /** Destroys every chart and removes the layout root. */\n destroy(): void;\n}\n\ninterface InternalSlot {\n readonly slot: ChartLayoutSlot;\n readonly unsubscribe: readonly Unsubscribe[];\n overlay: ChartOverlayApi | null;\n readonly onPointerDown: () => void;\n readonly onFocusIn: () => void;\n}\n\ninterface CrosshairState {\n sourceId: string;\n sourceGroup: string;\n time: number;\n yRatio: number | null;\n}\n\nconst DEFAULT_COLUMNS = 2;\nconst DEFAULT_GAP = 1;\nconst DEFAULT_SYNC_GROUP = \"default\";\nconst MAX_COLUMNS = 12;\nconst DEFAULT_CROSSHAIR_COLOR = \"rgba(139, 149, 167, 0.6)\";\n\n/**\n * Creates a framework-agnostic grid of Wicklib charts.\n *\n * The layout owns chart creation, grid placement, time-range synchronization, and a\n * mirrored crosshair. It does not own data, symbol state, accounts, or persistence.\n */\nexport function createChartLayout(\n container: HTMLElement,\n options: ChartLayoutOptions = {},\n): ChartLayoutApi {\n return new ChartLayout(container, options);\n}\n\nclass ChartLayout implements ChartLayoutApi {\n readonly element: HTMLElement;\n\n private readonly slots: InternalSlot[] = [];\n private readonly activeHandlers = new Set<(chartId: string | null) => void>();\n private readonly crosshairOverlays = new Map<string, ChartOverlayApi>();\n private readonly chartOptions: ChartOptions | undefined;\n private readonly crosshairColor: string;\n private columns: number;\n private gap: number;\n private sync: ChartLayoutSyncOptions;\n private activeId: string | null = null;\n private counter = 0;\n private applyingRange = false;\n private crosshair: CrosshairState | null = null;\n private destroyed = false;\n\n constructor(container: HTMLElement, options: ChartLayoutOptions) {\n if (!container || typeof container.appendChild !== \"function\") {\n throw new WickError(\"INVALID_ARGUMENT\", \"createChartLayout requires a DOM container\");\n }\n\n this.columns = validateColumns(options.columns ?? DEFAULT_COLUMNS);\n this.gap = validateGap(options.gap ?? DEFAULT_GAP);\n this.chartOptions = options.chartOptions;\n this.sync = resolveSyncOptions(options);\n this.crosshairColor = options.crosshairColor ?? DEFAULT_CROSSHAIR_COLOR;\n if (options.onActiveChange) {\n this.activeHandlers.add(options.onActiveChange);\n }\n\n this.element = document.createElement(\"div\");\n this.element.className = [\"wicklib-chart-layout\", options.className].filter(Boolean).join(\" \");\n Object.assign(this.element.style, {\n display: \"grid\",\n width: \"100%\",\n height: \"100%\",\n minWidth: \"0\",\n minHeight: \"0\",\n gridAutoRows: \"minmax(0, 1fr)\",\n gap: `${this.gap}px`,\n });\n this.applyGrid();\n container.appendChild(this.element);\n }\n\n addChart(options: ChartLayoutSlotOptions = {}): ChartLayoutSlot {\n this.assertAlive();\n const id = options.id ?? `chart-${++this.counter}`;\n if (this.findSlot(id)) {\n throw new WickError(\"INVALID_ARGUMENT\", `Duplicate chart layout id: ${id}`);\n }\n const rowSpan = validateSpan(options.rowSpan ?? 1, \"rowSpan\");\n const columnSpan = validateSpan(options.columnSpan ?? 1, \"columnSpan\");\n const syncGroup = validateSyncGroup(options.syncGroup ?? DEFAULT_SYNC_GROUP);\n\n const element = document.createElement(\"div\");\n element.className = \"wicklib-chart-layout__cell\";\n element.dataset.chartId = id;\n Object.assign(element.style, {\n position: \"relative\",\n minWidth: \"0\",\n minHeight: \"0\",\n overflow: \"hidden\",\n gridRow: `span ${rowSpan}`,\n gridColumn: `span ${columnSpan}`,\n });\n this.element.appendChild(element);\n\n let chart: ChartApi;\n try {\n chart = createChart(element, mergeChartOptions(this.chartOptions, options.chartOptions));\n } catch (error) {\n element.remove();\n throw error;\n }\n\n const onPointerDown = (): void => this.setActiveChart(id);\n const onFocusIn = (): void => this.setActiveChart(id);\n element.addEventListener(\"pointerdown\", onPointerDown, true);\n element.addEventListener(\"focusin\", onFocusIn);\n\n const unsubscribes: Unsubscribe[] = [\n chart.on(\"visible-range\", (event) => this.handleVisibleRange(id, event)),\n chart.on(\"crosshair\", (event) => this.handleCrosshair(id, event)),\n ];\n\n const slot: ChartLayoutSlot = {\n id,\n chart,\n element,\n rowSpan,\n columnSpan,\n syncGroup,\n };\n const entry: InternalSlot = {\n slot,\n unsubscribe: unsubscribes,\n overlay: null,\n onPointerDown,\n onFocusIn,\n };\n this.slots.push(entry);\n this.ensureCrosshairOverlay(entry);\n\n if (this.activeId === null) {\n this.setActiveChart(id);\n }\n\n return slot;\n }\n\n removeChart(id: string): void {\n this.assertAlive();\n const index = this.slots.findIndex((entry) => entry.slot.id === id);\n if (index < 0) {\n return;\n }\n const entry = this.slots[index]!;\n this.disposeSlot(entry);\n this.slots.splice(index, 1);\n if (this.activeId === id) {\n this.setActiveChart(null);\n }\n if (this.crosshair?.sourceId === id) {\n this.crosshair = null;\n this.requestCrosshairRender();\n }\n }\n\n getChart(id: string): ChartApi | null {\n this.assertAlive();\n return this.findSlot(id)?.slot.chart ?? null;\n }\n\n getSlots(): readonly ChartLayoutSlot[] {\n this.assertAlive();\n return this.slots.map((entry) => entry.slot);\n }\n\n chartIds(): readonly string[] {\n this.assertAlive();\n return this.slots.map((entry) => entry.slot.id);\n }\n\n setColumns(columns: number): void {\n this.assertAlive();\n this.columns = validateColumns(columns);\n this.applyGrid();\n }\n\n setGap(gap: number): void {\n this.assertAlive();\n this.gap = validateGap(gap);\n this.element.style.gap = `${this.gap}px`;\n }\n\n setSyncOptions(options: ChartLayoutSyncUpdate): void {\n this.assertAlive();\n const next: ChartLayoutSyncOptions = {\n timeRange: options.timeRange ?? this.sync.timeRange,\n crosshair:\n options.crosshair === undefined\n ? this.sync.crosshair\n : normalizeCrosshair(options.crosshair),\n };\n if (next.timeRange === this.sync.timeRange && next.crosshair === this.sync.crosshair) {\n return;\n }\n this.sync = next;\n for (const entry of this.slots) {\n this.ensureCrosshairOverlay(entry);\n }\n if (next.crosshair === \"off\" && this.crosshair !== null) {\n this.crosshair = null;\n }\n this.requestCrosshairRender();\n }\n\n getSyncOptions(): Readonly<ChartLayoutSyncOptions> {\n this.assertAlive();\n return { ...this.sync };\n }\n\n setActiveChart(id: string | null): void {\n this.assertAlive();\n if (id !== null && !this.findSlot(id)) {\n throw new WickError(\"INVALID_ARGUMENT\", `Unknown chart layout id: ${id}`);\n }\n if (this.activeId === id) {\n return;\n }\n const previous = this.activeId;\n this.activeId = id;\n for (const entry of this.slots) {\n if (entry.slot.id === previous) {\n delete entry.slot.element.dataset.active;\n }\n if (entry.slot.id === id) {\n entry.slot.element.dataset.active = \"true\";\n }\n }\n for (const handler of this.activeHandlers) {\n handler(id);\n }\n }\n\n activeChartId(): string | null {\n this.assertAlive();\n return this.activeId;\n }\n\n onActiveChange(handler: (chartId: string | null) => void): Unsubscribe {\n this.assertAlive();\n this.activeHandlers.add(handler);\n return () => {\n this.activeHandlers.delete(handler);\n };\n }\n\n syncNow(): void {\n this.assertAlive();\n const source = this.findSlot(this.activeId ?? this.slots[0]?.slot.id ?? \"\");\n if (!source) {\n return;\n }\n this.applyRange(\n source.slot.id,\n source.slot.syncGroup,\n source.slot.chart.timeScale().getVisibleRange(),\n );\n }\n\n serialize(): ChartLayoutSnapshot {\n this.assertAlive();\n return {\n version: 1,\n columns: this.columns,\n gap: this.gap,\n sync: { ...this.sync },\n slots: this.slots.map((entry) => ({\n id: entry.slot.id,\n rowSpan: entry.slot.rowSpan,\n columnSpan: entry.slot.columnSpan,\n syncGroup: entry.slot.syncGroup,\n })),\n };\n }\n\n destroy(): void {\n if (this.destroyed) {\n return;\n }\n this.destroyed = true;\n for (const entry of this.slots) {\n this.disposeSlot(entry);\n }\n this.slots.length = 0;\n this.activeHandlers.clear();\n this.crosshairOverlays.clear();\n this.element.remove();\n }\n\n private applyGrid(): void {\n this.element.style.gridTemplateColumns = `repeat(${this.columns}, minmax(0, 1fr))`;\n }\n\n private findSlot(id: string | null): InternalSlot | undefined {\n if (id === null) {\n return undefined;\n }\n return this.slots.find((entry) => entry.slot.id === id);\n }\n\n private handleVisibleRange(sourceId: string, event: VisibleRangeEvent): void {\n if (!this.sync.timeRange || this.applyingRange) {\n return;\n }\n const source = this.findSlot(sourceId);\n if (!source) {\n return;\n }\n this.applyRange(sourceId, source.slot.syncGroup, { from: event.from, to: event.to });\n }\n\n private applyRange(\n sourceId: string,\n syncGroup: string,\n range: { from: number; to: number },\n ): void {\n if (!Number.isFinite(range.from) || !Number.isFinite(range.to) || range.to <= range.from) {\n return;\n }\n this.applyingRange = true;\n try {\n for (const entry of this.slots) {\n if (entry.slot.id === sourceId || entry.slot.syncGroup !== syncGroup) {\n continue;\n }\n try {\n entry.slot.chart.timeScale().setVisibleRange(range);\n } catch {\n // A chart with incompatible data can reject the range; leave it unchanged.\n }\n }\n } finally {\n this.applyingRange = false;\n }\n }\n\n private handleCrosshair(sourceId: string, event: CrosshairEvent): void {\n if (this.sync.crosshair === \"off\") {\n return;\n }\n if (event.time === null || event.point === null) {\n if (this.crosshair !== null) {\n this.crosshair = null;\n this.requestCrosshairRender();\n }\n return;\n }\n const source = this.findSlot(sourceId);\n if (!source) {\n return;\n }\n const height = source.slot.element.clientHeight;\n const yRatio = height > 0 ? Math.min(1, Math.max(0, event.point.y / height)) : null;\n this.crosshair = {\n sourceId,\n sourceGroup: source.slot.syncGroup,\n time: event.time,\n yRatio,\n };\n this.requestCrosshairRender();\n }\n\n private requestCrosshairRender(): void {\n for (const overlay of this.crosshairOverlays.values()) {\n overlay.requestRender();\n }\n }\n\n private ensureCrosshairOverlay(entry: InternalSlot): void {\n if (this.sync.crosshair === \"off\") {\n if (entry.overlay) {\n entry.overlay.remove();\n entry.overlay = null;\n this.crosshairOverlays.delete(entry.slot.id);\n }\n return;\n }\n if (entry.overlay) {\n return;\n }\n const id = entry.slot.id;\n const group = entry.slot.syncGroup;\n entry.overlay = entry.slot.chart.addOverlay({\n id: \"wicklib-layout-crosshair\",\n draw: (context, frame) => this.drawCrosshair(context, frame, id, group),\n });\n this.crosshairOverlays.set(id, entry.overlay);\n }\n\n private drawCrosshair(\n context: CanvasRenderingContext2D,\n frame: OverlayRenderFrame,\n id: string,\n group: string,\n ): void {\n const current = this.crosshair;\n if (\n !current ||\n current.sourceId === id ||\n current.sourceGroup !== group ||\n this.sync.crosshair === \"off\"\n ) {\n return;\n }\n const x = frame.timeToX(current.time);\n if (x === null || x < frame.plot.left || x > frame.plot.right) {\n return;\n }\n context.save();\n context.beginPath();\n context.rect(frame.plot.left, frame.plot.top, frame.plot.width, frame.plot.height);\n context.clip();\n context.strokeStyle = this.crosshairColor;\n context.lineWidth = 1;\n context.setLineDash([3, 4]);\n\n const crispX = Math.round(x) + 0.5;\n context.beginPath();\n context.moveTo(crispX, frame.plot.top);\n context.lineTo(crispX, frame.plot.bottom);\n context.stroke();\n\n if (this.sync.crosshair === \"full\" && current.yRatio !== null) {\n const y = frame.plot.top + current.yRatio * frame.plot.height;\n const crispY = Math.round(y) + 0.5;\n context.beginPath();\n context.moveTo(frame.plot.left, crispY);\n context.lineTo(frame.plot.right, crispY);\n context.stroke();\n }\n\n context.restore();\n }\n\n private disposeSlot(entry: InternalSlot): void {\n entry.slot.element.removeEventListener(\"pointerdown\", entry.onPointerDown, true);\n entry.slot.element.removeEventListener(\"focusin\", entry.onFocusIn);\n for (const unsubscribe of entry.unsubscribe) {\n unsubscribe();\n }\n entry.overlay?.remove();\n entry.overlay = null;\n this.crosshairOverlays.delete(entry.slot.id);\n entry.slot.chart.destroy();\n entry.slot.element.remove();\n }\n\n private assertAlive(): void {\n if (this.destroyed) {\n throw new WickError(\"DISPOSED\", \"Chart layout is disposed\");\n }\n }\n}\n\nfunction resolveSyncOptions(options: ChartLayoutOptions): ChartLayoutSyncOptions {\n return {\n timeRange: options.sync?.timeRange ?? options.syncTimeRange ?? true,\n crosshair: normalizeCrosshair(options.sync?.crosshair ?? options.syncCrosshair ?? \"time\"),\n };\n}\n\nfunction normalizeCrosshair(\n value: boolean | ChartLayoutCrosshairMode | undefined,\n): ChartLayoutCrosshairMode {\n if (value === undefined || value === true || value === \"time\") {\n return \"time\";\n }\n if (value === false || value === \"off\") {\n return \"off\";\n }\n if (value === \"full\") {\n return \"full\";\n }\n throw new WickError(\"INVALID_ARGUMENT\", \"crosshair must be off, time, or full\");\n}\n\nfunction mergeChartOptions(\n base: ChartOptions | undefined,\n override: ChartOptions | undefined,\n): ChartOptions | undefined {\n if (!base) {\n return override;\n }\n if (!override) {\n return base;\n }\n return { ...base, ...override };\n}\n\nfunction validateColumns(columns: number): number {\n if (!Number.isInteger(columns) || columns < 1 || columns > MAX_COLUMNS) {\n throw new WickError(\"INVALID_ARGUMENT\", `columns must be an integer from 1 to ${MAX_COLUMNS}`);\n }\n return columns;\n}\n\nfunction validateGap(gap: number): number {\n if (!Number.isFinite(gap) || gap < 0) {\n throw new WickError(\"INVALID_ARGUMENT\", \"gap must be a non-negative finite number\");\n }\n return gap;\n}\n\nfunction validateSpan(value: number, name: string): number {\n if (!Number.isInteger(value) || value < 1) {\n throw new WickError(\"INVALID_ARGUMENT\", `${name} must be a positive integer`);\n }\n return value;\n}\n\nfunction validateSyncGroup(value: string): string {\n if (typeof value !== \"string\" || value.trim() === \"\") {\n throw new WickError(\"INVALID_ARGUMENT\", \"syncGroup must be a non-empty string\");\n }\n return value;\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wicklib/layouts",
3
- "version": "0.12.0",
3
+ "version": "0.13.0",
4
4
  "description": "Framework-agnostic multi-chart layouts for Wicklib",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
@@ -37,7 +37,7 @@
37
37
  "directory": "packages/layouts"
38
38
  },
39
39
  "peerDependencies": {
40
- "@wicklib/core": "^0.12.0"
40
+ "@wicklib/core": "^0.13.0"
41
41
  },
42
42
  "devDependencies": {
43
43
  "@microsoft/api-extractor": "^7.59.4",
@@ -45,7 +45,7 @@
45
45
  "tsup": "^9.0.0",
46
46
  "typescript": "^5.9.3",
47
47
  "vitest": "^5.0.5",
48
- "@wicklib/core": "0.12.0"
48
+ "@wicklib/core": "0.13.0"
49
49
  },
50
50
  "scripts": {
51
51
  "build": "tsup",