@trdrs/quickcharts 1.1.0 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -8949,24 +8949,52 @@ var OPERATOR_GLYPHS = {
8949
8949
 
8950
8950
  // src/ui/chrome/dialog.ts
8951
8951
  function openDialog(options) {
8952
- const scrim = h("div", { class: "qc-scrim qc-dialog-scrim" });
8952
+ const scrim = h("div", { class: `qc-scrim qc-dialog-scrim${options.scrimClassName ? ` ${options.scrimClassName}` : ""}` });
8953
8953
  const box = h("div", { class: `qc-overlay qc-dialog${options.className ? ` ${options.className}` : ""}`, role: options.ariaRole ?? "dialog", "aria-modal": "true", "aria-label": options.label, "data-role": options.role });
8954
8954
  if (options.width) box.style.width = `${options.width}px`;
8955
8955
  stopPointer(box);
8956
8956
  scrim.appendChild(box);
8957
+ if (options.exitMs) scrim.dataset.state = "opening";
8957
8958
  let isOpen = true;
8958
8959
  const previouslyFocused = document.activeElement;
8959
8960
  const escape = pushEscapeOwner();
8960
8961
  let untrack = () => void 0;
8961
- const close = () => {
8962
- if (!isOpen) return;
8962
+ let removed = false;
8963
+ let exitTimer = null;
8964
+ const remove = () => {
8965
+ if (removed) return;
8966
+ removed = true;
8967
+ if (exitTimer !== null) clearTimeout(exitTimer);
8968
+ scrim.remove();
8969
+ options.onClose?.();
8970
+ };
8971
+ const close = (closeOptions) => {
8972
+ if (!isOpen) {
8973
+ if (closeOptions?.animate === false) remove();
8974
+ return;
8975
+ }
8963
8976
  isOpen = false;
8977
+ options.onClosing?.();
8964
8978
  escape.release();
8965
8979
  untrack();
8966
8980
  document.removeEventListener("keydown", onKey, true);
8967
- scrim.remove();
8968
- options.onClose?.();
8969
8981
  previouslyFocused?.focus?.();
8982
+ const reduceMotion = document.defaultView?.matchMedia?.("(prefers-reduced-motion: reduce)").matches ?? false;
8983
+ if (closeOptions?.animate === false || !options.exitMs || reduceMotion) {
8984
+ remove();
8985
+ return;
8986
+ }
8987
+ scrim.dataset.state = "closing";
8988
+ scrim.style.pointerEvents = "none";
8989
+ box.inert = true;
8990
+ box.removeAttribute("role");
8991
+ box.removeAttribute("aria-modal");
8992
+ box.setAttribute("aria-hidden", "true");
8993
+ const onAnimationEnd = (event) => {
8994
+ if (event.target === box) remove();
8995
+ };
8996
+ box.addEventListener("animationend", onAnimationEnd, { once: true });
8997
+ exitTimer = setTimeout(remove, options.exitMs + 50);
8970
8998
  };
8971
8999
  const onKey = (event) => {
8972
9000
  if (event.key === "Escape" && ownsEscape(escape.token)) {
@@ -8998,6 +9026,10 @@ function openDialog(options) {
8998
9026
  const handle = { element: box, close, open: () => isOpen };
8999
9027
  options.build(box, handle);
9000
9028
  (layerFor(options.host) ?? options.host).appendChild(scrim);
9029
+ if (options.exitMs) {
9030
+ scrim.getBoundingClientRect();
9031
+ scrim.dataset.state = "open";
9032
+ }
9001
9033
  untrack = trackOverlay(options.host, close);
9002
9034
  document.addEventListener("keydown", onKey, true);
9003
9035
  const target = options.initialFocus?.(box) ?? focusables(box)[0] ?? null;
@@ -14974,6 +15006,7 @@ function mountChartLegend(container, strings, controls) {
14974
15006
  update(next) {
14975
15007
  chip = next;
14976
15008
  row2.dataset.qcHidden = String(chip.hidden);
15009
+ row2.title = chip.description ?? "";
14977
15010
  rowMark.element.hidden = !chip.mark;
14978
15011
  if (!chip.mark) {
14979
15012
  dropMark?.();
@@ -15008,7 +15041,7 @@ function mountChartLegend(container, strings, controls) {
15008
15041
  if (chip.tone && !chip.hidden && !chip.note) value.dataset.qcTone = chip.tone;
15009
15042
  else delete value.dataset.qcTone;
15010
15043
  gear.hidden = !chip.hasInputs || !controls.onSettings;
15011
- nameButton(gear, strings.t("legend.indicatorSettings"));
15044
+ nameButton(gear, chip.settingsLabel ?? strings.t("legend.indicatorSettings"));
15012
15045
  collapse.hidden = !chip.pane || !controls.onPaneOp || chip.hidden;
15013
15046
  collapse.replaceChildren(controls.icons.glyph(chip.collapsed ? ICONS.paneRestore : ICONS.paneCollapse, { size: 18 }));
15014
15047
  nameButton(collapse, strings.t(chip.collapsed ? "legend.restorePane" : "legend.collapsePane"));
@@ -15021,6 +15054,7 @@ function mountChartLegend(container, strings, controls) {
15021
15054
  eye.dataset.hidden = eyeState;
15022
15055
  }
15023
15056
  nameButton(eye, strings.t(chip.hidden ? "legend.showIndicator" : "legend.hideIndicator"));
15057
+ eye.hidden = chip.hideable === false;
15024
15058
  remove.hidden = !chip.removable || !controls.onRemove;
15025
15059
  nameButton(remove, chip.removeLabel ?? strings.t("legend.removeCompare"));
15026
15060
  },
@@ -15566,6 +15600,7 @@ function openMarketStatus(anchor, deps) {
15566
15600
 
15567
15601
  // src/widget/legend.ts
15568
15602
  var COMPARE_ROW_PREFIX = "cmp:";
15603
+ var HOST_ROW_PREFIX = "host:";
15569
15604
  function barIndexAt(bars, time) {
15570
15605
  if (bars.length === 0) return -1;
15571
15606
  if (time === null) return bars.length - 1;
@@ -15594,10 +15629,12 @@ function attachLegendPlane(deps) {
15594
15629
  push: () => void 0,
15595
15630
  setHeader: () => void 0,
15596
15631
  setDot: () => void 0,
15632
+ setHostRows: () => void 0,
15597
15633
  destroy: () => void 0
15598
15634
  };
15599
15635
  }
15600
15636
  let destroyed = false;
15637
+ const hostRows = /* @__PURE__ */ new Map();
15601
15638
  const paneRemembered = /* @__PURE__ */ new Map();
15602
15639
  const paneKeys = () => {
15603
15640
  const keys = { 0: "main" };
@@ -15653,6 +15690,11 @@ function attachLegendPlane(deps) {
15653
15690
  }
15654
15691
  } : {},
15655
15692
  onSettings: (id, rect) => {
15693
+ const host2 = hostRows.get(id);
15694
+ if (host2) {
15695
+ host2.onSettings?.();
15696
+ return;
15697
+ }
15656
15698
  if (deps.openIndicatorSettings(id)) return;
15657
15699
  const inst = deps.indicators.list().find((i) => i.id === id);
15658
15700
  if (!inst) return;
@@ -15723,7 +15765,21 @@ function attachLegendPlane(deps) {
15723
15765
  const remembered = paneRemembered.get(`study:${row2.id}`);
15724
15766
  return { ...row2, paneIndex, maximized: remembered !== void 0 && !row2.collapsed && (deps.chart.panes()[paneIndex]?.getHeight() ?? 0) > remembered };
15725
15767
  }),
15726
- ...(deps.compare?.chips(hovered) ?? []).map((row2) => ({ ...row2, paneIndex: deps.compare.handle.paneIndexOf(row2.id.slice(COMPARE_ROW_PREFIX.length)) ?? 0 }))
15768
+ ...(deps.compare?.chips(hovered) ?? []).map((row2) => ({ ...row2, paneIndex: deps.compare.handle.paneIndexOf(row2.id.slice(COMPARE_ROW_PREFIX.length)) ?? 0 })),
15769
+ ...[...hostRows].map(([id, row2]) => ({
15770
+ id,
15771
+ title: row2.title,
15772
+ inputs: row2.inputs,
15773
+ value: null,
15774
+ note: row2.status,
15775
+ description: row2.description,
15776
+ settingsLabel: row2.settingsLabel,
15777
+ hidden: false,
15778
+ hideable: false,
15779
+ removable: false,
15780
+ hasInputs: !!row2.onSettings,
15781
+ paneIndex: 0
15782
+ }))
15727
15783
  ]);
15728
15784
  const tops = {};
15729
15785
  let top = 0;
@@ -15766,6 +15822,12 @@ function attachLegendPlane(deps) {
15766
15822
  };
15767
15823
  deps.chart.subscribeCrosshairMove(onCrosshair);
15768
15824
  return {
15825
+ setHostRows(rows2) {
15826
+ if (destroyed) return;
15827
+ hostRows.clear();
15828
+ for (const row2 of rows2) hostRows.set(`${HOST_ROW_PREFIX}${row2.id}`, { ...row2 });
15829
+ paint();
15830
+ },
15769
15831
  push() {
15770
15832
  paint();
15771
15833
  },
@@ -15791,6 +15853,7 @@ function attachLegendPlane(deps) {
15791
15853
  },
15792
15854
  destroy() {
15793
15855
  destroyed = true;
15856
+ hostRows.clear();
15794
15857
  resize?.disconnect();
15795
15858
  observed.clear();
15796
15859
  deps.chart.unsubscribeCrosshairMove(onCrosshair);
@@ -16929,7 +16992,13 @@ function registerChartCommands(deps) {
16929
16992
  available: () => features.history && handle.history.canRedo(),
16930
16993
  execute: () => handle.history.redo()
16931
16994
  });
16932
- add({ id: "chart.indicators.open", scope: "chart", label: "chrome.indicators", available: () => ui.indicatorPicker, execute: () => deps.indicatorsOpen() });
16995
+ add({
16996
+ id: "chart.indicators.open",
16997
+ scope: "chart",
16998
+ label: "chrome.indicators",
16999
+ available: () => ui.indicatorPicker,
17000
+ execute: (arg) => deps.indicatorsOpen(arg && typeof arg === "object" && "collection" in arg && typeof arg.collection === "string" ? arg.collection : void 0)
17001
+ });
16933
17002
  const isInstance = (arg) => !!arg && typeof arg === "object" && typeof arg.id === "string" && typeof arg.definition === "object";
16934
17003
  add({
16935
17004
  id: "chart.indicators.add",
@@ -18726,7 +18795,7 @@ function createChartInstance(deps) {
18726
18795
  level: () => menuLevel,
18727
18796
  formatter: () => symbolFormatter,
18728
18797
  compareOpen: (mode, changeFrom) => compare?.openDialog(mode, changeFrom),
18729
- indicatorsOpen: () => deps.doors.showIndicatorPicker(),
18798
+ indicatorsOpen: (collection) => deps.doors.showIndicatorPicker(collection),
18730
18799
  symbolSearchOpen: () => deps.doors.openSearch({ mode: "search", chart: handle }),
18731
18800
  drawingVerbs: () => drawings2.verbs
18732
18801
  });
@@ -18773,6 +18842,7 @@ function createChartInstance(deps) {
18773
18842
  }
18774
18843
  return {
18775
18844
  handle,
18845
+ setLegendRows: (rows2) => legend2.setHostRows(rows2),
18776
18846
  screenshot: () => chart.takeScreenshot(),
18777
18847
  repaintTheme() {
18778
18848
  applyLook();
@@ -20596,7 +20666,7 @@ function openIndicatorPicker(deps) {
20596
20666
  } catch {
20597
20667
  }
20598
20668
  let query = "";
20599
- let collection = "builtin";
20669
+ let collection = deps.initialCollection === "favorites" || collections?.some((row2) => row2.id === deps.initialCollection) ? deps.initialCollection : "builtin";
20600
20670
  let generation = 0;
20601
20671
  let controller = null;
20602
20672
  let busy = false;
@@ -21165,6 +21235,8 @@ function openSearchDialog(deps) {
21165
21235
  host: deps.host,
21166
21236
  label: title,
21167
21237
  className: `qc-search-dialog qc-search-dialog--${mode}`,
21238
+ scrimClassName: "qc-search-dialog-scrim",
21239
+ exitMs: 140,
21168
21240
  // The compare family (compare, and a compare row's change-symbol) shares one role for a host
21169
21241
  // test to find; a symbol search, with or without a chart behind it, is the other.
21170
21242
  role: mode === "compare" || mode === "change-symbol" ? "compare-dialog" : "symbol-search",
@@ -21944,127 +22016,238 @@ var TOGGLE_ROWS = [
21944
22016
  { leaf: "grid", label: "settings.gridLines" },
21945
22017
  { leaf: "sessions", label: "settings.sessionShading" }
21946
22018
  ];
22019
+ var nextSettingsId = 0;
21947
22020
  function mountSettingsMenu(deps) {
21948
22021
  const t = () => deps.i18n.t;
21949
- let menu2 = null;
22022
+ const settingsId = ++nextSettingsId;
22023
+ let dialog = null;
22024
+ let content = null;
22025
+ let activePage = "appearance";
22026
+ let committed = false;
22027
+ let initial = null;
21950
22028
  const controls = [];
21951
22029
  const retireControls = () => {
21952
22030
  for (const control of controls) control.destroy();
21953
22031
  controls.length = 0;
21954
22032
  };
21955
- const trigger = button2({ label: t()("settings.menu"), icon: deps.icons.glyph(ICONS.settings), className: "qc-toolbar-button", onClick: () => toggleMenu(trigger, open2) });
22033
+ const trigger = button2({
22034
+ label: t()("settings.menu"),
22035
+ icon: deps.icons.glyph(ICONS.settings),
22036
+ className: "qc-toolbar-button",
22037
+ onClick: () => {
22038
+ if (dialog) {
22039
+ if (dialog.open()) dialog.close();
22040
+ return;
22041
+ }
22042
+ open2();
22043
+ }
22044
+ });
21956
22045
  trigger.setAttribute("aria-haspopup", "dialog");
21957
22046
  trigger.setAttribute("aria-expanded", "false");
21958
22047
  const apply = (partial) => {
21959
22048
  deps.commands.execute("chart.appearance.apply", { appearance: partial });
21960
22049
  };
22050
+ const refresh = () => {
22051
+ if (!dialog?.open() || !content) return;
22052
+ retireControls();
22053
+ content.replaceChildren();
22054
+ buildContent(content);
22055
+ };
22056
+ const pageDefinitions = () => [
22057
+ { id: "appearance", label: t()("settings.sectionAppearance"), icon: STYLE_ICONS.candles },
22058
+ { id: "display", label: t()("settings.sectionDisplay"), icon: ICONS.template },
22059
+ { id: "scale", label: t()("settings.sectionScale"), icon: ICONS.ruler },
22060
+ ...deps.ui.settingsTheme ? [{ id: "theme", label: t()("settings.sectionTheme"), icon: ICONS.settings }] : []
22061
+ ];
22062
+ const showPage = (page, focus = false) => {
22063
+ activePage = page;
22064
+ refresh();
22065
+ if (focus) content?.querySelector(`[data-settings-page="${page}"]`)?.focus();
22066
+ };
22067
+ const buildAppearancePage = (panel) => {
22068
+ const appearance = activeChart(deps).appearance().appearance;
22069
+ const canApply = deps.commands.available("chart.appearance.apply");
22070
+ panel.appendChild(menuHeading(t()("settings.sectionAppearance")));
22071
+ for (const row2 of COLOR_ROWS) {
22072
+ const label2 = t()(row2.label);
22073
+ const value = appearance[row2.leaf];
22074
+ const control = createColorControl(t(), {
22075
+ label: label2,
22076
+ value,
22077
+ opacity: readColor(value)?.alpha ?? 1,
22078
+ disabled: !canApply,
22079
+ closeOnPick: true,
22080
+ onPick: (color2) => {
22081
+ control.update(color2);
22082
+ apply({ [row2.leaf]: color2 });
22083
+ },
22084
+ openPanel: (anchor, palette, onClosed) => openInlinePanel(anchor, palette, anchor.closest(".qc-settings-row") ?? anchor, onClosed)
22085
+ });
22086
+ controls.push(control);
22087
+ panel.appendChild(fieldRow(label2, control.element, { className: "qc-settings-row" }));
22088
+ }
22089
+ };
22090
+ const buildDisplayPage = (panel) => {
22091
+ const appearance = activeChart(deps).appearance().appearance;
22092
+ const canApply = deps.commands.available("chart.appearance.apply");
22093
+ panel.appendChild(menuHeading(t()("settings.sectionDisplay")));
22094
+ for (const row2 of TOGGLE_ROWS) {
22095
+ panel.appendChild(switchRow({ label: t()(row2.label), checked: appearance[row2.leaf], disabled: !canApply, onChange: (on) => apply({ [row2.leaf]: on }) }));
22096
+ }
22097
+ };
22098
+ const choice = (label2, checked, disabled, onSelect) => {
22099
+ const row2 = h("button", {
22100
+ type: "button",
22101
+ class: "qc-chart-settings-choice",
22102
+ role: "radio",
22103
+ "aria-checked": String(checked),
22104
+ disabled
22105
+ }, h("span", { class: "qc-chart-settings-radio", "aria-hidden": "true" }), h("span", { class: "qc-chart-settings-choice-label" }, label2));
22106
+ row2.addEventListener("click", onSelect);
22107
+ return row2;
22108
+ };
22109
+ const buildScalePage = (panel) => {
22110
+ const scale = activeChart(deps).scaleMode();
22111
+ panel.appendChild(menuHeading(t()("settings.sectionScale")));
22112
+ const group = h("div", { class: "qc-chart-settings-choices", role: "radiogroup", "aria-label": t()("settings.sectionScale") });
22113
+ for (const option of SCALE_MODE_OPTIONS) {
22114
+ const id = `chart.scale.${option.id}`;
22115
+ group.appendChild(choice(commandLabel(deps, id), option.id === scale, option.id !== scale && !deps.commands.available(id), () => {
22116
+ deps.commands.execute(id);
22117
+ refresh();
22118
+ }));
22119
+ }
22120
+ panel.appendChild(group);
22121
+ };
22122
+ const buildThemePage = (panel) => {
22123
+ const mode = deps.widget.theme.mode();
22124
+ panel.appendChild(menuHeading(t()("settings.sectionTheme")));
22125
+ const group = h("div", { class: "qc-chart-settings-choices", role: "radiogroup", "aria-label": t()("settings.sectionTheme") });
22126
+ for (const themeMode of THEME_MODES) {
22127
+ const id = `widget.theme.${themeMode}`;
22128
+ group.appendChild(choice(commandLabel(deps, id), themeMode === mode, themeMode !== mode && !deps.commands.available(id), () => {
22129
+ deps.commands.execute(id);
22130
+ refresh();
22131
+ }));
22132
+ }
22133
+ panel.appendChild(group);
22134
+ };
22135
+ const buildContent = (body) => {
22136
+ const pages = pageDefinitions();
22137
+ if (!pages.some((page) => page.id === activePage)) activePage = "appearance";
22138
+ const panelId = `qc-chart-settings-panel-${settingsId}`;
22139
+ const selectedTabId = `qc-chart-settings-tab-${settingsId}-${activePage}`;
22140
+ const nav = h("div", { class: "qc-chart-settings-nav", role: "tablist", "aria-orientation": "vertical", "aria-label": t()("settings.menu") });
22141
+ const panel = h("section", { id: panelId, class: "qc-chart-settings-panel", role: "tabpanel", "aria-labelledby": selectedTabId, tabindex: "0" });
22142
+ pages.forEach((page, index) => {
22143
+ const selected = page.id === activePage;
22144
+ const tab = h("button", {
22145
+ id: `qc-chart-settings-tab-${settingsId}-${page.id}`,
22146
+ type: "button",
22147
+ class: "qc-chart-settings-nav-item",
22148
+ role: "tab",
22149
+ "aria-selected": String(selected),
22150
+ "aria-controls": panelId,
22151
+ tabindex: selected ? "0" : "-1",
22152
+ "data-settings-page": page.id
22153
+ }, deps.icons.glyph(page.icon, { size: 20 }), h("span", {}, page.label));
22154
+ tab.addEventListener("click", () => showPage(page.id, true));
22155
+ tab.addEventListener("keydown", (event) => {
22156
+ let next = index;
22157
+ if (event.key === "ArrowDown" || event.key === "ArrowRight") next = (index + 1) % pages.length;
22158
+ else if (event.key === "ArrowUp" || event.key === "ArrowLeft") next = (index - 1 + pages.length) % pages.length;
22159
+ else if (event.key === "Home") next = 0;
22160
+ else if (event.key === "End") next = pages.length - 1;
22161
+ else return;
22162
+ event.preventDefault();
22163
+ showPage(pages[next].id, true);
22164
+ });
22165
+ nav.appendChild(tab);
22166
+ });
22167
+ if (activePage === "appearance") buildAppearancePage(panel);
22168
+ else if (activePage === "display") buildDisplayPage(panel);
22169
+ else if (activePage === "scale") buildScalePage(panel);
22170
+ else buildThemePage(panel);
22171
+ body.append(nav, panel);
22172
+ };
22173
+ const restoreInitialState = () => {
22174
+ if (!initial) return;
22175
+ const chart = activeChart(deps);
22176
+ deps.commands.execute("chart.appearance.apply", { appearance: initial.appearance });
22177
+ if (chart.scaleMode() !== initial.scale) deps.commands.execute(`chart.scale.${initial.scale}`);
22178
+ if (deps.ui.settingsTheme && deps.widget.theme.mode() !== initial.theme) deps.commands.execute(`widget.theme.${initial.theme}`);
22179
+ };
21961
22180
  const open2 = () => {
21962
- menu2 = openMenu({
22181
+ const chart = activeChart(deps);
22182
+ activePage = "appearance";
22183
+ committed = false;
22184
+ initial = {
22185
+ appearance: { ...chart.appearance().appearance },
22186
+ scale: chart.scaleMode(),
22187
+ theme: deps.widget.theme.mode()
22188
+ };
22189
+ dialog = openDialog({
21963
22190
  host: deps.overlays,
21964
- anchor: trigger,
21965
22191
  label: t()("settings.menu"),
21966
- role: "dialog",
21967
- className: "qc-settings-menu",
21968
- width: 280,
21969
- align: "end",
21970
- build(body, handle) {
21971
- retireControls();
21972
- const chart = activeChart(deps);
21973
- const appearance = chart.appearance().appearance;
21974
- const canApply = deps.commands.available("chart.appearance.apply");
21975
- body.appendChild(menuHeading(t()("settings.sectionAppearance")));
21976
- for (const row2 of COLOR_ROWS) {
21977
- const label2 = t()(row2.label);
21978
- const value = appearance[row2.leaf];
21979
- const control = createColorControl(t(), {
21980
- label: label2,
21981
- value,
21982
- opacity: readColor(value)?.alpha ?? 1,
21983
- disabled: !canApply,
21984
- closeOnPick: true,
21985
- onPick: (color2) => {
21986
- control.update(color2);
21987
- apply({ [row2.leaf]: color2 });
21988
- },
21989
- openPanel: (anchor, content, onClosed) => openInlinePanel(anchor, content, anchor.closest(".qc-settings-row") ?? anchor, () => {
21990
- onClosed();
21991
- handle.reposition();
21992
- })
21993
- });
21994
- controls.push(control);
21995
- body.appendChild(fieldRow(label2, control.element, { className: "qc-settings-row" }));
21996
- }
21997
- body.appendChild(menuSeparator());
21998
- body.appendChild(menuHeading(t()("settings.sectionDisplay")));
21999
- for (const row2 of TOGGLE_ROWS) {
22000
- body.appendChild(switchRow({ label: t()(row2.label), checked: appearance[row2.leaf], disabled: !canApply, onChange: (on) => apply({ [row2.leaf]: on }) }));
22001
- }
22002
- body.appendChild(menuSeparator());
22003
- body.appendChild(menuHeading(t()("settings.sectionScale")));
22004
- const scale = chart.scaleMode();
22005
- for (const option of SCALE_MODE_OPTIONS) {
22006
- const id = `chart.scale.${option.id}`;
22007
- body.appendChild(
22008
- menuItem({
22009
- text: commandLabel(deps, id),
22010
- role: "menuitemradio",
22011
- checked: option.id === scale,
22012
- disabled: option.id !== scale && !deps.commands.available(id),
22013
- onSelect: () => {
22014
- deps.commands.execute(id);
22015
- handle.refresh();
22016
- }
22017
- })
22018
- );
22019
- }
22020
- body.appendChild(menuSeparator());
22021
- body.appendChild(
22022
- menuItem({
22023
- text: commandLabel(deps, "chart.appearance.reset"),
22024
- disabled: !deps.commands.available("chart.appearance.reset"),
22025
- onSelect: () => {
22026
- deps.commands.execute("chart.appearance.reset");
22027
- handle.refresh();
22192
+ className: "qc-settings-menu qc-chart-settings-dialog",
22193
+ scrimClassName: "qc-chart-settings-scrim",
22194
+ exitMs: 180,
22195
+ width: 750,
22196
+ build(box, handle) {
22197
+ content = h("div", { class: "qc-dialog-body qc-chart-settings-body" });
22198
+ const footer = h("div", { class: "qc-chart-settings-footer" });
22199
+ const reset = button2({
22200
+ label: commandLabel(deps, "chart.appearance.reset"),
22201
+ text: commandLabel(deps, "chart.appearance.reset"),
22202
+ className: "qc-chart-settings-reset",
22203
+ disabled: !deps.commands.available("chart.appearance.reset"),
22204
+ onClick: () => {
22205
+ deps.commands.execute("chart.appearance.reset");
22206
+ refresh();
22207
+ }
22208
+ });
22209
+ const actions2 = h("div", { class: "qc-chart-settings-actions" });
22210
+ actions2.append(
22211
+ button2({ label: t()("drawing.cancel"), text: t()("drawing.cancel"), className: "qc-chart-settings-cancel", onClick: () => handle.close() }),
22212
+ button2({
22213
+ label: t()("drawing.ok"),
22214
+ text: t()("drawing.ok"),
22215
+ className: "qc-button--primary qc-chart-settings-ok",
22216
+ onClick: () => {
22217
+ committed = true;
22218
+ handle.close();
22028
22219
  }
22029
22220
  })
22030
22221
  );
22031
- if (deps.ui.settingsTheme) {
22032
- body.appendChild(menuSeparator());
22033
- body.appendChild(menuHeading(t()("settings.sectionTheme")));
22034
- const mode = deps.widget.theme.mode();
22035
- for (const themeMode of THEME_MODES) {
22036
- const id = `widget.theme.${themeMode}`;
22037
- body.appendChild(
22038
- menuItem({
22039
- text: commandLabel(deps, id),
22040
- role: "menuitemradio",
22041
- checked: themeMode === mode,
22042
- disabled: themeMode !== mode && !deps.commands.available(id),
22043
- onSelect: () => {
22044
- deps.commands.execute(id);
22045
- handle.refresh();
22046
- }
22047
- })
22048
- );
22049
- }
22050
- }
22222
+ footer.append(reset, actions2);
22223
+ box.append(dialogTitle(t()("drawing.settings"), t()("layouts.close"), () => handle.close(), deps.icons), content, footer);
22224
+ buildContent(content);
22225
+ },
22226
+ initialFocus: (box) => box.querySelector('[role="tab"][aria-selected="true"]'),
22227
+ onClosing: () => {
22228
+ trigger.setAttribute("aria-expanded", "false");
22229
+ if (!committed) restoreInitialState();
22051
22230
  },
22052
22231
  onClose: () => {
22053
22232
  retireControls();
22054
- menu2 = null;
22233
+ content = null;
22234
+ dialog = null;
22235
+ initial = null;
22236
+ trigger.setAttribute("aria-expanded", "false");
22055
22237
  }
22056
22238
  });
22239
+ trigger.setAttribute("aria-expanded", "true");
22057
22240
  };
22058
22241
  const sync = () => {
22059
22242
  name(trigger, t()("settings.menu"));
22060
- menu2?.refresh();
22243
+ refresh();
22061
22244
  };
22062
22245
  sync();
22063
22246
  return {
22064
22247
  element: trigger,
22065
22248
  sync,
22066
22249
  destroy() {
22067
- menu2?.close();
22250
+ dialog?.close({ animate: false });
22068
22251
  trigger.remove();
22069
22252
  }
22070
22253
  };
@@ -23049,14 +23232,54 @@ function mountReplayTransport(deps) {
23049
23232
  };
23050
23233
  const offStrings = i18n.onChange(sync);
23051
23234
  sync();
23235
+ let destroyed = false;
23052
23236
  return {
23053
23237
  element: bar,
23238
+ enter() {
23239
+ const view = bar.ownerDocument.defaultView;
23240
+ const reduceMotion = typeof view?.matchMedia === "function" && view.matchMedia("(prefers-reduced-motion: reduce)").matches;
23241
+ if (destroyed || !bar.isConnected) return;
23242
+ if (reduceMotion) {
23243
+ bar.dataset.state = "open";
23244
+ return;
23245
+ }
23246
+ bar.dataset.state = "opening";
23247
+ bar.getBoundingClientRect();
23248
+ bar.dataset.state = "open";
23249
+ },
23054
23250
  sync,
23055
- destroy() {
23251
+ destroy({ animate = false } = {}) {
23252
+ if (destroyed) return;
23253
+ destroyed = true;
23056
23254
  releasePicking();
23057
23255
  for (const handle2 of [...open2]) handle2.close();
23058
23256
  offStrings();
23059
- bar.remove();
23257
+ const view = bar.ownerDocument.defaultView;
23258
+ const reduceMotion = typeof view?.matchMedia === "function" && view.matchMedia("(prefers-reduced-motion: reduce)").matches;
23259
+ if (!animate || reduceMotion || !bar.isConnected || !view) {
23260
+ bar.remove();
23261
+ return;
23262
+ }
23263
+ bar.style.pointerEvents = "none";
23264
+ bar.inert = true;
23265
+ bar.removeAttribute("role");
23266
+ bar.setAttribute("aria-hidden", "true");
23267
+ let timer = null;
23268
+ const remove = () => {
23269
+ if (timer !== null) clearTimeout(timer);
23270
+ bar.remove();
23271
+ };
23272
+ const onTransitionEnd = (event) => {
23273
+ if (event.target === bar) remove();
23274
+ };
23275
+ bar.dataset.state = "closing";
23276
+ const transitionDuration = view.getComputedStyle(bar).transitionDuration;
23277
+ if (!transitionDuration.split(",").some((value) => Number.parseFloat(value) > 0)) {
23278
+ remove();
23279
+ return;
23280
+ }
23281
+ bar.addEventListener("transitionend", onTransitionEnd, { once: true });
23282
+ timer = setTimeout(remove, 240);
23060
23283
  }
23061
23284
  };
23062
23285
  }
@@ -23080,12 +23303,12 @@ function mountChrome(deps) {
23080
23303
  };
23081
23304
  prefetchSearch();
23082
23305
  disposers.push(() => {
23083
- searchDialog?.close();
23306
+ searchDialog?.close({ animate: false });
23084
23307
  searchOwner.dispose();
23085
23308
  deps.doors.openSearch = () => void 0;
23086
23309
  });
23087
23310
  disposers.push(i18n.onChange(() => {
23088
- searchDialog?.close();
23311
+ searchDialog?.close({ animate: false });
23089
23312
  searchOwner.dispose();
23090
23313
  searchOwner = createSearchSessionOwner(deps.datafeed, { pageSize: 50 });
23091
23314
  prefetchSearch();
@@ -23101,8 +23324,8 @@ function mountChrome(deps) {
23101
23324
  let replayOwner = null;
23102
23325
  let replayBar = null;
23103
23326
  const replayWasUp = /* @__PURE__ */ new WeakMap();
23104
- const releaseReplayRow = () => {
23105
- replayBar?.destroy();
23327
+ const releaseReplayRow = (animate = true) => {
23328
+ replayBar?.destroy({ animate });
23106
23329
  replayBar = null;
23107
23330
  replayOwner = null;
23108
23331
  };
@@ -23119,18 +23342,19 @@ function mountChrome(deps) {
23119
23342
  replayOwner = target;
23120
23343
  replayBar = mountReplayTransport({ chrome: overlays, i18n, icons: deps.icons, commands: target.commands, handle: target.chart, bars: target.bars, intraday: target.intraday });
23121
23344
  deps.panes.after(replayBar.element);
23345
+ replayBar.enter();
23122
23346
  }
23123
23347
  replayBar?.sync();
23124
23348
  };
23125
23349
  disposers.push(() => {
23126
23350
  deps.doors.replayChanged = () => void 0;
23127
- releaseReplayRow();
23351
+ releaseReplayRow(false);
23128
23352
  });
23129
23353
  deps.doors.notify = notify;
23130
23354
  let indicatorDialog = null;
23131
- deps.doors.showIndicatorPicker = () => {
23355
+ deps.doors.showIndicatorPicker = (initialCollection) => {
23132
23356
  if (!ui.indicatorPicker || indicatorDialog?.open()) return;
23133
- indicatorDialog = openIndicatorPicker({ ...ctx, access: deps.access, storage: deps.storage, indicatorPicker: deps.indicatorPicker });
23357
+ indicatorDialog = openIndicatorPicker({ ...ctx, access: deps.access, storage: deps.storage, indicatorPicker: deps.indicatorPicker, initialCollection });
23134
23358
  };
23135
23359
  disposers.push(() => {
23136
23360
  indicatorDialog?.close();
@@ -23140,7 +23364,7 @@ function mountChrome(deps) {
23140
23364
  if (disposed) return;
23141
23365
  if (request.mode !== "compare" && !ui.symbolSearch && !request.onPick) return;
23142
23366
  if (request.mode === "compare" && !features.compare) return;
23143
- searchDialog?.close();
23367
+ searchDialog?.close({ animate: false });
23144
23368
  searchDialog = openSearchDialog({
23145
23369
  host: overlays,
23146
23370
  i18n,
@@ -23743,6 +23967,9 @@ function createChart(options) {
23743
23967
  // Read on demand, because the chrome mounts below this object: a host asks for the slot when it
23744
23968
  // has a door to put there, which is always after the widget exists.
23745
23969
  chrome: {
23970
+ legendRows: (chartId, rows2) => {
23971
+ if (!disposed) instances.get(chartId)?.setLegendRows(rows2);
23972
+ },
23746
23973
  topBar: (slot) => chrome2.topBarSlot(slot),
23747
23974
  toolbarButton: (options2) => chrome2.toolbarButton(options2),
23748
23975
  iconDiagnostics: () => iconDiagnostics.list()
@@ -23862,7 +24089,6 @@ function openSymbolSearch(options) {
23862
24089
  return {
23863
24090
  close() {
23864
24091
  dialog.close();
23865
- teardown();
23866
24092
  }
23867
24093
  };
23868
24094
  }