@trdrs/quickcharts 1.3.0 → 2.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (86) hide show
  1. package/CHANGELOG.md +94 -0
  2. package/README.md +209 -200
  3. package/THIRD-PARTY-NOTICES.md +4 -4
  4. package/dist/adapters/rest.d.ts +3 -3
  5. package/dist/adapters/rest.js +2 -2
  6. package/dist/adapters/rest.js.map +1 -1
  7. package/dist/{ar-G7MVXRE4.js → ar-KSXQ2USS.js} +20 -20
  8. package/dist/ar-KSXQ2USS.js.map +1 -0
  9. package/dist/{assets-ySndlUS3.d.ts → assets-DOuBBsOp.d.ts} +50 -50
  10. package/dist/{ca_ES-EUSRU4VG.js → ca_ES-JG4FM55E.js} +20 -20
  11. package/dist/ca_ES-JG4FM55E.js.map +1 -0
  12. package/dist/{chunk-KFBQSPNA.js → chunk-D3S26ARP.js} +2 -2
  13. package/dist/chunk-D3S26ARP.js.map +1 -0
  14. package/dist/{chunk-AKXIFHQN.js → chunk-KLPTCWA2.js} +1 -1
  15. package/dist/{chunk-AKXIFHQN.js.map → chunk-KLPTCWA2.js.map} +1 -1
  16. package/dist/{chunk-AA7XKXH4.js → chunk-YR7NT5ZW.js} +67 -33
  17. package/dist/chunk-YR7NT5ZW.js.map +1 -0
  18. package/dist/{de-2SVTG25S.js → de-VSW2LHNF.js} +20 -20
  19. package/dist/de-VSW2LHNF.js.map +1 -0
  20. package/dist/drawings.d.ts +16 -16
  21. package/dist/drawings.js +12 -12
  22. package/dist/{es-UDHZM4W7.js → es-5ZXIPVOB.js} +20 -20
  23. package/dist/es-5ZXIPVOB.js.map +1 -0
  24. package/dist/feature-manifest.json +3 -3
  25. package/dist/{fr-35VN4G4S.js → fr-4F6DAWGD.js} +20 -20
  26. package/dist/fr-4F6DAWGD.js.map +1 -0
  27. package/dist/{he_IL-JPZMCURP.js → he_IL-B7PBQUEV.js} +20 -20
  28. package/dist/he_IL-B7PBQUEV.js.map +1 -0
  29. package/dist/{id_ID-XSTFHHJQ.js → id_ID-CYJRMXPC.js} +20 -20
  30. package/dist/id_ID-CYJRMXPC.js.map +1 -0
  31. package/dist/index.d.ts +167 -163
  32. package/dist/index.js +342 -268
  33. package/dist/index.js.map +1 -1
  34. package/dist/{it-EIY6JESQ.js → it-KTX4SG5P.js} +20 -20
  35. package/dist/it-KTX4SG5P.js.map +1 -0
  36. package/dist/{ja-42N4XRCW.js → ja-V7OURU5D.js} +20 -20
  37. package/dist/ja-V7OURU5D.js.map +1 -0
  38. package/dist/{ko-3VRPTIP3.js → ko-2RNEBYVE.js} +20 -20
  39. package/dist/ko-2RNEBYVE.js.map +1 -0
  40. package/dist/{ms_MY-SKN7O6M5.js → ms_MY-EY235E7Q.js} +20 -20
  41. package/dist/ms_MY-EY235E7Q.js.map +1 -0
  42. package/dist/{pl-TYARHCLT.js → pl-ADNVRP55.js} +20 -20
  43. package/dist/pl-ADNVRP55.js.map +1 -0
  44. package/dist/{pt-72IIFLL4.js → pt-OL2DR3DO.js} +20 -20
  45. package/dist/pt-OL2DR3DO.js.map +1 -0
  46. package/dist/quickcharts.css +97 -96
  47. package/dist/{resources-Ch8eA1Mw.d.ts → resources-Le6flcEn.d.ts} +3 -3
  48. package/dist/rest-openapi.json +11 -11
  49. package/dist/{ru-TPH4F7IB.js → ru-V6YFEPAF.js} +20 -20
  50. package/dist/ru-V6YFEPAF.js.map +1 -0
  51. package/dist/{sv-DC5RS67Z.js → sv-TORXQVE7.js} +20 -20
  52. package/dist/sv-TORXQVE7.js.map +1 -0
  53. package/dist/{th-I6H6AOZ3.js → th-K6SJSYSY.js} +20 -20
  54. package/dist/th-K6SJSYSY.js.map +1 -0
  55. package/dist/theme-manifest.json +10 -10
  56. package/dist/{tr-7FSUVCNW.js → tr-4AEM66KW.js} +20 -20
  57. package/dist/tr-4AEM66KW.js.map +1 -0
  58. package/dist/{vi-DLRPHLLH.js → vi-YU5XCEUU.js} +20 -20
  59. package/dist/vi-YU5XCEUU.js.map +1 -0
  60. package/dist/{zh-ZXIQBBEP.js → zh-QXKRYHU5.js} +20 -20
  61. package/dist/zh-QXKRYHU5.js.map +1 -0
  62. package/dist/{zh_TW-TYNUJU7U.js → zh_TW-AYPDKG4Q.js} +20 -20
  63. package/dist/zh_TW-AYPDKG4Q.js.map +1 -0
  64. package/package.json +2 -2
  65. package/dist/ar-G7MVXRE4.js.map +0 -1
  66. package/dist/ca_ES-EUSRU4VG.js.map +0 -1
  67. package/dist/chunk-AA7XKXH4.js.map +0 -1
  68. package/dist/chunk-KFBQSPNA.js.map +0 -1
  69. package/dist/de-2SVTG25S.js.map +0 -1
  70. package/dist/es-UDHZM4W7.js.map +0 -1
  71. package/dist/fr-35VN4G4S.js.map +0 -1
  72. package/dist/he_IL-JPZMCURP.js.map +0 -1
  73. package/dist/id_ID-XSTFHHJQ.js.map +0 -1
  74. package/dist/it-EIY6JESQ.js.map +0 -1
  75. package/dist/ja-42N4XRCW.js.map +0 -1
  76. package/dist/ko-3VRPTIP3.js.map +0 -1
  77. package/dist/ms_MY-SKN7O6M5.js.map +0 -1
  78. package/dist/pl-TYARHCLT.js.map +0 -1
  79. package/dist/pt-72IIFLL4.js.map +0 -1
  80. package/dist/ru-TPH4F7IB.js.map +0 -1
  81. package/dist/sv-DC5RS67Z.js.map +0 -1
  82. package/dist/th-I6H6AOZ3.js.map +0 -1
  83. package/dist/tr-7FSUVCNW.js.map +0 -1
  84. package/dist/vi-DLRPHLLH.js.map +0 -1
  85. package/dist/zh-ZXIQBBEP.js.map +0 -1
  86. package/dist/zh_TW-TYNUJU7U.js.map +0 -1
package/dist/index.js CHANGED
@@ -31,7 +31,7 @@ import {
31
31
  TRANSIENT_TOOLS,
32
32
  alphaOf,
33
33
  blanks,
34
- buildRailGroups,
34
+ buildDrawingToolbarGroups,
35
35
  button,
36
36
  cancelText,
37
37
  chooseHideMode,
@@ -44,6 +44,7 @@ import {
44
44
  cursorButtonArmed,
45
45
  dismissOnOutside,
46
46
  dragUntilRelease,
47
+ drawingToolbarFaceOf,
47
48
  drawingTools,
48
49
  editRefused,
49
50
  el,
@@ -65,12 +66,11 @@ import {
65
66
  paintedPosition,
66
67
  parseCssColor,
67
68
  parseDrawingPreferences,
68
- parseIntervalContext,
69
+ parseTimeframeContext,
69
70
  placePanel,
70
71
  pushEscapeOwner,
71
- railFaceOf,
72
72
  readColor,
73
- rememberRailTool,
73
+ rememberDrawingToolbarTool,
74
74
  removableDrawings,
75
75
  removeRows,
76
76
  restoreDrawings,
@@ -82,11 +82,11 @@ import {
82
82
  viewportOf,
83
83
  visibilityPreset,
84
84
  withAlpha
85
- } from "./chunk-AA7XKXH4.js";
85
+ } from "./chunk-YR7NT5ZW.js";
86
86
  import {
87
87
  ResourceAbortError,
88
88
  memorySaveLoadAdapter
89
- } from "./chunk-KFBQSPNA.js";
89
+ } from "./chunk-D3S26ARP.js";
90
90
  import {
91
91
  DRAWING_CONTEXT_VERSION,
92
92
  DRAWING_DOCUMENT_VERSION,
@@ -99,7 +99,7 @@ import {
99
99
  parseDrawingDocument,
100
100
  reviseDrawingDocument,
101
101
  sameDrawingContext
102
- } from "./chunk-AKXIFHQN.js";
102
+ } from "./chunk-KLPTCWA2.js";
103
103
 
104
104
  // src/datafeed.ts
105
105
  function olderPageVerdict(page, askedTo, alreadyHopped) {
@@ -484,8 +484,7 @@ function createUdfDatafeed(options) {
484
484
  name: String(col(raw.description, i) ?? symbol),
485
485
  exchange: String(col(raw["exchange-listed"], i) ?? group),
486
486
  type: String(col(raw.type, i) ?? ""),
487
- provider: null,
488
- via: null
487
+ dataSource: null
489
488
  });
490
489
  }
491
490
  }
@@ -528,8 +527,7 @@ function createUdfDatafeed(options) {
528
527
  name: r.description ?? r.symbol ?? "",
529
528
  exchange: r.exchange ?? "",
530
529
  type: r.type ?? "",
531
- provider: null,
532
- via: null
530
+ dataSource: null
533
531
  }));
534
532
  return { hits, hasMore: false };
535
533
  },
