openalgo-charts 2.5.1 → 2.5.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,4 +1,4 @@
1
- import { AlertDrawingValue, AlertDrawingLevel, PrimitiveRenderContext, IPrimitive, PrimitiveHost, ZOrder, PrimitiveHit, DataLayer, AlertDrawingInfo } from 'openalgo-charts';
1
+ import { AlertDrawingValue, AlertDrawingLevel, PrimitiveRenderContext, Bar, IPrimitive, DataLayer, AlertDrawingInfo, PrimitiveHost, ZOrder, PrimitiveHit } from 'openalgo-charts';
2
2
 
3
3
  /**
4
4
  * The per-tool settings schema: what a host may show in a drawing's settings
@@ -35,6 +35,8 @@ interface SettingsField {
35
35
  label: string;
36
36
  }>;
37
37
  group?: FieldGroup;
38
+ /** Value displayed when the drawing has not overridden this setting. */
39
+ defaultValue?: string | number | boolean;
38
40
  }
39
41
  interface SettingsSchema {
40
42
  fields: SettingsField[];
@@ -111,9 +113,8 @@ declare function composeSettings(parts: ReadonlyArray<readonly SettingsField[] |
111
113
  /** The value at a dot path, or `undefined` when unset or the path is not one the model has. */
112
114
  declare function readDrawingSetting(d: Drawing, path: string): unknown;
113
115
  /**
114
- * Every field of a schema read off a drawing, keyed by path. Absent values
115
- * are present as `undefined`, so a host can iterate the schema and render
116
- * each control in its "default" state rather than guessing.
116
+ * Every field of a schema read off a drawing, keyed by path. Unset fields use
117
+ * their declared default value, or remain `undefined` when no default exists.
117
118
  */
118
119
  declare function readDrawingSettings(d: Drawing, schema: SettingsSchema): Record<string, unknown>;
119
120
  /**
@@ -295,9 +296,16 @@ declare const DRAWING_STATE_VERSION = 2;
295
296
  * What `toJSON` returns and `ChartState.drawings` carries. Versioned so a 1.9.x
296
297
  * save (a bare `Drawing[]`) is recognisable and upgraded rather than misread.
297
298
  */
299
+ /** A named set of drawing ids. A drawing belongs to at most one group. */
300
+ interface DrawingGroup {
301
+ id: string;
302
+ name: string;
303
+ members: string[];
304
+ }
298
305
  interface DrawingsDocument {
299
306
  version: 2;
300
307
  drawings: Drawing[];
308
+ groups?: DrawingGroup[];
301
309
  }
302
310
  /** A point mapped to the pane, in media px. */
303
311
  interface ScreenPoint {
@@ -424,849 +432,296 @@ interface DrawingTool {
424
432
  distance(x: number, y: number, c: HitContext): number | null;
425
433
  }
426
434
 
427
- /**
428
- * Built-in drawing tools + the tool registry. Same philosophy as the chart-type
429
- * and indicator registries: a tool is a descriptor, the layer just runs it, and
430
- * `registerDrawingTool` makes a custom one first-class.
431
- *
432
- * `draw` receives anchors already in device px; `distance` receives them in
433
- * media px, the same space as the incoming cursor.
434
- *
435
- * Text lives in `drawing.text` (a `DrawingText`), never in the style bag: a
436
- * shape's outline colour and its label colour are two decisions, and a tool
437
- * that prints only a readout (a fib ladder, a measure chip) still reads its
438
- * face from there. Ladders read `FibLevel[]` and stroke each level in its own
439
- * colour, taken from the shared palette in levels.ts.
440
- *
441
- * Every tool declares `settings`: the fields a host may show for it. The
442
- * schema is a contract, not a wish list. A field is declared only when this
443
- * file reads it, because a control with nothing behind it is a defect.
444
- */
435
+ /** Time-anchored analysis drawings use the owning pane's bars, never a tier cache. */
445
436
 
446
- declare function registerDrawingTool(tool: DrawingTool): void;
447
- declare function getDrawingTool(id: string): DrawingTool;
448
- declare function hasDrawingTool(id: string): boolean;
449
- declare function registeredDrawingTools(): DrawingTool[];
450
- /** Keyboard event fields a shortcut is matched against. */
451
- interface ShortcutEvent {
452
- key: string;
453
- altKey?: boolean;
454
- ctrlKey?: boolean;
455
- metaKey?: boolean;
456
- shiftKey?: boolean;
437
+ declare const ANCHORED_VWAP: DrawingTool;
438
+ declare const FIXED_RANGE_VOLUME_PROFILE: DrawingTool;
439
+
440
+ /** Pure OHLCV analysis. Inputs are sorted by ascending UTC timestamp. */
441
+
442
+ /** Partial results have unavailable anchor history, missing volume or unusable prices. */
443
+ type AnalysisStatus = 'ready' | 'partial' | 'missing-volume' | 'zero-volume' | 'empty' | 'invalid-data';
444
+ type AnchoredVwapSource = 'hlc3' | 'close' | 'hl2' | 'ohlc4';
445
+ interface AnchoredVwapOptions {
446
+ source?: AnchoredVwapSource;
447
+ }
448
+ interface AnchoredVwapPoint {
449
+ time: number;
450
+ value: number;
451
+ /** Volume-weighted population standard deviation of the selected source. */
452
+ deviation: number;
453
+ /** Do not connect a line across an omitted observation. */
454
+ breakBefore: boolean;
455
+ }
456
+ interface AnchoredVwapResult {
457
+ status: AnalysisStatus;
458
+ /** The requested anchor precedes the earliest supplied bar. */
459
+ historyPartial: boolean;
460
+ points: AnchoredVwapPoint[];
461
+ missingVolumeBars: number;
462
+ invalidPriceBars: number;
463
+ }
464
+ interface FixedRangeVolumeProfileOptions {
465
+ /** Requested price rows, rounded and clamped to 4..200. Fewer rows are used when prices cannot represent distinct edges. Default 48. */
466
+ rows?: number;
467
+ /** Percentage of volume in the contiguous area around the POC, 1..100. Default 70. */
468
+ valueArea?: number;
469
+ }
470
+ interface AnalysisProfileRow {
471
+ low: number;
472
+ high: number;
473
+ volume: number;
474
+ valueArea: boolean;
475
+ }
476
+ interface FixedRangeVolumeProfileResult {
477
+ status: AnalysisStatus;
478
+ /** The requested range begins before the earliest supplied bar. */
479
+ historyPartial: boolean;
480
+ rows: AnalysisProfileRow[];
481
+ totalVolume: number;
482
+ /** Midpoint of the largest row. Ties choose the lower price. */
483
+ poc: number | null;
484
+ valueAreaLow: number | null;
485
+ valueAreaHigh: number | null;
486
+ missingVolumeBars: number;
487
+ invalidPriceBars: number;
457
488
  }
458
489
  /**
459
- * The id of the tool whose `shortcut` matches this key event, or `null`.
460
- *
461
- * Pure, so a host can bind one `keydown` listener and decide for itself when
462
- * shortcuts apply. The library installs no listener, because only the host
463
- * knows whether the chart has focus, a dialog is open, or the user is typing.
464
- *
465
- * Modifiers must match exactly: `Alt+T` will not fire for `Ctrl+Alt+T`, so a
466
- * tool shortcut cannot shadow a browser or host chord. `metaKey` (Cmd) is
467
- * treated as Ctrl, which is what a Mac user expects.
468
- */
469
- declare function matchDrawingShortcut(e: ShortcutEvent): string | null;
470
- /** Every registered tool that has a shortcut, as `id -> shortcut`. */
471
- declare function drawingShortcuts(): Record<string, string>;
472
- /**
473
- * The settings a host may show for a tool: the tool's own declaration, else
474
- * the line fields, which every stroked custom tool reads. This is a registry
475
- * lookup, which is why it lives here and not in schema.ts: that module stays
476
- * free of any import that could loop back into this one.
477
- */
478
- declare function drawingSettingsSchema(toolId: string): SettingsSchema;
479
- declare const TREND_LINE: DrawingTool;
480
- declare const RAY: DrawingTool;
481
- declare const EXTENDED_LINE: DrawingTool;
482
- declare const ARROW: DrawingTool;
483
- declare const HORIZONTAL_LINE: DrawingTool;
484
- declare const HORIZONTAL_RAY: DrawingTool;
485
- declare const VERTICAL_LINE: DrawingTool;
486
- declare const CROSS_LINE: DrawingTool;
487
- declare const RECTANGLE: DrawingTool;
488
- declare const ELLIPSE: DrawingTool;
489
- declare const PARALLEL_CHANNEL: DrawingTool;
490
- declare const FIB_RETRACEMENT: DrawingTool;
491
- declare const FIB_EXTENSION: DrawingTool;
492
- declare const MEASURE: DrawingTool;
493
- declare const LONG_POSITION: DrawingTool;
494
- declare const SHORT_POSITION: DrawingTool;
495
- declare const TEXT: DrawingTool;
496
- /**
497
- * Path: click each vertex, double-click to finish, arrowhead on the last leg.
498
- * The arrow is what separates it from `polyline`: a path points somewhere.
499
- */
500
- declare const PATH: DrawingTool;
501
- /**
502
- * Brush: freehand ink. Press, drag, release. The anchors are the thinned
503
- * samples of the gesture and the spline between them is what is stroked, so
504
- * the ink stays a curve after the thinning that keeps a stroke small.
505
- */
506
- declare const BRUSH: DrawingTool;
507
- /**
508
- * Rotated rectangle: anchors 0 and 1 lay out one edge (and so the rotation),
509
- * and anchor 2 sets the depth perpendicular to it. An axis-aligned `rectangle`
510
- * cannot follow a trend channel; this can.
490
+ * Accumulate from the first bar at or after the anchor, including the live tail.
491
+ * Missing volume is omitted and reported; zero volume adds no statistical weight.
492
+ * Recompute on each call so mutations of the forming candle are always visible.
511
493
  */
512
- declare const ROTATED_RECTANGLE: DrawingTool;
494
+ declare function anchoredVwapAnalysis(bars: readonly Bar[], anchorTime: number, options?: AnchoredVwapOptions): AnchoredVwapResult;
513
495
  /**
514
- * Double curve: an S through three anchors. `curve` bends one way off a single
515
- * control; this mirrors that control about the midpoint so the second half bends
516
- * back, which is the shape a rounded top-then-bottom actually needs.
496
+ * Estimate a volume histogram over the inclusive timestamp range. Each candle's
497
+ * volume is uniform over low..high; this is not trade-by-trade volume. Difference
498
+ * arrays distribute interior rows without visiting each row per candle. Row
499
+ * lookup is bounded by 200 edges: O(bars log(rows) + rows).
517
500
  */
518
- declare const DOUBLE_CURVE: DrawingTool;
501
+ declare function fixedRangeVolumeProfileAnalysis(bars: readonly Bar[], fromTime: number, toTime: number, options?: FixedRangeVolumeProfileOptions): FixedRangeVolumeProfileResult;
502
+
519
503
  /**
520
- * Cyclic lines: vertical lines repeating at the interval the two anchors set,
521
- * for reading a rhythm forward off a measured swing.
504
+ * Clipboard transfer for drawings: the layer between the drawing model and the
505
+ * OS clipboard.
506
+ *
507
+ * Two things make this more than `JSON.stringify`:
508
+ *
509
+ * 1. **The OS clipboard is shared with everything else on the machine.** A
510
+ * paste can arrive from a spreadsheet, another application, or a hand-edited
511
+ * copy of our own payload. So the payload is written under a namespaced key
512
+ * and everything read back is validated field by field before it can reach
513
+ * the model. Foreign text is *not ours* and is ignored, never an exception
514
+ * the host has to catch on every Ctrl+V.
515
+ * 2. **Clipboard access is async and permission-gated.** `navigator.clipboard`
516
+ * rejects when the document is not focused, the page is not secure, or the
517
+ * user denied the permission. Copy must still work inside the tab, so every
518
+ * write also lands in a module-level in-memory clipboard which is shared by
519
+ * every controller in the page. That is what makes chart-to-chart paste work
520
+ * with the permission denied, and it is why the memory store is a singleton
521
+ * rather than per-controller state.
522
+ *
523
+ * The payload body is a drawings document (`DrawingsDocument`), so a copy made
524
+ * by a 1.9.x build (a version 1 body carrying the old style-bag text fields)
525
+ * is upgraded by the same migration a saved layout goes through.
522
526
  */
523
- declare const CYCLIC_LINES: DrawingTool;
527
+
524
528
  /**
525
- * Time cycles: semicircles of the anchors' width repeating along the axis, the
526
- * classic cycle-projection overlay.
529
+ * Top-level key of the JSON payload. Namespaced so a paste of arbitrary text,
530
+ * or of JSON belonging to some other application, is recognisable as not ours
531
+ * by looking at one property.
527
532
  */
528
- declare const TIME_CYCLES: DrawingTool;
533
+ declare const DRAWING_CLIPBOARD_KEY = "openalgo-charts/drawings";
529
534
  /**
530
- * Sine line: a full wave between the anchors. The horizontal span is one
531
- * period, the vertical offset its amplitude.
535
+ * Payload format version. Tracks the model version, since the body *is* a
536
+ * drawings document: version 1 carried 1.9.x drawings, version 2 carries the
537
+ * split text and levelled fibs.
532
538
  */
533
- declare const SINE_LINE: DrawingTool;
539
+ declare const DRAWING_CLIPBOARD_VERSION: number;
540
+ /** The async slice of `navigator.clipboard` this module uses. */
541
+ interface ClipboardPort {
542
+ writeText(text: string): Promise<void>;
543
+ readText(): Promise<string>;
544
+ }
545
+ /** Test seam: drop whatever the in-memory clipboard is holding. */
546
+ declare function clearMemoryClipboard(): void;
547
+ /** `navigator.clipboard` when the browser exposes it, else null. */
548
+ declare function systemClipboard(): ClipboardPort | null;
549
+ /** Deep copy of the persisted fields, so a clone shares nothing with its source. */
550
+ declare function cloneDrawing(d: Drawing): Drawing;
551
+ /** Serialise drawings into the namespaced payload written to the clipboard. */
552
+ declare function encodeClipboardPayload(drawings: readonly Drawing[]): string;
534
553
  /**
535
- * Price label: a pill of the anchored price with a tail pointing at the bar.
536
- * Reads its value off the anchor, so dragging it re-reads rather than going
537
- * stale the way a typed-in text would. Text, when given, replaces the price.
554
+ * Validate one entry into a drawing with no id. Returns null for anything that
555
+ * cannot be rendered: an unknown tool would throw inside the controller, and a
556
+ * NaN anchor produces a drawing that can never be drawn or hit-tested again.
557
+ * Accepts the 1.9.x shape too, lifting its text fields into `text`.
538
558
  */
539
- declare const PRICE_LABEL: DrawingTool;
559
+ declare function sanitizeDrawing(value: unknown): Omit<Drawing, 'id'> | null;
540
560
  /**
541
- * Callout: a text bubble on its own anchor with a tail back to the point it
542
- * annotates, so the note can sit clear of the price action it refers to.
561
+ * Parse clipboard text into drawings, or null when the text is not ours or is
562
+ * not usable. All-or-nothing on purpose: a payload with one corrupt entry is a
563
+ * corrupt payload, and pasting the other nine silently would be worse than
564
+ * pasting none.
543
565
  */
544
- declare const CALLOUT: DrawingTool;
545
- /** Flag mark: a pennant on a pole at one anchor, for tagging a bar. */
546
- declare const FLAG_MARK: DrawingTool;
566
+ declare function decodeClipboardPayload(text: unknown): Omit<Drawing, 'id'>[] | null;
567
+ interface DrawingClipboardOptions {
568
+ /** Where to read and write. Defaults to `navigator.clipboard` when present. */
569
+ port?: ClipboardPort | null;
570
+ /**
571
+ * Keep a copy in the shared in-memory clipboard so copy and paste keep
572
+ * working when the OS clipboard is unavailable or the permission is refused.
573
+ * Default true. Set false to make a failed system write a failed copy, which
574
+ * a host that must not silently lose cross-tab transfer may prefer.
575
+ */
576
+ fallbackToMemory?: boolean;
577
+ }
547
578
  /**
548
- * Note: a pin at the bar with its text to the upper right.
549
- *
550
- * The pin is the point being annotated and the plate is the annotation, which
551
- * is why the two are drawn as separate marks joined by a stem rather than as
552
- * one bubble: the reader needs to see exactly which bar is meant, and a bubble
553
- * wide enough to hold a sentence covers several.
579
+ * The clipboard as the controller sees it: drawings in, drawings out, no
580
+ * exceptions escaping. Every failure mode (no clipboard API, refused
581
+ * permission, foreign text, corrupt payload) reduces to `false` or `null`.
554
582
  */
555
- declare const NOTE: DrawingTool;
583
+ declare class DrawingClipboard {
584
+ private _port;
585
+ private _fallback;
586
+ /** Why the last operation did not reach the OS clipboard, if it did not. */
587
+ private _lastError;
588
+ constructor(options?: DrawingClipboardOptions);
589
+ /** Swap the port at runtime (a host granting permission later, or a test). */
590
+ setPort(port: ClipboardPort | null): void;
591
+ /**
592
+ * Turn the in-memory backstop off or on. With it off, a refused system write
593
+ * is a failed copy, which is what a host wants when a cut that cannot reach
594
+ * the OS clipboard must not delete the drawing.
595
+ */
596
+ setFallbackToMemory(enabled: boolean): void;
597
+ /**
598
+ * Why the OS clipboard was not used by the last call, or null when it was.
599
+ * Set on a successful copy too, when the payload reached memory but not the
600
+ * system clipboard: that copy works in this tab and will not appear in
601
+ * another, which is exactly what a host wants to be able to tell the user.
602
+ */
603
+ lastError(): string | null;
604
+ /**
605
+ * Write drawings out. Resolves true when the payload is retrievable by a
606
+ * later `read()` (through the system clipboard, or through memory when the
607
+ * fallback is on), false when it is not, so a caller doing a cut knows
608
+ * whether it is safe to delete the original.
609
+ */
610
+ write(drawings: readonly Drawing[]): Promise<boolean>;
611
+ /**
612
+ * Read drawings back, or null when there is nothing of ours to paste. The
613
+ * system clipboard wins when it holds our payload, so a copy made in another
614
+ * tab beats a stale in-tab one; memory is consulted when the read fails or
615
+ * returns something that is not ours. That last case can paste an older copy
616
+ * after the user has copied unrelated text elsewhere, which is the deliberate
617
+ * trade: losing the copy whenever a browser refuses the read half of the
618
+ * permission would be the worse surprise.
619
+ */
620
+ read(): Promise<Omit<Drawing, 'id'>[] | null>;
621
+ }
622
+
556
623
  /**
557
- * Balloon: a speech bubble sitting above its anchor, tail pointing down.
624
+ * Drawing controller: the interaction and persistence layer over
625
+ * `DrawingLayer`. It is **headless**: no DOM, no toolbar. A host sets the
626
+ * active tool (from its own button, a shortcut, a command palette) and the
627
+ * controller runs placement, selection, dragging, undo, and serialisation.
558
628
  *
559
- * One anchor rather than the callout's two: a balloon is for saying something
560
- * *about this bar*, so letting the bubble be dragged away from what it labels
561
- * would only invite it to drift.
562
- */
563
- declare const BALLOON: DrawingTool;
564
- /**
565
- * Comment: a small square-cornered box with a tail off its bottom left.
629
+ * It listens on the chart's event bus (`click`, `crosshair:move`, `drag`,
630
+ * `drag:end`) rather than the single-slot `subscribeClick`/`subscribeDrag`
631
+ * callbacks, so a host keeps using those for its own order lines.
566
632
  *
567
- * Deliberately plainer than the balloon: a chart that says something at every
568
- * other bar needs one of these marks to be quiet, and the shape is the only
569
- * thing distinguishing them once both are the user's own colour.
633
+ * Selection is a list. Every method that edits takes the whole list into one
634
+ * undo entry, so a multi-drag, a batch delete or a paste of ten shapes is one
635
+ * Ctrl+Z, which is what the hand that did it expects.
570
636
  */
571
- declare const COMMENT: DrawingTool;
637
+
572
638
  /**
573
- * Signpost: a post standing on the bar with its plate at the top.
639
+ * The slice of the chart this controller needs.
574
640
  *
575
- * The one annotation anchored to *time* rather than to a level: the post is
576
- * vertical so the plate can clear the price action entirely while the foot
577
- * still names an exact bar. Useful for events, which happen at a moment and
578
- * not at a price.
579
- */
580
- declare const SIGNPOST: DrawingTool;
581
- /**
582
- * Price note: the anchored price, with the user's text under it.
583
- *
584
- * The price is read off the anchor rather than typed, so dragging the note
585
- * re-reads it. A typed price is a number that was true once, which on a chart
586
- * is worse than no number at all.
587
- */
588
- declare const PRICE_NOTE: DrawingTool;
589
- /**
590
- * Table: rows of text in a grid, anchored top-left.
591
- *
592
- * Cells come from the text: a newline starts a row and a pipe separates
593
- * columns, so a whole table is one editable string and survives `getState`
594
- * with no new shape in the drawing model. The first row is drawn as a header
595
- * because a table on a chart is nearly always labelled.
596
- */
597
- declare const TABLE: DrawingTool;
598
- /**
599
- * Measurers. `measure` reports price *and* time together; these two constrain it
600
- * to one axis, which is what you want when the other one is noise: sizing a
601
- * retracement without caring how long it took, or counting bars to an event
602
- * without caring where price went.
603
- */
604
- declare const PRICE_RANGE: DrawingTool;
605
- declare const DATE_RANGE: DrawingTool;
606
- /**
607
- * Position forecast: project a move from an anchor. Two anchors give the
608
- * projected leg; the shape extends the same slope past the target as a dashed
609
- * cone, so the drawing says "if this continues" rather than just marking a line.
610
- */
611
- declare const FORECAST: DrawingTool;
612
- /**
613
- * Circle from centre + a radius handle. The radius is measured in *pixels*, so
614
- * it stays a circle on screen instead of the ellipse the two axes' differing
615
- * scales would otherwise produce.
616
- */
617
- declare const CIRCLE: DrawingTool;
618
- declare const TRIANGLE: DrawingTool;
619
- /** Free-form polyline: click each vertex, double-click to finish. */
620
- declare const POLYLINE: DrawingTool;
621
- /** Arc through three anchors: the middle one is on the curve, not a handle. */
622
- declare const ARC: DrawingTool;
623
- /** Quadratic curve: the middle anchor is a control handle, off the curve. */
624
- declare const CURVE: DrawingTool;
625
- declare const ARROW_UP: DrawingTool;
626
- declare const ARROW_DOWN: DrawingTool;
627
- declare const ARROW_LEFT: DrawingTool;
628
- declare const ARROW_RIGHT: DrawingTool;
629
- /** Highlighter: the brush with a fat, translucent stroke. */
630
- declare const HIGHLIGHTER: DrawingTool;
631
- /** Fib channel: fib levels spread across a trend leg, parallel to it. */
632
- declare const FIB_CHANNEL: DrawingTool;
633
- /**
634
- * Fib time zone: vertical lines at Fibonacci multiples of the base leg's width.
635
- * Its levels count bars rather than divide a leg, so they carry no convention
636
- * colour and fall back to the drawing's own.
637
- */
638
- declare const FIB_TIME_ZONE: DrawingTool;
639
- /** Rays from the anchor at fib fractions of the leg: the speed resistance fan. */
640
- declare const FIB_FAN: DrawingTool;
641
- /**
642
- * Gann fan: rays from one anchor at the classic price/time angles. A level's
643
- * ratio is price per unit of time, so the 1x1 is 1, the 1x2 is 0.5 and the
644
- * 2x1 is 2; each angle keeps its own colour from the cycle palette.
645
- */
646
- declare const GANN_FAN: DrawingTool;
647
- /**
648
- * Gann box: the drawn rectangle divided at its levels on both axes, plus the
649
- * 1x1 diagonal. Ratio 0 is the first anchor's price and time, ratio 1 the
650
- * second's, so the box reads like a retracement laid over a date range.
651
- */
652
- declare const GANN_BOX: DrawingTool;
653
- declare const BUILTIN_DRAWING_TOOLS: readonly DrawingTool[];
654
- /** Register every built-in tool. Idempotent; called on tier import. */
655
- declare function registerBuiltinDrawingTools(): void;
656
-
657
- /**
658
- * The shared level palette for the Fibonacci and Gann family.
659
- *
660
- * Every ladder tool (retracement, extension, channel, fan, time zone, the Gann
661
- * fan and box) strokes a list of ratios, and each ratio has a colour the eye
662
- * expects: 0.618 reads as one thing across every tool, not a different hue per
663
- * tool. Before this module each tool would have restated those colours, and
664
- * restated colours drift. This is the one statement; tools take their defaults
665
- * from it and hosts seed a newly added level from it.
666
- *
667
- * Pure: no DOM, no registry. Safe to import from a worker or a settings panel.
668
- */
669
-
670
- /**
671
- * The colour of a ratio that is an anchor rather than a level (0, 1, 2, 3 are
672
- * where the measured leg starts and ends, or whole multiples of it) and of any
673
- * ratio the convention does not name. Neutral on purpose: it is the reference
674
- * the coloured levels are read against.
675
- */
676
- declare const LEVEL_NEUTRAL = "#787b86";
677
- /**
678
- * The conventional colour for a Fibonacci ratio. Anchors (0, 1, 2, 3) and any
679
- * ratio without a convention come back {@link LEVEL_NEUTRAL}, so a host can call
680
- * this for every new level and always get a usable colour.
681
- */
682
- declare function levelColor(ratio: number): string;
683
- /**
684
- * A short sequence of colours that stay distinguishable side by side, for
685
- * tools whose levels are positions rather than ratios: the angles of a Gann
686
- * fan, the repeats of a cycle. Index `i` wraps, so any count is safe.
687
- */
688
- declare const CYCLE_PALETTE: readonly string[];
689
- /** The `i`th cycle colour. Wraps in both directions; a negative index is fine. */
690
- declare function cycleColor(i: number): string;
691
- /** `0.618` as `61.8%`: the text a level prints when it carries no label. */
692
- declare function formatRatio(ratio: number): string;
693
- /**
694
- * The label for a Gann angle stored as a price-per-time ratio: `1` is the 1x1,
695
- * `0.5` the 1x2, `2` the 2x1. Ratios that are not clean reciprocals fall back
696
- * to the ratio itself, which is still true, just less idiomatic.
697
- */
698
- declare function gannLabel(ratio: number): string;
699
- /** A mutable copy of a default ladder, for a fresh drawing's own `style.levels`. */
700
- declare function cloneLevels(levels: readonly FibLevel[]): FibLevel[];
701
- /** Retracement and extension: the classic ladder, coloured by convention. */
702
- declare const DEFAULT_FIB: readonly FibLevel[];
703
- /** Speed fan: rays at the fractions of the leg. No 0 ray, it would be flat. */
704
- declare const DEFAULT_FIB_FAN: readonly FibLevel[];
705
- /** Gann box: quarters plus the golden pair, applied to both axes of the box. */
706
- declare const DEFAULT_GANN_BOX: readonly FibLevel[];
707
- /**
708
- * Gann fan: the classic angles as price-per-time ratios, 1x8 up to 8x1. These
709
- * are positions in a sequence rather than Fibonacci ratios, so they take the
710
- * cycle palette.
711
- */
712
- declare const DEFAULT_GANN_FAN: readonly FibLevel[];
713
- /**
714
- * Time zones count bars along the Fibonacci sequence, so `ratio` here is a
715
- * multiple of the anchor leg, not a fraction of it. No colour is set: there is
716
- * no convention for "the 13 line", so every zone takes the drawing's own colour
717
- * until the user gives one its own.
718
- */
719
- declare const DEFAULT_FIB_TIME_ZONE: readonly FibLevel[];
720
-
721
- /**
722
- * Glyphs for the drawing tools and for host chrome, as path data.
723
- *
724
- * The engine ships no DOM, and this does not change that: an icon here is a
725
- * string of SVG path commands, not an element. The host still builds its own
726
- * toolbar, flyouts and rail; what it no longer has to do is draw every glyph
727
- * before it can show them. `icon-svg.ts` turns these into markup strings for a
728
- * host that wants the convenience; this file is the single source both of them
729
- * and every other surface read from.
730
- *
731
- * That was the real cost of leaving them out. Every adopter drew their own set,
732
- * each drifted on stroke weight, grid and visual density independently, and the
733
- * result read as sixty icons rather than as one set, however carefully any
734
- * single glyph was made. A shipped set is worth more than a better glyph.
735
- *
736
- * # Two tiers, one weight
737
- *
738
- * Tool glyphs live on a 24-unit grid and are shown at 24px in the rail. Host
739
- * chrome (undo, close, the settings tab rail) is shown at 16px beside them, and
740
- * a 24-grid glyph scaled to 16 lands its 2-unit stroke on 1.33 pixels, blurred
741
- * across two rows on every edge. So chrome has its own registry drawn on a
742
- * 16-unit grid with a 1.5 stroke. The two strokes are deliberately not in exact
743
- * proportion: a small glyph needs a slightly heavier relative stroke than a
744
- * large one to read as the same weight, and `tests/draw-icons.test.ts` holds
745
- * the chrome tier inside that band rather than at parity.
746
- *
747
- * # The grid
748
- *
749
- * Every glyph is authored to the same constraints, and `tests/draw-icons.test.ts`
750
- * enforces all of them mechanically, because a set of this size cannot be kept
751
- * consistent by review:
752
- *
753
- * - **One viewBox per tier.** 24 by 24 for tools, 16 by 16 for chrome, so a
754
- * host sets the size once per surface.
755
- * - **A margin all round.** Live area 2 to 22 on the tool grid, 1 to 15 on the
756
- * chrome grid. Without it, glyphs that happen to reach the edge look larger
757
- * than their neighbours and the rail reads as ragged.
758
- * - **Integer coordinates.** With `STROKE` of 2, an orthogonal edge centred on
759
- * an integer covers exactly two device pixels at 1:1, which is what makes it
760
- * crisp. Half-unit coordinates were the previous set's crispness bug: at a
761
- * 1.5 stroke on integers, nothing landed on a pixel boundary at any size.
762
- * - **One stroke weight per tier.** Three different weights across identically
763
- * sized boxes is the single most visible tell of a set assembled rather than
764
- * drawn.
765
- *
766
- * # Rendering
767
- *
768
- * Tool glyphs are crispest at 24px, and at any integer multiple of it. At 18px
769
- * the 0.75 scale puts a 2-unit stroke on 1.5 device pixels and every edge is
770
- * anti-aliased across two rows: that is a host sizing choice, not something the
771
- * path data can fix. Prefer 24, or 12 with a heavier weight. Chrome glyphs are
772
- * drawn for 16px and its multiples.
773
- */
774
- /** The viewBox every glyph is authored in. */
775
- declare const ICON_VIEWBOX = "0 0 24 24";
776
- /**
777
- * Stroke width in viewBox units. Paths carry no presentation attributes, so a
778
- * host that wants a lighter set overrides this once rather than editing glyphs.
779
- */
780
- declare const ICON_STROKE = 2;
781
- /**
782
- * The attribute bag a tier hands its host. Structural, so the markup builders
783
- * in `icon-svg.ts` take either tier's bag through one signature.
784
- */
785
- interface IconAttrs {
786
- readonly viewBox: string;
787
- readonly fill: 'none';
788
- readonly stroke: 'currentColor';
789
- readonly strokeWidth: number;
790
- readonly strokeLinecap: 'round';
791
- readonly strokeLinejoin: 'round';
792
- }
793
- /** Attributes a host should apply to the `<svg>`, so every set matches. */
794
- declare const ICON_ATTRS: {
795
- readonly viewBox: "0 0 24 24";
796
- readonly fill: "none";
797
- readonly stroke: "currentColor";
798
- readonly strokeWidth: 2;
799
- readonly strokeLinecap: "round";
800
- readonly strokeLinejoin: "round";
801
- };
802
- /**
803
- * Path data by tool id, plus a few keys a host toolbar needs that are not
804
- * themselves tools (`cursor`, the group headers).
805
- *
806
- * Values are the `d` attribute of one `<path>`. Multiple subpaths are joined
807
- * into the same string rather than split across elements, so a host renders one
808
- * node per glyph.
809
- */
810
- declare const DRAWING_TOOL_ICONS: Readonly<Record<string, string>>;
811
- /**
812
- * The glyph for a tool, or `undefined` when it has none.
813
- *
814
- * Undefined rather than a placeholder: a host that renders an empty box has a
815
- * visible gap to fix, while one handed a question mark ships it.
641
+ * Declared structurally rather than as `Chart` on purpose. Each tier ships its
642
+ * own bundled `.d.ts`, so naming the class here made the draw tier re-declare
643
+ * `Chart`, and because `Chart` has private members, TypeScript treats the two
644
+ * declarations as *different* types. A TS consumer passing the chart from
645
+ * `createChart()` got "separate declarations of a private property", which made
646
+ * the tier unusable from TypeScript at all. An interface with no private
647
+ * members is structural, so the real `Chart` satisfies it with nothing to cast.
816
648
  */
817
- declare function drawingToolIcon(toolId: string): string | undefined;
818
- /** Every id this set covers, for a host building a palette from it. */
819
- declare function drawingToolIconIds(): string[];
820
- /** The viewBox every chrome glyph is authored in. */
821
- declare const CHROME_ICON_VIEWBOX = "0 0 16 16";
822
- /**
823
- * Chrome stroke width in viewBox units. Not the tool stroke scaled (that would
824
- * be 1.33): at 16px a glyph needs a touch more relative weight than at 24px to
825
- * read as the same line, and 1.5 is where the two rails match by eye.
826
- */
827
- declare const CHROME_ICON_STROKE = 1.5;
828
- /** Attributes a host should apply to the `<svg>` around a chrome glyph. */
829
- declare const CHROME_ICON_ATTRS: {
830
- readonly viewBox: "0 0 16 16";
831
- readonly fill: "none";
832
- readonly stroke: "currentColor";
833
- readonly strokeWidth: 1.5;
834
- readonly strokeLinecap: "round";
835
- readonly strokeLinejoin: "round";
836
- };
837
- /**
838
- * Path data for host chrome: the buttons around the chart rather than the
839
- * tools in it. Same rules as the tool tier (one path per glyph, no presentation
840
- * attributes, integer coordinates) on the 16 grid with a 1..15 live area.
841
- *
842
- * The pair `cursor` / `magnet` / `text` also exist in the tool registry, at 24.
843
- * They are drawn twice on purpose: a rail button and a toolbar button are
844
- * different sizes, and the whole point of a second grid is not scaling one
845
- * drawing to both.
846
- */
847
- declare const CHROME_ICONS: Readonly<Record<string, string>>;
848
- /**
849
- * Chrome glyphs meant to be painted solid. Paths carry no presentation
850
- * attributes, so the fill is applied by the wrapper (`chromeIconSvg` does it,
851
- * a host with its own wrapper reads this set) and the registry stays pure.
852
- */
853
- declare const CHROME_ICON_FILLED: ReadonlySet<string>;
854
- /** The chrome glyph for an id, or `undefined` when there is none. */
855
- declare function chromeIcon(id: string): string | undefined;
856
- /** Every id the chrome tier covers. */
857
- declare function chromeIconIds(): string[];
858
-
859
- /** Prefix of every `<symbol id>` in the sprite, and of every `<use href>`. */
860
- declare const ICON_SYMBOL_PREFIX = "oac-icon-";
861
- /** Options shared by the inline builders. */
862
- interface IconSvgOptions {
649
+ interface DrawingChartHost {
650
+ readonly isDestroyed?: boolean;
863
651
  /**
864
- * Width and height. A number is CSS pixels; a string is written verbatim, so
865
- * `'1em'` sizes the glyph with the surrounding text. Default: the grid's
866
- * native size (24 for tools, 16 for chrome), where the stroke is crispest.
652
+ * The event bus. The controller listens for `click`, `crosshair:move`,
653
+ * `drag`, `drag:end` and `dblclick`, and for `hover` (`{ id }`, the hit id
654
+ * under the pointer whenever it changes), which is what drives the hover
655
+ * state: the chart has already hit-tested the move, so the controller
656
+ * reads its answer rather than testing a second time. A host that never
657
+ * emits `hover` has drawings that select and drag but do not light up.
867
658
  */
868
- size?: number | string;
869
- /** Stroke width in viewBox units. Default: the tier's `*_STROKE`. */
870
- stroke?: number;
871
- /** A class for the host's own styling. */
872
- className?: string;
873
- }
874
- /** Options for `toolCursor`. */
875
- interface ToolCursorOptions {
876
- /** Cursor image size in CSS pixels. Default 20. Browsers reject images over 128. */
877
- size?: number;
878
- /** The pointer's active pixel, from the image's top-left. Default: the centre. */
879
- hotspot?: readonly [number, number];
880
- /** Glyph colour. Default white. */
881
- color?: string;
659
+ on(event: string, handler: (payload: unknown) => void): () => void;
660
+ emit(event: string, payload: unknown): void;
661
+ addPrimitive(primitive: IPrimitive, paneIndex?: number): void;
662
+ removePrimitive(primitive: IPrimitive): void;
663
+ readonly dataLayer: DataLayer;
664
+ getVisibleLogicalRange(): {
665
+ from: number;
666
+ to: number;
667
+ } | null;
668
+ drawingState(): unknown;
669
+ setDrawingState(state: unknown): void;
670
+ setPlacementMode?(active: boolean): void;
882
671
  /**
883
- * Halo colour. Default: black under a light `color`, white under a dark one,
884
- * judged from a hex colour. A non-hex `color` gets a black halo unless this
885
- * is set.
672
+ * Optional, and used only to move a drawing by a fixed screen distance (a
673
+ * paste offset, an arrow-key nudge, a multi-drag across panes). Going
674
+ * through pixels rather than adding a price delta keeps the offset the same
675
+ * visible nudge on a log scale as on a linear one, and the same on an RSI
676
+ * pane as on the price pane. A host without them still gets the time half.
886
677
  */
887
- halo?: string;
888
- /** The keyword after the image, for a browser that will not take it. Default `crosshair`. */
889
- fallback?: string;
890
- }
891
- /**
892
- * A complete inline `<svg>` for one tool glyph, stroked in `currentColor`.
893
- *
894
- * ```ts
895
- * button.innerHTML = iconSvg('trend-line');
896
- * button.innerHTML = iconSvg('trend-line', { size: '1em' });
897
- * ```
898
- */
899
- declare function iconSvg(id: string, opts?: IconSvgOptions): string;
900
- /**
901
- * A complete inline `<svg>` for one chrome glyph, on the 16 grid. Glyphs in
902
- * `CHROME_ICON_FILLED` are painted solid as well as stroked.
903
- */
904
- declare function chromeIconSvg(id: string, opts?: IconSvgOptions): string;
905
- /**
906
- * One hidden `<svg>` of `<symbol>`s, for a host that shows many glyphs and
907
- * wants each path in the document once. Inject it once, then place glyphs with
908
- * `iconUse`. Symbols carry the path and viewBox only; stroke and fill inherit
909
- * from the `<svg>` around each `<use>`, so one sprite serves every weight.
910
- *
911
- * Default: every tool glyph. Repeated ids collapse to one symbol, since a
912
- * duplicate element id is an invalid document.
913
- */
914
- declare function iconSprite(ids?: readonly string[]): string;
915
- /** An `<svg>` that references a symbol from `iconSprite`. Same frame as `iconSvg`. */
916
- declare function iconUse(id: string, opts?: IconSvgOptions): string;
917
- /**
918
- * A CSS `cursor` value carrying the tool's glyph, for the canvas while that
919
- * tool is armed.
920
- *
921
- * The glyph is drawn twice: once in `halo`, one pixel wider on each side, and
922
- * once in `color` on top, so it reads on a light chart and a dark one without
923
- * the host choosing per theme. The value is `url("data:image/svg+xml,...") x y,
924
- * fallback`, ready for `canvas.style.cursor`.
925
- */
926
- declare function toolCursor(id: string, opts?: ToolCursorOptions): string;
927
-
928
- /**
929
- * A `DrawingLayer` renders the drawings of one pane and answers the chart's
930
- * hit-test. Anchors live in `{ time, price }`, so the layer maps them through
931
- * the *fractional* index helpers: the gapless axis means an anchor can sit
932
- * between bars (inside a collapsed weekend) or past the last one (a forward
933
- * projection), and whole-index lookups have nothing to return there.
934
- *
935
- * Each pane carries **two** layers, one at z-order `bottom` for drawings with a
936
- * negative `zIndex` (under the series) and one at `top` for the rest. The top
937
- * one takes over handles and hit-testing for the pair through `setBelow`, so a
938
- * shape parked under the candles still shows its grab handles above them and
939
- * a click lands on whatever the eye sees on top.
940
- *
941
- * Hit ids are `draw:<id>` for the body and `draw:<id>#<n>` for anchor `n`, so
942
- * the controller can tell "move the whole shape" from "move this handle".
943
- *
944
- * The hover ring, the magnet ring and the hover handles are all overlay
945
- * state: they change on every pointer move, so they must only ever cost the
946
- * top canvas. The layer that paints them is the one whose `requestUpdate`
947
- * the chart maps to the cursor tier, which is why the bottom layer of a pair
948
- * stores that state and asks for nothing.
949
- */
950
-
951
- /** Which side of the series a layer paints on. */
952
- type DrawingLayerOrder = 'bottom' | 'top';
953
- /** The pointer kinds the layer sizes its targets for. */
954
- type DrawingPointerKind = 'mouse' | 'touch' | 'pen';
955
- /**
956
- * Paint order: by `zIndex`, ties by array position. `Array.prototype.sort` is
957
- * stable, which is what makes the tie rule hold without a second key.
958
- */
959
- declare function sortByZIndex(drawings: readonly Drawing[]): Drawing[];
960
- declare class DrawingLayer implements IPrimitive {
961
- private readonly _order;
962
- /** In paint order. */
963
- private _drawings;
964
- private _host;
965
- private _selected;
966
- /** The drawing under the pointer, whether or not it is selected. */
967
- private _hovered;
968
- /** Where the next click will land while the magnet is pulling it. */
969
- private _snap;
970
- /** Anchors of the in-progress drawing, plus the live cursor point. */
971
- private _preview;
972
- /** The layer under this one, whose handles and hits this layer answers for. */
973
- private _below;
974
- /** The layer that has taken over this one's handles and hit-testing. */
975
- private _above;
976
- /** Sizes grab targets for the device last seen. */
977
- private _touch;
978
- constructor(order?: DrawingLayerOrder);
979
- attached(host: PrimitiveHost): void;
980
- detached(): void;
981
- zOrder(): ZOrder;
982
- /** Drawings overlay the price range; they never drive it. */
983
- autoscaleInfo(): null;
984
- setDrawings(drawings: readonly Drawing[]): void;
985
- setSelected(ids: readonly string[]): void;
678
+ priceToCoordinate?(price: number, paneIndex?: number): number | null;
679
+ coordinateToPrice?(y: number, paneIndex?: number): number | null;
986
680
  /**
987
- * The drawing under the pointer. Its handles paint at a lighter weight than
988
- * a selection's, as the hint that it can be grabbed. Repaints only from the
989
- * layer that paints handles: the adopted bottom layer keeps the id for its
990
- * own bookkeeping and asks for nothing, since its repaint costs the series.
681
+ * Optional, the time-axis half of the same conversion. coordinateToTime also
682
+ * keeps previews and freehand strokes active in empty space beyond the bars.
683
+ * Without these methods a horizontal nudge assumes the default bar spacing.
991
684
  */
992
- setHovered(id: string | null): void;
993
- hovered(): string | null;
685
+ timeToCoordinate?(time: number): number;
686
+ coordinateToTime?(x: number): number;
994
687
  /**
995
- * The point the magnet will snap the next click to, or null when it is not
996
- * pulling. Painted as a hollow ring so the user sees the anchor land before
997
- * committing to it.
688
+ * Optional, and used only to keep a paste from a chart with more panes than
689
+ * this one landing on a pane the user cannot see. Adding a primitive creates
690
+ * the pane it names, so without this a drawing copied out of an indicator
691
+ * pane would conjure an empty pane in a single-pane chart.
998
692
  */
999
- setSnapPoint(point: DrawingPoint | null): void;
1000
- snapPoint(): DrawingPoint | null;
693
+ panes?(): readonly unknown[];
694
+ }
695
+ interface DrawingControllerOptions {
1001
696
  /**
1002
- * Size the grab targets for the device in use. Touch doubles the radii; a
1003
- * mouse or a pen keeps the desktop sizes. The render context may carry the
1004
- * same fact (`pointerType`), and when it does that wins per call, so a
1005
- * first touch on a fresh chart is already sized right.
697
+ * Snap new anchors to the O/H/L/C of the bar under the cursor. `'strong'`
698
+ * always takes the nearest of the four; `'weak'` only when one sits within
699
+ * a few pixels of the pointer, so a click on open space stays where it was
700
+ * made. `true` means `'strong'` and `false` means `'off'`, which is what
701
+ * the boolean meant before the modes existed. Default `'off'`. While a
702
+ * tool is armed the layer paints a ring where the next click will land.
1006
703
  */
1007
- setPointerType(kind: DrawingPointerKind | null | undefined): void;
1008
- setPreview(drawing: Drawing | null): void;
704
+ magnet?: boolean | MagnetMode;
705
+ /** Style merged under every tool's own defaults. */
706
+ defaultStyle?: DrawingStyle;
707
+ /** Stay in the active tool after finishing a drawing. Default false. */
708
+ stayInDrawingMode?: boolean;
709
+ /** Undo depth. Default 50. */
710
+ historyLimit?: number;
1009
711
  /**
1010
- * Adopt the layer under this one. From then on this layer paints the handles
1011
- * of both and answers `hitTest` for both, and the adopted layer paints bodies
1012
- * only. Handles have to live up here: a selected drawing under the series
1013
- * would otherwise have its handles buried under the candles, and a handle
1014
- * you cannot see is a handle you cannot grab. Pass null to release.
712
+ * Where copy and paste move text. Defaults to `navigator.clipboard`; pass a
713
+ * port to route through a host's own transfer, or `null` to stay in the
714
+ * process-local clipboard entirely.
1015
715
  */
1016
- setBelow(layer: DrawingLayer | null): void;
1017
- /** Repaint for overlay-only state, from the layer that paints it. */
1018
- private _requestOverlay;
1019
- /** Whether targets are sized for a fingertip on this call. */
1020
- private _isTouch;
1021
- private _grabRadius;
1022
- private _handleRadius;
1023
- /** Map an anchor to media px on this pane. */
1024
- private _project;
1025
- private _points;
1026
- /** Selected, unlocked drawings of this layer, in paint order. */
1027
- private _handled;
716
+ clipboard?: ClipboardPort | null;
1028
717
  /**
1029
- * The hovered drawing of this layer, when it is one that could be grabbed
1030
- * and is not already showing selection handles.
1031
- */
1032
- private _hoverHandled;
1033
- draw(ctx: CanvasRenderingContext2D, rc: PrimitiveRenderContext): void;
1034
- private _drawingContext;
1035
- private _drawContent;
1036
- private _drawPlacementGuide;
1037
- /**
1038
- * Anchor handles. A hover shows them light (thin, translucent) as a hint
1039
- * that the shape can be grabbed; a selection shows them at full weight.
1040
- */
1041
- private _drawHandles;
1042
- /** The magnet ring: hollow, in the axis text colour, at the snap point. */
1043
- private _drawSnapRing;
1044
- hitTest(x: number, y: number, rc: PrimitiveRenderContext): PrimitiveHit | null;
1045
- private _hitHandle;
1046
- private _hitBody;
1047
- }
1048
-
1049
- /**
1050
- * Clipboard transfer for drawings: the layer between the drawing model and the
1051
- * OS clipboard.
1052
- *
1053
- * Two things make this more than `JSON.stringify`:
1054
- *
1055
- * 1. **The OS clipboard is shared with everything else on the machine.** A
1056
- * paste can arrive from a spreadsheet, another application, or a hand-edited
1057
- * copy of our own payload. So the payload is written under a namespaced key
1058
- * and everything read back is validated field by field before it can reach
1059
- * the model. Foreign text is *not ours* and is ignored, never an exception
1060
- * the host has to catch on every Ctrl+V.
1061
- * 2. **Clipboard access is async and permission-gated.** `navigator.clipboard`
1062
- * rejects when the document is not focused, the page is not secure, or the
1063
- * user denied the permission. Copy must still work inside the tab, so every
1064
- * write also lands in a module-level in-memory clipboard which is shared by
1065
- * every controller in the page. That is what makes chart-to-chart paste work
1066
- * with the permission denied, and it is why the memory store is a singleton
1067
- * rather than per-controller state.
1068
- *
1069
- * The payload body is a drawings document (`DrawingsDocument`), so a copy made
1070
- * by a 1.9.x build (a version 1 body carrying the old style-bag text fields)
1071
- * is upgraded by the same migration a saved layout goes through.
1072
- */
1073
-
1074
- /**
1075
- * Top-level key of the JSON payload. Namespaced so a paste of arbitrary text,
1076
- * or of JSON belonging to some other application, is recognisable as not ours
1077
- * by looking at one property.
1078
- */
1079
- declare const DRAWING_CLIPBOARD_KEY = "openalgo-charts/drawings";
1080
- /**
1081
- * Payload format version. Tracks the model version, since the body *is* a
1082
- * drawings document: version 1 carried 1.9.x drawings, version 2 carries the
1083
- * split text and levelled fibs.
1084
- */
1085
- declare const DRAWING_CLIPBOARD_VERSION: number;
1086
- /** The async slice of `navigator.clipboard` this module uses. */
1087
- interface ClipboardPort {
1088
- writeText(text: string): Promise<void>;
1089
- readText(): Promise<string>;
1090
- }
1091
- /** Test seam: drop whatever the in-memory clipboard is holding. */
1092
- declare function clearMemoryClipboard(): void;
1093
- /** `navigator.clipboard` when the browser exposes it, else null. */
1094
- declare function systemClipboard(): ClipboardPort | null;
1095
- /** Deep copy of the persisted fields, so a clone shares nothing with its source. */
1096
- declare function cloneDrawing(d: Drawing): Drawing;
1097
- /** Serialise drawings into the namespaced payload written to the clipboard. */
1098
- declare function encodeClipboardPayload(drawings: readonly Drawing[]): string;
1099
- /**
1100
- * Validate one entry into a drawing with no id. Returns null for anything that
1101
- * cannot be rendered: an unknown tool would throw inside the controller, and a
1102
- * NaN anchor produces a drawing that can never be drawn or hit-tested again.
1103
- * Accepts the 1.9.x shape too, lifting its text fields into `text`.
1104
- */
1105
- declare function sanitizeDrawing(value: unknown): Omit<Drawing, 'id'> | null;
1106
- /**
1107
- * Parse clipboard text into drawings, or null when the text is not ours or is
1108
- * not usable. All-or-nothing on purpose: a payload with one corrupt entry is a
1109
- * corrupt payload, and pasting the other nine silently would be worse than
1110
- * pasting none.
1111
- */
1112
- declare function decodeClipboardPayload(text: unknown): Omit<Drawing, 'id'>[] | null;
1113
- interface DrawingClipboardOptions {
1114
- /** Where to read and write. Defaults to `navigator.clipboard` when present. */
1115
- port?: ClipboardPort | null;
1116
- /**
1117
- * Keep a copy in the shared in-memory clipboard so copy and paste keep
1118
- * working when the OS clipboard is unavailable or the permission is refused.
1119
- * Default true. Set false to make a failed system write a failed copy, which
1120
- * a host that must not silently lose cross-tab transfer may prefer.
1121
- */
1122
- fallbackToMemory?: boolean;
1123
- }
1124
- /**
1125
- * The clipboard as the controller sees it: drawings in, drawings out, no
1126
- * exceptions escaping. Every failure mode (no clipboard API, refused
1127
- * permission, foreign text, corrupt payload) reduces to `false` or `null`.
1128
- */
1129
- declare class DrawingClipboard {
1130
- private _port;
1131
- private _fallback;
1132
- /** Why the last operation did not reach the OS clipboard, if it did not. */
1133
- private _lastError;
1134
- constructor(options?: DrawingClipboardOptions);
1135
- /** Swap the port at runtime (a host granting permission later, or a test). */
1136
- setPort(port: ClipboardPort | null): void;
1137
- /**
1138
- * Turn the in-memory backstop off or on. With it off, a refused system write
1139
- * is a failed copy, which is what a host wants when a cut that cannot reach
1140
- * the OS clipboard must not delete the drawing.
1141
- */
1142
- setFallbackToMemory(enabled: boolean): void;
1143
- /**
1144
- * Why the OS clipboard was not used by the last call, or null when it was.
1145
- * Set on a successful copy too, when the payload reached memory but not the
1146
- * system clipboard: that copy works in this tab and will not appear in
1147
- * another, which is exactly what a host wants to be able to tell the user.
1148
- */
1149
- lastError(): string | null;
1150
- /**
1151
- * Write drawings out. Resolves true when the payload is retrievable by a
1152
- * later `read()` (through the system clipboard, or through memory when the
1153
- * fallback is on), false when it is not, so a caller doing a cut knows
1154
- * whether it is safe to delete the original.
1155
- */
1156
- write(drawings: readonly Drawing[]): Promise<boolean>;
1157
- /**
1158
- * Read drawings back, or null when there is nothing of ours to paste. The
1159
- * system clipboard wins when it holds our payload, so a copy made in another
1160
- * tab beats a stale in-tab one; memory is consulted when the read fails or
1161
- * returns something that is not ours. That last case can paste an older copy
1162
- * after the user has copied unrelated text elsewhere, which is the deliberate
1163
- * trade: losing the copy whenever a browser refuses the read half of the
1164
- * permission would be the worse surprise.
1165
- */
1166
- read(): Promise<Omit<Drawing, 'id'>[] | null>;
1167
- }
1168
-
1169
- /**
1170
- * Drawing controller: the interaction and persistence layer over
1171
- * `DrawingLayer`. It is **headless**: no DOM, no toolbar. A host sets the
1172
- * active tool (from its own button, a shortcut, a command palette) and the
1173
- * controller runs placement, selection, dragging, undo, and serialisation.
1174
- *
1175
- * It listens on the chart's event bus (`click`, `crosshair:move`, `drag`,
1176
- * `drag:end`) rather than the single-slot `subscribeClick`/`subscribeDrag`
1177
- * callbacks, so a host keeps using those for its own order lines.
1178
- *
1179
- * Selection is a list. Every method that edits takes the whole list into one
1180
- * undo entry, so a multi-drag, a batch delete or a paste of ten shapes is one
1181
- * Ctrl+Z, which is what the hand that did it expects.
1182
- */
1183
-
1184
- /**
1185
- * The slice of the chart this controller needs.
1186
- *
1187
- * Declared structurally rather than as `Chart` on purpose. Each tier ships its
1188
- * own bundled `.d.ts`, so naming the class here made the draw tier re-declare
1189
- * `Chart`, and because `Chart` has private members, TypeScript treats the two
1190
- * declarations as *different* types. A TS consumer passing the chart from
1191
- * `createChart()` got "separate declarations of a private property", which made
1192
- * the tier unusable from TypeScript at all. An interface with no private
1193
- * members is structural, so the real `Chart` satisfies it with nothing to cast.
1194
- */
1195
- interface DrawingChartHost {
1196
- /**
1197
- * The event bus. The controller listens for `click`, `crosshair:move`,
1198
- * `drag`, `drag:end` and `dblclick`, and for `hover` (`{ id }`, the hit id
1199
- * under the pointer whenever it changes), which is what drives the hover
1200
- * state: the chart has already hit-tested the move, so the controller
1201
- * reads its answer rather than testing a second time. A host that never
1202
- * emits `hover` has drawings that select and drag but do not light up.
1203
- */
1204
- on(event: string, handler: (payload: unknown) => void): () => void;
1205
- emit(event: string, payload: unknown): void;
1206
- addPrimitive(primitive: IPrimitive, paneIndex?: number): void;
1207
- removePrimitive(primitive: IPrimitive): void;
1208
- readonly dataLayer: DataLayer;
1209
- getVisibleLogicalRange(): {
1210
- from: number;
1211
- to: number;
1212
- } | null;
1213
- drawingState(): unknown;
1214
- setDrawingState(state: unknown): void;
1215
- setPlacementMode?(active: boolean): void;
1216
- /**
1217
- * Optional, and used only to move a drawing by a fixed screen distance (a
1218
- * paste offset, an arrow-key nudge, a multi-drag across panes). Going
1219
- * through pixels rather than adding a price delta keeps the offset the same
1220
- * visible nudge on a log scale as on a linear one, and the same on an RSI
1221
- * pane as on the price pane. A host without them still gets the time half.
1222
- */
1223
- priceToCoordinate?(price: number, paneIndex?: number): number | null;
1224
- coordinateToPrice?(y: number, paneIndex?: number): number | null;
1225
- /**
1226
- * Optional, the time-axis half of the same conversion. coordinateToTime also
1227
- * keeps previews and freehand strokes active in empty space beyond the bars.
1228
- * Without these methods a horizontal nudge assumes the default bar spacing.
1229
- */
1230
- timeToCoordinate?(time: number): number;
1231
- coordinateToTime?(x: number): number;
1232
- /**
1233
- * Optional, and used only to keep a paste from a chart with more panes than
1234
- * this one landing on a pane the user cannot see. Adding a primitive creates
1235
- * the pane it names, so without this a drawing copied out of an indicator
1236
- * pane would conjure an empty pane in a single-pane chart.
1237
- */
1238
- panes?(): readonly unknown[];
1239
- }
1240
- interface DrawingControllerOptions {
1241
- /**
1242
- * Snap new anchors to the O/H/L/C of the bar under the cursor. `'strong'`
1243
- * always takes the nearest of the four; `'weak'` only when one sits within
1244
- * a few pixels of the pointer, so a click on open space stays where it was
1245
- * made. `true` means `'strong'` and `false` means `'off'`, which is what
1246
- * the boolean meant before the modes existed. Default `'off'`. While a
1247
- * tool is armed the layer paints a ring where the next click will land.
1248
- */
1249
- magnet?: boolean | MagnetMode;
1250
- /** Style merged under every tool's own defaults. */
1251
- defaultStyle?: DrawingStyle;
1252
- /** Stay in the active tool after finishing a drawing. Default false. */
1253
- stayInDrawingMode?: boolean;
1254
- /** Undo depth. Default 50. */
1255
- historyLimit?: number;
1256
- /**
1257
- * Where copy and paste move text. Defaults to `navigator.clipboard`; pass a
1258
- * port to route through a host's own transfer, or `null` to stay in the
1259
- * process-local clipboard entirely.
1260
- */
1261
- clipboard?: ClipboardPort | null;
1262
- /**
1263
- * Whether a refused or failing clipboard write still lands in the in-process
1264
- * clipboard. Defaults to true, which is what makes copy and paste work between
1265
- * two charts on a page where the browser has denied clipboard permission.
1266
- *
1267
- * Pass false for a host that would rather a failed copy be a failed copy: with
1268
- * it off, `cut` leaves the drawing alone when the write does not land, so a
1269
- * shape is never destroyed for a transfer that did not happen.
718
+ * Whether a refused or failing clipboard write still lands in the in-process
719
+ * clipboard. Defaults to true, which is what makes copy and paste work between
720
+ * two charts on a page where the browser has denied clipboard permission.
721
+ *
722
+ * Pass false for a host that would rather a failed copy be a failed copy: with
723
+ * it off, `cut` leaves the drawing alone when the write does not land, so a
724
+ * shape is never destroyed for a transfer that did not happen.
1270
725
  */
1271
726
  clipboardFallbackToMemory?: boolean;
1272
727
  /**
@@ -1279,13 +734,17 @@ interface DrawingControllerOptions {
1279
734
  pasteOffsetPixels?: number;
1280
735
  }
1281
736
  /** What `drawing:change` reports happened to the listed ids. */
1282
- type DrawingChangeKind = 'add' | 'update' | 'remove' | 'reorder';
737
+ type DrawingChangeKind = 'add' | 'update' | 'remove' | 'reorder' | 'undo' | 'redo';
1283
738
  declare class DrawingController {
1284
739
  private readonly _chart;
1285
740
  private _opts;
1286
741
  private readonly _clipboard;
1287
742
  private readonly _layers;
1288
743
  private _drawings;
744
+ private _groups;
745
+ private _nextGroup;
746
+ private readonly _linkedPreviews;
747
+ private _destroyed;
1289
748
  private _tool;
1290
749
  private _pending;
1291
750
  private _pendingPane;
@@ -1307,6 +766,7 @@ declare class DrawingController {
1307
766
  /** Snapshots for undo/redo; each is a full drawing list (they are small). */
1308
767
  private _undo;
1309
768
  private _redo;
769
+ private _pendingHistory;
1310
770
  /**
1311
771
  * One gesture's starting state. `items` are ids rather than objects because
1312
772
  * an undo mid-drag replaces every drawing object, and a stale reference
@@ -1346,8 +806,25 @@ declare class DrawingController {
1346
806
  * moves one; the list is the tie-break for equal `zIndex`, so it is also the
1347
807
  * paint order within a band.
1348
808
  */
809
+ /** Named drawing sets, returned as detached records. */
810
+ groups(): readonly DrawingGroup[];
811
+ /** Group live drawings, replacing any previous membership for those ids. */
812
+ createGroup(name: string, ids: readonly string[]): DrawingGroup | null;
813
+ renameGroup(id: string, name: string): boolean;
814
+ /** Remove a group, optionally deleting all its drawings as one edit. */
815
+ removeGroup(id: string, removeDrawings?: boolean): boolean;
816
+ /** Move one step through the rendered stack, preserving the side of the series. */
817
+ reorder(id: string, direction: -1 | 1): boolean;
818
+ private _remapPanes;
1349
819
  drawings(): readonly Drawing[];
1350
820
  get(id: string): Drawing | undefined;
821
+ get isDestroyed(): boolean;
822
+ /** Apply a linked commit without adding to this chart's local undo history. */
823
+ applyLinkedDrawing(id: string, drawing: Drawing | null): void;
824
+ /** A linked drag paints over its committed drawing without changing saved state. */
825
+ setLinkedPreview(id: string, drawing: Drawing | null): void;
826
+ /** Reorder related drawings inside their existing slots, preserving unrelated local order. */
827
+ reorderLinkedDrawings(ids: readonly string[]): void;
1351
828
  /** Supported numeric levels, independent of whether the queried time lies on the shape. */
1352
829
  alertInfo(id: string): AlertDrawingInfo;
1353
830
  /** Numeric drawing value using the actual pane projection and collapsed time axis. */
@@ -1457,6 +934,7 @@ declare class DrawingController {
1457
934
  private _offsetTime;
1458
935
  undo(): boolean;
1459
936
  redo(): boolean;
937
+ private _applyHistory;
1460
938
  /** Drop selected ids the model no longer holds, after a history jump. */
1461
939
  private _pruneSelection;
1462
940
  canUndo(): boolean;
@@ -1546,78 +1024,753 @@ declare class DrawingController {
1546
1024
  private _onClick;
1547
1025
  private _placePoint;
1548
1026
  /**
1549
- * Where a click at `point` actually lands for the armed tool: on the 45
1550
- * degree lock while Shift holds the free end of a line, else on the magnet
1551
- * when it pulls, else where it was. The lock wins over the magnet because a
1552
- * snapped price would bend the exact angle the lock exists to give.
1027
+ * Where a click at `point` actually lands for the armed tool: on the 45
1028
+ * degree lock while Shift holds the free end of a line, else on the magnet
1029
+ * when it pulls, else where it was. The lock wins over the magnet because a
1030
+ * snapped price would bend the exact angle the lock exists to give.
1031
+ */
1032
+ private _aimPoint;
1033
+ /**
1034
+ * The free end of a two-anchor line under angle lock, or null when the lock
1035
+ * does not apply: no Shift, a tool without the flag, no anchor yet to
1036
+ * measure from, or a host that cannot map pixels (the lock is a screen
1037
+ * angle, so there is nothing to lock to in data space).
1038
+ */
1039
+ private _lockedPoint;
1040
+ /**
1041
+ * The nearest O/H/L/C of the hovered bar, at that bar's time, when the
1042
+ * magnet pulls; null when it does not. `strong` always pulls; `weak` only
1043
+ * within a few pixels, measured on screen so the pull is the same reach at
1044
+ * every zoom. Price panes only: an indicator pane's values are not prices.
1045
+ */
1046
+ private _snapPoint;
1047
+ private _onDrag;
1048
+ private _emitDragPreview;
1049
+ /** Roll back an interrupted drag and leave the pre-gesture undo/redo stacks intact. */
1050
+ cancelDrag(): boolean;
1051
+ /** Apply one drag frame to the model, from the gesture's snapshot. */
1052
+ private _moveDrag;
1053
+ /** How far down the screen `to` is from `from` on one pane, in media px. */
1054
+ private _pixelDelta;
1055
+ /** An anchor in container media px, or null on a host without the mapping. */
1056
+ private _toPixel;
1057
+ /** The inverse of `_toPixel`. */
1058
+ private _fromPixel;
1059
+ /**
1060
+ * `free` projected onto the nearest 45 degree ray from `anchor`, all in
1061
+ * screen space: the angle the eye reads is the one on the canvas, and a log
1062
+ * scale or a tall pane would make a data-space angle anything but. The
1063
+ * projection rather than a rotation, so a level line still ends under the
1064
+ * pointer's x and a vertical one under its y; only the stray axis is
1065
+ * dropped. Null when the host cannot map pixels, or the two coincide.
1066
+ */
1067
+ private _lockAngle;
1068
+ private _onDragEnd;
1069
+ /**
1070
+ * The pair of layers for a pane, made on first use. The bottom one is added
1071
+ * first so a host that lists primitives sees them in paint order; the top one
1072
+ * adopts it so handles and hit-tests come from one place.
1073
+ */
1074
+ private _layerFor;
1075
+ /** Whether a drawing paints on the top layer: over the series, or lifted for a drag. */
1076
+ private _onTop;
1077
+ /** Push the current list into each pane's layers and into the chart state. */
1078
+ private _sync;
1079
+ /**
1080
+ * The per-frame half of a drag: only the top layers of the panes the
1081
+ * gesture touches are re-listed, so the repaint stays on the cursor tier.
1082
+ * Everything moving is on a top layer by then (anything under the series
1083
+ * was lifted when the gesture began), and the bottom layers have not
1084
+ * changed since, so re-listing them would cost a series repaint for
1085
+ * nothing.
1086
+ */
1087
+ private _syncDrag;
1088
+ /**
1089
+ * Mirror the in-progress anchors (plus the cursor) into the preview slot.
1090
+ * The cursor point goes through the same aim as a click would, so the
1091
+ * preview shows the locked angle or the snapped anchor before it lands.
1092
+ */
1093
+ private _syncPreview;
1094
+ /**
1095
+ * Show where the magnet will land the next click, or nothing. A ring only
1096
+ * while a click would place an anchor: a tool armed, not a brush (which
1097
+ * inks where the pointer is), and the pull actually applying at the cursor.
1098
+ * Angle lock bypasses the magnet, so it hides the ring too.
1099
+ */
1100
+ private _syncSnapRing;
1101
+ private _pushUndo;
1102
+ }
1103
+
1104
+ /** Both fields must be known. A host may use its feed namespace as the exchange. */
1105
+ interface DrawingLinkContext {
1106
+ symbol?: string;
1107
+ exchange?: string;
1108
+ }
1109
+ /** Resolve a host-owned identity without modifying the chart's data context. */
1110
+ type DrawingLinkContextSource = DrawingLinkContext | (() => DrawingLinkContext | null | undefined);
1111
+ interface DrawingLinkOptions {
1112
+ enabled?: boolean;
1113
+ }
1114
+ interface DrawingLinkChart extends Pick<DrawingChartHost, 'on'> {
1115
+ readonly isDestroyed?: boolean;
1116
+ getDataContext?(): Readonly<DrawingLinkContext> | undefined;
1117
+ }
1118
+ /**
1119
+ * Existing local drawings stay local until `share` is called. Persisted shared
1120
+ * lineage reconnects across restores. Only price-pane drawings participate; time anchors
1121
+ * cross the boundary unchanged so each interval uses its own coordinate map.
1122
+ */
1123
+ declare class DrawingLinkGroup {
1124
+ private readonly _members;
1125
+ private _enabled;
1126
+ private _broadcasting;
1127
+ private _destroyed;
1128
+ constructor(options?: DrawingLinkOptions);
1129
+ options(): Required<DrawingLinkOptions>;
1130
+ add(chart: DrawingLinkChart, controller: DrawingController, context?: DrawingLinkContextSource): void;
1131
+ setOptions(options: DrawingLinkOptions): void;
1132
+ /** Report the current instrument. Unknown identity stops sharing immediately. */
1133
+ setContext(chart: DrawingLinkChart, context: DrawingLinkContext | undefined): void;
1134
+ /** Share selected ids, or every existing eligible drawing when ids are omitted. */
1135
+ share(chart: DrawingLinkChart, ids?: readonly string[]): number;
1136
+ has(chart: DrawingLinkChart): boolean;
1137
+ /** Committed drawings remain on peers; transient previews are discarded. */
1138
+ remove(chart: DrawingLinkChart): void;
1139
+ destroy(): void;
1140
+ private _eligible;
1141
+ private _create;
1142
+ private _reconnect;
1143
+ private _change;
1144
+ private _commit;
1145
+ private _preview;
1146
+ private _syncOrder;
1147
+ private _orderFrom;
1148
+ private _clearPreview;
1149
+ private _clearMemberPreviews;
1150
+ private _detachDrawings;
1151
+ }
1152
+ declare function createDrawingLinkGroup(options?: DrawingLinkOptions): DrawingLinkGroup;
1153
+
1154
+ /**
1155
+ * Built-in drawing tools + the tool registry. Same philosophy as the chart-type
1156
+ * and indicator registries: a tool is a descriptor, the layer just runs it, and
1157
+ * `registerDrawingTool` makes a custom one first-class.
1158
+ *
1159
+ * `draw` receives anchors already in device px; `distance` receives them in
1160
+ * media px, the same space as the incoming cursor.
1161
+ *
1162
+ * Text lives in `drawing.text` (a `DrawingText`), never in the style bag: a
1163
+ * shape's outline colour and its label colour are two decisions, and a tool
1164
+ * that prints only a readout (a fib ladder, a measure chip) still reads its
1165
+ * face from there. Ladders read `FibLevel[]` and stroke each level in its own
1166
+ * colour, taken from the shared palette in levels.ts.
1167
+ *
1168
+ * Every tool declares `settings`: the fields a host may show for it. The
1169
+ * schema is a contract, not a wish list. A field is declared only when this
1170
+ * file reads it, because a control with nothing behind it is a defect.
1171
+ */
1172
+
1173
+ declare function registerDrawingTool(tool: DrawingTool): void;
1174
+ declare function getDrawingTool(id: string): DrawingTool;
1175
+ declare function hasDrawingTool(id: string): boolean;
1176
+ declare function registeredDrawingTools(): DrawingTool[];
1177
+ /** Keyboard event fields a shortcut is matched against. */
1178
+ interface ShortcutEvent {
1179
+ key: string;
1180
+ altKey?: boolean;
1181
+ ctrlKey?: boolean;
1182
+ metaKey?: boolean;
1183
+ shiftKey?: boolean;
1184
+ }
1185
+ /**
1186
+ * The id of the tool whose `shortcut` matches this key event, or `null`.
1187
+ *
1188
+ * Pure, so a host can bind one `keydown` listener and decide for itself when
1189
+ * shortcuts apply. The library installs no listener, because only the host
1190
+ * knows whether the chart has focus, a dialog is open, or the user is typing.
1191
+ *
1192
+ * Modifiers must match exactly: `Alt+T` will not fire for `Ctrl+Alt+T`, so a
1193
+ * tool shortcut cannot shadow a browser or host chord. `metaKey` (Cmd) is
1194
+ * treated as Ctrl, which is what a Mac user expects.
1195
+ */
1196
+ declare function matchDrawingShortcut(e: ShortcutEvent): string | null;
1197
+ /** Every registered tool that has a shortcut, as `id -> shortcut`. */
1198
+ declare function drawingShortcuts(): Record<string, string>;
1199
+ /**
1200
+ * The settings a host may show for a tool: the tool's own declaration, else
1201
+ * the line fields, which every stroked custom tool reads. This is a registry
1202
+ * lookup, which is why it lives here and not in schema.ts: that module stays
1203
+ * free of any import that could loop back into this one.
1204
+ */
1205
+ declare function drawingSettingsSchema(toolId: string): SettingsSchema;
1206
+ declare const TREND_LINE: DrawingTool;
1207
+ declare const RAY: DrawingTool;
1208
+ declare const EXTENDED_LINE: DrawingTool;
1209
+ declare const ARROW: DrawingTool;
1210
+ declare const HORIZONTAL_LINE: DrawingTool;
1211
+ declare const HORIZONTAL_RAY: DrawingTool;
1212
+ declare const VERTICAL_LINE: DrawingTool;
1213
+ declare const CROSS_LINE: DrawingTool;
1214
+ declare const RECTANGLE: DrawingTool;
1215
+ declare const ELLIPSE: DrawingTool;
1216
+ declare const PARALLEL_CHANNEL: DrawingTool;
1217
+ declare const FIB_RETRACEMENT: DrawingTool;
1218
+ declare const FIB_EXTENSION: DrawingTool;
1219
+ declare const MEASURE: DrawingTool;
1220
+ declare const LONG_POSITION: DrawingTool;
1221
+ declare const SHORT_POSITION: DrawingTool;
1222
+ declare const TEXT: DrawingTool;
1223
+ /**
1224
+ * Path: click each vertex, double-click to finish, arrowhead on the last leg.
1225
+ * The arrow is what separates it from `polyline`: a path points somewhere.
1226
+ */
1227
+ declare const PATH: DrawingTool;
1228
+ /**
1229
+ * Brush: freehand ink. Press, drag, release. The anchors are the thinned
1230
+ * samples of the gesture and the spline between them is what is stroked, so
1231
+ * the ink stays a curve after the thinning that keeps a stroke small.
1232
+ */
1233
+ declare const BRUSH: DrawingTool;
1234
+ /**
1235
+ * Rotated rectangle: anchors 0 and 1 lay out one edge (and so the rotation),
1236
+ * and anchor 2 sets the depth perpendicular to it. An axis-aligned `rectangle`
1237
+ * cannot follow a trend channel; this can.
1238
+ */
1239
+ declare const ROTATED_RECTANGLE: DrawingTool;
1240
+ /**
1241
+ * Double curve: an S through three anchors. `curve` bends one way off a single
1242
+ * control; this mirrors that control about the midpoint so the second half bends
1243
+ * back, which is the shape a rounded top-then-bottom actually needs.
1244
+ */
1245
+ declare const DOUBLE_CURVE: DrawingTool;
1246
+ /**
1247
+ * Cyclic lines: vertical lines repeating at the interval the two anchors set,
1248
+ * for reading a rhythm forward off a measured swing.
1249
+ */
1250
+ declare const CYCLIC_LINES: DrawingTool;
1251
+ /**
1252
+ * Time cycles: semicircles of the anchors' width repeating along the axis, the
1253
+ * classic cycle-projection overlay.
1254
+ */
1255
+ declare const TIME_CYCLES: DrawingTool;
1256
+ /**
1257
+ * Sine line: a full wave between the anchors. The horizontal span is one
1258
+ * period, the vertical offset its amplitude.
1259
+ */
1260
+ declare const SINE_LINE: DrawingTool;
1261
+ /**
1262
+ * Price label: a pill of the anchored price with a tail pointing at the bar.
1263
+ * Reads its value off the anchor, so dragging it re-reads rather than going
1264
+ * stale the way a typed-in text would. Text, when given, replaces the price.
1265
+ */
1266
+ declare const PRICE_LABEL: DrawingTool;
1267
+ /**
1268
+ * Callout: a text bubble on its own anchor with a tail back to the point it
1269
+ * annotates, so the note can sit clear of the price action it refers to.
1270
+ */
1271
+ declare const CALLOUT: DrawingTool;
1272
+ /** Flag mark: a pennant on a pole at one anchor, for tagging a bar. */
1273
+ declare const FLAG_MARK: DrawingTool;
1274
+ /**
1275
+ * Note: a pin at the bar with its text to the upper right.
1276
+ *
1277
+ * The pin is the point being annotated and the plate is the annotation, which
1278
+ * is why the two are drawn as separate marks joined by a stem rather than as
1279
+ * one bubble: the reader needs to see exactly which bar is meant, and a bubble
1280
+ * wide enough to hold a sentence covers several.
1281
+ */
1282
+ declare const NOTE: DrawingTool;
1283
+ /**
1284
+ * Balloon: a speech bubble sitting above its anchor, tail pointing down.
1285
+ *
1286
+ * One anchor rather than the callout's two: a balloon is for saying something
1287
+ * *about this bar*, so letting the bubble be dragged away from what it labels
1288
+ * would only invite it to drift.
1289
+ */
1290
+ declare const BALLOON: DrawingTool;
1291
+ /**
1292
+ * Comment: a small square-cornered box with a tail off its bottom left.
1293
+ *
1294
+ * Deliberately plainer than the balloon: a chart that says something at every
1295
+ * other bar needs one of these marks to be quiet, and the shape is the only
1296
+ * thing distinguishing them once both are the user's own colour.
1297
+ */
1298
+ declare const COMMENT: DrawingTool;
1299
+ /**
1300
+ * Signpost: a post standing on the bar with its plate at the top.
1301
+ *
1302
+ * The one annotation anchored to *time* rather than to a level: the post is
1303
+ * vertical so the plate can clear the price action entirely while the foot
1304
+ * still names an exact bar. Useful for events, which happen at a moment and
1305
+ * not at a price.
1306
+ */
1307
+ declare const SIGNPOST: DrawingTool;
1308
+ /**
1309
+ * Price note: the anchored price, with the user's text under it.
1310
+ *
1311
+ * The price is read off the anchor rather than typed, so dragging the note
1312
+ * re-reads it. A typed price is a number that was true once, which on a chart
1313
+ * is worse than no number at all.
1314
+ */
1315
+ declare const PRICE_NOTE: DrawingTool;
1316
+ /**
1317
+ * Table: rows of text in a grid, anchored top-left.
1318
+ *
1319
+ * Cells come from the text: a newline starts a row and a pipe separates
1320
+ * columns, so a whole table is one editable string and survives `getState`
1321
+ * with no new shape in the drawing model. The first row is drawn as a header
1322
+ * because a table on a chart is nearly always labelled.
1323
+ */
1324
+ declare const TABLE: DrawingTool;
1325
+ /**
1326
+ * Measurers. `measure` reports price *and* time together; these two constrain it
1327
+ * to one axis, which is what you want when the other one is noise: sizing a
1328
+ * retracement without caring how long it took, or counting bars to an event
1329
+ * without caring where price went.
1330
+ */
1331
+ declare const PRICE_RANGE: DrawingTool;
1332
+ declare const DATE_RANGE: DrawingTool;
1333
+ /**
1334
+ * Position forecast: project a move from an anchor. Two anchors give the
1335
+ * projected leg; the shape extends the same slope past the target as a dashed
1336
+ * cone, so the drawing says "if this continues" rather than just marking a line.
1337
+ */
1338
+ declare const FORECAST: DrawingTool;
1339
+ /**
1340
+ * Circle from centre + a radius handle. The radius is measured in *pixels*, so
1341
+ * it stays a circle on screen instead of the ellipse the two axes' differing
1342
+ * scales would otherwise produce.
1343
+ */
1344
+ declare const CIRCLE: DrawingTool;
1345
+ declare const TRIANGLE: DrawingTool;
1346
+ /** Free-form polyline: click each vertex, double-click to finish. */
1347
+ declare const POLYLINE: DrawingTool;
1348
+ /** Arc through three anchors: the middle one is on the curve, not a handle. */
1349
+ declare const ARC: DrawingTool;
1350
+ /** Quadratic curve: the middle anchor is a control handle, off the curve. */
1351
+ declare const CURVE: DrawingTool;
1352
+ declare const ARROW_UP: DrawingTool;
1353
+ declare const ARROW_DOWN: DrawingTool;
1354
+ declare const ARROW_LEFT: DrawingTool;
1355
+ declare const ARROW_RIGHT: DrawingTool;
1356
+ /** Highlighter: the brush with a fat, translucent stroke. */
1357
+ declare const HIGHLIGHTER: DrawingTool;
1358
+ /** Fib channel: fib levels spread across a trend leg, parallel to it. */
1359
+ declare const FIB_CHANNEL: DrawingTool;
1360
+ /**
1361
+ * Fib time zone: vertical lines at Fibonacci multiples of the base leg's width.
1362
+ * Its levels count bars rather than divide a leg, so they carry no convention
1363
+ * colour and fall back to the drawing's own.
1364
+ */
1365
+ declare const FIB_TIME_ZONE: DrawingTool;
1366
+ /** Rays from the anchor at fib fractions of the leg: the speed resistance fan. */
1367
+ declare const FIB_FAN: DrawingTool;
1368
+ /**
1369
+ * Gann fan: rays from one anchor at the classic price/time angles. A level's
1370
+ * ratio is price per unit of time, so the 1x1 is 1, the 1x2 is 0.5 and the
1371
+ * 2x1 is 2; each angle keeps its own colour from the cycle palette.
1372
+ */
1373
+ declare const GANN_FAN: DrawingTool;
1374
+ /**
1375
+ * Gann box: the drawn rectangle divided at its levels on both axes, plus the
1376
+ * 1x1 diagonal. Ratio 0 is the first anchor's price and time, ratio 1 the
1377
+ * second's, so the box reads like a retracement laid over a date range.
1378
+ */
1379
+ declare const GANN_BOX: DrawingTool;
1380
+ declare const BUILTIN_DRAWING_TOOLS: readonly DrawingTool[];
1381
+ /** Register every built-in tool. Idempotent; called on tier import. */
1382
+ declare function registerBuiltinDrawingTools(): void;
1383
+
1384
+ /**
1385
+ * The shared level palette for the Fibonacci and Gann family.
1386
+ *
1387
+ * Every ladder tool (retracement, extension, channel, fan, time zone, the Gann
1388
+ * fan and box) strokes a list of ratios, and each ratio has a colour the eye
1389
+ * expects: 0.618 reads as one thing across every tool, not a different hue per
1390
+ * tool. Before this module each tool would have restated those colours, and
1391
+ * restated colours drift. This is the one statement; tools take their defaults
1392
+ * from it and hosts seed a newly added level from it.
1393
+ *
1394
+ * Pure: no DOM, no registry. Safe to import from a worker or a settings panel.
1395
+ */
1396
+
1397
+ /**
1398
+ * The colour of a ratio that is an anchor rather than a level (0, 1, 2, 3 are
1399
+ * where the measured leg starts and ends, or whole multiples of it) and of any
1400
+ * ratio the convention does not name. Neutral on purpose: it is the reference
1401
+ * the coloured levels are read against.
1402
+ */
1403
+ declare const LEVEL_NEUTRAL = "#787b86";
1404
+ /**
1405
+ * The conventional colour for a Fibonacci ratio. Anchors (0, 1, 2, 3) and any
1406
+ * ratio without a convention come back {@link LEVEL_NEUTRAL}, so a host can call
1407
+ * this for every new level and always get a usable colour.
1408
+ */
1409
+ declare function levelColor(ratio: number): string;
1410
+ /**
1411
+ * A short sequence of colours that stay distinguishable side by side, for
1412
+ * tools whose levels are positions rather than ratios: the angles of a Gann
1413
+ * fan, the repeats of a cycle. Index `i` wraps, so any count is safe.
1414
+ */
1415
+ declare const CYCLE_PALETTE: readonly string[];
1416
+ /** The `i`th cycle colour. Wraps in both directions; a negative index is fine. */
1417
+ declare function cycleColor(i: number): string;
1418
+ /** `0.618` as `61.8%`: the text a level prints when it carries no label. */
1419
+ declare function formatRatio(ratio: number): string;
1420
+ /**
1421
+ * The label for a Gann angle stored as a price-per-time ratio: `1` is the 1x1,
1422
+ * `0.5` the 1x2, `2` the 2x1. Ratios that are not clean reciprocals fall back
1423
+ * to the ratio itself, which is still true, just less idiomatic.
1424
+ */
1425
+ declare function gannLabel(ratio: number): string;
1426
+ /** A mutable copy of a default ladder, for a fresh drawing's own `style.levels`. */
1427
+ declare function cloneLevels(levels: readonly FibLevel[]): FibLevel[];
1428
+ /** Retracement and extension: the classic ladder, coloured by convention. */
1429
+ declare const DEFAULT_FIB: readonly FibLevel[];
1430
+ /** Speed fan: rays at the fractions of the leg. No 0 ray, it would be flat. */
1431
+ declare const DEFAULT_FIB_FAN: readonly FibLevel[];
1432
+ /** Gann box: quarters plus the golden pair, applied to both axes of the box. */
1433
+ declare const DEFAULT_GANN_BOX: readonly FibLevel[];
1434
+ /**
1435
+ * Gann fan: the classic angles as price-per-time ratios, 1x8 up to 8x1. These
1436
+ * are positions in a sequence rather than Fibonacci ratios, so they take the
1437
+ * cycle palette.
1438
+ */
1439
+ declare const DEFAULT_GANN_FAN: readonly FibLevel[];
1440
+ /**
1441
+ * Time zones count bars along the Fibonacci sequence, so `ratio` here is a
1442
+ * multiple of the anchor leg, not a fraction of it. No colour is set: there is
1443
+ * no convention for "the 13 line", so every zone takes the drawing's own colour
1444
+ * until the user gives one its own.
1445
+ */
1446
+ declare const DEFAULT_FIB_TIME_ZONE: readonly FibLevel[];
1447
+
1448
+ /**
1449
+ * Glyphs for the drawing tools and for host chrome, as path data.
1450
+ *
1451
+ * The engine ships no DOM, and this does not change that: an icon here is a
1452
+ * string of SVG path commands, not an element. The host still builds its own
1453
+ * toolbar, flyouts and rail; what it no longer has to do is draw every glyph
1454
+ * before it can show them. `icon-svg.ts` turns these into markup strings for a
1455
+ * host that wants the convenience; this file is the single source both of them
1456
+ * and every other surface read from.
1457
+ *
1458
+ * That was the real cost of leaving them out. Every adopter drew their own set,
1459
+ * each drifted on stroke weight, grid and visual density independently, and the
1460
+ * result read as sixty icons rather than as one set, however carefully any
1461
+ * single glyph was made. A shipped set is worth more than a better glyph.
1462
+ *
1463
+ * # Two tiers, one weight
1464
+ *
1465
+ * Tool glyphs live on a 24-unit grid and are shown at 24px in the rail. Host
1466
+ * chrome (undo, close, the settings tab rail) is shown at 16px beside them, and
1467
+ * a 24-grid glyph scaled to 16 lands its 2-unit stroke on 1.33 pixels, blurred
1468
+ * across two rows on every edge. So chrome has its own registry drawn on a
1469
+ * 16-unit grid with a 1.5 stroke. The two strokes are deliberately not in exact
1470
+ * proportion: a small glyph needs a slightly heavier relative stroke than a
1471
+ * large one to read as the same weight, and `tests/draw-icons.test.ts` holds
1472
+ * the chrome tier inside that band rather than at parity.
1473
+ *
1474
+ * # The grid
1475
+ *
1476
+ * Every glyph is authored to the same constraints, and `tests/draw-icons.test.ts`
1477
+ * enforces all of them mechanically, because a set of this size cannot be kept
1478
+ * consistent by review:
1479
+ *
1480
+ * - **One viewBox per tier.** 24 by 24 for tools, 16 by 16 for chrome, so a
1481
+ * host sets the size once per surface.
1482
+ * - **A margin all round.** Live area 2 to 22 on the tool grid, 1 to 15 on the
1483
+ * chrome grid. Without it, glyphs that happen to reach the edge look larger
1484
+ * than their neighbours and the rail reads as ragged.
1485
+ * - **Integer coordinates.** With `STROKE` of 2, an orthogonal edge centred on
1486
+ * an integer covers exactly two device pixels at 1:1, which is what makes it
1487
+ * crisp. Half-unit coordinates were the previous set's crispness bug: at a
1488
+ * 1.5 stroke on integers, nothing landed on a pixel boundary at any size.
1489
+ * - **One stroke weight per tier.** Three different weights across identically
1490
+ * sized boxes is the single most visible tell of a set assembled rather than
1491
+ * drawn.
1492
+ *
1493
+ * # Rendering
1494
+ *
1495
+ * Tool glyphs are crispest at 24px, and at any integer multiple of it. At 18px
1496
+ * the 0.75 scale puts a 2-unit stroke on 1.5 device pixels and every edge is
1497
+ * anti-aliased across two rows: that is a host sizing choice, not something the
1498
+ * path data can fix. Prefer 24, or 12 with a heavier weight. Chrome glyphs are
1499
+ * drawn for 16px and its multiples.
1500
+ */
1501
+ /** The viewBox every glyph is authored in. */
1502
+ declare const ICON_VIEWBOX = "0 0 24 24";
1503
+ /**
1504
+ * Stroke width in viewBox units. Paths carry no presentation attributes, so a
1505
+ * host that wants a lighter set overrides this once rather than editing glyphs.
1506
+ */
1507
+ declare const ICON_STROKE = 2;
1508
+ /**
1509
+ * The attribute bag a tier hands its host. Structural, so the markup builders
1510
+ * in `icon-svg.ts` take either tier's bag through one signature.
1511
+ */
1512
+ interface IconAttrs {
1513
+ readonly viewBox: string;
1514
+ readonly fill: 'none';
1515
+ readonly stroke: 'currentColor';
1516
+ readonly strokeWidth: number;
1517
+ readonly strokeLinecap: 'round';
1518
+ readonly strokeLinejoin: 'round';
1519
+ }
1520
+ /** Attributes a host should apply to the `<svg>`, so every set matches. */
1521
+ declare const ICON_ATTRS: {
1522
+ readonly viewBox: "0 0 24 24";
1523
+ readonly fill: "none";
1524
+ readonly stroke: "currentColor";
1525
+ readonly strokeWidth: 2;
1526
+ readonly strokeLinecap: "round";
1527
+ readonly strokeLinejoin: "round";
1528
+ };
1529
+ /**
1530
+ * Path data by tool id, plus a few keys a host toolbar needs that are not
1531
+ * themselves tools (`cursor`, the group headers).
1532
+ *
1533
+ * Values are the `d` attribute of one `<path>`. Multiple subpaths are joined
1534
+ * into the same string rather than split across elements, so a host renders one
1535
+ * node per glyph.
1536
+ */
1537
+ declare const DRAWING_TOOL_ICONS: Readonly<Record<string, string>>;
1538
+ /**
1539
+ * The glyph for a tool, or `undefined` when it has none.
1540
+ *
1541
+ * Undefined rather than a placeholder: a host that renders an empty box has a
1542
+ * visible gap to fix, while one handed a question mark ships it.
1543
+ */
1544
+ declare function drawingToolIcon(toolId: string): string | undefined;
1545
+ /** Every id this set covers, for a host building a palette from it. */
1546
+ declare function drawingToolIconIds(): string[];
1547
+ /** The viewBox every chrome glyph is authored in. */
1548
+ declare const CHROME_ICON_VIEWBOX = "0 0 16 16";
1549
+ /**
1550
+ * Chrome stroke width in viewBox units. Not the tool stroke scaled (that would
1551
+ * be 1.33): at 16px a glyph needs a touch more relative weight than at 24px to
1552
+ * read as the same line, and 1.5 is where the two rails match by eye.
1553
+ */
1554
+ declare const CHROME_ICON_STROKE = 1.5;
1555
+ /** Attributes a host should apply to the `<svg>` around a chrome glyph. */
1556
+ declare const CHROME_ICON_ATTRS: {
1557
+ readonly viewBox: "0 0 16 16";
1558
+ readonly fill: "none";
1559
+ readonly stroke: "currentColor";
1560
+ readonly strokeWidth: 1.5;
1561
+ readonly strokeLinecap: "round";
1562
+ readonly strokeLinejoin: "round";
1563
+ };
1564
+ /**
1565
+ * Path data for host chrome: the buttons around the chart rather than the
1566
+ * tools in it. Same rules as the tool tier (one path per glyph, no presentation
1567
+ * attributes, integer coordinates) on the 16 grid with a 1..15 live area.
1568
+ *
1569
+ * The pair `cursor` / `magnet` / `text` also exist in the tool registry, at 24.
1570
+ * They are drawn twice on purpose: a rail button and a toolbar button are
1571
+ * different sizes, and the whole point of a second grid is not scaling one
1572
+ * drawing to both.
1573
+ */
1574
+ declare const CHROME_ICONS: Readonly<Record<string, string>>;
1575
+ /**
1576
+ * Chrome glyphs meant to be painted solid. Paths carry no presentation
1577
+ * attributes, so the fill is applied by the wrapper (`chromeIconSvg` does it,
1578
+ * a host with its own wrapper reads this set) and the registry stays pure.
1579
+ */
1580
+ declare const CHROME_ICON_FILLED: ReadonlySet<string>;
1581
+ /** The chrome glyph for an id, or `undefined` when there is none. */
1582
+ declare function chromeIcon(id: string): string | undefined;
1583
+ /** Every id the chrome tier covers. */
1584
+ declare function chromeIconIds(): string[];
1585
+
1586
+ /** Prefix of every `<symbol id>` in the sprite, and of every `<use href>`. */
1587
+ declare const ICON_SYMBOL_PREFIX = "oac-icon-";
1588
+ /** Options shared by the inline builders. */
1589
+ interface IconSvgOptions {
1590
+ /**
1591
+ * Width and height. A number is CSS pixels; a string is written verbatim, so
1592
+ * `'1em'` sizes the glyph with the surrounding text. Default: the grid's
1593
+ * native size (24 for tools, 16 for chrome), where the stroke is crispest.
1553
1594
  */
1554
- private _aimPoint;
1595
+ size?: number | string;
1596
+ /** Stroke width in viewBox units. Default: the tier's `*_STROKE`. */
1597
+ stroke?: number;
1598
+ /** A class for the host's own styling. */
1599
+ className?: string;
1600
+ }
1601
+ /** Options for `toolCursor`. */
1602
+ interface ToolCursorOptions {
1603
+ /** Cursor image size in CSS pixels. Default 20. Browsers reject images over 128. */
1604
+ size?: number;
1605
+ /** The pointer's active pixel, from the image's top-left. Default: the centre. */
1606
+ hotspot?: readonly [number, number];
1607
+ /** Glyph colour. Default white. */
1608
+ color?: string;
1555
1609
  /**
1556
- * The free end of a two-anchor line under angle lock, or null when the lock
1557
- * does not apply: no Shift, a tool without the flag, no anchor yet to
1558
- * measure from, or a host that cannot map pixels (the lock is a screen
1559
- * angle, so there is nothing to lock to in data space).
1610
+ * Halo colour. Default: black under a light `color`, white under a dark one,
1611
+ * judged from a hex colour. A non-hex `color` gets a black halo unless this
1612
+ * is set.
1560
1613
  */
1561
- private _lockedPoint;
1614
+ halo?: string;
1615
+ /** The keyword after the image, for a browser that will not take it. Default `crosshair`. */
1616
+ fallback?: string;
1617
+ }
1618
+ /**
1619
+ * A complete inline `<svg>` for one tool glyph, stroked in `currentColor`.
1620
+ *
1621
+ * ```ts
1622
+ * button.innerHTML = iconSvg('trend-line');
1623
+ * button.innerHTML = iconSvg('trend-line', { size: '1em' });
1624
+ * ```
1625
+ */
1626
+ declare function iconSvg(id: string, opts?: IconSvgOptions): string;
1627
+ /**
1628
+ * A complete inline `<svg>` for one chrome glyph, on the 16 grid. Glyphs in
1629
+ * `CHROME_ICON_FILLED` are painted solid as well as stroked.
1630
+ */
1631
+ declare function chromeIconSvg(id: string, opts?: IconSvgOptions): string;
1632
+ /**
1633
+ * One hidden `<svg>` of `<symbol>`s, for a host that shows many glyphs and
1634
+ * wants each path in the document once. Inject it once, then place glyphs with
1635
+ * `iconUse`. Symbols carry the path and viewBox only; stroke and fill inherit
1636
+ * from the `<svg>` around each `<use>`, so one sprite serves every weight.
1637
+ *
1638
+ * Default: every tool glyph. Repeated ids collapse to one symbol, since a
1639
+ * duplicate element id is an invalid document.
1640
+ */
1641
+ declare function iconSprite(ids?: readonly string[]): string;
1642
+ /** An `<svg>` that references a symbol from `iconSprite`. Same frame as `iconSvg`. */
1643
+ declare function iconUse(id: string, opts?: IconSvgOptions): string;
1644
+ /**
1645
+ * A CSS `cursor` value carrying the tool's glyph, for the canvas while that
1646
+ * tool is armed.
1647
+ *
1648
+ * The glyph is drawn twice: once in `halo`, one pixel wider on each side, and
1649
+ * once in `color` on top, so it reads on a light chart and a dark one without
1650
+ * the host choosing per theme. The value is `url("data:image/svg+xml,...") x y,
1651
+ * fallback`, ready for `canvas.style.cursor`.
1652
+ */
1653
+ declare function toolCursor(id: string, opts?: ToolCursorOptions): string;
1654
+
1655
+ /**
1656
+ * A `DrawingLayer` renders the drawings of one pane and answers the chart's
1657
+ * hit-test. Anchors live in `{ time, price }`, so the layer maps them through
1658
+ * the *fractional* index helpers: the gapless axis means an anchor can sit
1659
+ * between bars (inside a collapsed weekend) or past the last one (a forward
1660
+ * projection), and whole-index lookups have nothing to return there.
1661
+ *
1662
+ * Each pane carries **two** layers, one at z-order `bottom` for drawings with a
1663
+ * negative `zIndex` (under the series) and one at `top` for the rest. The top
1664
+ * one takes over handles and hit-testing for the pair through `setBelow`, so a
1665
+ * shape parked under the candles still shows its grab handles above them and
1666
+ * a click lands on whatever the eye sees on top.
1667
+ *
1668
+ * Hit ids are `draw:<id>` for the body and `draw:<id>#<n>` for anchor `n`, so
1669
+ * the controller can tell "move the whole shape" from "move this handle".
1670
+ *
1671
+ * The hover ring, the magnet ring and the hover handles are all overlay
1672
+ * state: they change on every pointer move, so they must only ever cost the
1673
+ * top canvas. The layer that paints them is the one whose `requestUpdate`
1674
+ * the chart maps to the cursor tier, which is why the bottom layer of a pair
1675
+ * stores that state and asks for nothing.
1676
+ */
1677
+
1678
+ /** Which side of the series a layer paints on. */
1679
+ type DrawingLayerOrder = 'bottom' | 'top';
1680
+ /** The pointer kinds the layer sizes its targets for. */
1681
+ type DrawingPointerKind = 'mouse' | 'touch' | 'pen';
1682
+ /**
1683
+ * Paint order: by `zIndex`, ties by array position. `Array.prototype.sort` is
1684
+ * stable, which is what makes the tie rule hold without a second key.
1685
+ */
1686
+ declare function sortByZIndex(drawings: readonly Drawing[]): Drawing[];
1687
+ declare class DrawingLayer implements IPrimitive {
1688
+ private readonly _order;
1689
+ /** In paint order. */
1690
+ private _drawings;
1691
+ private _host;
1692
+ private _selected;
1693
+ /** The drawing under the pointer, whether or not it is selected. */
1694
+ private _hovered;
1695
+ /** Where the next click will land while the magnet is pulling it. */
1696
+ private _snap;
1697
+ /** Anchors of the in-progress drawing, plus the live cursor point. */
1698
+ private _preview;
1699
+ /** The layer under this one, whose handles and hits this layer answers for. */
1700
+ private _below;
1701
+ /** The layer that has taken over this one's handles and hit-testing. */
1702
+ private _above;
1703
+ /** Sizes grab targets for the device last seen. */
1704
+ private _touch;
1705
+ constructor(order?: DrawingLayerOrder);
1706
+ attached(host: PrimitiveHost): void;
1707
+ detached(): void;
1708
+ zOrder(): ZOrder;
1709
+ /** Drawings overlay the price range; they never drive it. */
1710
+ autoscaleInfo(): null;
1711
+ setDrawings(drawings: readonly Drawing[]): void;
1712
+ setSelected(ids: readonly string[]): void;
1562
1713
  /**
1563
- * The nearest O/H/L/C of the hovered bar, at that bar's time, when the
1564
- * magnet pulls; null when it does not. `strong` always pulls; `weak` only
1565
- * within a few pixels, measured on screen so the pull is the same reach at
1566
- * every zoom. Price panes only: an indicator pane's values are not prices.
1714
+ * The drawing under the pointer. Its handles paint at a lighter weight than
1715
+ * a selection's, as the hint that it can be grabbed. Repaints only from the
1716
+ * layer that paints handles: the adopted bottom layer keeps the id for its
1717
+ * own bookkeeping and asks for nothing, since its repaint costs the series.
1567
1718
  */
1568
- private _snapPoint;
1569
- private _onDrag;
1570
- /** Apply one drag frame to the model, from the gesture's snapshot. */
1571
- private _moveDrag;
1572
- /** How far down the screen `to` is from `from` on one pane, in media px. */
1573
- private _pixelDelta;
1574
- /** An anchor in container media px, or null on a host without the mapping. */
1575
- private _toPixel;
1576
- /** The inverse of `_toPixel`. */
1577
- private _fromPixel;
1719
+ setHovered(id: string | null): void;
1720
+ hovered(): string | null;
1578
1721
  /**
1579
- * `free` projected onto the nearest 45 degree ray from `anchor`, all in
1580
- * screen space: the angle the eye reads is the one on the canvas, and a log
1581
- * scale or a tall pane would make a data-space angle anything but. The
1582
- * projection rather than a rotation, so a level line still ends under the
1583
- * pointer's x and a vertical one under its y; only the stray axis is
1584
- * dropped. Null when the host cannot map pixels, or the two coincide.
1722
+ * The point the magnet will snap the next click to, or null when it is not
1723
+ * pulling. Painted as a hollow ring so the user sees the anchor land before
1724
+ * committing to it.
1585
1725
  */
1586
- private _lockAngle;
1587
- private _onDragEnd;
1726
+ setSnapPoint(point: DrawingPoint | null): void;
1727
+ snapPoint(): DrawingPoint | null;
1588
1728
  /**
1589
- * The pair of layers for a pane, made on first use. The bottom one is added
1590
- * first so a host that lists primitives sees them in paint order; the top one
1591
- * adopts it so handles and hit-tests come from one place.
1729
+ * Size the grab targets for the device in use. Touch doubles the radii; a
1730
+ * mouse or a pen keeps the desktop sizes. The render context may carry the
1731
+ * same fact (`pointerType`), and when it does that wins per call, so a
1732
+ * first touch on a fresh chart is already sized right.
1592
1733
  */
1593
- private _layerFor;
1594
- /** Whether a drawing paints on the top layer: over the series, or lifted for a drag. */
1595
- private _onTop;
1596
- /** Push the current list into each pane's layers and into the chart state. */
1597
- private _sync;
1734
+ setPointerType(kind: DrawingPointerKind | null | undefined): void;
1735
+ setPreview(drawing: Drawing | null): void;
1598
1736
  /**
1599
- * The per-frame half of a drag: only the top layers of the panes the
1600
- * gesture touches are re-listed, so the repaint stays on the cursor tier.
1601
- * Everything moving is on a top layer by then (anything under the series
1602
- * was lifted when the gesture began), and the bottom layers have not
1603
- * changed since, so re-listing them would cost a series repaint for
1604
- * nothing.
1737
+ * Adopt the layer under this one. From then on this layer paints the handles
1738
+ * of both and answers `hitTest` for both, and the adopted layer paints bodies
1739
+ * only. Handles have to live up here: a selected drawing under the series
1740
+ * would otherwise have its handles buried under the candles, and a handle
1741
+ * you cannot see is a handle you cannot grab. Pass null to release.
1605
1742
  */
1606
- private _syncDrag;
1743
+ setBelow(layer: DrawingLayer | null): void;
1744
+ /** Repaint for overlay-only state, from the layer that paints it. */
1745
+ private _requestOverlay;
1746
+ /** Whether targets are sized for a fingertip on this call. */
1747
+ private _isTouch;
1748
+ private _grabRadius;
1749
+ private _handleRadius;
1750
+ /** Map an anchor to media px on this pane. */
1751
+ private _project;
1752
+ private _points;
1753
+ /** Selected, unlocked drawings of this layer, in paint order. */
1754
+ private _handled;
1607
1755
  /**
1608
- * Mirror the in-progress anchors (plus the cursor) into the preview slot.
1609
- * The cursor point goes through the same aim as a click would, so the
1610
- * preview shows the locked angle or the snapped anchor before it lands.
1756
+ * The hovered drawing of this layer, when it is one that could be grabbed
1757
+ * and is not already showing selection handles.
1611
1758
  */
1612
- private _syncPreview;
1759
+ private _hoverHandled;
1760
+ draw(ctx: CanvasRenderingContext2D, rc: PrimitiveRenderContext): void;
1761
+ private _drawingContext;
1762
+ private _drawContent;
1763
+ private _drawPlacementGuide;
1613
1764
  /**
1614
- * Show where the magnet will land the next click, or nothing. A ring only
1615
- * while a click would place an anchor: a tool armed, not a brush (which
1616
- * inks where the pointer is), and the pull actually applying at the cursor.
1617
- * Angle lock bypasses the magnet, so it hides the ring too.
1765
+ * Anchor handles. A hover shows them light (thin, translucent) as a hint
1766
+ * that the shape can be grabbed; a selection shows them at full weight.
1618
1767
  */
1619
- private _syncSnapRing;
1620
- private _pushUndo;
1768
+ private _drawHandles;
1769
+ /** The magnet ring: hollow, in the axis text colour, at the snap point. */
1770
+ private _drawSnapRing;
1771
+ hitTest(x: number, y: number, rc: PrimitiveRenderContext): PrimitiveHit | null;
1772
+ private _hitHandle;
1773
+ private _hitBody;
1621
1774
  }
1622
1775
 
1623
1776
  /**
@@ -1825,4 +1978,4 @@ declare const PATTERN_DRAWING_TOOLS: readonly DrawingTool[];
1825
1978
 
1826
1979
  declare const DRAW_TIER: "draw";
1827
1980
 
1828
- export { ADVANCED_GEOMETRY_TOOLS, ADVANCED_LINE_TOOLS, ALIGN_OPTIONS, ARC, ARROW, ARROW_DOWN, ARROW_LEFT, ARROW_RIGHT, ARROW_UP, BALLOON, BRUSH, BUILTIN_DRAWING_TOOLS, CALLOUT, CHROME_ICONS, CHROME_ICON_ATTRS, CHROME_ICON_FILLED, CHROME_ICON_STROKE, CHROME_ICON_VIEWBOX, CIRCLE, COLOR_FIELD, COMMENT, CROSS_LINE, CURVE, CYCLE_PALETTE, CYCLIC_LINES, type ClipboardPort, DATE_RANGE, DEFAULT_FIB, DEFAULT_FIB_FAN, DEFAULT_FIB_TIME_ZONE, DEFAULT_GANN_BOX, DEFAULT_GANN_FAN, DOUBLE_CURVE, DRAWING_CLIPBOARD_KEY, DRAWING_CLIPBOARD_VERSION, DRAWING_STATE_VERSION, DRAWING_TOOL_ICONS, DRAW_TIER, type DrawContext, type Drawing, type DrawingChangeKind, type DrawingChartHost, DrawingClipboard, type DrawingClipboardOptions, DrawingController, type DrawingControllerOptions, type DrawingInput, type DrawingKeyAction, type DrawingKeyContext, type DrawingKeyEvent, DrawingLayer, type DrawingLayerOrder, type DrawingPatch, type DrawingPoint, type DrawingPointerKind, type DrawingStyle, type DrawingText, type DrawingTool, type DrawingValueContext, type DrawingsDocument, ELLIPSE, EXTENDED_LINE, EXTEND_FIELDS, type ExpandContext, FIB_CHANNEL, FIB_EXTENSION, FIB_FAN, FIB_RETRACEMENT, FIB_TIME_ZONE, FILL_FIELDS, FLAG_MARK, FONT_FIELDS, FONT_OPTIONS, FORECAST, type FibLevel, type FieldGroup, type FieldKind, GANN_BOX, GANN_FAN, HIGHLIGHTER, HORIZONTAL_LINE, HORIZONTAL_RAY, type HitContext, ICON_ATTRS, ICON_STROKE, ICON_SYMBOL_PREFIX, ICON_VIEWBOX, type IconAttrs, type IconSvgOptions, LEVEL_FIELDS, LEVEL_NEUTRAL, LINE_FIELDS, LINE_STYLE_FIELD, LINE_STYLE_OPTIONS, LINE_WIDTH_FIELD, LONG_POSITION, MEASURE, type MagnetMode, NOTE, NUDGE_STEP_PX, NUDGE_STEP_SHIFT_PX, PARALLEL_CHANNEL, PATH, PATTERN_DRAWING_TOOLS, PLATE_TEXT_FIELDS, POLYLINE, PRICE_LABEL, PRICE_NOTE, PRICE_RANGE, RAY, RECTANGLE, ROTATED_RECTANGLE, SHAPE_TEXT_FIELDS, SHORT_POSITION, SHOW_LABELS_FIELD, SIGNPOST, SINE_LINE, type ScreenPoint, type SettingsField, type SettingsSchema, type ShortcutEvent, TABLE, TEXT, TEXT_FIELDS, TEXT_POSITION_OPTIONS, TEXT_VALUE_FIELD, TIME_CYCLES, TREND_LINE, TRIANGLE, type ToolCursorOptions, VALIGN_OPTIONS, VERTICAL_LINE, applyDrawingSettings, boundsOf, catmullRom, chromeIcon, chromeIconIds, chromeIconSvg, clearMemoryClipboard, cloneDrawing, cloneLevels, coerceSettingValue, composeSettings, cycleColor, decodeClipboardPayload, distToEllipse, distToLine, distToPolyline, distToRect, distToSegment, drawingSettingsSchema, drawingShortcuts, drawingToolIcon, drawingToolIconIds, encodeClipboardPayload, extendSegment, formatRatio, gannLabel, getDrawingTool, hasDrawingTool, iconSprite, iconSvg, iconUse, keyToDrawingAction, levelColor, matchDrawingShortcut, migrateDrawings, pressureWidth, rdpSimplify, readDrawingSetting, readDrawingSettings, rectOf, registerBuiltinDrawingTools, registerDrawingTool, registeredDrawingTools, sanitizeDrawing, sortByZIndex, systemClipboard, toolCursor };
1981
+ export { ADVANCED_GEOMETRY_TOOLS, ADVANCED_LINE_TOOLS, ALIGN_OPTIONS, ANCHORED_VWAP, ARC, ARROW, ARROW_DOWN, ARROW_LEFT, ARROW_RIGHT, ARROW_UP, type AnalysisProfileRow, type AnalysisStatus, type AnchoredVwapOptions, type AnchoredVwapPoint, type AnchoredVwapResult, type AnchoredVwapSource, BALLOON, BRUSH, BUILTIN_DRAWING_TOOLS, CALLOUT, CHROME_ICONS, CHROME_ICON_ATTRS, CHROME_ICON_FILLED, CHROME_ICON_STROKE, CHROME_ICON_VIEWBOX, CIRCLE, COLOR_FIELD, COMMENT, CROSS_LINE, CURVE, CYCLE_PALETTE, CYCLIC_LINES, type ClipboardPort, DATE_RANGE, DEFAULT_FIB, DEFAULT_FIB_FAN, DEFAULT_FIB_TIME_ZONE, DEFAULT_GANN_BOX, DEFAULT_GANN_FAN, DOUBLE_CURVE, DRAWING_CLIPBOARD_KEY, DRAWING_CLIPBOARD_VERSION, DRAWING_STATE_VERSION, DRAWING_TOOL_ICONS, DRAW_TIER, type DrawContext, type Drawing, type DrawingChangeKind, type DrawingChartHost, DrawingClipboard, type DrawingClipboardOptions, DrawingController, type DrawingControllerOptions, type DrawingGroup, type DrawingInput, type DrawingKeyAction, type DrawingKeyContext, type DrawingKeyEvent, DrawingLayer, type DrawingLayerOrder, type DrawingLinkChart, type DrawingLinkContext, type DrawingLinkContextSource, DrawingLinkGroup, type DrawingLinkOptions, type DrawingPatch, type DrawingPoint, type DrawingPointerKind, type DrawingStyle, type DrawingText, type DrawingTool, type DrawingValueContext, type DrawingsDocument, ELLIPSE, EXTENDED_LINE, EXTEND_FIELDS, type ExpandContext, FIB_CHANNEL, FIB_EXTENSION, FIB_FAN, FIB_RETRACEMENT, FIB_TIME_ZONE, FILL_FIELDS, FIXED_RANGE_VOLUME_PROFILE, FLAG_MARK, FONT_FIELDS, FONT_OPTIONS, FORECAST, type FibLevel, type FieldGroup, type FieldKind, type FixedRangeVolumeProfileOptions, type FixedRangeVolumeProfileResult, GANN_BOX, GANN_FAN, HIGHLIGHTER, HORIZONTAL_LINE, HORIZONTAL_RAY, type HitContext, ICON_ATTRS, ICON_STROKE, ICON_SYMBOL_PREFIX, ICON_VIEWBOX, type IconAttrs, type IconSvgOptions, LEVEL_FIELDS, LEVEL_NEUTRAL, LINE_FIELDS, LINE_STYLE_FIELD, LINE_STYLE_OPTIONS, LINE_WIDTH_FIELD, LONG_POSITION, MEASURE, type MagnetMode, NOTE, NUDGE_STEP_PX, NUDGE_STEP_SHIFT_PX, PARALLEL_CHANNEL, PATH, PATTERN_DRAWING_TOOLS, PLATE_TEXT_FIELDS, POLYLINE, PRICE_LABEL, PRICE_NOTE, PRICE_RANGE, RAY, RECTANGLE, ROTATED_RECTANGLE, SHAPE_TEXT_FIELDS, SHORT_POSITION, SHOW_LABELS_FIELD, SIGNPOST, SINE_LINE, type ScreenPoint, type SettingsField, type SettingsSchema, type ShortcutEvent, TABLE, TEXT, TEXT_FIELDS, TEXT_POSITION_OPTIONS, TEXT_VALUE_FIELD, TIME_CYCLES, TREND_LINE, TRIANGLE, type ToolCursorOptions, VALIGN_OPTIONS, VERTICAL_LINE, anchoredVwapAnalysis, applyDrawingSettings, boundsOf, catmullRom, chromeIcon, chromeIconIds, chromeIconSvg, clearMemoryClipboard, cloneDrawing, cloneLevels, coerceSettingValue, composeSettings, createDrawingLinkGroup, cycleColor, decodeClipboardPayload, distToEllipse, distToLine, distToPolyline, distToRect, distToSegment, drawingSettingsSchema, drawingShortcuts, drawingToolIcon, drawingToolIconIds, encodeClipboardPayload, extendSegment, fixedRangeVolumeProfileAnalysis, formatRatio, gannLabel, getDrawingTool, hasDrawingTool, iconSprite, iconSvg, iconUse, keyToDrawingAction, levelColor, matchDrawingShortcut, migrateDrawings, pressureWidth, rdpSimplify, readDrawingSetting, readDrawingSettings, rectOf, registerBuiltinDrawingTools, registerDrawingTool, registeredDrawingTools, sanitizeDrawing, sortByZIndex, systemClipboard, toolCursor };