@trdrs/quickcharts 1.3.0 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (85) hide show
  1. package/CHANGELOG.md +84 -0
  2. package/README.md +209 -200
  3. package/dist/adapters/rest.d.ts +3 -3
  4. package/dist/adapters/rest.js +2 -2
  5. package/dist/adapters/rest.js.map +1 -1
  6. package/dist/{ar-G7MVXRE4.js → ar-KSXQ2USS.js} +20 -20
  7. package/dist/ar-KSXQ2USS.js.map +1 -0
  8. package/dist/{assets-ySndlUS3.d.ts → assets-DOuBBsOp.d.ts} +50 -50
  9. package/dist/{ca_ES-EUSRU4VG.js → ca_ES-JG4FM55E.js} +20 -20
  10. package/dist/ca_ES-JG4FM55E.js.map +1 -0
  11. package/dist/{chunk-AA7XKXH4.js → chunk-6S6TO2N6.js} +33 -33
  12. package/dist/chunk-6S6TO2N6.js.map +1 -0
  13. package/dist/{chunk-KFBQSPNA.js → chunk-D3S26ARP.js} +2 -2
  14. package/dist/chunk-D3S26ARP.js.map +1 -0
  15. package/dist/{chunk-AKXIFHQN.js → chunk-KLPTCWA2.js} +1 -1
  16. package/dist/{chunk-AKXIFHQN.js.map → chunk-KLPTCWA2.js.map} +1 -1
  17. package/dist/{de-2SVTG25S.js → de-VSW2LHNF.js} +20 -20
  18. package/dist/de-VSW2LHNF.js.map +1 -0
  19. package/dist/drawings.d.ts +16 -16
  20. package/dist/drawings.js +12 -12
  21. package/dist/{es-UDHZM4W7.js → es-5ZXIPVOB.js} +20 -20
  22. package/dist/es-5ZXIPVOB.js.map +1 -0
  23. package/dist/feature-manifest.json +3 -3
  24. package/dist/{fr-35VN4G4S.js → fr-4F6DAWGD.js} +20 -20
  25. package/dist/fr-4F6DAWGD.js.map +1 -0
  26. package/dist/{he_IL-JPZMCURP.js → he_IL-B7PBQUEV.js} +20 -20
  27. package/dist/he_IL-B7PBQUEV.js.map +1 -0
  28. package/dist/{id_ID-XSTFHHJQ.js → id_ID-CYJRMXPC.js} +20 -20
  29. package/dist/id_ID-CYJRMXPC.js.map +1 -0
  30. package/dist/index.d.ts +167 -163
  31. package/dist/index.js +248 -237
  32. package/dist/index.js.map +1 -1
  33. package/dist/{it-EIY6JESQ.js → it-KTX4SG5P.js} +20 -20
  34. package/dist/it-KTX4SG5P.js.map +1 -0
  35. package/dist/{ja-42N4XRCW.js → ja-V7OURU5D.js} +20 -20
  36. package/dist/ja-V7OURU5D.js.map +1 -0
  37. package/dist/{ko-3VRPTIP3.js → ko-2RNEBYVE.js} +20 -20
  38. package/dist/ko-2RNEBYVE.js.map +1 -0
  39. package/dist/{ms_MY-SKN7O6M5.js → ms_MY-EY235E7Q.js} +20 -20
  40. package/dist/ms_MY-EY235E7Q.js.map +1 -0
  41. package/dist/{pl-TYARHCLT.js → pl-ADNVRP55.js} +20 -20
  42. package/dist/pl-ADNVRP55.js.map +1 -0
  43. package/dist/{pt-72IIFLL4.js → pt-OL2DR3DO.js} +20 -20
  44. package/dist/pt-OL2DR3DO.js.map +1 -0
  45. package/dist/quickcharts.css +97 -96
  46. package/dist/{resources-Ch8eA1Mw.d.ts → resources-Le6flcEn.d.ts} +3 -3
  47. package/dist/rest-openapi.json +11 -11
  48. package/dist/{ru-TPH4F7IB.js → ru-V6YFEPAF.js} +20 -20
  49. package/dist/ru-V6YFEPAF.js.map +1 -0
  50. package/dist/{sv-DC5RS67Z.js → sv-TORXQVE7.js} +20 -20
  51. package/dist/sv-TORXQVE7.js.map +1 -0
  52. package/dist/{th-I6H6AOZ3.js → th-K6SJSYSY.js} +20 -20
  53. package/dist/th-K6SJSYSY.js.map +1 -0
  54. package/dist/theme-manifest.json +10 -10
  55. package/dist/{tr-7FSUVCNW.js → tr-4AEM66KW.js} +20 -20
  56. package/dist/tr-4AEM66KW.js.map +1 -0
  57. package/dist/{vi-DLRPHLLH.js → vi-YU5XCEUU.js} +20 -20
  58. package/dist/vi-YU5XCEUU.js.map +1 -0
  59. package/dist/{zh-ZXIQBBEP.js → zh-QXKRYHU5.js} +20 -20
  60. package/dist/zh-QXKRYHU5.js.map +1 -0
  61. package/dist/{zh_TW-TYNUJU7U.js → zh_TW-AYPDKG4Q.js} +20 -20
  62. package/dist/zh_TW-AYPDKG4Q.js.map +1 -0
  63. package/package.json +1 -1
  64. package/dist/ar-G7MVXRE4.js.map +0 -1
  65. package/dist/ca_ES-EUSRU4VG.js.map +0 -1
  66. package/dist/chunk-AA7XKXH4.js.map +0 -1
  67. package/dist/chunk-KFBQSPNA.js.map +0 -1
  68. package/dist/de-2SVTG25S.js.map +0 -1
  69. package/dist/es-UDHZM4W7.js.map +0 -1
  70. package/dist/fr-35VN4G4S.js.map +0 -1
  71. package/dist/he_IL-JPZMCURP.js.map +0 -1
  72. package/dist/id_ID-XSTFHHJQ.js.map +0 -1
  73. package/dist/it-EIY6JESQ.js.map +0 -1
  74. package/dist/ja-42N4XRCW.js.map +0 -1
  75. package/dist/ko-3VRPTIP3.js.map +0 -1
  76. package/dist/ms_MY-SKN7O6M5.js.map +0 -1
  77. package/dist/pl-TYARHCLT.js.map +0 -1
  78. package/dist/pt-72IIFLL4.js.map +0 -1
  79. package/dist/ru-TPH4F7IB.js.map +0 -1
  80. package/dist/sv-DC5RS67Z.js.map +0 -1
  81. package/dist/th-I6H6AOZ3.js.map +0 -1
  82. package/dist/tr-7FSUVCNW.js.map +0 -1
  83. package/dist/vi-DLRPHLLH.js.map +0 -1
  84. package/dist/zh-ZXIQBBEP.js.map +0 -1
  85. 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-6S6TO2N6.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",