@@ -724,8 +722,8 @@ var legend = {
724
722
  * its percentage. Both arrive already written, sign included. */
725
723
  "legend.change": "{change} ({percent}%)",
726
724
  "legend.indicatorSettings": "Indicator settings",
727
- "legend.showRows": "Show study rows",
728
- "legend.hideRows": "Hide study rows",
725
+ "legend.showRows": "Show indicator rows",
726
+ "legend.hideRows": "Hide indicator rows",
729
727
  "legend.restorePane": "Restore pane",
730
728
  "legend.collapsePane": "Collapse pane",
731
729
  "legend.maximizePane": "Maximize pane",
@@ -740,7 +738,7 @@ var legend = {
740
738
  // The compare surface's legend side: the header door and the compare chips' verbs. The dialog
741
739
  // both open belongs to the search family (search.ts).
742
740
  "legend.changeSymbol": "Change symbol",
743
- "legend.removeCompare": "Remove compare",
741
+ "legend.removeCompare": "Remove comparison",
744
742
  "legend.removeIndicator": "Remove indicator"
745
743
  };
746
744
 
@@ -891,14 +889,14 @@ var replay = {
891
889
  "replay.speed": "Replay speed",
892
890
  "replay.updatesPerSecond": { one: "{count} update per second", other: "{count} updates per second" },
893
891
  "replay.oneUpdatePerSeconds": { one: "1 update per {count} second", other: "1 update per {count} seconds" },
894
- // The update interval.
895
- "replay.interval": "Update interval",
896
- "replay.intervalHelp": "How much time each replay update advances. Finer than the chart interval, each bar forms from real finer bars.",
892
+ // The update timeframe.
893
+ "replay.timeframe": "Update timeframe",
894
+ "replay.timeframeHelp": "How much time each replay update advances. Finer than the chart timeframe, each bar forms from real finer bars.",
897
895
  /** Why the control is unavailable: this timeframe has nothing finer to form its bars from. */
898
- "replay.intervalNone": "No finer interval for this chart interval",
899
- /** The interval option that picks the grain itself, rather than a named token. */
896
+ "replay.timeframeNone": "No finer timeframe for this chart timeframe",
897
+ /** The timeframe option that picks the grain itself, rather than a named token. */
900
898
  "replay.auto": "Auto",
901
- "replay.autoSelectInterval": "Auto select interval"
899
+ "replay.autoSelectTimeframe": "Auto select timeframe"
902
900
  };
903
901
 
904
902
  // src/i18n/en/inputs.ts
@@ -980,17 +978,17 @@ var layouts = {
980
978
  "layouts.arrangement": "Chart arrangement",
981
979
  "layouts.syncInLayout": "Sync in layout",
982
980
  "layouts.syncSymbol": "Symbol",
983
- "layouts.syncInterval": "Interval",
981
+ "layouts.syncTimeframe": "Timeframe",
984
982
  "layouts.syncCrosshair": "Crosshair",
985
983
  "layouts.syncTime": "Time",
986
984
  "layouts.syncDateRange": "Date range",
987
985
  "layouts.syncSymbolTip": "Symbol changes on all charts within the layout",
988
- "layouts.syncIntervalTip": "Interval changes on all charts within the layout",
986
+ "layouts.syncTimeframeTip": "Timeframe changes on all charts within the layout",
989
987
  "layouts.syncCrosshairTip": "Crosshair is synced across all charts within the layout",
990
988
  "layouts.syncTimeTip": "When a chart is clicked, all charts within the layout display the same point of time",
991
989
  "layouts.syncDateRangeTip": "Date range changes on all charts within the layout",
992
990
  "layouts.syncSymbolToggle": "Sync symbol",
993
- "layouts.syncIntervalToggle": "Sync interval",
991
+ "layouts.syncTimeframeToggle": "Sync timeframe",
994
992
  "layouts.syncCrosshairToggle": "Sync crosshair",
995
993
  "layouts.syncTimeToggle": "Sync time",
996
994
  "layouts.syncDateRangeToggle": "Sync date range",
@@ -1029,9 +1027,9 @@ var layouts = {
1029
1027
  "layouts.loading": "Loading",
1030
1028
  "layouts.emptyNone": "No saved layouts yet.",
1031
1029
  "layouts.openLayoutRow": "Open layout\u2026",
1032
- /** A saved layout's second line: the market and interval its active chart showed at the last save,
1033
- * written as the toolbar writes them (`BTCUSDC`, `1h`). */
1034
- "layouts.listingFacts": "{symbol}, {interval}",
1030
+ /** A saved layout's second line: the market and timeframe its active chart showed at the last
1031
+ * save, written as the toolbar writes them (`BTCUSDC`, `1h`). */
1032
+ "layouts.listingFacts": "{symbol}, {timeframe}",
1035
1033
  /** A saved layout's line in the Layouts dialog: what it shows, then when it was last saved. */
1036
1034
  "layouts.listingDated": "{facts} ({date})",
1037
1035
  // The Layouts dialog.
@@ -1144,7 +1142,7 @@ var timeframe = {
1144
1142
  "timeframe.unitDays": "Days",
1145
1143
  "timeframe.unitWeeks": "Weeks",
1146
1144
  "timeframe.unitMonths": "Months",
1147
- /** A counted interval, one plural per unit: '5t' reads "5 Ticks", '1h' reads "1 Hour". */
1145
+ /** A counted timeframe, one plural per unit: '5t' reads "5 Ticks", '1h' reads "1 Hour". */
1148
1146
  "timeframe.countTicks": { one: "{count} Tick", other: "{count} Ticks" },
1149
1147
  "timeframe.countSeconds": { one: "{count} Second", other: "{count} Seconds" },
1150
1148
  "timeframe.countMinutes": { one: "{count} Minute", other: "{count} Minutes" },
@@ -1152,11 +1150,11 @@ var timeframe = {
1152
1150
  "timeframe.countDays": { one: "{count} Day", other: "{count} Days" },
1153
1151
  "timeframe.countWeeks": { one: "{count} Week", other: "{count} Weeks" },
1154
1152
  "timeframe.countMonths": { one: "{count} Month", other: "{count} Months" },
1155
- /** The per-row verbs; `{timeframe}` is the counted interval. */
1153
+ /** The per-row verbs; `{timeframe}` is the counted timeframe. */
1156
1154
  "timeframe.delete": "Delete {timeframe}",
1157
1155
  "timeframe.save": "Save {timeframe}",
1158
1156
  "timeframe.exists": "{timeframe} already exists",
1159
- // The custom-interval footer: a count stepper, a unit picker, and Add.
1157
+ // The custom-timeframe footer: a count stepper, a unit picker, and Add.
1160
1158
  "timeframe.custom": "Custom timeframe",
1161
1159
  "timeframe.customCount": "Custom timeframe count",
1162
1160
  "timeframe.customUnit": "Custom timeframe unit",
@@ -1184,7 +1182,7 @@ var range = {
1184
1182
  "range.fiveYears": "5 Years",
1185
1183
  "range.all": "All data",
1186
1184
  /** A preset's tooltip: what it frames, and the bars it frames it in. */
1187
- "range.tip": "{range} \xB7 {interval} bars",
1185
+ "range.tip": "{range} \xB7 {timeframe} bars",
1188
1186
  "range.zoomIn": "Zoom in",
1189
1187
  "range.zoomOut": "Zoom out",
1190
1188
  "range.scrollLeft": "Scroll left",
@@ -1374,7 +1372,7 @@ var drawing = {
1374
1372
  "drawing.more": "More",
1375
1373
  "drawing.moreActions": "More drawing actions",
1376
1374
  "drawing.visualOrder": "Visual order",
1377
- "drawing.visibilityOnIntervals": "Visibility on intervals",
1375
+ "drawing.visibilityOnTimeframes": "Visibility on timeframes",
1378
1376
  "drawing.clone": "Clone",
1379
1377
  "drawing.copy": "Copy",
1380
1378
  "drawing.modifierControl": "Ctrl",
@@ -1396,10 +1394,10 @@ var drawing = {
1396
1394
  "drawing.sendToBack": "Send to back",
1397
1395
  "drawing.bringForward": "Bring forward",
1398
1396
  "drawing.sendBackward": "Send backward",
1399
- "drawing.visCurrentAndAbove": "Current interval and above",
1400
- "drawing.visCurrentAndBelow": "Current interval and below",
1401
- "drawing.visCurrentOnly": "Current interval only",
1402
- "drawing.visAll": "All intervals",
1397
+ "drawing.visCurrentAndAbove": "Current timeframe and above",
1398
+ "drawing.visCurrentAndBelow": "Current timeframe and below",
1399
+ "drawing.visCurrentOnly": "Current timeframe only",
1400
+ "drawing.visAll": "All timeframes",
1403
1401
  // The template dialogs: naming a new template, and confirming a delete.
1404
1402
  "drawing.saveTemplate": "Save drawing template",
1405
1403
  "drawing.templateName": "Template name",
@@ -1559,7 +1557,7 @@ var drawing = {
1559
1557
  // The Coordinates page: one row per anchor, numbered.
1560
1558
  "drawing.coordBar": "#{n} (bar)",
1561
1559
  "drawing.coordPriceBar": "#{n} (price, bar)",
1562
- // The Visibility page: which intervals paint the drawing.
1560
+ // The Visibility page: which timeframes paint the drawing.
1563
1561
  "drawing.unitTicks": "Ticks",
1564
1562
  "drawing.unitSeconds": "Seconds",
1565
1563
  "drawing.unitMinutes": "Minutes",
@@ -1567,7 +1565,7 @@ var drawing = {
1567
1565
  "drawing.unitDays": "Days",
1568
1566
  "drawing.unitWeeks": "Weeks",
1569
1567
  "drawing.unitMonths": "Months",
1570
- "drawing.intervalPinnedNote": "A drawing stays on at least one interval, so it can always be reached again.",
1568
+ "drawing.timeframePinnedNote": "A drawing stays on at least one timeframe, so it can always be reached again.",
1571
1569
  // The color palette: swatches, the custom color panel, the opacity row.
1572
1570
  "drawing.colorSwatch": "Color {hex}",
1573
1571
  "drawing.customColor": "Custom color",
@@ -1651,7 +1649,7 @@ var command = {
1651
1649
  "command.indicatorShow": "Show indicator",
1652
1650
  "command.drawingsRemoveAll": "Remove drawings",
1653
1651
  "command.drawingDeleteSelected": "Delete selected drawing",
1654
- /** Arm a drawing tool. The tool is the argument, so the ninety tools share one command. */
1652
+ /** Arm a drawing tool. The tool is the argument, so the 90 tools share one command. */
1655
1653
  "command.drawingArm": "Drawing tool",
1656
1654
  // The toolbar's switches and menus. Each takes its value as the argument.
1657
1655
  "command.drawingCursor": "Cursor mode",
@@ -1675,7 +1673,7 @@ var command = {
1675
1673
  "command.drawingBringForward": "Bring drawing forward",
1676
1674
  "command.drawingSendBackward": "Send drawing backward",
1677
1675
  "command.drawingHideSelected": "Hide drawing",
1678
- "command.drawingVisibility": "Drawing visibility on intervals",
1676
+ "command.drawingVisibility": "Drawing visibility on timeframes",
1679
1677
  "command.drawingSettings": "Drawing settings",
1680
1678
  "command.drawingCommitEdit": "Apply drawing settings",
1681
1679
  "command.drawingPlaceImage": "Place image",
@@ -1700,7 +1698,7 @@ var command = {
1700
1698
  "command.replayStepBack": "Step back",
1701
1699
  "command.replayGoLive": "Replay to the live edge",
1702
1700
  "command.replaySpeed": "Replay speed",
1703
- "command.replayInterval": "Replay update interval",
1701
+ "command.replayTimeframe": "Replay update timeframe",
1704
1702
  "command.timeframeSet": "Timeframe",
1705
1703
  /** Which subsession intraday bars are shown for. A symbol with no extended hours offers only
1706
1704
  * the first. */
@@ -1893,26 +1891,26 @@ function readingDirection(i18n) {
1893
1891
  return BUILT_IN_LOCALES.find((l) => l.code === i18n.locale())?.dir ?? "ltr";
1894
1892
  }
1895
1893
  var chartDictionaries = createDictionaryLoader(en, {
1896
- de: () => import("./de-2SVTG25S.js"),
1897
- fr: () => import("./fr-35VN4G4S.js"),
1898
- es: () => import("./es-UDHZM4W7.js"),
1899
- it: () => import("./it-EIY6JESQ.js"),
1900
- ca_ES: () => import("./ca_ES-EUSRU4VG.js"),
1901
- pl: () => import("./pl-TYARHCLT.js"),
1902
- sv: () => import("./sv-DC5RS67Z.js"),
1903
- tr: () => import("./tr-7FSUVCNW.js"),
1904
- ru: () => import("./ru-TPH4F7IB.js"),
1905
- pt: () => import("./pt-72IIFLL4.js"),
1906
- id_ID: () => import("./id_ID-XSTFHHJQ.js"),
1907
- ms_MY: () => import("./ms_MY-SKN7O6M5.js"),
1908
- th: () => import("./th-I6H6AOZ3.js"),
1909
- vi: () => import("./vi-DLRPHLLH.js"),
1910
- ja: () => import("./ja-42N4XRCW.js"),
1911
- ko: () => import("./ko-3VRPTIP3.js"),
1912
- zh: () => import("./zh-ZXIQBBEP.js"),
1913
- zh_TW: () => import("./zh_TW-TYNUJU7U.js"),
1914
- ar: () => import("./ar-G7MVXRE4.js"),
1915
- he_IL: () => import("./he_IL-JPZMCURP.js")
1894
+ de: () => import("./de-VSW2LHNF.js"),
1895
+ fr: () => import("./fr-4F6DAWGD.js"),
1896
+ es: () => import("./es-5ZXIPVOB.js"),
1897
+ it: () => import("./it-KTX4SG5P.js"),
1898
+ ca_ES: () => import("./ca_ES-JG4FM55E.js"),
1899
+ pl: () => import("./pl-ADNVRP55.js"),
1900
+ sv: () => import("./sv-TORXQVE7.js"),
1901
+ tr: () => import("./tr-4AEM66KW.js"),
1902
+ ru: () => import("./ru-V6YFEPAF.js"),
1903
+ pt: () => import("./pt-OL2DR3DO.js"),
1904
+ id_ID: () => import("./id_ID-CYJRMXPC.js"),
1905
+ ms_MY: () => import("./ms_MY-EY235E7Q.js"),
1906
+ th: () => import("./th-K6SJSYSY.js"),
1907
+ vi: () => import("./vi-YU5XCEUU.js"),
1908
+ ja: () => import("./ja-V7OURU5D.js"),
1909
+ ko: () => import("./ko-2RNEBYVE.js"),
1910
+ zh: () => import("./zh-QXKRYHU5.js"),
1911
+ zh_TW: () => import("./zh_TW-AYPDKG4Q.js"),
1912
+ ar: () => import("./ar-KSXQ2USS.js"),
1913
+ he_IL: () => import("./he_IL-B7PBQUEV.js")
1916
1914
  });
1917
1915
  var dynamic = (t) => t;
1918
1916
  var source = null;
@@ -2180,9 +2178,9 @@ var THEME_ROLES = [
2180
2178
  { id: "canvas.background", family: "canvas", kind: "color", description: "The fill behind the plotted series." },
2181
2179
  { id: "canvas.paneBorder", family: "canvas", kind: "color", description: "The divider between stacked panes and around the plot area." },
2182
2180
  // ── series: direction, before any chart appearance override ─────────────────────────────────
2183
- { id: "series.up", family: "series", kind: "color", description: "A rising value: up volume, a rising study tint, a positive series default." },
2184
- { id: "series.down", family: "series", kind: "color", description: "A falling value: down volume, a falling study tint, a negative series default." },
2185
- { id: "series.neutral", family: "series", kind: "color", description: "A series or level with no direction, such as a study level line." },
2181
+ { id: "series.up", family: "series", kind: "color", description: "A rising value: up volume, a rising indicator tint, a positive series default." },
2182
+ { id: "series.down", family: "series", kind: "color", description: "A falling value: down volume, a falling indicator tint, a negative series default." },
2183
+ { id: "series.neutral", family: "series", kind: "color", description: "A series or level with no direction, such as an indicator level line." },
2186
2184
  // ── scale: grid, axes, crosshair, and session shading ───────────────────────────────────────
2187
2185
  { id: "scale.grid", family: "scale", kind: "color", description: "The grid lines drawn across the plot area." },
2188
2186
  { id: "scale.background", family: "scale", kind: "color", description: "The ground behind the price and time scales." },
@@ -2257,7 +2255,7 @@ var THEME_ROLES = [
2257
2255
  { id: "text.fontSizeSmall", family: "text", kind: "length", description: "Size of dense control text, such as a legend row." },
2258
2256
  { id: "text.fontSizeMicro", family: "text", kind: "length", description: "Size of the smallest chip and hint text." },
2259
2257
  // ── chrome: the surfaces and borders of the chart's own controls ────────────────────────────
2260
- { id: "chrome.surface", family: "chrome", kind: "color", description: "The fill of toolbars, rails, bars, and panels." },
2258
+ { id: "chrome.surface", family: "chrome", kind: "color", description: "The fill of toolbars, bars, and panels." },
2261
2259
  { id: "chrome.surfaceRaised", family: "chrome", kind: "color", description: "The fill of a control sitting on a chrome surface, such as a button or field." },
2262
2260
  { id: "chrome.border", family: "chrome", kind: "color", description: "The ordinary border of a control or panel." },
2263
2261
  { id: "chrome.borderStrong", family: "chrome", kind: "color", description: "A border or divider that must stay visible at a glance.", contrast: [{ over: "chrome.surface", min: 3 }] },
@@ -2359,7 +2357,7 @@ var THEME_ROLES = [
2359
2357
  { id: "illustration.ink", family: "illustration", kind: "color", description: "The line drawing of an empty-state illustration.", contrast: [{ over: "overlay.surface", min: 3 }] },
2360
2358
  { id: "illustration.accent", family: "illustration", kind: "color", description: "The badge an empty-state illustration wears on the action it invites.", contrast: [{ over: "overlay.surface", min: 3 }] },
2361
2359
  { id: "illustration.accentInk", family: "illustration", kind: "color", description: "The mark on an illustration's badge.", contrast: [{ over: "illustration.accent", min: 3 }] },
2362
- // ── drawing: what a new drawing wears before a user styles it ───────────────────────────────
2360
+ // ── drawing: what a new drawing wears before a viewer styles it ─────────────────────────────
2363
2361
  { id: "drawing.line", family: "drawing", kind: "color", description: "The stroke of a newly placed drawing." },
2364
2362
  { id: "drawing.fill", family: "drawing", kind: "color", description: "The fill of a newly placed drawing that has an area." },
2365
2363
  { id: "drawing.text", family: "drawing", kind: "color", description: "The ink of a drawing label." },
@@ -2372,7 +2370,7 @@ var THEME_ROLES = [
2372
2370
  { id: "motion.durationBase", family: "motion", kind: "duration", description: "An ordinary transition: a modal dialog and its backdrop opening and closing, and the replay row entering and leaving." },
2373
2371
  { id: "motion.durationModerate", family: "motion", kind: "duration", description: "A control moving to its new state, such as a switch knob sliding." },
2374
2372
  { id: "motion.durationSlow", family: "motion", kind: "duration", description: "A small mark settling, such as a disclosure caret turning or a checkbox filling." },
2375
- { id: "motion.durationSlower", family: "motion", kind: "duration", description: "A larger mark turning, such as the drawing rail's chevron." },
2373
+ { id: "motion.durationSlower", family: "motion", kind: "duration", description: "A larger mark turning, such as the drawing toolbar's chevron." },
2376
2374
  { id: "motion.easingStandard", family: "motion", kind: "easing", description: "The timing of an ordinary transition: a modal dialog box, the replay row, a caret dip, a checkbox fill." },
2377
2375
  { id: "motion.easingOut", family: "motion", kind: "easing", description: "The timing of a motion that settles as it ends: a modal backdrop fading, a switch knob sliding." },
2378
2376
  { id: "motion.easingLinear", family: "motion", kind: "easing", description: "The timing of a color or opacity change under the pointer." },
@@ -2521,9 +2519,9 @@ function cssVarName(roleId) {
2521
2519
  function themeDeclarations(theme) {
2522
2520
  return Object.keys(theme).sort().map((roleId) => ({ property: cssVarName(roleId), value: String(theme[roleId]) }));
2523
2521
  }
2524
- var STYLE_LAYERS = { tokens: "trdrs.tokens", chart: "trdrs.chart" };
2522
+ var STYLE_LAYERS = { tokens: "quickcharts.tokens", chart: "quickcharts.chart" };
2525
2523
  var LAYER_ORDER_STATEMENT = `@layer ${STYLE_LAYERS.tokens}, ${STYLE_LAYERS.chart};`;
2526
- var HOST_LAYER_ORDER = `@layer reset, ${STYLE_LAYERS.tokens}, ${STYLE_LAYERS.chart}, trdrs.platform, host;`;
2524
+ var HOST_LAYER_ORDER = `@layer reset, ${STYLE_LAYERS.tokens}, ${STYLE_LAYERS.chart}, host;`;
2527
2525
 
2528
2526
  // src/theme/renderer.ts
2529
2527
  function themeRootStyle(mode, theme) {
@@ -4491,7 +4489,7 @@ function rangeAvailable(preset, earliestBarSecs, nowSecs = Math.floor(Date.now()
4491
4489
  return nowSecs - earliestBarSecs >= preset.span;
4492
4490
  }
4493
4491
  function rangePresetTip(t, preset) {
4494
- return t("range.tip", { range: t(preset.label), interval: timeframeLabel(t, preset.tf) });
4492
+ return t("range.tip", { range: t(preset.label), timeframe: timeframeLabel(t, preset.tf) });
4495
4493
  }
4496
4494
  function frameRange(chart, series2, span, tf, nowSecs = Math.floor(Date.now() / 1e3)) {
4497
4495
  const data = series2.data();
@@ -4892,7 +4890,7 @@ var LAYOUT_MENU_ROWS = [
4892
4890
  ];
4893
4891
 
4894
4892
  // src/widget/arrangements.ts
4895
- var LAYOUT_SYNC_KEYS = ["symbol", "interval", "crosshair", "time", "dateRange"];
4893
+ var LAYOUT_SYNC_KEYS = ["symbol", "timeframe", "crosshair", "time", "dateRange"];
4896
4894
  var ALL_LAYOUTS_OFFERED = { arrangements: ARRANGEMENTS.map((a) => a.code), named: false, sync: LAYOUT_SYNC_KEYS };
4897
4895
  var SYNC_PHRASE = `it takes ${LAYOUT_SYNC_KEYS.join(", ")}`;
4898
4896
  function resolveOfferedLayouts(layouts2, layoutSync, arrangement) {
@@ -4962,9 +4960,9 @@ var DEFAULT_OVERRIDES = {
4962
4960
  background: "#ece7c0",
4963
4961
  upColor: "#26a69a",
4964
4962
  downColor: "#ffa726",
4965
- // Candle anatomy: solid black ring and wick, the SAME ink both directions — not a shade of the
4966
- // body color the way the old brand-pair defaults were. On a paper canvas the black is what
4967
- // separates a candle from the background, and that job does not change with direction.
4963
+ // Candle anatomy: solid black ring and wick, the SAME ink both directions. On a paper canvas
4964
+ // the black is what separates a candle from the background, and that job does not change with
4965
+ // direction.
4968
4966
  borderUpColor: "#000000",
4969
4967
  borderDownColor: "#000000",
4970
4968
  wickUpColor: "#000000",
@@ -8134,6 +8132,10 @@ var PLACE_DRAG_PX = 6;
8134
8132
  var MOVE_EPSILON_PX = 2;
8135
8133
  var HANDLE_GRAB_PX = 11;
8136
8134
  var GRIP_GRAB_PX = 9;
8135
+ var TOUCH_REACH_PX = 14;
8136
+ var TOUCH_GRAB_PX = 22;
8137
+ var TOUCH_PLACE_DRAG_PX = 12;
8138
+ var TOUCH_MOVE_PX = 6;
8137
8139
  var STROKE_STEP_PX = 3;
8138
8140
  var isTransientArmed = (tool) => tool === "measure" || tool === "zoom" || tool === "eraser";
8139
8141
  function bindGestures(ctx) {
@@ -8165,11 +8167,38 @@ function bindGestures(ctx) {
8165
8167
  if (mode === "off") return raw;
8166
8168
  return magnetSnap(chart, series2, p, mode) ?? raw;
8167
8169
  };
8170
+ let touching = false;
8171
+ const hitAt = (p) => {
8172
+ const under = manager.hitTest(p);
8173
+ if (under || !touching) return under;
8174
+ for (const radius of [TOUCH_REACH_PX / 2, TOUCH_REACH_PX]) {
8175
+ for (let k = 0; k < 8; k++) {
8176
+ const angle = k * Math.PI / 4;
8177
+ const near2 = manager.hitTest({ x: p.x + radius * Math.cos(angle), y: p.y + radius * Math.sin(angle) });
8178
+ if (near2) return near2;
8179
+ }
8180
+ }
8181
+ return null;
8182
+ };
8183
+ const showPoint = (anchor) => {
8184
+ if (!touching || !anchor) return;
8185
+ try {
8186
+ chart.setCrosshairPosition(anchor.price, anchor.time, series2);
8187
+ } catch {
8188
+ }
8189
+ };
8190
+ const hidePoint = () => {
8191
+ if (!touching) return;
8192
+ try {
8193
+ chart.clearCrosshairPosition();
8194
+ } catch {
8195
+ }
8196
+ };
8168
8197
  const anchorHit = (drawing2, p) => {
8169
8198
  const vp = viewport();
8170
8199
  if (!vp) return null;
8171
8200
  let best = null;
8172
- let bestD = HANDLE_GRAB_PX;
8201
+ let bestD = touching ? TOUCH_GRAB_PX : HANDLE_GRAB_PX;
8173
8202
  for (const cp of drawing2.getControlPoints(vp)) {
8174
8203
  const d = Math.hypot(cp.x - p.x, cp.y - p.y);
8175
8204
  if (d <= bestD) {
@@ -8241,6 +8270,7 @@ function bindGestures(ctx) {
8241
8270
  };
8242
8271
  const onDown = (e) => {
8243
8272
  if (e.button !== 0) return;
8273
+ touching = e.pointerType === "touch";
8244
8274
  if (ctx.textEditOpen()) return;
8245
8275
  wearCursor(ctx.cursorCss());
8246
8276
  container.focus({ preventScroll: true });
@@ -8249,7 +8279,7 @@ function bindGestures(ctx) {
8249
8279
  if (!ctx.draft && ctx.transient.size && tool !== "measure" && tool !== "zoom") ctx.clearTransients();
8250
8280
  if (tool === "eraser") {
8251
8281
  if (ctx.locked()) return;
8252
- const hit = manager.hitTest(p);
8282
+ const hit = hitAt(p);
8253
8283
  if (hit && !editRefused("erase", hit.options, false)) {
8254
8284
  manager.remove(hit.id);
8255
8285
  ctx.persist();
@@ -8286,7 +8316,8 @@ function bindGestures(ctx) {
8286
8316
  }
8287
8317
  const vp = viewport();
8288
8318
  if (vp) {
8289
- const grip = sel.resizeHandles(vp).findIndex((g) => Math.hypot(g.x - p.x, g.y - p.y) <= GRIP_GRAB_PX);
8319
+ const gripReach = touching ? TOUCH_GRAB_PX : GRIP_GRAB_PX;
8320
+ const grip = sel.resizeHandles(vp).findIndex((g) => Math.hypot(g.x - p.x, g.y - p.y) <= gripReach);
8290
8321
  if (grip !== -1) {
8291
8322
  startDrag("resize", sel, grip, p);
8292
8323
  return;
@@ -8304,7 +8335,7 @@ function bindGestures(ctx) {
8304
8335
  return;
8305
8336
  }
8306
8337
  }
8307
- const hit = manager.hitTest(p);
8338
+ const hit = hitAt(p);
8308
8339
  if (hit) {
8309
8340
  if ((e.ctrlKey || e.metaKey) && !editRefused("cloneDrag", hit.options, false) && ctx.copies(hit.type)) {
8310
8341
  const copy = drawingTools.restore({ ...hit.toJSON(), id: ctx.nextId() });
@@ -8324,7 +8355,10 @@ function bindGestures(ctx) {
8324
8355
  manager.deselect();
8325
8356
  return;
8326
8357
  }
8327
- if (ctx.draft) return;
8358
+ if (ctx.draft) {
8359
+ if (touching) followDraft(ctx.draft, p, e.shiftKey);
8360
+ return;
8361
+ }
8328
8362
  if (ctx.locked()) return;
8329
8363
  const def = drawingTools.get(tool);
8330
8364
  if (!def) return;
@@ -8357,6 +8391,7 @@ function bindGestures(ctx) {
8357
8391
  }
8358
8392
  manager.add(drawing2);
8359
8393
  ctx.draft = { drawing: drawing2, required, placed: 1, downX: p.x, downY: p.y, pendingDrag: true, hasText: !!def.hasText, mode, lastX: p.x, lastY: p.y };
8394
+ showPoint(anchor);
8360
8395
  if (mode === "fixed" && required === 1) completePlacement(ctx.draft, p);
8361
8396
  };
8362
8397
  const constrainedEnd = (draft, p, shift) => {
@@ -8365,12 +8400,22 @@ function bindGestures(ctx) {
8365
8400
  const first = vp && draft.drawing.anchors[0] ? draft.drawing.anchorToPixel(draft.drawing.anchors[0], vp) : null;
8366
8401
  return first ? constrain45(first, p) : p;
8367
8402
  };
8403
+ const followDraft = (draft, p, shift) => {
8404
+ const live = constrainedEnd(draft, p, shift);
8405
+ const anchor = isTransientArmed(ctx.armed()) ? anchorAt(live) : snappedAnchorAt(live, shift);
8406
+ if (anchor) draft.drawing.updateAnchor(draft.drawing.anchors.length - 1, anchor);
8407
+ showPoint(anchor);
8408
+ };
8368
8409
  const onMove = (e) => {
8369
8410
  const p = localXY(e);
8370
8411
  const drag = ctx.drag;
8371
8412
  if (drag) {
8372
8413
  let { x, y } = p;
8373
- if (!drag.moved && Math.abs(x - drag.grabX) + Math.abs(y - drag.grabY) > MOVE_EPSILON_PX) drag.moved = true;
8414
+ if (!drag.moved) {
8415
+ const travel = Math.abs(x - drag.grabX) + Math.abs(y - drag.grabY);
8416
+ if (touching && travel <= TOUCH_MOVE_PX) return;
8417
+ if (travel > MOVE_EPSILON_PX) drag.moved = true;
8418
+ }
8374
8419
  const vp = viewport();
8375
8420
  if (!vp) return;
8376
8421
  if (drag.mode === "resize" && drag.anchorIndex !== null) {
@@ -8384,6 +8429,7 @@ function bindGestures(ctx) {
8384
8429
  }
8385
8430
  const anchor = snappedAnchorAt({ x, y }, e.shiftKey);
8386
8431
  if (anchor) drag.drawing.updateAnchor(drag.anchorIndex, anchor);
8432
+ showPoint(anchor);
8387
8433
  return;
8388
8434
  }
8389
8435
  const dx = x - drag.grabX;
@@ -8406,18 +8452,16 @@ function bindGestures(ctx) {
8406
8452
  const draft = ctx.draft;
8407
8453
  if (draft) {
8408
8454
  if (draft.mode === "freehand") {
8409
- const anchor2 = anchorAt(p);
8410
- if (!anchor2) return;
8455
+ const anchor = anchorAt(p);
8456
+ if (!anchor) return;
8411
8457
  if (Math.hypot(p.x - draft.lastX, p.y - draft.lastY) >= STROKE_STEP_PX) {
8412
- draft.drawing.appendAnchor(anchor2);
8458
+ draft.drawing.appendAnchor(anchor);
8413
8459
  draft.lastX = p.x;
8414
8460
  draft.lastY = p.y;
8415
8461
  }
8416
8462
  return;
8417
8463
  }
8418
- const live = constrainedEnd(draft, p, e.shiftKey);
8419
- const anchor = isTransientArmed(ctx.armed()) ? anchorAt(live) : snappedAnchorAt(live, e.shiftKey);
8420
- if (anchor) draft.drawing.updateAnchor(draft.drawing.anchors.length - 1, anchor);
8464
+ followDraft(draft, p, e.shiftKey);
8421
8465
  return;
8422
8466
  }
8423
8467
  wearCursor(ctx.cursorCss());
@@ -8427,33 +8471,38 @@ function bindGestures(ctx) {
8427
8471
  ctx.setHovered(manager.hitTest(localXY(e))?.id ?? null);
8428
8472
  };
8429
8473
  const onLeave = () => ctx.setHovered(null);
8474
+ const placeSlop = () => touching ? TOUCH_PLACE_DRAG_PX : PLACE_DRAG_PX;
8475
+ const endDrag = (drag) => {
8476
+ ctx.drag = null;
8477
+ freezePan(!!ctx.armed());
8478
+ if (drag.cloned && !drag.moved) manager.remove(drag.drawing.id);
8479
+ if (!drag.moved && !drag.cloned && drag.mode === "move" && drag.drawing.type === "table") {
8480
+ const vp = viewport();
8481
+ const table = drag.drawing;
8482
+ const cell2 = vp && table.cellAt ? table.cellAt({ x: drag.grabX, y: drag.grabY }, vp) : null;
8483
+ if (cell2) {
8484
+ ctx.openCellEdit(drag.drawing, cell2);
8485
+ return;
8486
+ }
8487
+ }
8488
+ if (drag.moved || drag.cloned) {
8489
+ ctx.persist();
8490
+ ctx.changed();
8491
+ }
8492
+ };
8430
8493
  const onUp = (e) => {
8431
8494
  const p = localXY(e);
8495
+ hidePoint();
8432
8496
  const drag = ctx.drag;
8433
8497
  if (drag) {
8434
- ctx.drag = null;
8435
- freezePan(!!ctx.armed());
8436
- if (drag.cloned && !drag.moved) manager.remove(drag.drawing.id);
8437
- if (!drag.moved && !drag.cloned && drag.mode === "move" && drag.drawing.type === "table") {
8438
- const vp = viewport();
8439
- const table = drag.drawing;
8440
- const cell2 = vp && table.cellAt ? table.cellAt({ x: drag.grabX, y: drag.grabY }, vp) : null;
8441
- if (cell2) {
8442
- ctx.openCellEdit(drag.drawing, cell2);
8443
- return;
8444
- }
8445
- }
8446
- if (drag.moved || drag.cloned) {
8447
- ctx.persist();
8448
- ctx.changed();
8449
- }
8498
+ endDrag(drag);
8450
8499
  return;
8451
8500
  }
8452
8501
  const draft = ctx.draft;
8453
8502
  if (!draft) return;
8454
8503
  const tool = ctx.armed();
8455
8504
  if (tool === "measure" || tool === "zoom") {
8456
- const moved2 = Math.abs(p.x - draft.downX) + Math.abs(p.y - draft.downY) > 4;
8505
+ const moved2 = Math.abs(p.x - draft.downX) + Math.abs(p.y - draft.downY) > (touching ? TOUCH_PLACE_DRAG_PX : 4);
8457
8506
  if (!moved2 && draft.pendingDrag) {
8458
8507
  draft.pendingDrag = false;
8459
8508
  return;
@@ -8473,7 +8522,7 @@ function bindGestures(ctx) {
8473
8522
  return;
8474
8523
  }
8475
8524
  if (draft.mode === "multipoint") {
8476
- if (draft.pendingDrag && Math.abs(p.x - draft.downX) + Math.abs(p.y - draft.downY) <= PLACE_DRAG_PX) {
8525
+ if (draft.pendingDrag && Math.abs(p.x - draft.downX) + Math.abs(p.y - draft.downY) <= placeSlop()) {
8477
8526
  draft.pendingDrag = false;
8478
8527
  return;
8479
8528
  }
@@ -8489,7 +8538,7 @@ function bindGestures(ctx) {
8489
8538
  const snapped = snappedAnchorAt(constrainedEnd(draft, p, e.shiftKey), e.shiftKey);
8490
8539
  const lastIndex = draft.drawing.anchors.length - 1;
8491
8540
  if (snapped) draft.drawing.updateAnchor(lastIndex, snapped);
8492
- const moved = Math.abs(p.x - draft.downX) + Math.abs(p.y - draft.downY) > PLACE_DRAG_PX;
8541
+ const moved = Math.abs(p.x - draft.downX) + Math.abs(p.y - draft.downY) > placeSlop();
8493
8542
  if (draft.pendingDrag && !moved) {
8494
8543
  draft.pendingDrag = false;
8495
8544
  return;
@@ -8503,6 +8552,14 @@ function bindGestures(ctx) {
8503
8552
  draft.drawing.appendAnchor({ ...next });
8504
8553
  }
8505
8554
  };
8555
+ const onCancel = () => {
8556
+ hidePoint();
8557
+ if (ctx.drag) endDrag(ctx.drag);
8558
+ else if (ctx.draft) ctx.draft.pendingDrag = false;
8559
+ };
8560
+ const onTouchStart = (e) => {
8561
+ if (ctx.drag || ctx.draft) e.preventDefault();
8562
+ };
8506
8563
  const onDblClick = (e) => {
8507
8564
  if (ctx.locked()) return;
8508
8565
  const draft = ctx.draft;
@@ -8527,18 +8584,22 @@ function bindGestures(ctx) {
8527
8584
  ctx.openTextEdit(hit, p.x, p.y, false);
8528
8585
  };
8529
8586
  container.addEventListener("pointerdown", onDown);
8587
+ container.addEventListener("touchstart", onTouchStart, { passive: false });
8530
8588
  container.addEventListener("pointermove", onHover);
8531
8589
  container.addEventListener("pointerleave", onLeave);
8532
8590
  container.addEventListener("dblclick", onDblClick);
8533
8591
  window.addEventListener("pointermove", onMove);
8534
8592
  window.addEventListener("pointerup", onUp);
8593
+ window.addEventListener("pointercancel", onCancel);
8535
8594
  return () => {
8536
8595
  container.removeEventListener("pointerdown", onDown);
8596
+ container.removeEventListener("touchstart", onTouchStart);
8537
8597
  container.removeEventListener("pointermove", onHover);
8538
8598
  container.removeEventListener("pointerleave", onLeave);
8539
8599
  container.removeEventListener("dblclick", onDblClick);
8540
8600
  window.removeEventListener("pointermove", onMove);
8541
8601
  window.removeEventListener("pointerup", onUp);
8602
+ window.removeEventListener("pointercancel", onCancel);
8542
8603
  };
8543
8604
  }
8544
8605
 
@@ -8596,7 +8657,7 @@ function attachDrawings(options) {
8596
8657
  const manager = new DrawingManager();
8597
8658
  manager.setGlyphSource(options.glyphSource ?? bundledGlyphSource);
8598
8659
  manager.attach(chart, series2);
8599
- manager.setIntervalContext(parseIntervalContext(options.timeframe ?? ""));
8660
+ manager.setTimeframeContext(parseTimeframeContext(options.timeframe ?? ""));
8600
8661
  if (options.bars) {
8601
8662
  const read = options.bars;
8602
8663
  let cacheKey = "";
@@ -8974,7 +9035,7 @@ function attachDrawings(options) {
8974
9035
  setVisibilityPreset(preset) {
8975
9036
  const sel = selection();
8976
9037
  if (!sel) return;
8977
- sel.updateOptions({ visibility: visibilityPreset(preset, parseIntervalContext(timeframe2)) });
9038
+ sel.updateOptions({ visibility: visibilityPreset(preset, parseTimeframeContext(timeframe2)) });
8978
9039
  persist();
8979
9040
  changed();
8980
9041
  },
@@ -9041,7 +9102,7 @@ function attachDrawings(options) {
9041
9102
  },
9042
9103
  setTimeframe(tf) {
9043
9104
  timeframe2 = tf;
9044
- manager.setIntervalContext(parseIntervalContext(tf));
9105
+ manager.setTimeframeContext(parseTimeframeContext(tf));
9045
9106
  },
9046
9107
  setTick: (tick) => manager.setTickSize(tick),
9047
9108
  setPriceFormatter: (format2) => manager.setPriceFormatter(format2),
@@ -9776,17 +9837,17 @@ var HIDE_ICON = {
9776
9837
  };
9777
9838
  function mountDrawingToolbar(deps) {
9778
9839
  const { t } = deps;
9779
- const groups = buildRailGroups();
9840
+ const groups = buildDrawingToolbarGroups();
9780
9841
  const shown2 = (command2) => deps.shown?.(command2) ?? true;
9781
9842
  const toolShown = (type) => deps.toolShown?.(type) ?? true;
9782
9843
  const drawnGroup = (group) => ({
9783
9844
  ...group,
9784
9845
  sections: group.sections.map((section) => ({ ...section, tools: section.tools.filter((tool) => toolShown(tool.type)) })).filter((section) => section.tools.length > 0)
9785
9846
  });
9786
- const rail = el("div", { class: "qc-surface qc-drawing-toolbar", role: "toolbar", "aria-orientation": "vertical", "aria-label": t("drawing.toolbar"), "data-role": "drawing-toolbar" });
9787
- ownPointer(rail);
9847
+ const toolbar = el("div", { class: "qc-surface qc-drawing-toolbar", role: "toolbar", "aria-orientation": "vertical", "aria-label": t("drawing.toolbar"), "data-role": "drawing-toolbar" });
9848
+ ownPointer(toolbar);
9788
9849
  const column = el("div", { class: "qc-drawing-toolbar-column" });
9789
- rail.appendChild(column);
9850
+ toolbar.appendChild(column);
9790
9851
  let closeFlyout = null;
9791
9852
  let panelHost = deps.chrome;
9792
9853
  const closeOpen = () => {
@@ -9832,16 +9893,16 @@ function mountDrawingToolbar(deps) {
9832
9893
  };
9833
9894
  const cell2 = (face, arrow) => {
9834
9895
  const box = el("div", { class: "qc-drawing-cell" }, face);
9835
- face.classList.add("qc-drawing-rail-button");
9896
+ face.classList.add("qc-drawing-toolbar-button");
9836
9897
  if (arrow) {
9837
- arrow.classList.add("qc-drawing-rail-arrow");
9898
+ arrow.classList.add("qc-drawing-toolbar-arrow");
9838
9899
  arrow.setAttribute("aria-haspopup", "menu");
9839
9900
  arrow.setAttribute("aria-expanded", "false");
9840
9901
  box.appendChild(arrow);
9841
9902
  }
9842
9903
  return box;
9843
9904
  };
9844
- const divider = (where = "rail") => el("div", { class: `qc-separator qc-drawing-${where === "rail" ? "divider" : where === "flyout" ? "flyout-rule" : "menu-rule"}`, role: "separator" });
9905
+ const divider = (where = "toolbar") => el("div", { class: `qc-separator qc-drawing-${where === "toolbar" ? "divider" : where === "flyout" ? "flyout-rule" : "menu-rule"}`, role: "separator" });
9845
9906
  const cursorFace = button({ class: "qc-button", label: t("drawing.cursor"), onClick: () => deps.run("chart.drawings.arm", null) });
9846
9907
  const cursorArrow = button({ class: "qc-button", label: t("drawing.cursorMenu"), icon: deps.icons.icon("chevronRight16") });
9847
9908
  cursorArrow.addEventListener("click", () => {
@@ -9926,7 +9987,7 @@ function mountDrawingToolbar(deps) {
9926
9987
  const face = button({ class: "qc-button", label: t(group.label) });
9927
9988
  const arrow = button({ class: "qc-button", label: t("drawing.groupMenu", { group: t(group.label) }), icon: deps.icons.icon("chevronRight16") });
9928
9989
  face.addEventListener("click", () => {
9929
- const faceTool = railFaceOf(drawnGroup(group), deps.state().railTools);
9990
+ const faceTool = drawingToolbarFaceOf(drawnGroup(group), deps.state().drawingToolbarTools);
9930
9991
  if (group.id === "glyphs" || !faceTool) {
9931
9992
  openGroup(group, arrow);
9932
9993
  return;
@@ -9962,7 +10023,7 @@ function mountDrawingToolbar(deps) {
9962
10023
  )
9963
10024
  );
9964
10025
  });
9965
- const stay = button({ class: "qc-button qc-drawing-rail-mode", label: t("drawing.stayInDrawingMode"), onClick: () => deps.run("chart.drawings.stayInMode", !deps.state().stayInDrawingMode) });
10026
+ const stay = button({ class: "qc-button qc-drawing-toolbar-mode", label: t("drawing.stayInDrawingMode"), onClick: () => deps.run("chart.drawings.stayInMode", !deps.state().stayInDrawingMode) });
9966
10027
  const lockAll = button({ class: "qc-button", label: t("drawing.lockAll"), onClick: () => deps.run("chart.drawings.lockAll", !deps.state().allLocked) });
9967
10028
  column.append(cell2(magnetFace, magnetArrow), cell2(stay, null), cell2(lockAll, null));
9968
10029
  const eyeFace = button({ class: "qc-button", label: t("drawing.hideDrawings"), onClick: () => deps.run("chart.drawings.hide", { mode: deps.state().hide.mode, on: !deps.state().hide.on }) });
@@ -10020,9 +10081,9 @@ function mountDrawingToolbar(deps) {
10020
10081
  openFlyout(removeArrow, menu2(t("drawing.removeMenu"), ...items2, divider("menu"), policy));
10021
10082
  });
10022
10083
  column.append(cell2(removeFace, removeArrow));
10023
- const favorites = button({ class: "qc-button qc-drawing-rail-mode", label: t("drawing.favToolsBar"), title: t("drawing.favTools"), onClick: () => deps.run("chart.drawings.favoritesBar", !deps.state().favorites.visible) });
10084
+ const favorites = button({ class: "qc-button qc-drawing-toolbar-mode", label: t("drawing.favToolsBar"), title: t("drawing.favTools"), onClick: () => deps.run("chart.drawings.favoritesBar", !deps.state().favorites.visible) });
10024
10085
  column.append(el("div", { class: "qc-drawing-toolbar-end" }, cell2(favorites, null)));
10025
- const unrove = rovingFocus(rail, () => [...column.querySelectorAll("button")].filter((b) => !b.closest("[hidden]")), "vertical");
10086
+ const unrove = rovingFocus(toolbar, () => [...column.querySelectorAll("button")].filter((b) => !b.closest("[hidden]")), "vertical");
10026
10087
  const setActive = (b, active) => {
10027
10088
  b.dataset.qcActive = String(active);
10028
10089
  };
@@ -10045,7 +10106,7 @@ function mountDrawingToolbar(deps) {
10045
10106
  for (const group of groups) {
10046
10107
  const entry = groupFaces.get(group.id);
10047
10108
  const drawn = drawnGroup(group);
10048
- const faceTool = activeGroup === group.id && s.activeTool && toolShown(s.activeTool) ? s.activeTool : railFaceOf(drawn, s.railTools);
10109
+ const faceTool = activeGroup === group.id && s.activeTool && toolShown(s.activeTool) ? s.activeTool : drawingToolbarFaceOf(drawn, s.drawingToolbarTools);
10049
10110
  entry.face.replaceChildren(...[group.id === "glyphs" ? deps.icons.icon("groupGlyphs") : faceTool ? deps.icons.tool(faceTool) : null].filter((face) => face !== null));
10050
10111
  const faceName = faceTool && group.id !== "glyphs" ? toolName(t, faceTool, faceTool) : t(group.label);
10051
10112
  relabelButton(entry.face, faceName);
@@ -10085,7 +10146,7 @@ function mountDrawingToolbar(deps) {
10085
10146
  gate(eyeArrow, "chart.drawings.hide");
10086
10147
  if (s.layoutCharts > 1) {
10087
10148
  if (!syncButton) {
10088
- syncButton = button({ class: "qc-button qc-drawing-rail-button", label: t("drawing.syncLabel"), icon: deps.icons.icon("sync"), onClick: () => deps.run("chart.drawings.sync", !deps.state().sync) });
10149
+ syncButton = button({ class: "qc-button qc-drawing-toolbar-button", label: t("drawing.syncLabel"), icon: deps.icons.icon("sync"), onClick: () => deps.run("chart.drawings.sync", !deps.state().sync) });
10089
10150
  syncCell.appendChild(syncButton);
10090
10151
  }
10091
10152
  syncButton.title = t(s.sync ? "drawing.syncOnHelp" : "drawing.syncOffHelp");
@@ -10111,7 +10172,7 @@ function mountDrawingToolbar(deps) {
10111
10172
  tidyRules(column, (child) => !child.hidden && [...child.querySelectorAll("button")].some((control) => !control.closest("[hidden]")));
10112
10173
  };
10113
10174
  const relabel = () => {
10114
- rail.setAttribute("aria-label", t("drawing.toolbar"));
10175
+ toolbar.setAttribute("aria-label", t("drawing.toolbar"));
10115
10176
  relabelButton(cursorFace, t("drawing.cursor"));
10116
10177
  relabelButton(cursorArrow, t("drawing.cursorMenu"));
10117
10178
  for (const group of groups) relabelButton(groupFaces.get(group.id).arrow, t("drawing.groupMenu", { group: t(group.label) }));
@@ -10129,11 +10190,11 @@ function mountDrawingToolbar(deps) {
10129
10190
  };
10130
10191
  const mount = (container) => {
10131
10192
  const nextPanelHost = panelHostFor(container, deps.chrome);
10132
- if (rail.parentElement === container && panelHost === nextPanelHost) return;
10193
+ if (toolbar.parentElement === container && panelHost === nextPanelHost) return;
10133
10194
  closeOpen();
10134
10195
  panelHost = nextPanelHost;
10135
- rail.remove();
10136
- container?.appendChild(rail);
10196
+ toolbar.remove();
10197
+ container?.appendChild(toolbar);
10137
10198
  render();
10138
10199
  };
10139
10200
  mount(deps.container === void 0 ? deps.chrome : deps.container);
@@ -10147,7 +10208,7 @@ function mountDrawingToolbar(deps) {
10147
10208
  unrove();
10148
10209
  glyphPicker?.destroy();
10149
10210
  glyphPicker = null;
10150
- rail.remove();
10211
+ toolbar.remove();
10151
10212
  }
10152
10213
  };
10153
10214
  }
@@ -10283,9 +10344,9 @@ var ICONS = {
10283
10344
  viewBox: "0 0 120 120",
10284
10345
  body: '<path fill="currentColor" d="M113 2.5A7 7 0 0 1 120 9.5L120 71.5A6 6 0 0 1 114 77.5L83 77.5A3 3 0 0 1 80 74.5L80 42.5A4 4 0 0 1 84 38.5L110 38.5A4 4 0 0 0 114 34.5L114 12.5A4 4 0 0 0 110 8.5L10 8.5A4 4 0 0 0 6 12.5L6 34.5A4 4 0 0 0 10 38.5L36 38.5A4 4 0 0 1 40 42.5L40 74.5A3 3 0 0 1 37 77.5L6 77.5A6 6 0 0 1 0 71.5L0 9.5A7 7 0 0 1 7 2.5Z"/><path fill="currentColor" d="M40 77.5H80V110.5A7 7 0 0 1 73 117.5H47A7 7 0 0 1 40 110.5Z"/>'
10285
10346
  },
10286
- /** Baseline market-status mark, on its native 18 grid. */
10347
+ /** The market-status mark, on its native 18 grid. */
10287
10348
  marketStatus: { viewBox: "0 0 18 18", body: '<circle cx="9" cy="9" r="7" fill="currentColor" opacity="0.2"/><path fill="currentColor" d="M9 5a4 4 0 1 1 0 8 4 4 0 0 1 0-8"/>' },
10288
- /** Independently drawn replay marks on the baseline 18 grid. */
10349
+ /** The replay marks, on their native 18 grid. */
10289
10350
  // The disc carries the phase in `currentColor`; the rewind stays ONE ink in both modes, because a
10290
10351
  // mark that changed the colour of its own shape between modes would read as a different mark.
10291
10352
  replayStatus: { viewBox: "0 0 18 18", body: '<circle cx="9" cy="9" r="9" fill="currentColor"/><path d="M8 5.5v7L4.5 9 8 5.5Zm5 0v7L9.5 9 13 5.5Z" fill="var(--qc-state-markInk)"/>' },
@@ -10314,18 +10375,18 @@ var ICONS = {
10314
10375
  viewBox: "0 0 28 28",
10315
10376
  body: fill("M18 14a4 4 0 1 1-8 0 4 4 0 0 1 8 0Zm-1 0a3 3 0 1 1-6 0 3 3 0 0 1 6 0Z") + fill("M8.5 5h11l5 9-5 9h-11l-5-9 5-9Zm-3.86 9L9.1 6h9.82l4.45 8-4.45 8H9.1l-4.45-8Z")
10316
10377
  },
10317
- // Enter fullscreen: the baseline's four corner brackets turning outward, drawn as a one-unit
10378
+ // Enter fullscreen: four corner brackets turning outward, drawn as a one-unit
10318
10379
  // wall on the 28 grid.
10319
10380
  fullscreen: { viewBox: "0 0 28 28", body: fill("M7 18.5A2.5 2.5 0 0 0 9.5 21H12v1H9.5A3.5 3.5 0 0 1 6 18.5V16h1zm15 0a3.5 3.5 0 0 1-3.5 3.5H16v-1h2.5a2.5 2.5 0 0 0 2.5-2.5V16h1zM12 7H9.5A2.5 2.5 0 0 0 7 9.5V12H6V9.5A3.5 3.5 0 0 1 9.5 6H12zm6.5-1A3.5 3.5 0 0 1 22 9.5V12h-1V9.5A2.5 2.5 0 0 0 18.5 7H16V6z") },
10320
- // Exit fullscreen: the same brackets folded inward, stroked at the baseline's 1.5 weight.
10381
+ // Exit fullscreen: the same brackets folded inward, stroked at a 1.5 weight.
10321
10382
  exitFullscreen: { viewBox: "0 0 28 28", body: stroke("M11 6v1.5A3.5 3.5 0 0 1 7.5 11H6M22 11h-1.5A3.5 3.5 0 0 1 17 7.5V6M17 22v-1.5a3.5 3.5 0 0 1 3.5-3.5H22M6 17h1.5a3.5 3.5 0 0 1 3.5 3.5V22", 1.5) },
10322
- // Chart image: the baseline camera, a one-unit-walled body with the shutter hump left of centre
10383
+ // Chart image: a camera, a one-unit-walled body with the shutter hump left of centre
10323
10384
  // and the lens ring offset toward it.
10324
10385
  camera: {
10325
10386
  viewBox: "0 0 28 28",
10326
10387
  body: fill("M11.118 6a.5.5 0 0 0-.447.276L9.809 8H5.5A1.5 1.5 0 0 0 4 9.5v10A1.5 1.5 0 0 0 5.5 21h16a1.5 1.5 0 0 0 1.5-1.5v-10A1.5 1.5 0 0 0 21.5 8h-4.309l-.862-1.724A.5.5 0 0 0 15.882 6h-4.764zm-1.342-.17A1.5 1.5 0 0 1 11.118 5h4.764a1.5 1.5 0 0 1 1.342.83L17.809 7H21.5A2.5 2.5 0 0 1 24 9.5v10a2.5 2.5 0 0 1-2.5 2.5h-16A2.5 2.5 0 0 1 3 19.5v-10A2.5 2.5 0 0 1 5.5 7h3.691l.585-1.17z") + fill("M13.5 18a3.5 3.5 0 1 0 0-7 3.5 3.5 0 0 0 0 7zm0 1a4.5 4.5 0 1 0 0-9 4.5 4.5 0 0 0 0 9z")
10327
10388
  },
10328
- /** The layout tile's own maximize and restore marks, on the baseline's native 18 grid: two
10389
+ /** The layout tile's own maximize and restore marks, on their native 18 grid: two
10329
10390
  * opposite corner brackets turning outward to fill the layout, and inward to give the tile back. */
10330
10391
  tileMaximize: { viewBox: "0 0 18 18", body: '<path fill="currentColor" d="M14.5 8V3.5H10V2h6v6h-1.5Zm-11 2v4.5H8V16H2v-6h1.5Z"/>' },
10331
10392
  tileRestore: { viewBox: "0 0 18 18", body: '<path fill="currentColor" d="M10 8V2h1.5v4.5H16V8h-6Zm-2 2v6H6.5v-4.5H2V10h6Z"/>' },
@@ -10336,7 +10397,7 @@ var ICONS = {
10336
10397
  navZoomIn: { viewBox: "0 0 18 18", body: '<path fill="currentColor" d="M8.25 13.75v-9.5h1.5v9.5h-1.5Z"/><path fill="currentColor" d="M13.75 9.75h-9.5v-1.5h9.5v1.5Z"/>' },
10337
10398
  navScroll: { viewBox: "0 0 18 18", body: '<path fill="currentColor" d="M7.83 3.92 12.28 9l-4.45 5.08-1.13-1L10.29 9l-3.6-4.09 1.14-.99Z"/>' },
10338
10399
  navReset: { viewBox: "0 0 18 18", body: '<path fill="currentColor" d="M10 6.38V8L6 5.5 10 3v1.85A5.25 5.25 0 1 1 3.75 10a.75.75 0 0 1 1.5 0A3.75 3.75 0 1 0 10 6.38Z"/>' },
10339
- /** The pickers' wide caret, on its native 16 by 8 grid: the baseline's own trigger arrow, which
10400
+ /** The pickers' wide caret, on its native 16 by 8 grid: the trigger arrow, which
10340
10401
  * renders half size beside a picker's text. */
10341
10402
  menuArrowWide: { viewBox: "0 0 16 8", body: '<path fill="currentColor" d="M0 1.475l7.396 6.04.596.485.593-.49L16 1.39 14.807 0 7.393 6.122 8.58 6.12 1.186.08z"/>' },
10342
10403
  // Hairline weight: these two sit beside a label rather than alone in a 38px cell.
@@ -10447,7 +10508,7 @@ var ICONS = {
10447
10508
  ruler: { viewBox: "0 0 28 28", body: '<path fill="currentColor" fill-rule="evenodd" transform="rotate(-45 14 14)" d="M2 9.75a1.5 1.5 0 0 0-1.5 1.5v5.5a1.5 1.5 0 0 0 1.5 1.5h24a1.5 1.5 0 0 0 1.5-1.5v-5.5a1.5 1.5 0 0 0-1.5-1.5zm0 1h3v2.5h1v-2.5h3.25v3.9h1v-3.9h3.25v2.5h1v-2.5h3.25v3.9h1v-3.9H22v2.5h1v-2.5h3a.5.5 0 0 1 .5.5v5.5a.5.5 0 0 1-.5.5H2a.5.5 0 0 1-.5-.5v-5.5a.5.5 0 0 1 .5-.5z"/>' },
10448
10509
  zoomIn: { viewBox: "0 0 28 28", body: '<path fill="currentColor" d="M17.646 18.354l4 4 .708-.708-4-4z"/><path fill="currentColor" d="M12.5 21a8.5 8.5 0 1 1 0-17 8.5 8.5 0 0 1 0 17zm0-1a7.5 7.5 0 1 0 0-15 7.5 7.5 0 0 0 0 15z"/><path fill="currentColor" d="M9 13h7v-1H9z"/><path fill="currentColor" d="M13 16V9h-1v7z"/>' },
10449
10510
  groupGlyphs: { viewBox: "0 0 28 28", body: '<path fill="currentColor" d="M4.05 14a9.95 9.95 0 1 1 19.9 0 9.95 9.95 0 0 1-19.9 0ZM14 3a11 11 0 1 0 0 22 11 11 0 0 0 0-22Zm-3 13.03a.5.5 0 0 1 .64.3 2.5 2.5 0 0 0 4.72 0 .5.5 0 0 1 .94.34 3.5 3.5 0 0 1-6.6 0 .5.5 0 0 1 .3-.64Zm.5-4.53a1 1 0 1 0 0 2 1 1 0 0 0 0-2Zm5 0a1 1 0 1 0 0 2 1 1 0 0 0 0-2Z"/>' },
10450
- // The rail arrow: a filled chevron on a 10 by 16 grid, drawn 4 by 7 in the strip.
10511
+ // The drawing toolbar's arrow: a filled chevron on a 10 by 16 grid, drawn 4 by 7 in the strip.
10451
10512
  chevronRight16: { viewBox: "0 0 10 16", aspect: 4 / 7, size: 7, body: '<path fill="currentColor" d="M.6 1.4l1.4-1.4 8 8-8 8-1.4-1.4 6.389-6.532-6.389-6.668z"/>' },
10452
10513
  // Six dots on an 8 by 12 grid, each PAINTED in currentColor: the svg the glyph is wrapped in
10453
10514
  // carries `fill="none"`, so a rect with no fill of its own is a grip nobody can see.
@@ -10467,8 +10528,8 @@ var ICONS = {
10467
10528
  textTee: { size: 15, aspect: 13 / 15, viewBox: "0 0 13 15", body: '<path stroke="currentColor" d="M4 14.5h2.5m2.5 0H6.5m0 0V.5m0 0h-5a1 1 0 0 0-1 1V4m6-3.5h5a1 1 0 0 1 1 1V4"/>' },
10468
10529
  // A tool's star in a flyout: an outline at rest and a filled star once saved, on an 18 grid.
10469
10530
  star: { viewBox: "0 0 18 18", body: '<path stroke="currentColor" d="M9 2.13l1.903 3.855.116.236.26.038 4.255.618-3.079 3.001-.188.184.044.259.727 4.237-3.805-2L9 12.434l-.233.122-3.805 2.001.727-4.237.044-.26-.188-.183-3.079-3.001 4.255-.618.26-.038.116-.236L9 2.13z"/>' },
10470
- // The favorites bar's toggle on the rail: one outlined star in either state, the state carried by
10471
- // the button's fill.
10531
+ // The favorites bar's toggle on the drawing toolbar: one outlined star in either state, the state
10532
+ // carried by the button's fill.
10472
10533
  favoritesBar: { viewBox: "0 0 28 28", body: '<path fill="currentColor" fill-rule="evenodd" d="m17.13 9.74 7.37.9-5.44 5.06L20.4 23 14 19.38 7.6 23l1.34-7.3-5.44-5.06 7.37-.9L14 3l3.13 6.74Zm5.11 1.63-4.26 3.97 1.04 5.74L14 18.24l-5.02 2.84 1.04-5.74-4.26-3.97 5.79-.7L14 5.37l2.45 5.3 5.8.7Z"/>' },
10473
10534
  starFilled: { viewBox: "0 0 18 18", body: '<path fill="currentColor" d="M9 1l2.35 4.76 5.26.77-3.8 3.7.9 5.24L9 13l-4.7 2.47.9-5.23-3.8-3.71 5.25-.77L9 1z"/>' },
10474
10535
  trash28: { viewBox: "0 0 28 28", body: '<path fill="currentColor" d="M18 7h5v1h-2.01l-1.33 14.64a1.5 1.5 0 0 1-1.5 1.36H9.84a1.5 1.5 0 0 1-1.49-1.36L7.01 8H5V7h5V6c0-1.1.9-2 2-2h4a2 2 0 0 1 2 2v1Zm-6-2a1 1 0 0 0-1 1v1h6V6a1 1 0 0 0-1-1h-4ZM8.02 8l1.32 14.54a.5.5 0 0 0 .5.46h8.33a.5.5 0 0 0 .5-.46L19.99 8H8.02Z"/>' },
@@ -10493,7 +10554,7 @@ var ICONS = {
10493
10554
  submenuArrow: { viewBox: "0 0 24 24", body: '<path d="M9 18l6-6-6-6" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>' },
10494
10555
  close18: { viewBox: "0 0 18 18", body: '<path d="M4.5 4.5l9 9M13.5 4.5l-9 9" stroke="currentColor" stroke-width="1.4" stroke-linecap="round"/>' },
10495
10556
  chevronDown18: { viewBox: "0 0 18 18", body: '<path fill="currentColor" d="M3.92 7.83 9 12.29l5.08-4.46-1-1.13L9 10.29l-4.09-3.6-.99 1.14Z"/>' },
10496
- // The indicator browser's marks, on the grids its rows were drawn for.
10557
+ // The indicator picker's marks, on the grids its rows were drawn for.
10497
10558
  pickerStar: { viewBox: "0 0 18 18", body: '<path fill="none" stroke="currentColor" stroke-width="1.2" stroke-linejoin="round" d="M9 2.13L11.057 6.168L15.534 6.877L12.329 10.082L13.038 14.558L9 12.5L4.962 14.558L5.671 10.082L2.466 6.877L6.943 6.168z"/>' },
10498
10559
  pickerStarFilled: { viewBox: "0 0 18 18", body: '<path fill="currentColor" stroke="currentColor" stroke-width="1.2" stroke-linejoin="round" d="M9 2.13L11.057 6.168L15.534 6.877L12.329 10.082L13.038 14.558L9 12.5L4.962 14.558L5.671 10.082L2.466 6.877L6.943 6.168z"/>' },
10499
10560
  plus24: { viewBox: "0 0 24 24", body: '<path d="M12 5v14M5 12h14" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>' },
@@ -10537,7 +10598,7 @@ function openDialog(options) {
10537
10598
  if (options.width) box.style.width = `${options.width}px`;
10538
10599
  stopPointer(box);
10539
10600
  scrim.appendChild(box);
10540
- if (options.animated) scrim.dataset.state = "opening";
10601
+ scrim.dataset.state = "opening";
10541
10602
  let isOpen = true;
10542
10603
  const previouslyFocused = document.activeElement;
10543
10604
  const escape = pushEscapeOwner();
@@ -10562,7 +10623,7 @@ function openDialog(options) {
10562
10623
  untrack();
10563
10624
  document.removeEventListener("keydown", onKey, true);
10564
10625
  previouslyFocused?.focus?.();
10565
- const exitMs = options.animated && closeOptions?.animate !== false ? motionDurationMs(scrim, "motion.durationBase") : 0;
10626
+ const exitMs = closeOptions?.animate !== false ? motionDurationMs(scrim, "motion.durationBase") : 0;
10566
10627
  if (exitMs <= 0) {
10567
10628
  remove();
10568
10629
  return;
@@ -10608,12 +10669,10 @@ function openDialog(options) {
10608
10669
  const handle = { element: box, close, open: () => isOpen };
10609
10670
  options.build(box, handle);
10610
10671
  (layerFor(options.host) ?? options.host).appendChild(scrim);
10611
- if (options.animated) {
10612
- scrim.getBoundingClientRect();
10613
- scrim.dataset.state = "open";
10614
- }
10672
+ scrim.getBoundingClientRect();
10673
+ scrim.dataset.state = "open";
10615
10674
  const refresh = options.refresh;
10616
- untrack = trackOverlay(options.host, close, refresh ? () => {
10675
+ untrack = trackOverlay(options.host, () => close({ animate: false }), refresh ? () => {
10617
10676
  if (isOpen) refresh();
10618
10677
  } : void 0);
10619
10678
  document.addEventListener("keydown", onKey, true);
@@ -11000,7 +11059,7 @@ function mountSettingsBar(deps) {
11000
11059
  const at = deps.stackPosition();
11001
11060
  return menuOf(t("drawing.visualOrder"), "narrow", ...ORDER_MOVES.map((move) => menuRow(t(move.label), () => deps.run(move.command), { disabled: move.dead(at), command: move.command })));
11002
11061
  }
11003
- return menuOf(t("drawing.visibilityOnIntervals"), "wide", ...VISIBILITY_PRESETS.map((v) => menuRow(t(v.label), () => deps.run("chart.drawings.visibility", v.preset), { command: "chart.drawings.visibility" })));
11062
+ return menuOf(t("drawing.visibilityOnTimeframes"), "wide", ...VISIBILITY_PRESETS.map((v) => menuRow(t(v.label), () => deps.run("chart.drawings.visibility", v.preset), { command: "chart.drawings.visibility" })));
11004
11063
  };
11005
11064
  const arm = (kind, row2) => {
11006
11065
  cancelClose();
@@ -11034,7 +11093,7 @@ function mountSettingsBar(deps) {
11034
11093
  t("drawing.moreActions"),
11035
11094
  "wide",
11036
11095
  submenuRow("order", t("drawing.visualOrder"), "layers"),
11037
- submenuRow("visibility", t("drawing.visibilityOnIntervals")),
11096
+ submenuRow("visibility", t("drawing.visibilityOnTimeframes")),
11038
11097
  separator2(),
11039
11098
  plain(menuRow(t("drawing.clone"), () => deps.run("chart.drawings.clone"), { icon: deps.icons.icon("clone"), hint: t("drawing.hintClone", { modifier: modifier() }), command: "chart.drawings.clone" })),
11040
11099
  plain(menuRow(t("drawing.copy"), () => deps.run("chart.drawings.copy"), { spacer: true, hint: t("drawing.hintCopy", { modifier: modifier() }), command: "chart.drawings.copy" })),
@@ -11377,11 +11436,15 @@ function openDialog2(options) {
11377
11436
  stopDrag = dragBy(header, element);
11378
11437
  element.append(header, body, footer);
11379
11438
  },
11380
- onClose: () => {
11439
+ // The drag and whatever the dialog opened over itself (a palette, a menu, a template prompt) go
11440
+ // as the dialog begins to close, so nothing answers the viewer from a box that no longer takes
11441
+ // input and no document listener outlives the box it belonged to.
11442
+ onClosing: () => {
11381
11443
  stopDrag();
11382
11444
  if (box) closeOverlays(box);
11383
- options.onClose?.();
11384
- }
11445
+ options.onClosing?.();
11446
+ },
11447
+ onClose: () => options.onClose?.()
11385
11448
  });
11386
11449
  return { body, footer, box: modal.element, close: modal.close };
11387
11450
  }
@@ -11411,7 +11474,9 @@ function openImagePicker(deps) {
11411
11474
  icons: deps.icons,
11412
11475
  role: "drawing-image-picker",
11413
11476
  width: 420,
11414
- onClose: () => {
11477
+ // The picker stops taking a paste as it begins to close, and the Image tool can open it again
11478
+ // while the leaving one finishes its exit.
11479
+ onClosing: () => {
11415
11480
  document.removeEventListener("paste", onPaste);
11416
11481
  deps.onClose?.();
11417
11482
  }
@@ -11901,7 +11966,7 @@ function visibilityRows(ctx) {
11901
11966
  const range2 = visibility[key];
11902
11967
  out.push(visibilityRangeRow(t, ctx.icons, { label: t(text2), range: range2, max, disabled: pinned(range2.on), onChange: (next) => ctx.patchVisibility({ [key]: next }) }));
11903
11968
  }
11904
- if (enabled === 1) out.push(el("span", { class: "qc-muted qc-drawing-note", text: t("drawing.intervalPinnedNote") }));
11969
+ if (enabled === 1) out.push(el("span", { class: "qc-muted qc-drawing-note", text: t("drawing.timeframePinnedNote") }));
11905
11970
  return out;
11906
11971
  }
11907
11972
 
@@ -11920,7 +11985,9 @@ function openSettingsDialog(deps) {
11920
11985
  icons: deps.icons,
11921
11986
  role: "drawing-settings",
11922
11987
  width: 380,
11923
- onClose: () => {
11988
+ // Escape, the corner close or a press outside ends the session as the dialog begins to close,
11989
+ // so the drawing is back as it was before the exit motion starts rather than after it.
11990
+ onClosing: () => {
11924
11991
  if (settled) return;
11925
11992
  cancel();
11926
11993
  }
@@ -12128,7 +12195,7 @@ function attachDrawingsPlane(deps) {
12128
12195
  indicators: { hidden: "drawing.statusIndicatorsHidden", shown: "drawing.statusIndicatorsShown" },
12129
12196
  all: { hidden: "drawing.statusAllHidden", shown: "drawing.statusAllShown" }
12130
12197
  };
12131
- const groups = buildRailGroups();
12198
+ const groups = buildDrawingToolbarGroups();
12132
12199
  const events = {};
12133
12200
  const handle = attachDrawings({
12134
12201
  chart: deps.chart,
@@ -12200,7 +12267,7 @@ function attachDrawingsPlane(deps) {
12200
12267
  removeLocked: p.removeLocked,
12201
12268
  counts: handle.counts(),
12202
12269
  indicatorCount: deps.indicators.count(),
12203
- railTools: p.railTools,
12270
+ drawingToolbarTools: p.drawingToolbarTools,
12204
12271
  favorites: p.favorites,
12205
12272
  recentGlyphs: p.recentGlyphs,
12206
12273
  layoutCharts: deps.chartCount()
@@ -12350,7 +12417,7 @@ function attachDrawingsPlane(deps) {
12350
12417
  }
12351
12418
  handle.armTool(tool, props);
12352
12419
  if (tool) {
12353
- const patch = { railTools: rememberRailTool(groups, prefs().railTools, tool) };
12420
+ const patch = { drawingToolbarTools: rememberDrawingToolbarTool(groups, prefs().drawingToolbarTools, tool) };
12354
12421
  const glyph2 = props && typeof props.glyph === "string" ? props.glyph : null;
12355
12422
  if (glyph2) patch.recentGlyphs = pushRecentGlyph(prefs().recentGlyphs, glyph2);
12356
12423
  write(patch);
@@ -12724,7 +12791,7 @@ var FillBetweenPrimitive = class {
12724
12791
  lower = [];
12725
12792
  colors;
12726
12793
  /** Nothing until the first `setData`, which every render path makes before a draw. A tint chosen
12727
- * here would be a second study default with no owner. */
12794
+ * here would be a second indicator default with no owner. */
12728
12795
  color = "transparent";
12729
12796
  attached(param) {
12730
12797
  this.chart = param.chart;
@@ -12925,7 +12992,7 @@ function shapeKeyOf(built) {
12925
12992
  return JSON.stringify([built.placement, built.plots.map((p) => [p.key, p.type, p.location === "absolute", p.scale ?? null])]);
12926
12993
  }
12927
12994
  function makeEntry(chart, built, neutral) {
12928
- const paneIndex = built.placement === "pane" ? studyPaneIndex(chart) : 0;
12995
+ const paneIndex = built.placement === "pane" ? indicatorPaneIndex(chart) : 0;
12929
12996
  const labels = built.display?.labelsOnPriceScale !== false;
12930
12997
  const series2 = [];
12931
12998
  const markers = [];
@@ -13124,7 +13191,7 @@ function safeRemove(chart, series2) {
13124
13191
  } catch {
13125
13192
  }
13126
13193
  }
13127
- function studyPaneIndex(chart) {
13194
+ function indicatorPaneIndex(chart) {
13128
13195
  sweepEmptyPanes(chart);
13129
13196
  return chart.panes().length;
13130
13197
  }
@@ -13693,7 +13760,7 @@ function attachIndicatorsPlane(deps) {
13693
13760
  };
13694
13761
  const renderer = attachIndicators(deps.chart, {
13695
13762
  candles: deps.candleSeries,
13696
- // A study that declares no precision writes its scale through the symbol formatter, so a
13763
+ // An indicator that declares no precision writes its scale through the symbol formatter, so a
13697
13764
  // moving average on a Treasury reads in thirty-seconds like the bars beside it.
13698
13765
  symbolPriceFormat: () => ({ key: deps.formatKey(), formatter: (price) => deps.formatter().format(price), minMove: deps.minMove() }),
13699
13766
  neutral: () => deps.canvas().neutral
@@ -14562,7 +14629,7 @@ var GRAIN_GROUPS = [
14562
14629
  { unit: "d", rungs: [{ tf: "1d", sec: 86400 }] }
14563
14630
  ];
14564
14631
  var DAY_RUNG = { tf: "1d", sec: 86400 };
14565
- function subIntervalsFor(chartTf) {
14632
+ function subTimeframesFor(chartTf) {
14566
14633
  const parsed = parseTimeframe(chartTf);
14567
14634
  const parent = tfSeconds(chartTf);
14568
14635
  if (!parsed || !parent) return [];
@@ -14572,8 +14639,8 @@ function subIntervalsFor(chartTf) {
14572
14639
  const intraday = parsed.unit === "s" || parsed.unit === "m" || parsed.unit === "h";
14573
14640
  return GRAIN_GROUPS.filter((g, i) => i === own || i === own - 1 || intraday && g.unit === "s").flatMap((g) => g.rungs).filter((s) => s.sec <= parent && parent % s.sec === 0).sort((a, b) => a.sec - b.sec);
14574
14641
  }
14575
- function autoIntervalFor(chartTf) {
14576
- const offered = subIntervalsFor(chartTf);
14642
+ function autoTimeframeFor(chartTf) {
14643
+ const offered = subTimeframesFor(chartTf);
14577
14644
  return offered.length ? offered[offered.length - 1] : null;
14578
14645
  }
14579
14646
  function composeFormingBar(parent, subs, k) {
@@ -14595,8 +14662,8 @@ function attachReplayPlane(deps) {
14595
14662
  let arming = false;
14596
14663
  let timer = null;
14597
14664
  let speed = deps.initialSpeed;
14598
- let autoInterval = deps.initialInterval === "auto";
14599
- let manualInterval = autoInterval ? null : deps.initialInterval;
14665
+ let autoGrain = deps.initialGrain === "auto";
14666
+ let manualGrain = autoGrain ? null : deps.initialGrain;
14600
14667
  let subs = null;
14601
14668
  let formK = 0;
14602
14669
  let stepping = null;
@@ -14608,18 +14675,18 @@ function attachReplayPlane(deps) {
14608
14675
  };
14609
14676
  const grains = () => {
14610
14677
  const served = deps.resolutions();
14611
- const ladder = subIntervalsFor(deps.timeframe());
14678
+ const ladder = subTimeframesFor(deps.timeframe());
14612
14679
  return served && served.length > 0 ? ladder.filter((s) => served.includes(s.tf)) : ladder;
14613
14680
  };
14614
- const effectiveInterval = () => {
14681
+ const effectiveGrain = () => {
14615
14682
  const available = grains();
14616
- if (autoInterval) return available.length ? available[available.length - 1] : null;
14617
- return available.find((s) => s.tf === manualInterval) ?? null;
14683
+ if (autoGrain) return available.length ? available[available.length - 1] : null;
14684
+ return available.find((s) => s.tf === manualGrain) ?? null;
14618
14685
  };
14619
14686
  const sync = () => {
14620
14687
  deps.onChange();
14621
14688
  };
14622
- const currentInterval = () => autoInterval ? "auto" : manualInterval ?? "auto";
14689
+ const currentGrain = () => autoGrain ? "auto" : manualGrain ?? "auto";
14623
14690
  const paintCursor = () => {
14624
14691
  if (!master) return;
14625
14692
  deps.paint(master.slice(0, cursor));
@@ -14654,15 +14721,15 @@ function attachReplayPlane(deps) {
14654
14721
  deps.setCrosshair(!next);
14655
14722
  };
14656
14723
  const fetchSubs = async (parentIdx) => {
14657
- const interval = effectiveInterval();
14658
- if (!master || !interval) return null;
14659
- if (interval.sec >= tfSeconds(deps.timeframe())) return null;
14724
+ const grain = effectiveGrain();
14725
+ if (!master || !grain) return null;
14726
+ if (grain.sec >= tfSeconds(deps.timeframe())) return null;
14660
14727
  const parent = master[parentIdx];
14661
14728
  if (!parent) return null;
14662
14729
  const from = parent.t;
14663
14730
  const to = parent.t + tfSeconds(deps.timeframe()) - 1;
14664
14731
  try {
14665
- const page = await deps.datafeed.history(deps.symbol(), interval.tf, { from, to });
14732
+ const page = await deps.datafeed.history(deps.symbol(), grain.tf, { from, to });
14666
14733
  const found = page.bars.filter((b) => b.t >= from && b.t <= to);
14667
14734
  return found.length >= 2 ? found : null;
14668
14735
  } catch {
@@ -14693,11 +14760,11 @@ function attachReplayPlane(deps) {
14693
14760
  const epoch = generation;
14694
14761
  const symbol = deps.symbol();
14695
14762
  const timeframe2 = deps.timeframe();
14696
- const grain = currentInterval();
14697
- const chosen = effectiveInterval();
14763
+ const grain = currentGrain();
14764
+ const chosen = effectiveGrain();
14698
14765
  const wholeBar = !chosen || chosen.sec >= tfSeconds(deps.timeframe());
14699
14766
  const found = wholeBar ? null : await fetchSubs(targetCursor - 1);
14700
- if (destroyed || deps.disposed() || stepping !== flight || generation !== epoch || deps.symbol() !== symbol || deps.timeframe() !== timeframe2 || currentInterval() !== grain || !master || cursor !== targetCursor - 1 || master[targetCursor - 1] !== parent) return;
14767
+ if (destroyed || deps.disposed() || stepping !== flight || generation !== epoch || deps.symbol() !== symbol || deps.timeframe() !== timeframe2 || currentGrain() !== grain || !master || cursor !== targetCursor - 1 || master[targetCursor - 1] !== parent) return;
14701
14768
  cursor = targetCursor;
14702
14769
  if (found) {
14703
14770
  subs = found;
@@ -14796,26 +14863,26 @@ function attachReplayPlane(deps) {
14796
14863
  paintCursor();
14797
14864
  sync();
14798
14865
  },
14799
- interval: currentInterval,
14866
+ timeframe: currentGrain,
14800
14867
  // What `auto` actually RESOLVED to, so a surface can say the grain rather than the mode. Empty
14801
14868
  // when the chart's timeframe has nothing finer to form from and updates advance whole bars.
14802
- resolvedInterval: () => effectiveInterval()?.tf ?? "",
14803
- setInterval(token) {
14804
- if (token === currentInterval()) return;
14869
+ resolvedTimeframe: () => effectiveGrain()?.tf ?? "",
14870
+ setTimeframe(token) {
14871
+ if (token === currentGrain()) return;
14805
14872
  if (token === "auto") {
14806
- autoInterval = true;
14873
+ autoGrain = true;
14807
14874
  } else {
14808
14875
  if (!grains().some((s) => s.tf === token)) return;
14809
- autoInterval = false;
14810
- manualInterval = token;
14876
+ autoGrain = false;
14877
+ manualGrain = token;
14811
14878
  }
14812
14879
  invalidateStep();
14813
- deps.persist("interval", currentInterval());
14880
+ deps.persist("timeframe", currentGrain());
14814
14881
  subs = null;
14815
14882
  formK = 0;
14816
14883
  sync();
14817
14884
  },
14818
- subIntervals: () => grains().map((s) => s.tf),
14885
+ subTimeframes: () => grains().map((s) => s.tf),
14819
14886
  state: () => ({ on: master !== null, playing, cursor, total: master?.length ?? deps.bars().length, speed }),
14820
14887
  // An OPEN QUESTION outranks a running session. Re-arming mid-replay to choose a different start
14821
14888
  // puts the plot back to taking a click, and every surface that answers to the picker — the
@@ -14852,7 +14919,7 @@ function attachReplayPlane(deps) {
14852
14919
  abandon,
14853
14920
  snapshot: () => ({ on: master !== null, playing, cursor: master === null ? deps.bars().length : cursor, total: master?.length ?? deps.bars().length }),
14854
14921
  speed: () => speed,
14855
- interval: currentInterval,
14922
+ timeframe: currentGrain,
14856
14923
  destroy() {
14857
14924
  destroyed = true;
14858
14925
  abandon();
@@ -14931,7 +14998,7 @@ function createExtensionHost(deps, extensions = []) {
14931
14998
  }
14932
14999
  }
14933
15000
  clearLanes(record3.lanes);
14934
- record3.menuProviders.clear();
15001
+ record3.menuBuilders.clear();
14935
15002
  if (record3.hideLayers.size) {
14936
15003
  record3.hideLayers.clear();
14937
15004
  notifyHideLayers();
@@ -14955,7 +15022,7 @@ function createExtensionHost(deps, extensions = []) {
14955
15022
  } },
14956
15023
  live: true,
14957
15024
  lanes: newLanes(),
14958
- menuProviders: /* @__PURE__ */ new Set(),
15025
+ menuBuilders: /* @__PURE__ */ new Set(),
14959
15026
  hideLayers: /* @__PURE__ */ new Map(),
14960
15027
  commands: /* @__PURE__ */ new Map(),
14961
15028
  priceLines: /* @__PURE__ */ new Set(),
@@ -15044,12 +15111,12 @@ function createExtensionHost(deps, extensions = []) {
15044
15111
  record3.lanes.dispose.delete(callback);
15045
15112
  };
15046
15113
  },
15047
- contributeContextMenu(provider) {
15114
+ contributeContextMenu(build) {
15048
15115
  if (!record3.live) return () => {
15049
15116
  };
15050
- record3.menuProviders.add(provider);
15117
+ record3.menuBuilders.add(build);
15051
15118
  return () => {
15052
- record3.menuProviders.delete(provider);
15119
+ record3.menuBuilders.delete(build);
15053
15120
  };
15054
15121
  },
15055
15122
  contributeCommands(commands) {
@@ -15143,9 +15210,9 @@ function createExtensionHost(deps, extensions = []) {
15143
15210
  if (!hostLive) return [];
15144
15211
  const rows2 = [];
15145
15212
  for (const record3 of liveRecords()) {
15146
- for (const provider of [...record3.menuProviders]) {
15213
+ for (const build of [...record3.menuBuilders]) {
15147
15214
  try {
15148
- rows2.push(...provider(context));
15215
+ rows2.push(...build(context));
15149
15216
  } catch {
15150
15217
  }
15151
15218
  }
@@ -15540,7 +15607,7 @@ function mountChartLegend(container, strings, controls) {
15540
15607
  venueText.className = "qc-legend-venue qc-legend-row-venue";
15541
15608
  const value = document.createElement("span");
15542
15609
  value.className = "qc-legend-value";
15543
- value.dataset.role = "legend-study-value";
15610
+ value.dataset.role = "legend-indicator-value";
15544
15611
  const gear = chipButton(controls.icons.glyph(ICONS.legendSettings, { size: 18 }), "", () => {
15545
15612
  const r = gear.getBoundingClientRect();
15546
15613
  controls.onSettings?.(chip.id, { x: r.x, y: r.y, w: r.width, h: r.height });
@@ -16195,7 +16262,7 @@ function attachLegendPlane(deps) {
16195
16262
  const paneRemembered = /* @__PURE__ */ new Map();
16196
16263
  const paneKeys = () => {
16197
16264
  const keys = { 0: "main" };
16198
- for (const [id, pane] of Object.entries(deps.indicators.renderer.paneOf())) if (pane > 0) keys[pane] = `study:${id}`;
16265
+ for (const [id, pane] of Object.entries(deps.indicators.renderer.paneOf())) if (pane > 0) keys[pane] = `indicator:${id}`;
16199
16266
  for (const entry of deps.compare?.api.list() ?? []) {
16200
16267
  const pane = deps.compare.handle.paneIndexOf(entry.symbol);
16201
16268
  if (pane !== null && pane > 0) keys[pane] = `compare:${entry.symbol}`;
@@ -16320,7 +16387,7 @@ function attachLegendPlane(deps) {
16320
16387
  legend2?.setChips([
16321
16388
  ...deps.indicators.chipsAt(hovered).map((row2) => {
16322
16389
  const paneIndex = paneOf[row2.id] ?? 0;
16323
- const remembered = paneRemembered.get(`study:${row2.id}`);
16390
+ const remembered = paneRemembered.get(`indicator:${row2.id}`);
16324
16391
  return {
16325
16392
  ...row2,
16326
16393
  paneIndex,
@@ -16699,7 +16766,7 @@ var CONTROL_ICONS = byMeaning({
16699
16766
  chevronDown: [ICONS.chevronDown, ICONS.chevronDown18],
16700
16767
  chevronLeft: [ICONS.chevronLeft],
16701
16768
  chevronRight: [ICONS.chevronRight],
16702
- // The rail's arrow, pointing where the flyout it opens will stand.
16769
+ // The drawing toolbar's arrow, pointing where the flyout it opens will stand.
16703
16770
  flyout: [ICONS.chevronRight16],
16704
16771
  submenu: [ICONS.submenuArrow],
16705
16772
  more: [ICONS.kebab],
@@ -16725,7 +16792,7 @@ var CONTROL_ICONS = byMeaning({
16725
16792
  selectDate: [ICONS.calendar],
16726
16793
  firstAvailable: [ICONS.firstAvailable],
16727
16794
  randomBar: [ICONS.randomBar],
16728
- // The drawing rail, its menus, the favorites bar and the drawing settings.
16795
+ // The drawing toolbar, its menus, the favorites bar and the drawing settings.
16729
16796
  cursorCross: [ICONS.cursorCross],
16730
16797
  cursorDot: [ICONS.cursorDot],
16731
16798
  cursorArrow: [ICONS.cursorArrow],
@@ -17116,7 +17183,7 @@ function attachMenuPlane(deps) {
17116
17183
  {
17117
17184
  priceText,
17118
17185
  symbol: deps.symbol(),
17119
- // The level menu's paste row is live exactly when the paste command would run (a copied
17186
+ // The context menu's paste row is live exactly when the paste command would run (a copied
17120
17187
  // drawing on the clipboard, the verb permitted). It offers no settings row: the settings
17121
17188
  // dialog belongs to the selected drawing's own surfaces, which the drawing plane mounts
17122
17189
  // and the chart.drawings.* commands drive.
@@ -17886,12 +17953,12 @@ function registerChartCommands(deps) {
17886
17953
  }
17887
17954
  });
17888
17955
  add({
17889
- id: "chart.replay.setInterval",
17956
+ id: "chart.replay.setTimeframe",
17890
17957
  scope: "chart",
17891
- label: "command.replayInterval",
17958
+ label: "command.replayTimeframe",
17892
17959
  available: () => handle.replay.state().on,
17893
17960
  execute: (arg) => {
17894
- if (typeof arg === "string" && arg) handle.replay.setInterval(arg);
17961
+ if (typeof arg === "string" && arg) handle.replay.setTimeframe(arg);
17895
17962
  }
17896
17963
  });
17897
17964
  const servable = (token) => {
@@ -18220,7 +18287,7 @@ var SCALE_KEY = "quickcharts.scale.v1";
18220
18287
  var PRICE_AXIS_KEY = "quickcharts.priceAxis.v1";
18221
18288
  var HIDDEN_KEY = "quickcharts.indHidden.v1";
18222
18289
  var REPLAY_SPEED_KEY = "quickcharts.replaySpeed.v1";
18223
- var REPLAY_INTERVAL_KEY = "quickcharts.replayIv.v1";
18290
+ var REPLAY_TIMEFRAME_KEY = "quickcharts.replayTf.v1";
18224
18291
  var TIMEZONE_KEY = "quickcharts.timezone.v1";
18225
18292
  var SUBSESSION_KEY = "quickcharts.subsession.v1";
18226
18293
  function sameIds(left, right) {
@@ -18465,8 +18532,8 @@ function createChartInstance(deps) {
18465
18532
  const indicators2 = attachIndicatorsPlane({
18466
18533
  chart,
18467
18534
  candleSeries,
18468
- // A study drawn over bars the active subsession hides would not line up with the series beside
18469
- // it, so it computes over the painted model rather than the loaded one.
18535
+ // An indicator drawn over bars the active subsession hides would not line up with the series
18536
+ // beside it, so it computes over the painted model rather than the loaded one.
18470
18537
  bars: () => shownBars(),
18471
18538
  i18n,
18472
18539
  formatter: () => symbolFormatter,
@@ -18622,9 +18689,9 @@ function createChartInstance(deps) {
18622
18689
  // A chart whose crosshair the host switched OFF keeps it off: replay may take the crosshair
18623
18690
  // away for the length of a question, never hand one back that was never there.
18624
18691
  setCrosshair: (visible) => chart.applyOptions({ crosshair: { mode: visible && deps.features.crosshair ? CrosshairMode.Normal : CrosshairMode.Hidden } }),
18625
- // The feed's own grains: a chart never offers, or fetches, an interval its feed cannot serve.
18692
+ // The feed's own grains: a chart never offers, or fetches, a timeframe its feed cannot serve.
18626
18693
  resolutions: () => deps.capabilities().resolutions,
18627
- persist: (key, value) => storage.set(key === "speed" ? REPLAY_SPEED_KEY : REPLAY_INTERVAL_KEY, value),
18694
+ persist: (key, value) => storage.set(key === "speed" ? REPLAY_SPEED_KEY : REPLAY_TIMEFRAME_KEY, value),
18628
18695
  onChange: () => {
18629
18696
  const state = replay2.snapshot();
18630
18697
  countdown?.refresh();
@@ -18634,7 +18701,7 @@ function createChartInstance(deps) {
18634
18701
  events.emit("replay", state);
18635
18702
  },
18636
18703
  initialSpeed: coerceReplaySpeed(storage.get(REPLAY_SPEED_KEY) ?? deps.preferences.replaySpeed),
18637
- initialInterval: storage.get(REPLAY_INTERVAL_KEY) ?? deps.preferences.replayInterval ?? "auto"
18704
+ initialGrain: storage.get(REPLAY_TIMEFRAME_KEY) ?? deps.preferences.replayTimeframe ?? "auto"
18638
18705
  });
18639
18706
  const countdownClock = createCountdownClock(() => Date.now() / 1e3, datafeed.serverTime?.bind(datafeed));
18640
18707
  countdown = attachCountdown({
@@ -18681,7 +18748,8 @@ function createChartInstance(deps) {
18681
18748
  setTouchAction: (value) => {
18682
18749
  gestures.style.touchAction = value;
18683
18750
  },
18684
- // A contributed layer rides the same eye the rail drives, through the drawing plane's verbs.
18751
+ // A contributed layer rides the same eye the drawing toolbar drives, through the drawing
18752
+ // plane's verbs.
18685
18753
  hideState: () => drawings2.verbs?.hide() ?? { mode: "drawings", on: false },
18686
18754
  setHide: (state) => drawings2.verbs?.setHide(state),
18687
18755
  hideLayersChanged: () => drawings2.syncHideLayers()
@@ -19357,7 +19425,7 @@ function createChartInstance(deps) {
19357
19425
  resetAppearance,
19358
19426
  t: () => i18n.t,
19359
19427
  // What is PAINTED, which is the replay slice while replay is on: the data export writes what
19360
- // the trader can see and never a bar the cursor has not revealed.
19428
+ // the viewer can see and never a bar the cursor has not revealed.
19361
19429
  bars: () => shownBars(),
19362
19430
  // The feed's own statement of how deep its history goes, never the oldest bar that happens to
19363
19431
  // be loaded: the chart opens on a short window, and a preset judged against that would be
@@ -19623,7 +19691,14 @@ function validGeometry(value, catalog) {
19623
19691
  }
19624
19692
 
19625
19693
  // src/widget/layout.ts
19626
- var SYNC_OFF = { symbol: false, interval: false, crosshair: false, time: false, dateRange: false };
19694
+ var SYNC_OFF = { symbol: false, timeframe: false, crosshair: false, time: false, dateRange: false };
19695
+ var syncSwitches = (stated) => Object.fromEntries(LAYOUT_SYNC_KEYS.map((key) => [key, stated[key] ?? SYNC_OFF[key]]));
19696
+ function readSyncFlags(value, defaults) {
19697
+ if (!value || typeof value !== "object" || Array.isArray(value)) return null;
19698
+ const stated = value;
19699
+ if (LAYOUT_SYNC_KEYS.some((key) => key in stated && typeof stated[key] !== "boolean")) return null;
19700
+ return Object.fromEntries(LAYOUT_SYNC_KEYS.map((key) => [key, key in stated ? stated[key] : defaults[key]]));
19701
+ }
19627
19702
  var LAYOUT_CONTENT_VERSION = 2;
19628
19703
  var validEntityNamespace = (value) => /^(?:layout:(?:[A-Za-z0-9_.!~*'()-]|%[0-9A-F]{2})+|session:[a-z0-9-]+)$/.test(value);
19629
19704
  var cloneIndicators = (instances) => instances.map((instance) => ({
@@ -19638,7 +19713,7 @@ var cloneTemplate = (template) => ({
19638
19713
  });
19639
19714
  function createLayoutPlane(deps) {
19640
19715
  const slots = [];
19641
- let flags = { ...SYNC_OFF, ...deps.sync ?? {} };
19716
+ let flags = syncSwitches(deps.sync ?? {});
19642
19717
  const fixedSync = deps.syncOffered ? LAYOUT_SYNC_KEYS.filter((key) => !deps.syncOffered.includes(key)) : [];
19643
19718
  const fixedFlags = { ...flags };
19644
19719
  const holdFixed = (next) => {
@@ -19757,7 +19832,7 @@ function createLayoutPlane(deps) {
19757
19832
  if (!applying) emitActive();
19758
19833
  }));
19759
19834
  slot.unsubscribes.push(handle.on("timeframe", () => {
19760
- if (flags.interval) fanOut2(index(), (other) => other.setTimeframe(handle.timeframe()));
19835
+ if (flags.timeframe) fanOut2(index(), (other) => other.setTimeframe(handle.timeframe()));
19761
19836
  if (!applying) deps.onChange();
19762
19837
  }));
19763
19838
  };
@@ -20021,15 +20096,14 @@ function createLayoutPlane(deps) {
20021
20096
  if (!arrangement2) throw new Error(`unknown arrangement code ${String(c.arrangement)}`);
20022
20097
  if (charts.length !== arrangement2.count) throw new Error("layout chart count does not match arrangement");
20023
20098
  if (!validGeometry(c.geometry, arrangement2.rects)) throw new Error("invalid layout geometry");
20024
- const sync = c.sync && typeof c.sync === "object" ? c.sync : null;
20025
- const keys = LAYOUT_SYNC_KEYS;
20026
- if (!sync || keys.some((key) => typeof sync[key] !== "boolean")) throw new Error("invalid layout sync flags");
20099
+ const sync = readSyncFlags(c.sync, fixedFlags);
20100
+ if (!sync) throw new Error("invalid layout sync flags");
20027
20101
  if (!Number.isInteger(c.active) || c.active < 0 || c.active >= charts.length) throw new Error("invalid active chart");
20028
20102
  return {
20029
20103
  arrangement: arrangement2,
20030
20104
  identity: { namespace: identity.namespace, next: identity.next },
20031
20105
  geometry: c.geometry.map((rect) => ({ ...rect })),
20032
- sync: Object.fromEntries(keys.map((key) => [key, sync[key]])),
20106
+ sync,
20033
20107
  active: c.active,
20034
20108
  charts
20035
20109
  };
@@ -20111,7 +20185,7 @@ function createLayoutPlane(deps) {
20111
20185
  setSync(partial) {
20112
20186
  if (removed) return;
20113
20187
  if (fixedSync.length > 0 && Object.keys(partial).every((key) => fixedSync.includes(key))) return;
20114
- flags = holdFixed({ ...flags, ...partial });
20188
+ flags = holdFixed(syncSwitches({ ...flags, ...partial }));
20115
20189
  deps.onChange();
20116
20190
  emitCommitted();
20117
20191
  },
@@ -20759,9 +20833,9 @@ var FLYOUT_WIDTH = {
20759
20833
  session: 176,
20760
20834
  /** The replay row's starting-point choices, opening upward. */
20761
20835
  replayStart: 177,
20762
- /** The replay row's update-interval list. The speed list opens at the same width and grows to fit
20763
- * its rates. */
20764
- replayInterval: 196
20836
+ /** The replay row's update-timeframe list. The speed list opens at the same width and grows to
20837
+ * fit its rates. */
20838
+ replayTimeframe: 196
20765
20839
  };
20766
20840
 
20767
20841
  // src/ui/chrome/bottomBar.ts
@@ -21235,7 +21309,8 @@ function openIndicatorPicker(deps) {
21235
21309
  className: "qc-picker-dialog",
21236
21310
  width: 840,
21237
21311
  refresh: () => redraw(),
21238
- onClose: () => {
21312
+ // The host's listing is abandoned as the dialog begins to close, not when its exit ends.
21313
+ onClosing: () => {
21239
21314
  cancel();
21240
21315
  offLocale();
21241
21316
  },
@@ -21547,7 +21622,7 @@ var PLACEMENTS2 = [
21547
21622
  ];
21548
21623
  function rowLabels(row2) {
21549
21624
  const names = symbolNames(row2);
21550
- return { ticker: names.mark, description: names.description, source: row2.exchange || row2.provider || "" };
21625
+ return { ticker: names.mark, description: names.description, source: row2.exchange || row2.dataSource || "" };
21551
21626
  }
21552
21627
  function offeredOperators(spreads) {
21553
21628
  if (spreads === false) return [];
@@ -21776,10 +21851,10 @@ function buildSearchSurface(deps, box, frame) {
21776
21851
  option.appendChild(mark2);
21777
21852
  } else {
21778
21853
  const source3 = h("span", { class: "qc-search-source" });
21779
- source3.appendChild(h("span", { class: "qc-search-source-text" }, label2.source ? h("span", { class: "qc-search-venue" }, label2.source) : null, h("span", { class: "qc-search-kind" }, r.type)));
21854
+ source3.appendChild(h("span", { class: "qc-search-source-text" }, label2.source ? h("span", { class: "qc-search-source-name" }, label2.source) : null, h("span", { class: "qc-search-kind" }, r.type)));
21780
21855
  if (label2.source) {
21781
21856
  const sourceMark = h("span", { class: "qc-search-mark", "aria-hidden": "true" });
21782
- const dropSource = r.exchange ? deps.painters.venue?.({ exchange: r.exchange, host: sourceMark, size: SOURCE_MARK_SIZE }) : r.provider ? deps.painters.provider?.({ provider: r.provider, host: sourceMark, size: SOURCE_MARK_SIZE }) : void 0;
21857
+ const dropSource = r.exchange ? deps.painters.venue?.({ exchange: r.exchange, host: sourceMark, size: SOURCE_MARK_SIZE }) : r.dataSource ? deps.painters.dataSource?.({ dataSource: r.dataSource, host: sourceMark, size: SOURCE_MARK_SIZE }) : void 0;
21783
21858
  if (typeof dropSource === "function") {
21784
21859
  sourceMark.dataset.qcHost = "true";
21785
21860
  markDisposers.push(dropSource);
@@ -21886,7 +21961,6 @@ function openSearchDialog(deps) {
21886
21961
  host: deps.host,
21887
21962
  label: title,
21888
21963
  className: `qc-search-dialog qc-search-dialog--${mode}`,
21889
- animated: true,
21890
21964
  // The compare family (compare, and a compare row's change-symbol) shares one role for a host
21891
21965
  // test to find; a symbol search, with or without a chart behind it, is the other.
21892
21966
  role: mode === "compare" || mode === "change-symbol" ? "compare-dialog" : "symbol-search",
@@ -22021,7 +22095,7 @@ var SORT_LABEL = {
22021
22095
  "modified-desc": "layouts.sortModifiedDesc"
22022
22096
  };
22023
22097
  function listingFacts(t, row2) {
22024
- return row2.symbol ? row2.timeframe ? t("layouts.listingFacts", { symbol: row2.symbol, interval: timeframeChipLabel(row2.timeframe) }) : row2.symbol : null;
22098
+ return row2.symbol ? row2.timeframe ? t("layouts.listingFacts", { symbol: row2.symbol, timeframe: timeframeChipLabel(row2.timeframe) }) : row2.symbol : null;
22025
22099
  }
22026
22100
  function mountLayoutDialogs(deps) {
22027
22101
  const t = () => deps.i18n.t;
@@ -22037,7 +22111,7 @@ function mountLayoutDialogs(deps) {
22037
22111
  };
22038
22112
  const askName = (question) => {
22039
22113
  raise();
22040
- asking?.close();
22114
+ asking?.close({ animate: false });
22041
22115
  asking = openNameDialog({
22042
22116
  host: deps.overlays,
22043
22117
  t: t(),
@@ -22189,15 +22263,15 @@ function mountLayoutDialogs(deps) {
22189
22263
  render();
22190
22264
  redraw = render;
22191
22265
  closeBrowser = catalog.onChange(render);
22192
- browser = {
22193
- close: () => handle.close()
22194
- };
22266
+ browser = handle;
22195
22267
  void catalog.refresh().catch(() => {
22196
22268
  deps.notify("error", t()("layouts.errList"));
22197
22269
  });
22198
22270
  },
22199
22271
  initialFocus: (box) => box.querySelector(".qc-layouts-search-input"),
22200
- onClose: () => {
22272
+ // The dialog lets go of the catalog and its slot as it begins to close, so a Layouts dialog
22273
+ // asked for during the exit opens at once and the leaving one never unsubscribes it.
22274
+ onClosing: () => {
22201
22275
  closeBrowser();
22202
22276
  browser = null;
22203
22277
  }
@@ -22228,8 +22302,8 @@ function mountLayoutDialogs(deps) {
22228
22302
  destroy() {
22229
22303
  raising.clear();
22230
22304
  offLayout();
22231
- browser?.close();
22232
- asking?.close();
22305
+ browser?.close({ animate: false });
22306
+ asking?.close({ animate: false });
22233
22307
  }
22234
22308
  };
22235
22309
  }
@@ -22294,7 +22368,7 @@ function arrangementGlyph(code, icons) {
22294
22368
  }
22295
22369
  var SYNC_ROWS = [
22296
22370
  { key: "symbol", label: "layouts.syncSymbol", tip: "layouts.syncSymbolTip", toggle: "layouts.syncSymbolToggle" },
22297
- { key: "interval", label: "layouts.syncInterval", tip: "layouts.syncIntervalTip", toggle: "layouts.syncIntervalToggle" },
22371
+ { key: "timeframe", label: "layouts.syncTimeframe", tip: "layouts.syncTimeframeTip", toggle: "layouts.syncTimeframeToggle" },
22298
22372
  { key: "crosshair", label: "layouts.syncCrosshair", tip: "layouts.syncCrosshairTip", toggle: "layouts.syncCrosshairToggle" },
22299
22373
  { key: "time", label: "layouts.syncTime", tip: "layouts.syncTimeTip", toggle: "layouts.syncTimeToggle" },
22300
22374
  { key: "dateRange", label: "layouts.syncDateRange", tip: "layouts.syncDateRangeTip", toggle: "layouts.syncDateRangeToggle" }
@@ -22594,8 +22668,8 @@ function mountLayoutsMenu(deps) {
22594
22668
  text: t()("layouts.createNewRow"),
22595
22669
  icon: deps.icons.glyph(ICONS.plusThin, { size: 28 }),
22596
22670
  disabled: !can("widget.layout.create"),
22597
- // A new layout by the name it is given: the market and interval on screen, nothing else
22598
- // of the open layout, and the open one left as it was saved.
22671
+ // A new layout by the name it is given: the market and timeframe on screen, nothing
22672
+ // else of the open layout, and the open one left as it was saved.
22599
22673
  onSelect: () => askName({
22600
22674
  title: "layouts.createTitle",
22601
22675
  label: "layouts.newLayoutName",
@@ -22892,7 +22966,6 @@ function mountSettingsMenu(deps) {
22892
22966
  host: deps.overlays,
22893
22967
  label: t()("settings.menu"),
22894
22968
  className: "qc-settings-menu qc-chart-settings-dialog",
22895
- animated: true,
22896
22969
  width: 750,
22897
22970
  build(box, handle) {
22898
22971
  content = h("div", { class: "qc-dialog-body qc-chart-settings-body" });
@@ -23760,7 +23833,7 @@ function openDatePicker(deps) {
23760
23833
  function speedWords(t, speed) {
23761
23834
  return speed >= 1 ? t("replay.updatesPerSecond", { count: speed }) : t("replay.oneUpdatePerSeconds", { count: Math.round(1 / speed) });
23762
23835
  }
23763
- function intervalWords(t, token) {
23836
+ function timeframeWords(t, token) {
23764
23837
  const sec = tfSeconds(token);
23765
23838
  if (sec === 0) return token;
23766
23839
  return timeframeLabel(t, token);
@@ -23931,36 +24004,36 @@ function mountReplayTransport(deps) {
23931
24004
  }
23932
24005
  }));
23933
24006
  };
23934
- const intervalButton = button2({ label: t()("replay.interval"), text: "", className: "qc-toolbar-button qc-replay-interval", onClick: () => toggleMenu(intervalButton, openIntervalMenu) });
23935
- intervalButton.setAttribute("aria-haspopup", "menu");
23936
- const openIntervalMenu = () => {
24007
+ const timeframeButton = button2({ label: t()("replay.timeframe"), text: "", className: "qc-toolbar-button qc-replay-timeframe", onClick: () => toggleMenu(timeframeButton, openTimeframeMenu) });
24008
+ timeframeButton.setAttribute("aria-haspopup", "menu");
24009
+ const openTimeframeMenu = () => {
23937
24010
  const menu2 = hold(openMenu({
23938
24011
  host: deps.chrome,
23939
- anchor: intervalButton,
23940
- label: t()("replay.interval"),
24012
+ anchor: timeframeButton,
24013
+ label: t()("replay.timeframe"),
23941
24014
  role: "dialog",
23942
24015
  className: "qc-replay-menu",
23943
- width: FLYOUT_WIDTH.replayInterval,
24016
+ width: FLYOUT_WIDTH.replayTimeframe,
23944
24017
  placement: "up",
23945
24018
  onClose: () => open2.delete(menu2),
23946
24019
  build(body, menu3) {
23947
- const heading = menuHeading(t()("replay.interval"));
23948
- heading.title = t()("replay.intervalHelp");
24020
+ const heading = menuHeading(t()("replay.timeframe"));
24021
+ heading.title = t()("replay.timeframeHelp");
23949
24022
  body.appendChild(heading);
23950
- const current = handle.replay.interval();
24023
+ const current = handle.replay.timeframe();
23951
24024
  const rows2 = [];
23952
24025
  let previousUnit = null;
23953
- for (const token of handle.replay.subIntervals()) {
24026
+ for (const token of handle.replay.subTimeframes()) {
23954
24027
  const unit = timeframeGroupUnit(parseTimeframe(token)?.unit ?? "d");
23955
24028
  if (previousUnit !== null && unit !== previousUnit) body.appendChild(menuSeparator());
23956
24029
  previousUnit = unit;
23957
24030
  const row2 = menuItem({
23958
- text: intervalWords(t(), token),
24031
+ text: timeframeWords(t(), token),
23959
24032
  role: "menuitemradio",
23960
24033
  checked: current === token,
23961
24034
  onSelect: () => {
23962
24035
  menu3.close();
23963
- commands.execute("chart.replay.setInterval", token);
24036
+ commands.execute("chart.replay.setTimeframe", token);
23964
24037
  }
23965
24038
  });
23966
24039
  rows2.push({ token, row: row2 });
@@ -23969,12 +24042,12 @@ function mountReplayTransport(deps) {
23969
24042
  body.appendChild(menuSeparator());
23970
24043
  body.appendChild(
23971
24044
  switchRow({
23972
- label: t()("replay.autoSelectInterval"),
24045
+ label: t()("replay.autoSelectTimeframe"),
23973
24046
  checked: current === "auto",
23974
24047
  onChange: (on) => {
23975
- const first = handle.replay.subIntervals()[0];
23976
- commands.execute("chart.replay.setInterval", on ? "auto" : first ?? "auto");
23977
- const now = handle.replay.interval();
24048
+ const first = handle.replay.subTimeframes()[0];
24049
+ commands.execute("chart.replay.setTimeframe", on ? "auto" : first ?? "auto");
24050
+ const now = handle.replay.timeframe();
23978
24051
  for (const entry of rows2) entry.row.setAttribute("aria-checked", String(now === entry.token));
23979
24052
  }
23980
24053
  })
@@ -23994,7 +24067,7 @@ function mountReplayTransport(deps) {
23994
24067
  playPause,
23995
24068
  stepForward,
23996
24069
  speedButton,
23997
- intervalButton,
24070
+ timeframeButton,
23998
24071
  rule(),
23999
24072
  goLive
24000
24073
  );
@@ -24010,19 +24083,19 @@ function mountReplayTransport(deps) {
24010
24083
  stepForward.hidden = !shown2("chart.replay.stepForward");
24011
24084
  goLive.hidden = !shown2("chart.replay.goLive");
24012
24085
  speedButton.hidden = !shown2("chart.replay.setSpeed");
24013
- intervalButton.hidden = !shown2("chart.replay.setInterval");
24086
+ timeframeButton.hidden = !shown2("chart.replay.setTimeframe");
24014
24087
  exit.hidden = !shown2("chart.replay.exit");
24015
24088
  setDisabled(stepBack, !commands.available("chart.replay.stepBack") || state.cursor <= 2);
24016
24089
  setDisabled(playPause, !commands.available(playing ? "chart.replay.pause" : "chart.replay.play") || !playing && atLive);
24017
24090
  setDisabled(stepForward, !commands.available("chart.replay.stepForward") || atLive);
24018
24091
  setDisabled(goLive, !commands.available("chart.replay.goLive") || atLive);
24019
24092
  setDisabled(speedButton, !commands.available("chart.replay.setSpeed"));
24020
- const intervals = handle.replay.subIntervals();
24021
- const noGrain = intervals.length === 0;
24022
- setDisabled(intervalButton, noGrain || !commands.available("chart.replay.setInterval"));
24093
+ const grains = handle.replay.subTimeframes();
24094
+ const noGrain = grains.length === 0;
24095
+ setDisabled(timeframeButton, noGrain || !commands.available("chart.replay.setTimeframe"));
24023
24096
  retext(speedButton, t()("replay.speed"), `${state.speed}x`);
24024
- const resolved = handle.replay.resolvedInterval();
24025
- retext(intervalButton, noGrain ? t()("replay.intervalNone") : t()("replay.interval"), noGrain ? t()("replay.auto") : timeframeChipLabel(resolved));
24097
+ const resolved = handle.replay.resolvedTimeframe();
24098
+ retext(timeframeButton, noGrain ? t()("replay.timeframeNone") : t()("replay.timeframe"), noGrain ? t()("replay.auto") : timeframeChipLabel(resolved));
24026
24099
  setDisabled(exit, !commands.available("chart.replay.exit"));
24027
24100
  reconcilePicking();
24028
24101
  bar.setAttribute("aria-label", t()("chrome.replay"));
@@ -24053,7 +24126,7 @@ function mountReplayTransport(deps) {
24053
24126
  if (destroyed) return;
24054
24127
  destroyed = true;
24055
24128
  releasePicking();
24056
- for (const handle2 of [...open2]) handle2.close();
24129
+ for (const handle2 of [...open2]) handle2.close({ animate });
24057
24130
  offStrings();
24058
24131
  const view = bar.ownerDocument.defaultView;
24059
24132
  const exitMs = animate && bar.isConnected ? motionDurationMs(bar, "motion.durationBase") : 0;
@@ -24158,7 +24231,7 @@ function mountChrome(deps) {
24158
24231
  indicatorDialog = openIndicatorPicker({ ...ctx, storage: deps.storage, indicatorPicker: deps.indicatorPicker, initialCollection });
24159
24232
  };
24160
24233
  disposers.push(() => {
24161
- indicatorDialog?.close();
24234
+ indicatorDialog?.close({ animate: false });
24162
24235
  deps.doors.showIndicatorPicker = () => void 0;
24163
24236
  });
24164
24237
  deps.doors.openSearch = (request) => {
@@ -24213,7 +24286,8 @@ function mountChrome(deps) {
24213
24286
  widget.theme.onChange((theme, mode) => paintThemeRoot(surface, mode, theme)),
24214
24287
  i18n.onChange(() => surface.setAttribute("dir", readingDirection(i18n))),
24215
24288
  () => surface.remove(),
24216
- // A flyout still open in the layer closes with the rail, taking its document listeners.
24289
+ // A flyout still open in the layer closes with the drawing toolbar, taking its document
24290
+ // listeners.
24217
24291
  () => closeOverlays(deps.layer),
24218
24292
  releasePanelHost
24219
24293
  );
@@ -24404,7 +24478,7 @@ function resolveMarkPainters(hooks) {
24404
24478
  return {
24405
24479
  symbol: hooks.symbolMark ?? null,
24406
24480
  venue: hooks.venueMark ?? null,
24407
- provider: hooks.providerMark ?? null
24481
+ dataSource: hooks.dataSourceMark ?? null
24408
24482
  };
24409
24483
  }
24410
24484
 
@@ -24857,7 +24931,7 @@ function createChart(options) {
24857
24931
  i18n,
24858
24932
  features,
24859
24933
  ui,
24860
- // What the chrome keeps is the viewer's own choices about it (favorite and custom intervals,
24934
+ // What the chrome keeps is the viewer's own choices about it (favorite and custom timeframes,
24861
24935
  // starred layouts and indicators, how the layouts sort), which change no layout, so it reads and
24862
24936
  // writes the host's store directly rather than through the save-needed funnel.
24863
24937
  storage: backing,
@@ -25182,7 +25256,7 @@ export {
25182
25256
  attachCompare,
25183
25257
  attachDrawings,
25184
25258
  attachIndicators,
25185
- autoIntervalFor,
25259
+ autoTimeframeFor,
25186
25260
  buildManifestPlots,
25187
25261
  canvasTheme,
25188
25262
  canvasToBlob,
@@ -25269,7 +25343,7 @@ export {
25269
25343
  sessionTimeline,
25270
25344
  spreadExpression,
25271
25345
  spreadSearchQuery,
25272
- subIntervalsFor,
25346
+ subTimeframesFor,
25273
25347
  subsessionBarFilter,
25274
25348
  symbolNames,
25275
25349
  tfSeconds,