@@ -8596,7 +8594,7 @@ function attachDrawings(options) {
8596
8594
  const manager = new DrawingManager();
8597
8595
  manager.setGlyphSource(options.glyphSource ?? bundledGlyphSource);
8598
8596
  manager.attach(chart, series2);
8599
- manager.setIntervalContext(parseIntervalContext(options.timeframe ?? ""));
8597
+ manager.setTimeframeContext(parseTimeframeContext(options.timeframe ?? ""));
8600
8598
  if (options.bars) {
8601
8599
  const read = options.bars;
8602
8600
  let cacheKey = "";
@@ -8974,7 +8972,7 @@ function attachDrawings(options) {
8974
8972
  setVisibilityPreset(preset) {
8975
8973
  const sel = selection();
8976
8974
  if (!sel) return;
8977
- sel.updateOptions({ visibility: visibilityPreset(preset, parseIntervalContext(timeframe2)) });
8975
+ sel.updateOptions({ visibility: visibilityPreset(preset, parseTimeframeContext(timeframe2)) });
8978
8976
  persist();
8979
8977
  changed();
8980
8978
  },
@@ -9041,7 +9039,7 @@ function attachDrawings(options) {
9041
9039
  },
9042
9040
  setTimeframe(tf) {
9043
9041
  timeframe2 = tf;
9044
- manager.setIntervalContext(parseIntervalContext(tf));
9042
+ manager.setTimeframeContext(parseTimeframeContext(tf));
9045
9043
  },
9046
9044
  setTick: (tick) => manager.setTickSize(tick),
9047
9045
  setPriceFormatter: (format2) => manager.setPriceFormatter(format2),
@@ -9776,17 +9774,17 @@ var HIDE_ICON = {
9776
9774
  };
9777
9775
  function mountDrawingToolbar(deps) {
9778
9776
  const { t } = deps;
9779
- const groups = buildRailGroups();
9777
+ const groups = buildDrawingToolbarGroups();
9780
9778
  const shown2 = (command2) => deps.shown?.(command2) ?? true;
9781
9779
  const toolShown = (type) => deps.toolShown?.(type) ?? true;
9782
9780
  const drawnGroup = (group) => ({
9783
9781
  ...group,
9784
9782
  sections: group.sections.map((section) => ({ ...section, tools: section.tools.filter((tool) => toolShown(tool.type)) })).filter((section) => section.tools.length > 0)
9785
9783
  });
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);
9784
+ const toolbar = el("div", { class: "qc-surface qc-drawing-toolbar", role: "toolbar", "aria-orientation": "vertical", "aria-label": t("drawing.toolbar"), "data-role": "drawing-toolbar" });
9785
+ ownPointer(toolbar);
9788
9786
  const column = el("div", { class: "qc-drawing-toolbar-column" });
9789
- rail.appendChild(column);
9787
+ toolbar.appendChild(column);
9790
9788
  let closeFlyout = null;
9791
9789
  let panelHost = deps.chrome;
9792
9790
  const closeOpen = () => {
@@ -9832,16 +9830,16 @@ function mountDrawingToolbar(deps) {
9832
9830
  };
9833
9831
  const cell2 = (face, arrow) => {
9834
9832
  const box = el("div", { class: "qc-drawing-cell" }, face);
9835
- face.classList.add("qc-drawing-rail-button");
9833
+ face.classList.add("qc-drawing-toolbar-button");
9836
9834
  if (arrow) {
9837
- arrow.classList.add("qc-drawing-rail-arrow");
9835
+ arrow.classList.add("qc-drawing-toolbar-arrow");
9838
9836
  arrow.setAttribute("aria-haspopup", "menu");
9839
9837
  arrow.setAttribute("aria-expanded", "false");
9840
9838
  box.appendChild(arrow);
9841
9839
  }
9842
9840
  return box;
9843
9841
  };
9844
- const divider = (where = "rail") => el("div", { class: `qc-separator qc-drawing-${where === "rail" ? "divider" : where === "flyout" ? "flyout-rule" : "menu-rule"}`, role: "separator" });
9842
+ const divider = (where = "toolbar") => el("div", { class: `qc-separator qc-drawing-${where === "toolbar" ? "divider" : where === "flyout" ? "flyout-rule" : "menu-rule"}`, role: "separator" });
9845
9843
  const cursorFace = button({ class: "qc-button", label: t("drawing.cursor"), onClick: () => deps.run("chart.drawings.arm", null) });
9846
9844
  const cursorArrow = button({ class: "qc-button", label: t("drawing.cursorMenu"), icon: deps.icons.icon("chevronRight16") });
9847
9845
  cursorArrow.addEventListener("click", () => {
@@ -9926,7 +9924,7 @@ function mountDrawingToolbar(deps) {
9926
9924
  const face = button({ class: "qc-button", label: t(group.label) });
9927
9925
  const arrow = button({ class: "qc-button", label: t("drawing.groupMenu", { group: t(group.label) }), icon: deps.icons.icon("chevronRight16") });
9928
9926
  face.addEventListener("click", () => {
9929
- const faceTool = railFaceOf(drawnGroup(group), deps.state().railTools);
9927
+ const faceTool = drawingToolbarFaceOf(drawnGroup(group), deps.state().drawingToolbarTools);
9930
9928
  if (group.id === "glyphs" || !faceTool) {
9931
9929
  openGroup(group, arrow);
9932
9930
  return;
@@ -9962,7 +9960,7 @@ function mountDrawingToolbar(deps) {
9962
9960
  )
9963
9961
  );
9964
9962
  });
9965
- const stay = button({ class: "qc-button qc-drawing-rail-mode", label: t("drawing.stayInDrawingMode"), onClick: () => deps.run("chart.drawings.stayInMode", !deps.state().stayInDrawingMode) });
9963
+ const stay = button({ class: "qc-button qc-drawing-toolbar-mode", label: t("drawing.stayInDrawingMode"), onClick: () => deps.run("chart.drawings.stayInMode", !deps.state().stayInDrawingMode) });
9966
9964
  const lockAll = button({ class: "qc-button", label: t("drawing.lockAll"), onClick: () => deps.run("chart.drawings.lockAll", !deps.state().allLocked) });
9967
9965
  column.append(cell2(magnetFace, magnetArrow), cell2(stay, null), cell2(lockAll, null));
9968
9966
  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 +10018,9 @@ function mountDrawingToolbar(deps) {
10020
10018
  openFlyout(removeArrow, menu2(t("drawing.removeMenu"), ...items2, divider("menu"), policy));
10021
10019
  });
10022
10020
  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) });
10021
+ 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
10022
  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");
10023
+ const unrove = rovingFocus(toolbar, () => [...column.querySelectorAll("button")].filter((b) => !b.closest("[hidden]")), "vertical");
10026
10024
  const setActive = (b, active) => {
10027
10025
  b.dataset.qcActive = String(active);
10028
10026
  };
@@ -10045,7 +10043,7 @@ function mountDrawingToolbar(deps) {
10045
10043
  for (const group of groups) {
10046
10044
  const entry = groupFaces.get(group.id);
10047
10045
  const drawn = drawnGroup(group);
10048
- const faceTool = activeGroup === group.id && s.activeTool && toolShown(s.activeTool) ? s.activeTool : railFaceOf(drawn, s.railTools);
10046
+ const faceTool = activeGroup === group.id && s.activeTool && toolShown(s.activeTool) ? s.activeTool : drawingToolbarFaceOf(drawn, s.drawingToolbarTools);
10049
10047
  entry.face.replaceChildren(...[group.id === "glyphs" ? deps.icons.icon("groupGlyphs") : faceTool ? deps.icons.tool(faceTool) : null].filter((face) => face !== null));
10050
10048
  const faceName = faceTool && group.id !== "glyphs" ? toolName(t, faceTool, faceTool) : t(group.label);
10051
10049
  relabelButton(entry.face, faceName);
@@ -10085,7 +10083,7 @@ function mountDrawingToolbar(deps) {
10085
10083
  gate(eyeArrow, "chart.drawings.hide");
10086
10084
  if (s.layoutCharts > 1) {
10087
10085
  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) });
10086
+ 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
10087
  syncCell.appendChild(syncButton);
10090
10088
  }
10091
10089
  syncButton.title = t(s.sync ? "drawing.syncOnHelp" : "drawing.syncOffHelp");
@@ -10111,7 +10109,7 @@ function mountDrawingToolbar(deps) {
10111
10109
  tidyRules(column, (child) => !child.hidden && [...child.querySelectorAll("button")].some((control) => !control.closest("[hidden]")));
10112
10110
  };
10113
10111
  const relabel = () => {
10114
- rail.setAttribute("aria-label", t("drawing.toolbar"));
10112
+ toolbar.setAttribute("aria-label", t("drawing.toolbar"));
10115
10113
  relabelButton(cursorFace, t("drawing.cursor"));
10116
10114
  relabelButton(cursorArrow, t("drawing.cursorMenu"));
10117
10115
  for (const group of groups) relabelButton(groupFaces.get(group.id).arrow, t("drawing.groupMenu", { group: t(group.label) }));
@@ -10129,11 +10127,11 @@ function mountDrawingToolbar(deps) {
10129
10127
  };
10130
10128
  const mount = (container) => {
10131
10129
  const nextPanelHost = panelHostFor(container, deps.chrome);
10132
- if (rail.parentElement === container && panelHost === nextPanelHost) return;
10130
+ if (toolbar.parentElement === container && panelHost === nextPanelHost) return;
10133
10131
  closeOpen();
10134
10132
  panelHost = nextPanelHost;
10135
- rail.remove();
10136
- container?.appendChild(rail);
10133
+ toolbar.remove();
10134
+ container?.appendChild(toolbar);
10137
10135
  render();
10138
10136
  };
10139
10137
  mount(deps.container === void 0 ? deps.chrome : deps.container);
@@ -10147,7 +10145,7 @@ function mountDrawingToolbar(deps) {
10147
10145
  unrove();
10148
10146
  glyphPicker?.destroy();
10149
10147
  glyphPicker = null;
10150
- rail.remove();
10148
+ toolbar.remove();
10151
10149
  }
10152
10150
  };
10153
10151
  }
@@ -10283,9 +10281,9 @@ var ICONS = {
10283
10281
  viewBox: "0 0 120 120",
10284
10282
  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
10283
  },
10286
- /** Baseline market-status mark, on its native 18 grid. */
10284
+ /** The market-status mark, on its native 18 grid. */
10287
10285
  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. */
10286
+ /** The replay marks, on their native 18 grid. */
10289
10287
  // The disc carries the phase in `currentColor`; the rewind stays ONE ink in both modes, because a
10290
10288
  // mark that changed the colour of its own shape between modes would read as a different mark.
10291
10289
  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 +10312,18 @@ var ICONS = {
10314
10312
  viewBox: "0 0 28 28",
10315
10313
  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
10314
  },
10317
- // Enter fullscreen: the baseline's four corner brackets turning outward, drawn as a one-unit
10315
+ // Enter fullscreen: four corner brackets turning outward, drawn as a one-unit
10318
10316
  // wall on the 28 grid.
10319
10317
  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.
10318
+ // Exit fullscreen: the same brackets folded inward, stroked at a 1.5 weight.
10321
10319
  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
10320
+ // Chart image: a camera, a one-unit-walled body with the shutter hump left of centre
10323
10321
  // and the lens ring offset toward it.
10324
10322
  camera: {
10325
10323
  viewBox: "0 0 28 28",
10326
10324
  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
10325
  },
10328
- /** The layout tile's own maximize and restore marks, on the baseline's native 18 grid: two
10326
+ /** The layout tile's own maximize and restore marks, on their native 18 grid: two
10329
10327
  * opposite corner brackets turning outward to fill the layout, and inward to give the tile back. */
10330
10328
  tileMaximize: { viewBox: "0 0 18 18", body: '<path fill="currentColor" d="M14.5 8V3.5H10V2h6v6h-1.5Zm-11 2v4.5H8V16H2v-6h1.5Z"/>' },
10331
10329
  tileRestore: { viewBox: "0 0 18 18", body: '<path fill="currentColor" d="M10 8V2h1.5v4.5H16V8h-6Zm-2 2v6H6.5v-4.5H2V10h6Z"/>' },
@@ -10336,7 +10334,7 @@ var ICONS = {
10336
10334
  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
10335
  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
10336
  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
10337
+ /** The pickers' wide caret, on its native 16 by 8 grid: the trigger arrow, which
10340
10338
  * renders half size beside a picker's text. */
10341
10339
  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
10340
  // Hairline weight: these two sit beside a label rather than alone in a 38px cell.
@@ -10447,7 +10445,7 @@ var ICONS = {
10447
10445
  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
10446
  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
10447
  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.
10448
+ // The drawing toolbar's arrow: a filled chevron on a 10 by 16 grid, drawn 4 by 7 in the strip.
10451
10449
  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
10450
  // Six dots on an 8 by 12 grid, each PAINTED in currentColor: the svg the glyph is wrapped in
10453
10451
  // carries `fill="none"`, so a rect with no fill of its own is a grip nobody can see.
@@ -10467,8 +10465,8 @@ var ICONS = {
10467
10465
  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
10466
  // A tool's star in a flyout: an outline at rest and a filled star once saved, on an 18 grid.
10469
10467
  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.
10468
+ // The favorites bar's toggle on the drawing toolbar: one outlined star in either state, the state
10469
+ // carried by the button's fill.
10472
10470
  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
10471
  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
10472
  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 +10491,7 @@ var ICONS = {
10493
10491
  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
10492
  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
10493
  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.
10494
+ // The indicator picker's marks, on the grids its rows were drawn for.
10497
10495
  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
10496
  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
10497
  plus24: { viewBox: "0 0 24 24", body: '<path d="M12 5v14M5 12h14" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>' },
@@ -10537,7 +10535,7 @@ function openDialog(options) {
10537
10535
  if (options.width) box.style.width = `${options.width}px`;
10538
10536
  stopPointer(box);
10539
10537
  scrim.appendChild(box);
10540
- if (options.animated) scrim.dataset.state = "opening";
10538
+ scrim.dataset.state = "opening";
10541
10539
  let isOpen = true;
10542
10540
  const previouslyFocused = document.activeElement;
10543
10541
  const escape = pushEscapeOwner();
@@ -10562,7 +10560,7 @@ function openDialog(options) {
10562
10560
  untrack();
10563
10561
  document.removeEventListener("keydown", onKey, true);
10564
10562
  previouslyFocused?.focus?.();
10565
- const exitMs = options.animated && closeOptions?.animate !== false ? motionDurationMs(scrim, "motion.durationBase") : 0;
10563
+ const exitMs = closeOptions?.animate !== false ? motionDurationMs(scrim, "motion.durationBase") : 0;
10566
10564
  if (exitMs <= 0) {
10567
10565
  remove();
10568
10566
  return;
@@ -10608,12 +10606,10 @@ function openDialog(options) {
10608
10606
  const handle = { element: box, close, open: () => isOpen };
10609
10607
  options.build(box, handle);
10610
10608
  (layerFor(options.host) ?? options.host).appendChild(scrim);
10611
- if (options.animated) {
10612
- scrim.getBoundingClientRect();
10613
- scrim.dataset.state = "open";
10614
- }
10609
+ scrim.getBoundingClientRect();
10610
+ scrim.dataset.state = "open";
10615
10611
  const refresh = options.refresh;
10616
- untrack = trackOverlay(options.host, close, refresh ? () => {
10612
+ untrack = trackOverlay(options.host, () => close({ animate: false }), refresh ? () => {
10617
10613
  if (isOpen) refresh();
10618
10614
  } : void 0);
10619
10615
  document.addEventListener("keydown", onKey, true);
@@ -11000,7 +10996,7 @@ function mountSettingsBar(deps) {
11000
10996
  const at = deps.stackPosition();
11001
10997
  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
10998
  }
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" })));
10999
+ 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
11000
  };
11005
11001
  const arm = (kind, row2) => {
11006
11002
  cancelClose();
@@ -11034,7 +11030,7 @@ function mountSettingsBar(deps) {
11034
11030
  t("drawing.moreActions"),
11035
11031
  "wide",
11036
11032
  submenuRow("order", t("drawing.visualOrder"), "layers"),
11037
- submenuRow("visibility", t("drawing.visibilityOnIntervals")),
11033
+ submenuRow("visibility", t("drawing.visibilityOnTimeframes")),
11038
11034
  separator2(),
11039
11035
  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
11036
  plain(menuRow(t("drawing.copy"), () => deps.run("chart.drawings.copy"), { spacer: true, hint: t("drawing.hintCopy", { modifier: modifier() }), command: "chart.drawings.copy" })),
@@ -11377,11 +11373,15 @@ function openDialog2(options) {
11377
11373
  stopDrag = dragBy(header, element);
11378
11374
  element.append(header, body, footer);
11379
11375
  },
11380
- onClose: () => {
11376
+ // The drag and whatever the dialog opened over itself (a palette, a menu, a template prompt) go
11377
+ // as the dialog begins to close, so nothing answers the viewer from a box that no longer takes
11378
+ // input and no document listener outlives the box it belonged to.
11379
+ onClosing: () => {
11381
11380
  stopDrag();
11382
11381
  if (box) closeOverlays(box);
11383
- options.onClose?.();
11384
- }
11382
+ options.onClosing?.();
11383
+ },
11384
+ onClose: () => options.onClose?.()
11385
11385
  });
11386
11386
  return { body, footer, box: modal.element, close: modal.close };
11387
11387
  }
@@ -11411,7 +11411,9 @@ function openImagePicker(deps) {
11411
11411
  icons: deps.icons,
11412
11412
  role: "drawing-image-picker",
11413
11413
  width: 420,
11414
- onClose: () => {
11414
+ // The picker stops taking a paste as it begins to close, and the Image tool can open it again
11415
+ // while the leaving one finishes its exit.
11416
+ onClosing: () => {
11415
11417
  document.removeEventListener("paste", onPaste);
11416
11418
  deps.onClose?.();
11417
11419
  }
@@ -11901,7 +11903,7 @@ function visibilityRows(ctx) {
11901
11903
  const range2 = visibility[key];
11902
11904
  out.push(visibilityRangeRow(t, ctx.icons, { label: t(text2), range: range2, max, disabled: pinned(range2.on), onChange: (next) => ctx.patchVisibility({ [key]: next }) }));
11903
11905
  }
11904
- if (enabled === 1) out.push(el("span", { class: "qc-muted qc-drawing-note", text: t("drawing.intervalPinnedNote") }));
11906
+ if (enabled === 1) out.push(el("span", { class: "qc-muted qc-drawing-note", text: t("drawing.timeframePinnedNote") }));
11905
11907
  return out;
11906
11908
  }
11907
11909
 
@@ -11920,7 +11922,9 @@ function openSettingsDialog(deps) {
11920
11922
  icons: deps.icons,
11921
11923
  role: "drawing-settings",
11922
11924
  width: 380,
11923
- onClose: () => {
11925
+ // Escape, the corner close or a press outside ends the session as the dialog begins to close,
11926
+ // so the drawing is back as it was before the exit motion starts rather than after it.
11927
+ onClosing: () => {
11924
11928
  if (settled) return;
11925
11929
  cancel();
11926
11930
  }
@@ -12128,7 +12132,7 @@ function attachDrawingsPlane(deps) {
12128
12132
  indicators: { hidden: "drawing.statusIndicatorsHidden", shown: "drawing.statusIndicatorsShown" },
12129
12133
  all: { hidden: "drawing.statusAllHidden", shown: "drawing.statusAllShown" }
12130
12134
  };
12131
- const groups = buildRailGroups();
12135
+ const groups = buildDrawingToolbarGroups();
12132
12136
  const events = {};
12133
12137
  const handle = attachDrawings({
12134
12138
  chart: deps.chart,
@@ -12200,7 +12204,7 @@ function attachDrawingsPlane(deps) {
12200
12204
  removeLocked: p.removeLocked,
12201
12205
  counts: handle.counts(),
12202
12206
  indicatorCount: deps.indicators.count(),
12203
- railTools: p.railTools,
12207
+ drawingToolbarTools: p.drawingToolbarTools,
12204
12208
  favorites: p.favorites,
12205
12209
  recentGlyphs: p.recentGlyphs,
12206
12210
  layoutCharts: deps.chartCount()
@@ -12350,7 +12354,7 @@ function attachDrawingsPlane(deps) {
12350
12354
  }
12351
12355
  handle.armTool(tool, props);
12352
12356
  if (tool) {
12353
- const patch = { railTools: rememberRailTool(groups, prefs().railTools, tool) };
12357
+ const patch = { drawingToolbarTools: rememberDrawingToolbarTool(groups, prefs().drawingToolbarTools, tool) };
12354
12358
  const glyph2 = props && typeof props.glyph === "string" ? props.glyph : null;
12355
12359
  if (glyph2) patch.recentGlyphs = pushRecentGlyph(prefs().recentGlyphs, glyph2);
12356
12360
  write(patch);
@@ -12724,7 +12728,7 @@ var FillBetweenPrimitive = class {
12724
12728
  lower = [];
12725
12729
  colors;
12726
12730
  /** 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. */
12731
+ * here would be a second indicator default with no owner. */
12728
12732
  color = "transparent";
12729
12733
  attached(param) {
12730
12734
  this.chart = param.chart;
@@ -12925,7 +12929,7 @@ function shapeKeyOf(built) {
12925
12929
  return JSON.stringify([built.placement, built.plots.map((p) => [p.key, p.type, p.location === "absolute", p.scale ?? null])]);
12926
12930
  }
12927
12931
  function makeEntry(chart, built, neutral) {
12928
- const paneIndex = built.placement === "pane" ? studyPaneIndex(chart) : 0;
12932
+ const paneIndex = built.placement === "pane" ? indicatorPaneIndex(chart) : 0;
12929
12933
  const labels = built.display?.labelsOnPriceScale !== false;
12930
12934
  const series2 = [];
12931
12935
  const markers = [];
@@ -13124,7 +13128,7 @@ function safeRemove(chart, series2) {
13124
13128
  } catch {
13125
13129
  }
13126
13130
  }
13127
- function studyPaneIndex(chart) {
13131
+ function indicatorPaneIndex(chart) {
13128
13132
  sweepEmptyPanes(chart);
13129
13133
  return chart.panes().length;
13130
13134
  }
@@ -13693,7 +13697,7 @@ function attachIndicatorsPlane(deps) {
13693
13697
  };
13694
13698
  const renderer = attachIndicators(deps.chart, {
13695
13699
  candles: deps.candleSeries,
13696
- // A study that declares no precision writes its scale through the symbol formatter, so a
13700
+ // An indicator that declares no precision writes its scale through the symbol formatter, so a
13697
13701
  // moving average on a Treasury reads in thirty-seconds like the bars beside it.
13698
13702
  symbolPriceFormat: () => ({ key: deps.formatKey(), formatter: (price) => deps.formatter().format(price), minMove: deps.minMove() }),
13699
13703
  neutral: () => deps.canvas().neutral
@@ -14562,7 +14566,7 @@ var GRAIN_GROUPS = [
14562
14566
  { unit: "d", rungs: [{ tf: "1d", sec: 86400 }] }
14563
14567
  ];
14564
14568
  var DAY_RUNG = { tf: "1d", sec: 86400 };
14565
- function subIntervalsFor(chartTf) {
14569
+ function subTimeframesFor(chartTf) {
14566
14570
  const parsed = parseTimeframe(chartTf);
14567
14571
  const parent = tfSeconds(chartTf);
14568
14572
  if (!parsed || !parent) return [];
@@ -14572,8 +14576,8 @@ function subIntervalsFor(chartTf) {
14572
14576
  const intraday = parsed.unit === "s" || parsed.unit === "m" || parsed.unit === "h";
14573
14577
  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
14578
  }
14575
- function autoIntervalFor(chartTf) {
14576
- const offered = subIntervalsFor(chartTf);
14579
+ function autoTimeframeFor(chartTf) {
14580
+ const offered = subTimeframesFor(chartTf);
14577
14581
  return offered.length ? offered[offered.length - 1] : null;
14578
14582
  }
14579
14583
  function composeFormingBar(parent, subs, k) {
@@ -14595,8 +14599,8 @@ function attachReplayPlane(deps) {
14595
14599
  let arming = false;
14596
14600
  let timer = null;
14597
14601
  let speed = deps.initialSpeed;
14598
- let autoInterval = deps.initialInterval === "auto";
14599
- let manualInterval = autoInterval ? null : deps.initialInterval;
14602
+ let autoGrain = deps.initialGrain === "auto";
14603
+ let manualGrain = autoGrain ? null : deps.initialGrain;
14600
14604
  let subs = null;
14601
14605
  let formK = 0;
14602
14606
  let stepping = null;
@@ -14608,18 +14612,18 @@ function attachReplayPlane(deps) {
14608
14612
  };
14609
14613
  const grains = () => {
14610
14614
  const served = deps.resolutions();
14611
- const ladder = subIntervalsFor(deps.timeframe());
14615
+ const ladder = subTimeframesFor(deps.timeframe());
14612
14616
  return served && served.length > 0 ? ladder.filter((s) => served.includes(s.tf)) : ladder;
14613
14617
  };
14614
- const effectiveInterval = () => {
14618
+ const effectiveGrain = () => {
14615
14619
  const available = grains();
14616
- if (autoInterval) return available.length ? available[available.length - 1] : null;
14617
- return available.find((s) => s.tf === manualInterval) ?? null;
14620
+ if (autoGrain) return available.length ? available[available.length - 1] : null;
14621
+ return available.find((s) => s.tf === manualGrain) ?? null;
14618
14622
  };
14619
14623
  const sync = () => {
14620
14624
  deps.onChange();
14621
14625
  };
14622
- const currentInterval = () => autoInterval ? "auto" : manualInterval ?? "auto";
14626
+ const currentGrain = () => autoGrain ? "auto" : manualGrain ?? "auto";
14623
14627
  const paintCursor = () => {
14624
14628
  if (!master) return;
14625
14629
  deps.paint(master.slice(0, cursor));
@@ -14654,15 +14658,15 @@ function attachReplayPlane(deps) {
14654
14658
  deps.setCrosshair(!next);
14655
14659
  };
14656
14660
  const fetchSubs = async (parentIdx) => {
14657
- const interval = effectiveInterval();
14658
- if (!master || !interval) return null;
14659
- if (interval.sec >= tfSeconds(deps.timeframe())) return null;
14661
+ const grain = effectiveGrain();
14662
+ if (!master || !grain) return null;
14663
+ if (grain.sec >= tfSeconds(deps.timeframe())) return null;
14660
14664
  const parent = master[parentIdx];
14661
14665
  if (!parent) return null;
14662
14666
  const from = parent.t;
14663
14667
  const to = parent.t + tfSeconds(deps.timeframe()) - 1;
14664
14668
  try {
14665
- const page = await deps.datafeed.history(deps.symbol(), interval.tf, { from, to });
14669
+ const page = await deps.datafeed.history(deps.symbol(), grain.tf, { from, to });
14666
14670
  const found = page.bars.filter((b) => b.t >= from && b.t <= to);
14667
14671
  return found.length >= 2 ? found : null;
14668
14672
  } catch {
@@ -14693,11 +14697,11 @@ function attachReplayPlane(deps) {
14693
14697
  const epoch = generation;
14694
14698
  const symbol = deps.symbol();
14695
14699
  const timeframe2 = deps.timeframe();
14696
- const grain = currentInterval();
14697
- const chosen = effectiveInterval();
14700
+ const grain = currentGrain();
14701
+ const chosen = effectiveGrain();
14698
14702
  const wholeBar = !chosen || chosen.sec >= tfSeconds(deps.timeframe());
14699
14703
  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;
14704
+ 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
14705
  cursor = targetCursor;
14702
14706
  if (found) {
14703
14707
  subs = found;
@@ -14796,26 +14800,26 @@ function attachReplayPlane(deps) {
14796
14800
  paintCursor();
14797
14801
  sync();
14798
14802
  },
14799
- interval: currentInterval,
14803
+ timeframe: currentGrain,
14800
14804
  // What `auto` actually RESOLVED to, so a surface can say the grain rather than the mode. Empty
14801
14805
  // 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;
14806
+ resolvedTimeframe: () => effectiveGrain()?.tf ?? "",
14807
+ setTimeframe(token) {
14808
+ if (token === currentGrain()) return;
14805
14809
  if (token === "auto") {
14806
- autoInterval = true;
14810
+ autoGrain = true;
14807
14811
  } else {
14808
14812
  if (!grains().some((s) => s.tf === token)) return;
14809
- autoInterval = false;
14810
- manualInterval = token;
14813
+ autoGrain = false;
14814
+ manualGrain = token;
14811
14815
  }
14812
14816
  invalidateStep();
14813
- deps.persist("interval", currentInterval());
14817
+ deps.persist("timeframe", currentGrain());
14814
14818
  subs = null;
14815
14819
  formK = 0;
14816
14820
  sync();
14817
14821
  },
14818
- subIntervals: () => grains().map((s) => s.tf),
14822
+ subTimeframes: () => grains().map((s) => s.tf),
14819
14823
  state: () => ({ on: master !== null, playing, cursor, total: master?.length ?? deps.bars().length, speed }),
14820
14824
  // An OPEN QUESTION outranks a running session. Re-arming mid-replay to choose a different start
14821
14825
  // puts the plot back to taking a click, and every surface that answers to the picker — the
@@ -14852,7 +14856,7 @@ function attachReplayPlane(deps) {
14852
14856
  abandon,
14853
14857
  snapshot: () => ({ on: master !== null, playing, cursor: master === null ? deps.bars().length : cursor, total: master?.length ?? deps.bars().length }),
14854
14858
  speed: () => speed,
14855
- interval: currentInterval,
14859
+ timeframe: currentGrain,
14856
14860
  destroy() {
14857
14861
  destroyed = true;
14858
14862
  abandon();
@@ -14931,7 +14935,7 @@ function createExtensionHost(deps, extensions = []) {
14931
14935
  }
14932
14936
  }
14933
14937
  clearLanes(record3.lanes);
14934
- record3.menuProviders.clear();
14938
+ record3.menuBuilders.clear();
14935
14939
  if (record3.hideLayers.size) {
14936
14940
  record3.hideLayers.clear();
14937
14941
  notifyHideLayers();
@@ -14955,7 +14959,7 @@ function createExtensionHost(deps, extensions = []) {
14955
14959
  } },
14956
14960
  live: true,
14957
14961
  lanes: newLanes(),
14958
- menuProviders: /* @__PURE__ */ new Set(),
14962
+ menuBuilders: /* @__PURE__ */ new Set(),
14959
14963
  hideLayers: /* @__PURE__ */ new Map(),
14960
14964
  commands: /* @__PURE__ */ new Map(),
14961
14965
  priceLines: /* @__PURE__ */ new Set(),
@@ -15044,12 +15048,12 @@ function createExtensionHost(deps, extensions = []) {
15044
15048
  record3.lanes.dispose.delete(callback);
15045
15049
  };
15046
15050
  },
15047
- contributeContextMenu(provider) {
15051
+ contributeContextMenu(build) {
15048
15052
  if (!record3.live) return () => {
15049
15053
  };
15050
- record3.menuProviders.add(provider);
15054
+ record3.menuBuilders.add(build);
15051
15055
  return () => {
15052
- record3.menuProviders.delete(provider);
15056
+ record3.menuBuilders.delete(build);
15053
15057
  };
15054
15058
  },
15055
15059
  contributeCommands(commands) {
@@ -15143,9 +15147,9 @@ function createExtensionHost(deps, extensions = []) {
15143
15147
  if (!hostLive) return [];
15144
15148
  const rows2 = [];
15145
15149
  for (const record3 of liveRecords()) {
15146
- for (const provider of [...record3.menuProviders]) {
15150
+ for (const build of [...record3.menuBuilders]) {
15147
15151
  try {
15148
- rows2.push(...provider(context));
15152
+ rows2.push(...build(context));
15149
15153
  } catch {
15150
15154
  }
15151
15155
  }
@@ -15540,7 +15544,7 @@ function mountChartLegend(container, strings, controls) {
15540
15544
  venueText.className = "qc-legend-venue qc-legend-row-venue";
15541
15545
  const value = document.createElement("span");
15542
15546
  value.className = "qc-legend-value";
15543
- value.dataset.role = "legend-study-value";
15547
+ value.dataset.role = "legend-indicator-value";
15544
15548
  const gear = chipButton(controls.icons.glyph(ICONS.legendSettings, { size: 18 }), "", () => {
15545
15549
  const r = gear.getBoundingClientRect();
15546
15550
  controls.onSettings?.(chip.id, { x: r.x, y: r.y, w: r.width, h: r.height });
@@ -16195,7 +16199,7 @@ function attachLegendPlane(deps) {
16195
16199
  const paneRemembered = /* @__PURE__ */ new Map();
16196
16200
  const paneKeys = () => {
16197
16201
  const keys = { 0: "main" };
16198
- for (const [id, pane] of Object.entries(deps.indicators.renderer.paneOf())) if (pane > 0) keys[pane] = `study:${id}`;
16202
+ for (const [id, pane] of Object.entries(deps.indicators.renderer.paneOf())) if (pane > 0) keys[pane] = `indicator:${id}`;
16199
16203
  for (const entry of deps.compare?.api.list() ?? []) {
16200
16204
  const pane = deps.compare.handle.paneIndexOf(entry.symbol);
16201
16205
  if (pane !== null && pane > 0) keys[pane] = `compare:${entry.symbol}`;
@@ -16320,7 +16324,7 @@ function attachLegendPlane(deps) {
16320
16324
  legend2?.setChips([
16321
16325
  ...deps.indicators.chipsAt(hovered).map((row2) => {
16322
16326
  const paneIndex = paneOf[row2.id] ?? 0;
16323
- const remembered = paneRemembered.get(`study:${row2.id}`);
16327
+ const remembered = paneRemembered.get(`indicator:${row2.id}`);
16324
16328
  return {
16325
16329
  ...row2,
16326
16330
  paneIndex,
@@ -16699,7 +16703,7 @@ var CONTROL_ICONS = byMeaning({
16699
16703
  chevronDown: [ICONS.chevronDown, ICONS.chevronDown18],
16700
16704
  chevronLeft: [ICONS.chevronLeft],
16701
16705
  chevronRight: [ICONS.chevronRight],
16702
- // The rail's arrow, pointing where the flyout it opens will stand.
16706
+ // The drawing toolbar's arrow, pointing where the flyout it opens will stand.
16703
16707
  flyout: [ICONS.chevronRight16],
16704
16708
  submenu: [ICONS.submenuArrow],
16705
16709
  more: [ICONS.kebab],
@@ -16725,7 +16729,7 @@ var CONTROL_ICONS = byMeaning({
16725
16729
  selectDate: [ICONS.calendar],
16726
16730
  firstAvailable: [ICONS.firstAvailable],
16727
16731
  randomBar: [ICONS.randomBar],
16728
- // The drawing rail, its menus, the favorites bar and the drawing settings.
16732
+ // The drawing toolbar, its menus, the favorites bar and the drawing settings.
16729
16733
  cursorCross: [ICONS.cursorCross],
16730
16734
  cursorDot: [ICONS.cursorDot],
16731
16735
  cursorArrow: [ICONS.cursorArrow],
@@ -17116,7 +17120,7 @@ function attachMenuPlane(deps) {
17116
17120
  {
17117
17121
  priceText,
17118
17122
  symbol: deps.symbol(),
17119
- // The level menu's paste row is live exactly when the paste command would run (a copied
17123
+ // The context menu's paste row is live exactly when the paste command would run (a copied
17120
17124
  // drawing on the clipboard, the verb permitted). It offers no settings row: the settings
17121
17125
  // dialog belongs to the selected drawing's own surfaces, which the drawing plane mounts
17122
17126
  // and the chart.drawings.* commands drive.
@@ -17886,12 +17890,12 @@ function registerChartCommands(deps) {
17886
17890
  }
17887
17891
  });
17888
17892
  add({
17889
- id: "chart.replay.setInterval",
17893
+ id: "chart.replay.setTimeframe",
17890
17894
  scope: "chart",
17891
- label: "command.replayInterval",
17895
+ label: "command.replayTimeframe",
17892
17896
  available: () => handle.replay.state().on,
17893
17897
  execute: (arg) => {
17894
- if (typeof arg === "string" && arg) handle.replay.setInterval(arg);
17898
+ if (typeof arg === "string" && arg) handle.replay.setTimeframe(arg);
17895
17899
  }
17896
17900
  });
17897
17901
  const servable = (token) => {
@@ -18220,7 +18224,7 @@ var SCALE_KEY = "quickcharts.scale.v1";
18220
18224
  var PRICE_AXIS_KEY = "quickcharts.priceAxis.v1";
18221
18225
  var HIDDEN_KEY = "quickcharts.indHidden.v1";
18222
18226
  var REPLAY_SPEED_KEY = "quickcharts.replaySpeed.v1";
18223
- var REPLAY_INTERVAL_KEY = "quickcharts.replayIv.v1";
18227
+ var REPLAY_TIMEFRAME_KEY = "quickcharts.replayTf.v1";
18224
18228
  var TIMEZONE_KEY = "quickcharts.timezone.v1";
18225
18229
  var SUBSESSION_KEY = "quickcharts.subsession.v1";
18226
18230
  function sameIds(left, right) {
@@ -18465,8 +18469,8 @@ function createChartInstance(deps) {
18465
18469
  const indicators2 = attachIndicatorsPlane({
18466
18470
  chart,
18467
18471
  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.
18472
+ // An indicator drawn over bars the active subsession hides would not line up with the series
18473
+ // beside it, so it computes over the painted model rather than the loaded one.
18470
18474
  bars: () => shownBars(),
18471
18475
  i18n,
18472
18476
  formatter: () => symbolFormatter,
@@ -18622,9 +18626,9 @@ function createChartInstance(deps) {
18622
18626
  // A chart whose crosshair the host switched OFF keeps it off: replay may take the crosshair
18623
18627
  // away for the length of a question, never hand one back that was never there.
18624
18628
  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.
18629
+ // The feed's own grains: a chart never offers, or fetches, a timeframe its feed cannot serve.
18626
18630
  resolutions: () => deps.capabilities().resolutions,
18627
- persist: (key, value) => storage.set(key === "speed" ? REPLAY_SPEED_KEY : REPLAY_INTERVAL_KEY, value),
18631
+ persist: (key, value) => storage.set(key === "speed" ? REPLAY_SPEED_KEY : REPLAY_TIMEFRAME_KEY, value),
18628
18632
  onChange: () => {
18629
18633
  const state = replay2.snapshot();
18630
18634
  countdown?.refresh();
@@ -18634,7 +18638,7 @@ function createChartInstance(deps) {
18634
18638
  events.emit("replay", state);
18635
18639
  },
18636
18640
  initialSpeed: coerceReplaySpeed(storage.get(REPLAY_SPEED_KEY) ?? deps.preferences.replaySpeed),
18637
- initialInterval: storage.get(REPLAY_INTERVAL_KEY) ?? deps.preferences.replayInterval ?? "auto"
18641
+ initialGrain: storage.get(REPLAY_TIMEFRAME_KEY) ?? deps.preferences.replayTimeframe ?? "auto"
18638
18642
  });
18639
18643
  const countdownClock = createCountdownClock(() => Date.now() / 1e3, datafeed.serverTime?.bind(datafeed));
18640
18644
  countdown = attachCountdown({
@@ -18681,7 +18685,8 @@ function createChartInstance(deps) {
18681
18685
  setTouchAction: (value) => {
18682
18686
  gestures.style.touchAction = value;
18683
18687
  },
18684
- // A contributed layer rides the same eye the rail drives, through the drawing plane's verbs.
18688
+ // A contributed layer rides the same eye the drawing toolbar drives, through the drawing
18689
+ // plane's verbs.
18685
18690
  hideState: () => drawings2.verbs?.hide() ?? { mode: "drawings", on: false },
18686
18691
  setHide: (state) => drawings2.verbs?.setHide(state),
18687
18692
  hideLayersChanged: () => drawings2.syncHideLayers()
@@ -19357,7 +19362,7 @@ function createChartInstance(deps) {
19357
19362
  resetAppearance,
19358
19363
  t: () => i18n.t,
19359
19364
  // 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.
19365
+ // the viewer can see and never a bar the cursor has not revealed.
19361
19366
  bars: () => shownBars(),
19362
19367
  // The feed's own statement of how deep its history goes, never the oldest bar that happens to
19363
19368
  // be loaded: the chart opens on a short window, and a preset judged against that would be
@@ -19623,7 +19628,14 @@ function validGeometry(value, catalog) {
19623
19628
  }
19624
19629
 
19625
19630
  // src/widget/layout.ts
19626
- var SYNC_OFF = { symbol: false, interval: false, crosshair: false, time: false, dateRange: false };
19631
+ var SYNC_OFF = { symbol: false, timeframe: false, crosshair: false, time: false, dateRange: false };
19632
+ var syncSwitches = (stated) => Object.fromEntries(LAYOUT_SYNC_KEYS.map((key) => [key, stated[key] ?? SYNC_OFF[key]]));
19633
+ function readSyncFlags(value, defaults) {
19634
+ if (!value || typeof value !== "object" || Array.isArray(value)) return null;
19635
+ const stated = value;
19636
+ if (LAYOUT_SYNC_KEYS.some((key) => key in stated && typeof stated[key] !== "boolean")) return null;
19637
+ return Object.fromEntries(LAYOUT_SYNC_KEYS.map((key) => [key, key in stated ? stated[key] : defaults[key]]));
19638
+ }
19627
19639
  var LAYOUT_CONTENT_VERSION = 2;
19628
19640
  var validEntityNamespace = (value) => /^(?:layout:(?:[A-Za-z0-9_.!~*'()-]|%[0-9A-F]{2})+|session:[a-z0-9-]+)$/.test(value);
19629
19641
  var cloneIndicators = (instances) => instances.map((instance) => ({
@@ -19638,7 +19650,7 @@ var cloneTemplate = (template) => ({
19638
19650
  });
19639
19651
  function createLayoutPlane(deps) {
19640
19652
  const slots = [];
19641
- let flags = { ...SYNC_OFF, ...deps.sync ?? {} };
19653
+ let flags = syncSwitches(deps.sync ?? {});
19642
19654
  const fixedSync = deps.syncOffered ? LAYOUT_SYNC_KEYS.filter((key) => !deps.syncOffered.includes(key)) : [];
19643
19655
  const fixedFlags = { ...flags };
19644
19656
  const holdFixed = (next) => {
@@ -19757,7 +19769,7 @@ function createLayoutPlane(deps) {
19757
19769
  if (!applying) emitActive();
19758
19770
  }));
19759
19771
  slot.unsubscribes.push(handle.on("timeframe", () => {
19760
- if (flags.interval) fanOut2(index(), (other) => other.setTimeframe(handle.timeframe()));
19772
+ if (flags.timeframe) fanOut2(index(), (other) => other.setTimeframe(handle.timeframe()));
19761
19773
  if (!applying) deps.onChange();
19762
19774
  }));
19763
19775
  };
@@ -20021,15 +20033,14 @@ function createLayoutPlane(deps) {
20021
20033
  if (!arrangement2) throw new Error(`unknown arrangement code ${String(c.arrangement)}`);
20022
20034
  if (charts.length !== arrangement2.count) throw new Error("layout chart count does not match arrangement");
20023
20035
  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");
20036
+ const sync = readSyncFlags(c.sync, fixedFlags);
20037
+ if (!sync) throw new Error("invalid layout sync flags");
20027
20038
  if (!Number.isInteger(c.active) || c.active < 0 || c.active >= charts.length) throw new Error("invalid active chart");
20028
20039
  return {
20029
20040
  arrangement: arrangement2,
20030
20041
  identity: { namespace: identity.namespace, next: identity.next },
20031
20042
  geometry: c.geometry.map((rect) => ({ ...rect })),
20032
- sync: Object.fromEntries(keys.map((key) => [key, sync[key]])),
20043
+ sync,
20033
20044
  active: c.active,
20034
20045
  charts
20035
20046
  };
@@ -20111,7 +20122,7 @@ function createLayoutPlane(deps) {
20111
20122
  setSync(partial) {
20112
20123
  if (removed) return;
20113
20124
  if (fixedSync.length > 0 && Object.keys(partial).every((key) => fixedSync.includes(key))) return;
20114
- flags = holdFixed({ ...flags, ...partial });
20125
+ flags = holdFixed(syncSwitches({ ...flags, ...partial }));
20115
20126
  deps.onChange();
20116
20127
  emitCommitted();
20117
20128
  },
@@ -20759,9 +20770,9 @@ var FLYOUT_WIDTH = {
20759
20770
  session: 176,
20760
20771
  /** The replay row's starting-point choices, opening upward. */
20761
20772
  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
20773
+ /** The replay row's update-timeframe list. The speed list opens at the same width and grows to
20774
+ * fit its rates. */
20775
+ replayTimeframe: 196
20765
20776
  };
20766
20777
 
20767
20778
  // src/ui/chrome/bottomBar.ts
@@ -21235,7 +21246,8 @@ function openIndicatorPicker(deps) {
21235
21246
  className: "qc-picker-dialog",
21236
21247
  width: 840,
21237
21248
  refresh: () => redraw(),
21238
- onClose: () => {
21249
+ // The host's listing is abandoned as the dialog begins to close, not when its exit ends.
21250
+ onClosing: () => {
21239
21251
  cancel();
21240
21252
  offLocale();
21241
21253
  },
@@ -21547,7 +21559,7 @@ var PLACEMENTS2 = [
21547
21559
  ];
21548
21560
  function rowLabels(row2) {
21549
21561
  const names = symbolNames(row2);
21550
- return { ticker: names.mark, description: names.description, source: row2.exchange || row2.provider || "" };
21562
+ return { ticker: names.mark, description: names.description, source: row2.exchange || row2.dataSource || "" };
21551
21563
  }
21552
21564
  function offeredOperators(spreads) {
21553
21565
  if (spreads === false) return [];
@@ -21776,10 +21788,10 @@ function buildSearchSurface(deps, box, frame) {
21776
21788
  option.appendChild(mark2);
21777
21789
  } else {
21778
21790
  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)));
21791
+ 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
21792
  if (label2.source) {
21781
21793
  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;
21794
+ 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
21795
  if (typeof dropSource === "function") {
21784
21796
  sourceMark.dataset.qcHost = "true";
21785
21797
  markDisposers.push(dropSource);
@@ -21886,7 +21898,6 @@ function openSearchDialog(deps) {
21886
21898
  host: deps.host,
21887
21899
  label: title,
21888
21900
  className: `qc-search-dialog qc-search-dialog--${mode}`,
21889
- animated: true,
21890
21901
  // The compare family (compare, and a compare row's change-symbol) shares one role for a host
21891
21902
  // test to find; a symbol search, with or without a chart behind it, is the other.
21892
21903
  role: mode === "compare" || mode === "change-symbol" ? "compare-dialog" : "symbol-search",
@@ -22021,7 +22032,7 @@ var SORT_LABEL = {
22021
22032
  "modified-desc": "layouts.sortModifiedDesc"
22022
22033
  };
22023
22034
  function listingFacts(t, row2) {
22024
- return row2.symbol ? row2.timeframe ? t("layouts.listingFacts", { symbol: row2.symbol, interval: timeframeChipLabel(row2.timeframe) }) : row2.symbol : null;
22035
+ return row2.symbol ? row2.timeframe ? t("layouts.listingFacts", { symbol: row2.symbol, timeframe: timeframeChipLabel(row2.timeframe) }) : row2.symbol : null;
22025
22036
  }
22026
22037
  function mountLayoutDialogs(deps) {
22027
22038
  const t = () => deps.i18n.t;
@@ -22037,7 +22048,7 @@ function mountLayoutDialogs(deps) {
22037
22048
  };
22038
22049
  const askName = (question) => {
22039
22050
  raise();
22040
- asking?.close();
22051
+ asking?.close({ animate: false });
22041
22052
  asking = openNameDialog({
22042
22053
  host: deps.overlays,
22043
22054
  t: t(),
@@ -22189,15 +22200,15 @@ function mountLayoutDialogs(deps) {
22189
22200
  render();
22190
22201
  redraw = render;
22191
22202
  closeBrowser = catalog.onChange(render);
22192
- browser = {
22193
- close: () => handle.close()
22194
- };
22203
+ browser = handle;
22195
22204
  void catalog.refresh().catch(() => {
22196
22205
  deps.notify("error", t()("layouts.errList"));
22197
22206
  });
22198
22207
  },
22199
22208
  initialFocus: (box) => box.querySelector(".qc-layouts-search-input"),
22200
- onClose: () => {
22209
+ // The dialog lets go of the catalog and its slot as it begins to close, so a Layouts dialog
22210
+ // asked for during the exit opens at once and the leaving one never unsubscribes it.
22211
+ onClosing: () => {
22201
22212
  closeBrowser();
22202
22213
  browser = null;
22203
22214
  }
@@ -22228,8 +22239,8 @@ function mountLayoutDialogs(deps) {
22228
22239
  destroy() {
22229
22240
  raising.clear();
22230
22241
  offLayout();
22231
- browser?.close();
22232
- asking?.close();
22242
+ browser?.close({ animate: false });
22243
+ asking?.close({ animate: false });
22233
22244
  }
22234
22245
  };
22235
22246
  }
@@ -22294,7 +22305,7 @@ function arrangementGlyph(code, icons) {
22294
22305
  }
22295
22306
  var SYNC_ROWS = [
22296
22307
  { key: "symbol", label: "layouts.syncSymbol", tip: "layouts.syncSymbolTip", toggle: "layouts.syncSymbolToggle" },
22297
- { key: "interval", label: "layouts.syncInterval", tip: "layouts.syncIntervalTip", toggle: "layouts.syncIntervalToggle" },
22308
+ { key: "timeframe", label: "layouts.syncTimeframe", tip: "layouts.syncTimeframeTip", toggle: "layouts.syncTimeframeToggle" },
22298
22309
  { key: "crosshair", label: "layouts.syncCrosshair", tip: "layouts.syncCrosshairTip", toggle: "layouts.syncCrosshairToggle" },
22299
22310
  { key: "time", label: "layouts.syncTime", tip: "layouts.syncTimeTip", toggle: "layouts.syncTimeToggle" },
22300
22311
  { key: "dateRange", label: "layouts.syncDateRange", tip: "layouts.syncDateRangeTip", toggle: "layouts.syncDateRangeToggle" }
@@ -22594,8 +22605,8 @@ function mountLayoutsMenu(deps) {
22594
22605
  text: t()("layouts.createNewRow"),
22595
22606
  icon: deps.icons.glyph(ICONS.plusThin, { size: 28 }),
22596
22607
  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.
22608
+ // A new layout by the name it is given: the market and timeframe on screen, nothing
22609
+ // else of the open layout, and the open one left as it was saved.
22599
22610
  onSelect: () => askName({
22600
22611
  title: "layouts.createTitle",
22601
22612
  label: "layouts.newLayoutName",
@@ -22892,7 +22903,6 @@ function mountSettingsMenu(deps) {
22892
22903
  host: deps.overlays,
22893
22904
  label: t()("settings.menu"),
22894
22905
  className: "qc-settings-menu qc-chart-settings-dialog",
22895
- animated: true,
22896
22906
  width: 750,
22897
22907
  build(box, handle) {
22898
22908
  content = h("div", { class: "qc-dialog-body qc-chart-settings-body" });
@@ -23760,7 +23770,7 @@ function openDatePicker(deps) {
23760
23770
  function speedWords(t, speed) {
23761
23771
  return speed >= 1 ? t("replay.updatesPerSecond", { count: speed }) : t("replay.oneUpdatePerSeconds", { count: Math.round(1 / speed) });
23762
23772
  }
23763
- function intervalWords(t, token) {
23773
+ function timeframeWords(t, token) {
23764
23774
  const sec = tfSeconds(token);
23765
23775
  if (sec === 0) return token;
23766
23776
  return timeframeLabel(t, token);
@@ -23931,36 +23941,36 @@ function mountReplayTransport(deps) {
23931
23941
  }
23932
23942
  }));
23933
23943
  };
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 = () => {
23944
+ const timeframeButton = button2({ label: t()("replay.timeframe"), text: "", className: "qc-toolbar-button qc-replay-timeframe", onClick: () => toggleMenu(timeframeButton, openTimeframeMenu) });
23945
+ timeframeButton.setAttribute("aria-haspopup", "menu");
23946
+ const openTimeframeMenu = () => {
23937
23947
  const menu2 = hold(openMenu({
23938
23948
  host: deps.chrome,
23939
- anchor: intervalButton,
23940
- label: t()("replay.interval"),
23949
+ anchor: timeframeButton,
23950
+ label: t()("replay.timeframe"),
23941
23951
  role: "dialog",
23942
23952
  className: "qc-replay-menu",
23943
- width: FLYOUT_WIDTH.replayInterval,
23953
+ width: FLYOUT_WIDTH.replayTimeframe,
23944
23954
  placement: "up",
23945
23955
  onClose: () => open2.delete(menu2),
23946
23956
  build(body, menu3) {
23947
- const heading = menuHeading(t()("replay.interval"));
23948
- heading.title = t()("replay.intervalHelp");
23957
+ const heading = menuHeading(t()("replay.timeframe"));
23958
+ heading.title = t()("replay.timeframeHelp");
23949
23959
  body.appendChild(heading);
23950
- const current = handle.replay.interval();
23960
+ const current = handle.replay.timeframe();
23951
23961
  const rows2 = [];
23952
23962
  let previousUnit = null;
23953
- for (const token of handle.replay.subIntervals()) {
23963
+ for (const token of handle.replay.subTimeframes()) {
23954
23964
  const unit = timeframeGroupUnit(parseTimeframe(token)?.unit ?? "d");
23955
23965
  if (previousUnit !== null && unit !== previousUnit) body.appendChild(menuSeparator());
23956
23966
  previousUnit = unit;
23957
23967
  const row2 = menuItem({
23958
- text: intervalWords(t(), token),
23968
+ text: timeframeWords(t(), token),
23959
23969
  role: "menuitemradio",
23960
23970
  checked: current === token,
23961
23971
  onSelect: () => {
23962
23972
  menu3.close();
23963
- commands.execute("chart.replay.setInterval", token);
23973
+ commands.execute("chart.replay.setTimeframe", token);
23964
23974
  }
23965
23975
  });
23966
23976
  rows2.push({ token, row: row2 });
@@ -23969,12 +23979,12 @@ function mountReplayTransport(deps) {
23969
23979
  body.appendChild(menuSeparator());
23970
23980
  body.appendChild(
23971
23981
  switchRow({
23972
- label: t()("replay.autoSelectInterval"),
23982
+ label: t()("replay.autoSelectTimeframe"),
23973
23983
  checked: current === "auto",
23974
23984
  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();
23985
+ const first = handle.replay.subTimeframes()[0];
23986
+ commands.execute("chart.replay.setTimeframe", on ? "auto" : first ?? "auto");
23987
+ const now = handle.replay.timeframe();
23978
23988
  for (const entry of rows2) entry.row.setAttribute("aria-checked", String(now === entry.token));
23979
23989
  }
23980
23990
  })
@@ -23994,7 +24004,7 @@ function mountReplayTransport(deps) {
23994
24004
  playPause,
23995
24005
  stepForward,
23996
24006
  speedButton,
23997
- intervalButton,
24007
+ timeframeButton,
23998
24008
  rule(),
23999
24009
  goLive
24000
24010
  );
@@ -24010,19 +24020,19 @@ function mountReplayTransport(deps) {
24010
24020
  stepForward.hidden = !shown2("chart.replay.stepForward");
24011
24021
  goLive.hidden = !shown2("chart.replay.goLive");
24012
24022
  speedButton.hidden = !shown2("chart.replay.setSpeed");
24013
- intervalButton.hidden = !shown2("chart.replay.setInterval");
24023
+ timeframeButton.hidden = !shown2("chart.replay.setTimeframe");
24014
24024
  exit.hidden = !shown2("chart.replay.exit");
24015
24025
  setDisabled(stepBack, !commands.available("chart.replay.stepBack") || state.cursor <= 2);
24016
24026
  setDisabled(playPause, !commands.available(playing ? "chart.replay.pause" : "chart.replay.play") || !playing && atLive);
24017
24027
  setDisabled(stepForward, !commands.available("chart.replay.stepForward") || atLive);
24018
24028
  setDisabled(goLive, !commands.available("chart.replay.goLive") || atLive);
24019
24029
  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"));
24030
+ const grains = handle.replay.subTimeframes();
24031
+ const noGrain = grains.length === 0;
24032
+ setDisabled(timeframeButton, noGrain || !commands.available("chart.replay.setTimeframe"));
24023
24033
  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));
24034
+ const resolved = handle.replay.resolvedTimeframe();
24035
+ retext(timeframeButton, noGrain ? t()("replay.timeframeNone") : t()("replay.timeframe"), noGrain ? t()("replay.auto") : timeframeChipLabel(resolved));
24026
24036
  setDisabled(exit, !commands.available("chart.replay.exit"));
24027
24037
  reconcilePicking();
24028
24038
  bar.setAttribute("aria-label", t()("chrome.replay"));
@@ -24053,7 +24063,7 @@ function mountReplayTransport(deps) {
24053
24063
  if (destroyed) return;
24054
24064
  destroyed = true;
24055
24065
  releasePicking();
24056
- for (const handle2 of [...open2]) handle2.close();
24066
+ for (const handle2 of [...open2]) handle2.close({ animate });
24057
24067
  offStrings();
24058
24068
  const view = bar.ownerDocument.defaultView;
24059
24069
  const exitMs = animate && bar.isConnected ? motionDurationMs(bar, "motion.durationBase") : 0;
@@ -24158,7 +24168,7 @@ function mountChrome(deps) {
24158
24168
  indicatorDialog = openIndicatorPicker({ ...ctx, storage: deps.storage, indicatorPicker: deps.indicatorPicker, initialCollection });
24159
24169
  };
24160
24170
  disposers.push(() => {
24161
- indicatorDialog?.close();
24171
+ indicatorDialog?.close({ animate: false });
24162
24172
  deps.doors.showIndicatorPicker = () => void 0;
24163
24173
  });
24164
24174
  deps.doors.openSearch = (request) => {
@@ -24213,7 +24223,8 @@ function mountChrome(deps) {
24213
24223
  widget.theme.onChange((theme, mode) => paintThemeRoot(surface, mode, theme)),
24214
24224
  i18n.onChange(() => surface.setAttribute("dir", readingDirection(i18n))),
24215
24225
  () => surface.remove(),
24216
- // A flyout still open in the layer closes with the rail, taking its document listeners.
24226
+ // A flyout still open in the layer closes with the drawing toolbar, taking its document
24227
+ // listeners.
24217
24228
  () => closeOverlays(deps.layer),
24218
24229
  releasePanelHost
24219
24230
  );
@@ -24404,7 +24415,7 @@ function resolveMarkPainters(hooks) {
24404
24415
  return {
24405
24416
  symbol: hooks.symbolMark ?? null,
24406
24417
  venue: hooks.venueMark ?? null,
24407
- provider: hooks.providerMark ?? null
24418
+ dataSource: hooks.dataSourceMark ?? null
24408
24419
  };
24409
24420
  }
24410
24421
 
@@ -24857,7 +24868,7 @@ function createChart(options) {
24857
24868
  i18n,
24858
24869
  features,
24859
24870
  ui,
24860
- // What the chrome keeps is the viewer's own choices about it (favorite and custom intervals,
24871
+ // What the chrome keeps is the viewer's own choices about it (favorite and custom timeframes,
24861
24872
  // starred layouts and indicators, how the layouts sort), which change no layout, so it reads and
24862
24873
  // writes the host's store directly rather than through the save-needed funnel.
24863
24874
  storage: backing,
@@ -25182,7 +25193,7 @@ export {
25182
25193
  attachCompare,
25183
25194
  attachDrawings,
25184
25195
  attachIndicators,
25185
- autoIntervalFor,
25196
+ autoTimeframeFor,
25186
25197
  buildManifestPlots,
25187
25198
  canvasTheme,
25188
25199
  canvasToBlob,
@@ -25269,7 +25280,7 @@ export {
25269
25280
  sessionTimeline,
25270
25281
  spreadExpression,
25271
25282
  spreadSearchQuery,
25272
- subIntervalsFor,
25283
+ subTimeframesFor,
25273
25284
  subsessionBarFilter,
25274
25285
  symbolNames,
25275
25286
  tfSeconds,