@tradecanvas/chart 1.71.0 → 1.72.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (177) hide show
  1. package/dist/LayoutSession-DO4bYkUi.cjs +2 -0
  2. package/dist/LayoutSession-DO4bYkUi.cjs.map +1 -0
  3. package/dist/{LayoutSession-BOQCN6yY.js → LayoutSession-qRYG8GTD.js} +539 -552
  4. package/dist/LayoutSession-qRYG8GTD.js.map +1 -0
  5. package/dist/WidgetCommandPalette-CeCLHPy1.js +177 -0
  6. package/dist/WidgetCommandPalette-CeCLHPy1.js.map +1 -0
  7. package/dist/WidgetCommandPalette-ZZNbWwCZ.cjs +2 -0
  8. package/dist/WidgetCommandPalette-ZZNbWwCZ.cjs.map +1 -0
  9. package/dist/WidgetDrawingSettings-BeW8fX7r.js +474 -0
  10. package/dist/WidgetDrawingSettings-BeW8fX7r.js.map +1 -0
  11. package/dist/WidgetDrawingSettings-Cyl2u_u8.cjs +2 -0
  12. package/dist/WidgetDrawingSettings-Cyl2u_u8.cjs.map +1 -0
  13. package/dist/WidgetGoToDate-BNdngHlg.cjs +2 -0
  14. package/dist/WidgetGoToDate-BNdngHlg.cjs.map +1 -0
  15. package/dist/WidgetGoToDate-hcDtezRm.js +191 -0
  16. package/dist/WidgetGoToDate-hcDtezRm.js.map +1 -0
  17. package/dist/WidgetHotkeySheet-1lRFSdvt.js +416 -0
  18. package/dist/WidgetHotkeySheet-1lRFSdvt.js.map +1 -0
  19. package/dist/WidgetHotkeySheet-y5LWKSG6.cjs +2 -0
  20. package/dist/WidgetHotkeySheet-y5LWKSG6.cjs.map +1 -0
  21. package/dist/WidgetIndicatorPicker-CGvZ5kYd.js +245 -0
  22. package/dist/WidgetIndicatorPicker-CGvZ5kYd.js.map +1 -0
  23. package/dist/WidgetIndicatorPicker-DuKJFPRC.cjs +2 -0
  24. package/dist/WidgetIndicatorPicker-DuKJFPRC.cjs.map +1 -0
  25. package/dist/WidgetIndicatorSettings-C7vpco6e.cjs +2 -0
  26. package/dist/WidgetIndicatorSettings-C7vpco6e.cjs.map +1 -0
  27. package/dist/WidgetIndicatorSettings-dDkqzzYo.js +326 -0
  28. package/dist/WidgetIndicatorSettings-dDkqzzYo.js.map +1 -0
  29. package/dist/WidgetSettings-D7Pq42zW.cjs +2 -0
  30. package/dist/WidgetSettings-D7Pq42zW.cjs.map +1 -0
  31. package/dist/WidgetSettings-eOo93L7Z.js +546 -0
  32. package/dist/WidgetSettings-eOo93L7Z.js.map +1 -0
  33. package/dist/WidgetSymbolSearch-C4OR42nZ.cjs +2 -0
  34. package/dist/WidgetSymbolSearch-C4OR42nZ.cjs.map +1 -0
  35. package/dist/WidgetSymbolSearch-CvPpjIwa.js +580 -0
  36. package/dist/WidgetSymbolSearch-CvPpjIwa.js.map +1 -0
  37. package/dist/focusTrap-Br9Zfje0.js +27 -0
  38. package/dist/focusTrap-Br9Zfje0.js.map +1 -0
  39. package/dist/focusTrap-DrhhmG0V.cjs +2 -0
  40. package/dist/focusTrap-DrhhmG0V.cjs.map +1 -0
  41. package/dist/i18n-C2nQBZTY.cjs +2 -0
  42. package/dist/i18n-C2nQBZTY.cjs.map +1 -0
  43. package/dist/{i18n-DUmd4Z_r.js → i18n-C5v6Tzp1.js} +3 -1
  44. package/dist/i18n-C5v6Tzp1.js.map +1 -0
  45. package/dist/index.cjs +1 -1
  46. package/dist/index.d.ts +1 -1
  47. package/dist/index.d.ts.map +1 -1
  48. package/dist/index.js +1 -1
  49. package/dist/indicatorInputWords-Bd9Pt6qp.js +25 -0
  50. package/dist/indicatorInputWords-Bd9Pt6qp.js.map +1 -0
  51. package/dist/indicatorInputWords-CTA4IcJR.cjs +2 -0
  52. package/dist/indicatorInputWords-CTA4IcJR.cjs.map +1 -0
  53. package/dist/indicatorLabel-BNZvaOcl.cjs +2 -0
  54. package/dist/indicatorLabel-BNZvaOcl.cjs.map +1 -0
  55. package/dist/indicatorLabel-Sbegyhp2.js +17 -0
  56. package/dist/indicatorLabel-Sbegyhp2.js.map +1 -0
  57. package/dist/indicatorSettingsTabs-B0liE3ST.cjs +2 -0
  58. package/dist/indicatorSettingsTabs-B0liE3ST.cjs.map +1 -0
  59. package/dist/indicatorSettingsTabs-XcgMdbLp.js +161 -0
  60. package/dist/indicatorSettingsTabs-XcgMdbLp.js.map +1 -0
  61. package/dist/monthView-B-CieFDJ.cjs +2 -0
  62. package/dist/monthView-B-CieFDJ.cjs.map +1 -0
  63. package/dist/monthView-N8yDaTqZ.js +198 -0
  64. package/dist/monthView-N8yDaTqZ.js.map +1 -0
  65. package/dist/platformKeys-B82-j1Fs.cjs +2 -0
  66. package/dist/platformKeys-B82-j1Fs.cjs.map +1 -0
  67. package/dist/platformKeys-Dd1qOCjF.js +5 -0
  68. package/dist/platformKeys-Dd1qOCjF.js.map +1 -0
  69. package/dist/settingsControls-DIjzCFHI.js +379 -0
  70. package/dist/settingsControls-DIjzCFHI.js.map +1 -0
  71. package/dist/settingsControls-Tn8_vJJv.cjs +2 -0
  72. package/dist/settingsControls-Tn8_vJJv.cjs.map +1 -0
  73. package/dist/symbolCatalog-CA1fpz3s.js +90 -0
  74. package/dist/symbolCatalog-CA1fpz3s.js.map +1 -0
  75. package/dist/symbolCatalog-Dx1YnZgm.cjs +2 -0
  76. package/dist/symbolCatalog-Dx1YnZgm.cjs.map +1 -0
  77. package/dist/widget/ChartWidget.d.ts +102 -582
  78. package/dist/widget/ChartWidget.d.ts.map +1 -1
  79. package/dist/widget/WidgetAlerts.d.ts +35 -0
  80. package/dist/widget/WidgetAlerts.d.ts.map +1 -0
  81. package/dist/widget/WidgetChartSettings.d.ts +194 -0
  82. package/dist/widget/WidgetChartSettings.d.ts.map +1 -0
  83. package/dist/widget/WidgetCommands.d.ts +54 -0
  84. package/dist/widget/WidgetCommands.d.ts.map +1 -0
  85. package/dist/widget/WidgetCompares.d.ts +140 -0
  86. package/dist/widget/WidgetCompares.d.ts.map +1 -0
  87. package/dist/widget/WidgetDockArea.d.ts +89 -0
  88. package/dist/widget/WidgetDockArea.d.ts.map +1 -0
  89. package/dist/widget/WidgetDrawingEdit.d.ts +73 -0
  90. package/dist/widget/WidgetDrawingEdit.d.ts.map +1 -0
  91. package/dist/widget/WidgetGoToDate.d.ts +1 -14
  92. package/dist/widget/WidgetGoToDate.d.ts.map +1 -1
  93. package/dist/widget/WidgetIndicatorSettings.d.ts.map +1 -1
  94. package/dist/widget/WidgetIntervals.d.ts +66 -0
  95. package/dist/widget/WidgetIntervals.d.ts.map +1 -0
  96. package/dist/widget/WidgetLayouts.d.ts +85 -0
  97. package/dist/widget/WidgetLayouts.d.ts.map +1 -0
  98. package/dist/widget/WidgetLegendView.d.ts +54 -0
  99. package/dist/widget/WidgetLegendView.d.ts.map +1 -0
  100. package/dist/widget/WidgetMenus.d.ts +79 -0
  101. package/dist/widget/WidgetMenus.d.ts.map +1 -0
  102. package/dist/widget/WidgetReplay.d.ts +199 -0
  103. package/dist/widget/WidgetReplay.d.ts.map +1 -0
  104. package/dist/widget/WidgetTrading.d.ts +48 -0
  105. package/dist/widget/WidgetTrading.d.ts.map +1 -0
  106. package/dist/widget/WidgetWatchlists.d.ts +77 -0
  107. package/dist/widget/WidgetWatchlists.d.ts.map +1 -0
  108. package/dist/widget/drawingSettingsTabs.d.ts +2 -19
  109. package/dist/widget/drawingSettingsTabs.d.ts.map +1 -1
  110. package/dist/widget/drawingText.d.ts +24 -0
  111. package/dist/widget/drawingText.d.ts.map +1 -0
  112. package/dist/widget/index.cjs +22 -23
  113. package/dist/widget/index.cjs.map +1 -1
  114. package/dist/widget/index.js +6193 -9868
  115. package/dist/widget/index.js.map +1 -1
  116. package/dist/widget/keyTarget.d.ts +4 -0
  117. package/dist/widget/keyTarget.d.ts.map +1 -1
  118. package/dist/widget/lazyPart.d.ts +47 -0
  119. package/dist/widget/lazyPart.d.ts.map +1 -0
  120. package/dist/widget/locales/ar.d.ts.map +1 -1
  121. package/dist/widget/locales/cs.d.ts.map +1 -1
  122. package/dist/widget/locales/da.d.ts.map +1 -1
  123. package/dist/widget/locales/de.d.ts.map +1 -1
  124. package/dist/widget/locales/el.d.ts.map +1 -1
  125. package/dist/widget/locales/en.d.ts +1 -0
  126. package/dist/widget/locales/en.d.ts.map +1 -1
  127. package/dist/widget/locales/es.d.ts.map +1 -1
  128. package/dist/widget/locales/et.d.ts.map +1 -1
  129. package/dist/widget/locales/fa.d.ts.map +1 -1
  130. package/dist/widget/locales/fr.d.ts.map +1 -1
  131. package/dist/widget/locales/he.d.ts.map +1 -1
  132. package/dist/widget/locales/hu.d.ts.map +1 -1
  133. package/dist/widget/locales/id.d.ts.map +1 -1
  134. package/dist/widget/locales/index.cjs +1 -1
  135. package/dist/widget/locales/index.cjs.map +1 -1
  136. package/dist/widget/locales/index.js +29 -1
  137. package/dist/widget/locales/index.js.map +1 -1
  138. package/dist/widget/locales/it.d.ts.map +1 -1
  139. package/dist/widget/locales/ja.d.ts.map +1 -1
  140. package/dist/widget/locales/ko.d.ts.map +1 -1
  141. package/dist/widget/locales/ms.d.ts.map +1 -1
  142. package/dist/widget/locales/nb.d.ts.map +1 -1
  143. package/dist/widget/locales/nl.d.ts.map +1 -1
  144. package/dist/widget/locales/pl.d.ts.map +1 -1
  145. package/dist/widget/locales/pt.d.ts.map +1 -1
  146. package/dist/widget/locales/ro.d.ts.map +1 -1
  147. package/dist/widget/locales/ru.d.ts.map +1 -1
  148. package/dist/widget/locales/sk.d.ts.map +1 -1
  149. package/dist/widget/locales/sv.d.ts.map +1 -1
  150. package/dist/widget/locales/th.d.ts.map +1 -1
  151. package/dist/widget/locales/tr.d.ts.map +1 -1
  152. package/dist/widget/locales/vi.d.ts.map +1 -1
  153. package/dist/widget/locales/zh.d.ts.map +1 -1
  154. package/dist/widget/locales/zhHant.d.ts.map +1 -1
  155. package/dist/widget/pageEnv.d.ts +5 -0
  156. package/dist/widget/pageEnv.d.ts.map +1 -0
  157. package/dist/widget/signals.d.ts +41 -0
  158. package/dist/widget/signals.d.ts.map +1 -0
  159. package/dist/widget/wallTime.d.ts +15 -0
  160. package/dist/widget/wallTime.d.ts.map +1 -0
  161. package/dist/widget/widgetDialogs.d.ts +17 -0
  162. package/dist/widget/widgetDialogs.d.ts.map +1 -0
  163. package/dist/widgetFeatures-BM6u0u74.js +186 -0
  164. package/dist/widgetFeatures-BM6u0u74.js.map +1 -0
  165. package/dist/widgetFeatures-BZ8OVPN2.cjs +3 -0
  166. package/dist/widgetFeatures-BZ8OVPN2.cjs.map +1 -0
  167. package/dist/widgetLocales-BPK5i0Ok.cjs +2 -0
  168. package/dist/widgetLocales-BPK5i0Ok.cjs.map +1 -0
  169. package/dist/widgetLocales-DgTR8JSu.js +838 -0
  170. package/dist/widgetLocales-DgTR8JSu.js.map +1 -0
  171. package/package.json +3 -3
  172. package/dist/LayoutSession-BOQCN6yY.js.map +0 -1
  173. package/dist/LayoutSession-RF8OqOXy.cjs +0 -2
  174. package/dist/LayoutSession-RF8OqOXy.cjs.map +0 -1
  175. package/dist/i18n-BsstZ9md.cjs +0 -2
  176. package/dist/i18n-BsstZ9md.cjs.map +0 -1
  177. package/dist/i18n-DUmd4Z_r.js.map +0 -1
@@ -0,0 +1,245 @@
1
+ import { o as e } from "./widgetFeatures-BM6u0u74.js";
2
+ import { i as t, n } from "./focusTrap-Br9Zfje0.js";
3
+ import { r } from "./i18n-C5v6Tzp1.js";
4
+ import { INDICATOR_CATEGORIES as i } from "@tradecanvas/core";
5
+ //#region src/widget/indicatorSearch.ts
6
+ function a(e) {
7
+ return e.normalize("NFD").replace(/[̀-ͯ]/g, "").toLowerCase();
8
+ }
9
+ var o = /[\s/()%-]+/;
10
+ function s(e, t) {
11
+ let n = a(e.shortName), r = a(e.name);
12
+ return n === t || a(e.id) === t ? 0 : n.startsWith(t) || r.startsWith(t) ? 1 : [...r.split(o), ...n.split(o)].some((e) => e.startsWith(t)) ? 2 : r.includes(t) || n.includes(t) ? 3 : null;
13
+ }
14
+ function c(e, t) {
15
+ let n = a(t.trim());
16
+ return n ? e.map((e) => ({
17
+ item: e,
18
+ rank: s(e, n)
19
+ })).filter((e) => e.rank !== null).sort((e, t) => e.rank - t.rank || e.item.name.localeCompare(t.item.name)).map((e) => e.item) : [];
20
+ }
21
+ //#endregion
22
+ //#region src/widget/WidgetIndicatorPicker.ts
23
+ var l = 1, u = (e, t) => e.name.localeCompare(t.name), d = class {
24
+ backdrop = null;
25
+ listEl = null;
26
+ navEl = null;
27
+ searchEl = null;
28
+ liveEl = null;
29
+ content = {
30
+ items: [],
31
+ favorites: [],
32
+ templates: []
33
+ };
34
+ favorites = /* @__PURE__ */ new Set();
35
+ counts = /* @__PURE__ */ new Map();
36
+ view = "all";
37
+ lastView = null;
38
+ query = "";
39
+ returnFocus = null;
40
+ uid = l++;
41
+ constructor(e, t, n) {
42
+ this.layer = e, this.callbacks = t, this.t = n;
43
+ }
44
+ open(e) {
45
+ this.backdrop && this.close(!1), this.returnFocus = document.activeElement instanceof HTMLElement ? document.activeElement : null, this.content = e, this.favorites = new Set(e.favorites), this.counts = e.counts ?? /* @__PURE__ */ new Map(), this.query = "", this.view = this.startView(), this.build(), this.searchEl?.focus();
46
+ }
47
+ close(e = !0) {
48
+ this.backdrop && (this.backdrop.remove(), this.backdrop = this.listEl = this.navEl = this.searchEl = this.liveEl = null, this.callbacks.onClose?.(), e && this.returnFocus?.isConnected && this.returnFocus.focus(), this.returnFocus = null);
49
+ }
50
+ isOpen() {
51
+ return this.backdrop !== null;
52
+ }
53
+ setTemplates(e) {
54
+ if (this.content = {
55
+ ...this.content,
56
+ templates: [...e]
57
+ }, !this.listEl || this.view !== "templates" || this.query) return;
58
+ let t = this.listEl.contains(document.activeElement);
59
+ this.renderList(), t && !this.listEl.contains(document.activeElement) && this.listEl.querySelector("[data-tpl-save]")?.focus();
60
+ }
61
+ setCounts(e) {
62
+ this.counts = e, this.listEl?.querySelectorAll(".tcw-ip-row[data-ind]").forEach((e) => this.markCount(e));
63
+ }
64
+ destroy() {
65
+ this.close(!1);
66
+ }
67
+ startView() {
68
+ let e = this.views();
69
+ return this.lastView && e.includes(this.lastView) && !(this.lastView === "favorites" && this.favorites.size === 0) ? this.lastView : this.favorites.size > 0 ? "favorites" : "all";
70
+ }
71
+ views() {
72
+ let e = new Set(this.content.items.map((e) => e.category));
73
+ return [
74
+ "favorites",
75
+ ...this.callbacks.templates && this.content.templates ? ["templates"] : [],
76
+ "all",
77
+ ...i.filter((t) => e.has(t))
78
+ ];
79
+ }
80
+ viewLabel(e) {
81
+ return e === "favorites" ? this.t("indicatorPicker.favorites") : e === "templates" ? this.t("toolbar.indicators.templates") : e === "all" ? this.t("toolbar.indicators.all") : this.t(`indicatorCategory.${e}`);
82
+ }
83
+ build() {
84
+ let e = document.createElement("div");
85
+ e.className = "tcw-modal-backdrop", e.addEventListener("click", (t) => {
86
+ t.target === e && this.close();
87
+ });
88
+ let t = document.createElement("div");
89
+ t.className = "tcw-modal tcw-ip", t.setAttribute("role", "dialog"), t.setAttribute("aria-modal", "true"), t.setAttribute("aria-labelledby", `tcw-ip-title-${this.uid}`), t.tabIndex = -1, t.addEventListener("keydown", (e) => this.onKeyDown(e, t));
90
+ let n = document.createElement("div");
91
+ n.className = "tcw-visually-hidden", n.setAttribute("aria-live", "polite"), t.append(this.buildHeader(), this.buildSearch(), this.buildBody(), n), e.appendChild(t), this.layer().appendChild(e), this.backdrop = e, this.liveEl = n, this.renderNav(), this.renderList();
92
+ }
93
+ buildHeader() {
94
+ let e = document.createElement("div");
95
+ e.className = "tcw-modal-header";
96
+ let n = document.createElement("h3");
97
+ n.id = `tcw-ip-title-${this.uid}`, n.textContent = this.t("toolbar.indicators");
98
+ let r = document.createElement("button");
99
+ return r.type = "button", r.className = "tcw-modal-close", r.setAttribute("aria-label", this.t("indicatorSettings.close")), r.innerHTML = t("x", 16), r.addEventListener("click", () => this.close()), e.append(n, r), e;
100
+ }
101
+ buildSearch() {
102
+ let e = document.createElement("label");
103
+ e.className = "tcw-ip-search";
104
+ let n = document.createElement("input");
105
+ return n.type = "search", n.id = `tcw-ip-search-${this.uid}`, n.placeholder = this.t("indicatorPicker.search"), n.setAttribute("aria-label", this.t("indicatorPicker.search")), n.autocomplete = "off", n.spellcheck = !1, n.addEventListener("input", () => {
106
+ this.query = n.value, this.renderList();
107
+ }), n.addEventListener("keydown", (e) => this.onSearchKey(e)), e.innerHTML = t("search", 16), e.appendChild(n), this.searchEl = n, e;
108
+ }
109
+ buildBody() {
110
+ let e = document.createElement("div");
111
+ e.className = "tcw-ip-body";
112
+ let t = document.createElement("div");
113
+ t.className = "tcw-ip-nav", t.setAttribute("role", "tablist"), t.setAttribute("aria-orientation", "vertical"), t.setAttribute("aria-label", this.t("indicatorPicker.groups")), t.addEventListener("keydown", (e) => this.onNavKey(e));
114
+ let n = document.createElement("div");
115
+ return n.className = "tcw-ip-list", n.id = `tcw-ip-list-${this.uid}`, n.setAttribute("role", "tabpanel"), n.addEventListener("keydown", (e) => this.onListKey(e)), n.addEventListener("focusin", (e) => this.rove(e.target)), e.append(t, n), this.navEl = t, this.listEl = n, e;
116
+ }
117
+ renderNav() {
118
+ let n = this.navEl;
119
+ n && (n.replaceChildren(...this.views().map((n) => {
120
+ let r = document.createElement("button");
121
+ return r.type = "button", r.className = "tcw-ip-tab", r.id = `tcw-ip-tab-${this.uid}-${n}`, r.dataset.view = n, r.setAttribute("role", "tab"), r.setAttribute("aria-controls", `tcw-ip-list-${this.uid}`), r.innerHTML = `${n === "favorites" ? t("star", 14) : n === "templates" ? t("layers", 14) : ""}<span></span>`, r.querySelector("span").textContent = this.viewLabel(n), n === "templates" && e(r, "indicatorTemplates"), r.addEventListener("click", () => this.select(n)), r;
122
+ })), this.markNav());
123
+ }
124
+ markNav() {
125
+ this.navEl?.querySelectorAll(".tcw-ip-tab").forEach((e) => {
126
+ let t = e.dataset.view === this.view;
127
+ e.setAttribute("aria-selected", String(t)), e.classList.toggle("tcw-active", t), e.tabIndex = t ? 0 : -1;
128
+ }), this.labelList();
129
+ }
130
+ labelList() {
131
+ let e = this.query.trim() !== "";
132
+ this.listEl?.setAttribute("aria-labelledby", e ? `tcw-ip-search-${this.uid}` : `tcw-ip-tab-${this.uid}-${this.view}`);
133
+ }
134
+ select(e, t = !1) {
135
+ this.view = this.lastView = e, this.searchEl && this.query && (this.searchEl.value = this.query = ""), this.markNav(), this.renderList(), t && this.navEl?.querySelector(`[data-view="${e}"]`)?.focus();
136
+ }
137
+ renderList() {
138
+ let e = this.listEl;
139
+ if (!e) return;
140
+ e.replaceChildren();
141
+ let t = this.content.items;
142
+ if (this.navEl?.classList.toggle("tcw-ip-nav--searching", this.query.trim() !== ""), this.labelList(), this.query.trim()) {
143
+ let n = c(t, this.query);
144
+ n.length === 0 ? e.appendChild(this.note(r(this.t("indicatorPicker.noMatch"), { query: this.query.trim() }))) : e.append(...n.map((e) => this.row(e)));
145
+ } else if (this.view === "templates") this.renderTemplates(e);
146
+ else if (this.view === "favorites") {
147
+ let n = t.filter((e) => this.favorites.has(e.id)).sort(u);
148
+ n.length === 0 ? e.appendChild(this.note(this.t("indicatorPicker.noFavorites"))) : e.append(...n.map((e) => this.row(e)));
149
+ } else if (this.view === "all") for (let n of i) {
150
+ let r = t.filter((e) => e.category === n).sort(u);
151
+ if (r.length === 0) continue;
152
+ let i = document.createElement("div");
153
+ i.className = "tcw-ip-heading", i.textContent = this.viewLabel(n), e.append(i, ...r.map((e) => this.row(e)));
154
+ }
155
+ else e.append(...t.filter((e) => e.category === this.view).sort(u).map((e) => this.row(e)));
156
+ e.querySelector(".tcw-ip-add")?.setAttribute("tabindex", "0");
157
+ }
158
+ row(e) {
159
+ let n = document.createElement("div");
160
+ n.className = "tcw-ip-row", n.dataset.ind = e.id;
161
+ let r = document.createElement("button");
162
+ r.type = "button", r.className = "tcw-ip-add", r.dataset.ind = e.id, r.tabIndex = -1;
163
+ let i = document.createElement("span");
164
+ i.className = "tcw-ip-name", i.textContent = e.name, r.appendChild(i), e.shortName && e.shortName !== e.name && r.appendChild(this.tag("tcw-ip-short", e.shortName)), r.appendChild(this.tag("tcw-ip-kind", this.t(`indicatorType.${e.placement}`))), r.addEventListener("click", () => this.add(e));
165
+ let a = document.createElement("button");
166
+ return a.type = "button", a.className = "tcw-ip-aside tcw-ip-star", a.tabIndex = -1, a.title = this.t("indicatorPicker.favorite"), a.setAttribute("aria-label", `${this.t("indicatorPicker.favorite")}: ${e.name}`), a.setAttribute("aria-pressed", String(this.favorites.has(e.id))), a.innerHTML = t("star", 14), a.addEventListener("click", () => this.toggleFavorite(e.id, a)), n.append(r, a), this.markCount(n), n;
167
+ }
168
+ renderTemplates(e) {
169
+ let n = document.createElement("button");
170
+ n.type = "button", n.className = "tcw-ip-add tcw-ip-save", n.tabIndex = -1, n.dataset.tplSave = "", n.innerHTML = `${t("save", 14)}<span class="tcw-ip-name"></span>`, n.querySelector("span").textContent = this.t("templates.save"), n.addEventListener("click", () => {
171
+ this.close(), this.callbacks.templates?.onSave();
172
+ }), e.appendChild(n);
173
+ let i = this.content.templates ?? [];
174
+ i.length === 0 && e.appendChild(this.note(this.t("indicatorPicker.noTemplates")));
175
+ for (let n of i) {
176
+ let i = document.createElement("div");
177
+ i.className = "tcw-ip-row";
178
+ let a = document.createElement("button");
179
+ a.type = "button", a.className = "tcw-ip-add", a.tabIndex = -1, a.dataset.tplApply = n, a.innerHTML = `${t("layers", 14)}<span class="tcw-ip-name"></span>`, a.querySelector("span").textContent = n, a.addEventListener("click", () => {
180
+ this.close(), this.callbacks.templates?.onApply(n);
181
+ });
182
+ let o = document.createElement("button");
183
+ o.type = "button", o.className = "tcw-ip-aside tcw-ip-remove", o.tabIndex = -1, o.dataset.tplDelete = n;
184
+ let s = r(this.t("templates.delete"), { name: n });
185
+ o.title = s, o.setAttribute("aria-label", s), o.innerHTML = t("x", 12), o.addEventListener("click", () => this.callbacks.templates?.onDelete(n)), i.append(a, o), e.appendChild(i);
186
+ }
187
+ }
188
+ add(e) {
189
+ if (this.callbacks.onAdd(e.id) === !1 || !this.liveEl) return;
190
+ let t = r(this.t("indicatorPicker.added"), { name: e.name });
191
+ this.liveEl.textContent = this.liveEl.textContent === t ? `${t}\u00a0` : t;
192
+ }
193
+ toggleFavorite(e, t) {
194
+ let n = !this.favorites.has(e);
195
+ if (n ? this.favorites.add(e) : this.favorites.delete(e), this.callbacks.onFavorite(e, n), t.setAttribute("aria-pressed", String(n)), this.view !== "favorites" || this.query || n) return;
196
+ let r = [...this.listEl?.querySelectorAll(".tcw-ip-star") ?? []].indexOf(t), i = document.activeElement === t;
197
+ if (this.renderList(), !i) return;
198
+ let a = [...this.listEl?.querySelectorAll(".tcw-ip-star") ?? []];
199
+ (a[r] ?? a[a.length - 1] ?? this.searchEl)?.focus();
200
+ }
201
+ markCount(e) {
202
+ let t = this.counts.get(e.dataset.ind ?? "") ?? 0, n = e.querySelector(".tcw-ip-add"), i = e.querySelector(".tcw-ip-count");
203
+ if (t === 0) {
204
+ i?.remove();
205
+ return;
206
+ }
207
+ i || (i = this.tag("tcw-ip-count", ""), n?.appendChild(i)), i.textContent = String(t), i.title = r(this.t("indicatorPicker.onChart"), { count: String(t) });
208
+ }
209
+ tag(e, t) {
210
+ let n = document.createElement("span");
211
+ return n.className = e, n.textContent = t, n;
212
+ }
213
+ note(e) {
214
+ let t = document.createElement("div");
215
+ return t.className = "tcw-ip-empty", t.textContent = e, t;
216
+ }
217
+ rove(e) {
218
+ !(e instanceof HTMLElement) || !e.classList.contains("tcw-ip-add") || (this.listEl?.querySelectorAll(".tcw-ip-add[tabindex=\"0\"]").forEach((t) => {
219
+ t !== e && (t.tabIndex = -1);
220
+ }), e.tabIndex = 0);
221
+ }
222
+ onKeyDown(e, t) {
223
+ if (e.key === "Escape") {
224
+ e.preventDefault(), e.stopPropagation(), this.close();
225
+ return;
226
+ }
227
+ n(e, t);
228
+ }
229
+ onSearchKey(e) {
230
+ let t = this.listEl?.querySelector(".tcw-ip-add");
231
+ e.key === "ArrowDown" && t ? (e.preventDefault(), t.focus()) : e.key === "Enter" && this.query.trim() && t && (e.preventDefault(), t.click());
232
+ }
233
+ onNavKey(e) {
234
+ let t = this.views(), n = t.indexOf(this.view), r = e.key === "ArrowDown" ? Math.min(t.length - 1, n + 1) : e.key === "ArrowUp" ? Math.max(0, n - 1) : e.key === "Home" ? 0 : e.key === "End" ? t.length - 1 : -1;
235
+ r < 0 || (e.preventDefault(), this.select(t[r], !0));
236
+ }
237
+ onListKey(e) {
238
+ let t = e.target, n = t.closest(".tcw-ip-row, .tcw-ip-save"), r = [...this.listEl?.querySelectorAll(".tcw-ip-add") ?? []], i = n?.classList.contains("tcw-ip-save") ? n : n?.querySelector(".tcw-ip-add"), a = i ? r.indexOf(i) : -1, o = null;
239
+ e.key === "ArrowDown" ? o = r[a + 1] : e.key === "ArrowUp" ? o = a > 0 ? r[a - 1] : this.searchEl : e.key === "Home" ? o = r[0] : e.key === "End" ? o = r[r.length - 1] : e.key === "ArrowRight" && t === i ? o = n?.querySelector(".tcw-ip-aside") : e.key === "ArrowLeft" && t !== i && (o = i), o && (e.preventDefault(), o.focus());
240
+ }
241
+ };
242
+ //#endregion
243
+ export { d as WidgetIndicatorPicker };
244
+
245
+ //# sourceMappingURL=WidgetIndicatorPicker-CGvZ5kYd.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"WidgetIndicatorPicker-CGvZ5kYd.js","names":[],"sources":["../src/widget/indicatorSearch.ts","../src/widget/WidgetIndicatorPicker.ts"],"sourcesContent":["/** What a search reads of an indicator. */\nexport interface SearchableIndicator {\n id: string;\n name: string;\n shortName: string;\n}\n\n/** Lower case without accents, so \"mômentum\" finds Momentum. */\nfunction fold(text: string): string {\n return text.normalize('NFD').replace(/[̀-ͯ]/g, '').toLowerCase();\n}\n\nconst WORD_BREAK = /[\\s/()%-]+/;\n\n/** How well an indicator matches `q` (folded): 0 best, null not at all. */\nfunction score(item: SearchableIndicator, q: string): number | null {\n const short = fold(item.shortName);\n const name = fold(item.name);\n if (short === q || fold(item.id) === q) return 0;\n if (short.startsWith(q) || name.startsWith(q)) return 1;\n if ([...name.split(WORD_BREAK), ...short.split(WORD_BREAK)].some((word) => word.startsWith(q))) return 2;\n if (name.includes(q) || short.includes(q)) return 3;\n return null;\n}\n\n/**\n * The indicators matching `query`, best first: the short name typed in full\n * (\"rsi\"), then names starting with it, then a word starting with it, then\n * any containing it. Ties go by name. Nothing typed finds nothing.\n */\nexport function searchIndicators<T extends SearchableIndicator>(items: readonly T[], query: string): T[] {\n const q = fold(query.trim());\n if (!q) return [];\n return items\n .map((item) => ({ item, rank: score(item, q) }))\n .filter((hit): hit is { item: T; rank: number } => hit.rank !== null)\n .sort((a, b) => a.rank - b.rank || a.item.name.localeCompare(b.item.name))\n .map((hit) => hit.item);\n}\n","import type { IndicatorCategory } from '@tradecanvas/commons';\nimport { INDICATOR_CATEGORIES } from '@tradecanvas/core';\nimport { createIcon } from './icons.js';\nimport { fill, type MessageKey, type Translator } from './i18n.js';\nimport { keepTabInside } from './focusTrap.js';\nimport { searchIndicators } from './indicatorSearch.js';\nimport { markPart } from './widgetFeatures.js';\n\n/** An indicator as the dialog lists it. */\nexport interface IndicatorPickerItem {\n id: string;\n name: string;\n shortName: string;\n placement: 'overlay' | 'panel';\n category: IndicatorCategory;\n}\n\nexport interface IndicatorPickerCallbacks {\n /** Add one; false when it wasn't added (nothing is announced then). */\n onAdd: (id: string) => boolean | void;\n onFavorite: (id: string, on: boolean) => void;\n /** The indicator templates; without them there is no Templates tab. */\n templates?: {\n onApply: (name: string) => void;\n onDelete: (name: string) => void;\n onSave: () => void;\n };\n onClose?: () => void;\n}\n\nexport interface IndicatorPickerContent {\n items: readonly IndicatorPickerItem[];\n favorites: readonly string[];\n /** The templates' names; null where they are switched off. */\n templates: readonly string[] | null;\n /** How many of each indicator are on the chart. */\n counts?: ReadonlyMap<string, number>;\n}\n\ntype View = 'favorites' | 'templates' | 'all' | IndicatorCategory;\n\nlet nextPickerId = 1;\n\nconst byName = (a: IndicatorPickerItem, b: IndicatorPickerItem) => a.name.localeCompare(b.name);\n\n/**\n * The indicators dialog: a search, the groups down the side (favourites,\n * templates, all, then each kind) and the list. A row adds its indicator and\n * the dialog stays open for the next; the star beside it keeps it in the\n * favourites. Mounts in the widget's overlay layer.\n */\nexport class WidgetIndicatorPicker {\n private backdrop: HTMLDivElement | null = null;\n private listEl: HTMLDivElement | null = null;\n private navEl: HTMLDivElement | null = null;\n private searchEl: HTMLInputElement | null = null;\n private liveEl: HTMLDivElement | null = null;\n private content: IndicatorPickerContent = { items: [], favorites: [], templates: [] };\n private favorites = new Set<string>();\n private counts: ReadonlyMap<string, number> = new Map();\n private view: View = 'all';\n /** The tab picked last, for the next opening. */\n private lastView: View | null = null;\n private query = '';\n private returnFocus: HTMLElement | null = null;\n private readonly uid = nextPickerId++;\n\n constructor(\n private readonly layer: () => HTMLElement,\n private readonly callbacks: IndicatorPickerCallbacks,\n private readonly t: Translator,\n ) {}\n\n open(content: IndicatorPickerContent): void {\n if (this.backdrop) this.close(false);\n this.returnFocus = document.activeElement instanceof HTMLElement ? document.activeElement : null;\n this.content = content;\n this.favorites = new Set(content.favorites);\n this.counts = content.counts ?? new Map();\n this.query = '';\n this.view = this.startView();\n this.build();\n this.searchEl?.focus();\n }\n\n close(giveFocusBack = true): void {\n if (!this.backdrop) return;\n this.backdrop.remove();\n this.backdrop = this.listEl = this.navEl = this.searchEl = this.liveEl = null;\n this.callbacks.onClose?.();\n if (giveFocusBack && this.returnFocus?.isConnected) this.returnFocus.focus();\n this.returnFocus = null;\n }\n\n isOpen(): boolean {\n return this.backdrop !== null;\n }\n\n /** The templates after one is saved or deleted; focus left on a deleted one goes to \"Save…\". */\n setTemplates(names: readonly string[]): void {\n this.content = { ...this.content, templates: [...names] };\n if (!this.listEl || this.view !== 'templates' || this.query) return;\n const hadFocus = this.listEl.contains(document.activeElement);\n this.renderList();\n if (hadFocus && !this.listEl.contains(document.activeElement)) {\n this.listEl.querySelector<HTMLButtonElement>('[data-tpl-save]')?.focus();\n }\n }\n\n /** How many of each indicator are on the chart, shown on their rows. */\n setCounts(counts: ReadonlyMap<string, number>): void {\n this.counts = counts;\n this.listEl?.querySelectorAll<HTMLElement>('.tcw-ip-row[data-ind]').forEach((row) => this.markCount(row));\n }\n\n destroy(): void {\n this.close(false);\n }\n\n private startView(): View {\n const views = this.views();\n if (this.lastView && views.includes(this.lastView) && !(this.lastView === 'favorites' && this.favorites.size === 0)) return this.lastView;\n return this.favorites.size > 0 ? 'favorites' : 'all';\n }\n\n /** The tabs down the side: favourites, templates, all, and each kind that has indicators. */\n private views(): View[] {\n const kinds = new Set(this.content.items.map((item) => item.category));\n return [\n 'favorites',\n ...(this.callbacks.templates && this.content.templates ? ['templates' as const] : []),\n 'all',\n ...INDICATOR_CATEGORIES.filter((category) => kinds.has(category)),\n ];\n }\n\n private viewLabel(view: View): string {\n if (view === 'favorites') return this.t('indicatorPicker.favorites');\n if (view === 'templates') return this.t('toolbar.indicators.templates');\n if (view === 'all') return this.t('toolbar.indicators.all');\n return this.t(`indicatorCategory.${view}` as MessageKey);\n }\n\n private build(): void {\n const backdrop = document.createElement('div');\n backdrop.className = 'tcw-modal-backdrop';\n backdrop.addEventListener('click', (e) => {\n if (e.target === backdrop) this.close();\n });\n const modal = document.createElement('div');\n modal.className = 'tcw-modal tcw-ip';\n modal.setAttribute('role', 'dialog');\n modal.setAttribute('aria-modal', 'true');\n modal.setAttribute('aria-labelledby', `tcw-ip-title-${this.uid}`);\n modal.tabIndex = -1;\n modal.addEventListener('keydown', (e) => this.onKeyDown(e, modal));\n\n const live = document.createElement('div');\n live.className = 'tcw-visually-hidden';\n live.setAttribute('aria-live', 'polite');\n modal.append(this.buildHeader(), this.buildSearch(), this.buildBody(), live);\n backdrop.appendChild(modal);\n this.layer().appendChild(backdrop);\n this.backdrop = backdrop;\n this.liveEl = live;\n this.renderNav();\n this.renderList();\n }\n\n private buildHeader(): HTMLDivElement {\n const header = document.createElement('div');\n header.className = 'tcw-modal-header';\n const title = document.createElement('h3');\n title.id = `tcw-ip-title-${this.uid}`;\n title.textContent = this.t('toolbar.indicators');\n const closeBtn = document.createElement('button');\n closeBtn.type = 'button';\n closeBtn.className = 'tcw-modal-close';\n closeBtn.setAttribute('aria-label', this.t('indicatorSettings.close'));\n closeBtn.innerHTML = createIcon('x', 16);\n closeBtn.addEventListener('click', () => this.close());\n header.append(title, closeBtn);\n return header;\n }\n\n private buildSearch(): HTMLLabelElement {\n const row = document.createElement('label');\n row.className = 'tcw-ip-search';\n const search = document.createElement('input');\n search.type = 'search';\n search.id = `tcw-ip-search-${this.uid}`;\n search.placeholder = this.t('indicatorPicker.search');\n search.setAttribute('aria-label', this.t('indicatorPicker.search'));\n search.autocomplete = 'off';\n search.spellcheck = false;\n search.addEventListener('input', () => {\n this.query = search.value;\n this.renderList();\n });\n search.addEventListener('keydown', (e) => this.onSearchKey(e));\n row.innerHTML = createIcon('search', 16);\n row.appendChild(search);\n this.searchEl = search;\n return row;\n }\n\n /** The groups down the side, and the list beside them. */\n private buildBody(): HTMLDivElement {\n const body = document.createElement('div');\n body.className = 'tcw-ip-body';\n const nav = document.createElement('div');\n nav.className = 'tcw-ip-nav';\n nav.setAttribute('role', 'tablist');\n nav.setAttribute('aria-orientation', 'vertical');\n nav.setAttribute('aria-label', this.t('indicatorPicker.groups'));\n nav.addEventListener('keydown', (e) => this.onNavKey(e));\n const list = document.createElement('div');\n list.className = 'tcw-ip-list';\n list.id = `tcw-ip-list-${this.uid}`;\n list.setAttribute('role', 'tabpanel');\n list.addEventListener('keydown', (e) => this.onListKey(e));\n list.addEventListener('focusin', (e) => this.rove(e.target));\n body.append(nav, list);\n this.navEl = nav;\n this.listEl = list;\n return body;\n }\n\n private renderNav(): void {\n const nav = this.navEl;\n if (!nav) return;\n nav.replaceChildren(...this.views().map((view) => {\n const tab = document.createElement('button');\n tab.type = 'button';\n tab.className = 'tcw-ip-tab';\n tab.id = `tcw-ip-tab-${this.uid}-${view}`;\n tab.dataset.view = view;\n tab.setAttribute('role', 'tab');\n tab.setAttribute('aria-controls', `tcw-ip-list-${this.uid}`);\n const icon = view === 'favorites' ? createIcon('star', 14) : view === 'templates' ? createIcon('layers', 14) : '';\n tab.innerHTML = `${icon}<span></span>`;\n tab.querySelector('span')!.textContent = this.viewLabel(view);\n if (view === 'templates') markPart(tab, 'indicatorTemplates');\n tab.addEventListener('click', () => this.select(view));\n return tab;\n }));\n this.markNav();\n }\n\n private markNav(): void {\n this.navEl?.querySelectorAll<HTMLButtonElement>('.tcw-ip-tab').forEach((tab) => {\n const on = tab.dataset.view === this.view;\n tab.setAttribute('aria-selected', String(on));\n tab.classList.toggle('tcw-active', on);\n tab.tabIndex = on ? 0 : -1;\n });\n this.labelList();\n }\n\n /** The list is named by its tab, or by the search while it shows the matches. */\n private labelList(): void {\n const searching = this.query.trim() !== '';\n this.listEl?.setAttribute('aria-labelledby', searching ? `tcw-ip-search-${this.uid}` : `tcw-ip-tab-${this.uid}-${this.view}`);\n }\n\n private select(view: View, focusTab = false): void {\n this.view = this.lastView = view;\n if (this.searchEl && this.query) this.searchEl.value = this.query = '';\n this.markNav();\n this.renderList();\n if (focusTab) this.navEl?.querySelector<HTMLButtonElement>(`[data-view=\"${view}\"]`)?.focus();\n }\n\n private renderList(): void {\n const list = this.listEl;\n if (!list) return;\n list.replaceChildren();\n const items = this.content.items;\n // While searching, the results come from every group: the one picked steps back.\n this.navEl?.classList.toggle('tcw-ip-nav--searching', this.query.trim() !== '');\n this.labelList();\n if (this.query.trim()) {\n const hits = searchIndicators(items, this.query);\n if (hits.length === 0) list.appendChild(this.note(fill(this.t('indicatorPicker.noMatch'), { query: this.query.trim() })));\n else list.append(...hits.map((item) => this.row(item)));\n } else if (this.view === 'templates') {\n this.renderTemplates(list);\n } else if (this.view === 'favorites') {\n const favorites = items.filter((item) => this.favorites.has(item.id)).sort(byName);\n if (favorites.length === 0) list.appendChild(this.note(this.t('indicatorPicker.noFavorites')));\n else list.append(...favorites.map((item) => this.row(item)));\n } else if (this.view === 'all') {\n for (const category of INDICATOR_CATEGORIES) {\n const group = items.filter((item) => item.category === category).sort(byName);\n if (group.length === 0) continue;\n const heading = document.createElement('div');\n heading.className = 'tcw-ip-heading';\n heading.textContent = this.viewLabel(category);\n list.append(heading, ...group.map((item) => this.row(item)));\n }\n } else {\n list.append(...items.filter((item) => item.category === this.view).sort(byName).map((item) => this.row(item)));\n }\n list.querySelector<HTMLButtonElement>('.tcw-ip-add')?.setAttribute('tabindex', '0');\n }\n\n private row(item: IndicatorPickerItem): HTMLDivElement {\n const row = document.createElement('div');\n row.className = 'tcw-ip-row';\n row.dataset.ind = item.id;\n const add = document.createElement('button');\n add.type = 'button';\n add.className = 'tcw-ip-add';\n add.dataset.ind = item.id;\n add.tabIndex = -1;\n const name = document.createElement('span');\n name.className = 'tcw-ip-name';\n name.textContent = item.name;\n add.appendChild(name);\n if (item.shortName && item.shortName !== item.name) add.appendChild(this.tag('tcw-ip-short', item.shortName));\n add.appendChild(this.tag('tcw-ip-kind', this.t(`indicatorType.${item.placement}`)));\n add.addEventListener('click', () => this.add(item));\n const star = document.createElement('button');\n star.type = 'button';\n star.className = 'tcw-ip-aside tcw-ip-star';\n star.tabIndex = -1;\n star.title = this.t('indicatorPicker.favorite');\n star.setAttribute('aria-label', `${this.t('indicatorPicker.favorite')}: ${item.name}`);\n star.setAttribute('aria-pressed', String(this.favorites.has(item.id)));\n star.innerHTML = createIcon('star', 14);\n star.addEventListener('click', () => this.toggleFavorite(item.id, star));\n row.append(add, star);\n this.markCount(row);\n return row;\n }\n\n private renderTemplates(list: HTMLElement): void {\n const save = document.createElement('button');\n save.type = 'button';\n save.className = 'tcw-ip-add tcw-ip-save';\n save.tabIndex = -1;\n save.dataset.tplSave = '';\n save.innerHTML = `${createIcon('save', 14)}<span class=\"tcw-ip-name\"></span>`;\n save.querySelector('span')!.textContent = this.t('templates.save');\n save.addEventListener('click', () => {\n this.close();\n this.callbacks.templates?.onSave();\n });\n list.appendChild(save);\n const names = this.content.templates ?? [];\n if (names.length === 0) list.appendChild(this.note(this.t('indicatorPicker.noTemplates')));\n for (const name of names) {\n const row = document.createElement('div');\n row.className = 'tcw-ip-row';\n const apply = document.createElement('button');\n apply.type = 'button';\n apply.className = 'tcw-ip-add';\n apply.tabIndex = -1;\n apply.dataset.tplApply = name;\n apply.innerHTML = `${createIcon('layers', 14)}<span class=\"tcw-ip-name\"></span>`;\n apply.querySelector('span')!.textContent = name;\n apply.addEventListener('click', () => {\n this.close();\n this.callbacks.templates?.onApply(name);\n });\n const remove = document.createElement('button');\n remove.type = 'button';\n remove.className = 'tcw-ip-aside tcw-ip-remove';\n remove.tabIndex = -1;\n remove.dataset.tplDelete = name;\n const label = fill(this.t('templates.delete'), { name });\n remove.title = label;\n remove.setAttribute('aria-label', label);\n remove.innerHTML = createIcon('x', 12);\n remove.addEventListener('click', () => this.callbacks.templates?.onDelete(name));\n row.append(apply, remove);\n list.appendChild(row);\n }\n }\n\n private add(item: IndicatorPickerItem): void {\n if (this.callbacks.onAdd(item.id) === false || !this.liveEl) return;\n const text = fill(this.t('indicatorPicker.added'), { name: item.name });\n // The same words again (a second RSI) are only read out when they change: a space tells them apart.\n this.liveEl.textContent = this.liveEl.textContent === text ? `${text}\\u00a0` : text;\n }\n\n private toggleFavorite(id: string, star: HTMLButtonElement): void {\n const on = !this.favorites.has(id);\n if (on) this.favorites.add(id);\n else this.favorites.delete(id);\n this.callbacks.onFavorite(id, on);\n star.setAttribute('aria-pressed', String(on));\n if (this.view !== 'favorites' || this.query || on) return;\n // Gone from the favourites: the keyboard moves to the next row's star.\n const stars = [...(this.listEl?.querySelectorAll<HTMLButtonElement>('.tcw-ip-star') ?? [])];\n const at = stars.indexOf(star);\n const hadFocus = document.activeElement === star;\n this.renderList();\n if (!hadFocus) return;\n const left = [...(this.listEl?.querySelectorAll<HTMLButtonElement>('.tcw-ip-star') ?? [])];\n (left[at] ?? left[left.length - 1] ?? this.searchEl)?.focus();\n }\n\n private markCount(row: HTMLElement): void {\n const n = this.counts.get(row.dataset.ind ?? '') ?? 0;\n const add = row.querySelector('.tcw-ip-add');\n let badge = row.querySelector<HTMLElement>('.tcw-ip-count');\n if (n === 0) {\n badge?.remove();\n return;\n }\n if (!badge) {\n badge = this.tag('tcw-ip-count', '');\n add?.appendChild(badge);\n }\n badge.textContent = String(n);\n badge.title = fill(this.t('indicatorPicker.onChart'), { count: String(n) });\n }\n\n private tag(className: string, text: string): HTMLSpanElement {\n const span = document.createElement('span');\n span.className = className;\n span.textContent = text;\n return span;\n }\n\n private note(text: string): HTMLDivElement {\n const el = document.createElement('div');\n el.className = 'tcw-ip-empty';\n el.textContent = text;\n return el;\n }\n\n /** One row in the list takes Tab: the one focused last. */\n private rove(target: EventTarget | null): void {\n if (!(target instanceof HTMLElement) || !target.classList.contains('tcw-ip-add')) return;\n this.listEl?.querySelectorAll<HTMLElement>('.tcw-ip-add[tabindex=\"0\"]').forEach((el) => {\n if (el !== target) el.tabIndex = -1;\n });\n target.tabIndex = 0;\n }\n\n private onKeyDown(e: KeyboardEvent, modal: HTMLElement): void {\n if (e.key === 'Escape') {\n e.preventDefault();\n e.stopPropagation();\n this.close();\n return;\n }\n keepTabInside(e, modal);\n }\n\n /** Down goes into the list; Enter adds the best match. */\n private onSearchKey(e: KeyboardEvent): void {\n const first = this.listEl?.querySelector<HTMLButtonElement>('.tcw-ip-add');\n if (e.key === 'ArrowDown' && first) {\n e.preventDefault();\n first.focus();\n } else if (e.key === 'Enter' && this.query.trim() && first) {\n e.preventDefault();\n first.click();\n }\n }\n\n /** Up and down through the tabs (Home, End), picking as they go. */\n private onNavKey(e: KeyboardEvent): void {\n const views = this.views();\n const at = views.indexOf(this.view);\n const next = e.key === 'ArrowDown' ? Math.min(views.length - 1, at + 1)\n : e.key === 'ArrowUp' ? Math.max(0, at - 1)\n : e.key === 'Home' ? 0\n : e.key === 'End' ? views.length - 1\n : -1;\n if (next < 0) return;\n e.preventDefault();\n this.select(views[next], true);\n }\n\n /** Up and down through the rows (from the top, back to the search); right to a row's star, left back. */\n private onListKey(e: KeyboardEvent): void {\n const target = e.target as HTMLElement;\n const row = target.closest('.tcw-ip-row, .tcw-ip-save');\n const adds = [...(this.listEl?.querySelectorAll<HTMLButtonElement>('.tcw-ip-add') ?? [])];\n const main = row?.classList.contains('tcw-ip-save') ? row as HTMLButtonElement : row?.querySelector<HTMLButtonElement>('.tcw-ip-add');\n const at = main ? adds.indexOf(main) : -1;\n let to: HTMLElement | null | undefined = null;\n if (e.key === 'ArrowDown') to = adds[at + 1];\n else if (e.key === 'ArrowUp') to = at > 0 ? adds[at - 1] : this.searchEl;\n else if (e.key === 'Home') to = adds[0];\n else if (e.key === 'End') to = adds[adds.length - 1];\n else if (e.key === 'ArrowRight' && target === main) to = row?.querySelector<HTMLButtonElement>('.tcw-ip-aside');\n else if (e.key === 'ArrowLeft' && target !== main) to = main;\n if (!to) return;\n e.preventDefault();\n to.focus();\n }\n}\n"],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ const e=require(`./widgetFeatures-BZ8OVPN2.cjs`),t=require(`./focusTrap-DrhhmG0V.cjs`),n=require(`./i18n-C2nQBZTY.cjs`);let r=require(`@tradecanvas/core`);function i(e){return e.normalize(`NFD`).replace(/[̀-ͯ]/g,``).toLowerCase()}var a=/[\s/()%-]+/;function o(e,t){let n=i(e.shortName),r=i(e.name);return n===t||i(e.id)===t?0:n.startsWith(t)||r.startsWith(t)?1:[...r.split(a),...n.split(a)].some(e=>e.startsWith(t))?2:r.includes(t)||n.includes(t)?3:null}function s(e,t){let n=i(t.trim());return n?e.map(e=>({item:e,rank:o(e,n)})).filter(e=>e.rank!==null).sort((e,t)=>e.rank-t.rank||e.item.name.localeCompare(t.item.name)).map(e=>e.item):[]}var c=1,l=(e,t)=>e.name.localeCompare(t.name),u=class{backdrop=null;listEl=null;navEl=null;searchEl=null;liveEl=null;content={items:[],favorites:[],templates:[]};favorites=new Set;counts=new Map;view=`all`;lastView=null;query=``;returnFocus=null;uid=c++;constructor(e,t,n){this.layer=e,this.callbacks=t,this.t=n}open(e){this.backdrop&&this.close(!1),this.returnFocus=document.activeElement instanceof HTMLElement?document.activeElement:null,this.content=e,this.favorites=new Set(e.favorites),this.counts=e.counts??new Map,this.query=``,this.view=this.startView(),this.build(),this.searchEl?.focus()}close(e=!0){this.backdrop&&(this.backdrop.remove(),this.backdrop=this.listEl=this.navEl=this.searchEl=this.liveEl=null,this.callbacks.onClose?.(),e&&this.returnFocus?.isConnected&&this.returnFocus.focus(),this.returnFocus=null)}isOpen(){return this.backdrop!==null}setTemplates(e){if(this.content={...this.content,templates:[...e]},!this.listEl||this.view!==`templates`||this.query)return;let t=this.listEl.contains(document.activeElement);this.renderList(),t&&!this.listEl.contains(document.activeElement)&&this.listEl.querySelector(`[data-tpl-save]`)?.focus()}setCounts(e){this.counts=e,this.listEl?.querySelectorAll(`.tcw-ip-row[data-ind]`).forEach(e=>this.markCount(e))}destroy(){this.close(!1)}startView(){let e=this.views();return this.lastView&&e.includes(this.lastView)&&!(this.lastView===`favorites`&&this.favorites.size===0)?this.lastView:this.favorites.size>0?`favorites`:`all`}views(){let e=new Set(this.content.items.map(e=>e.category));return[`favorites`,...this.callbacks.templates&&this.content.templates?[`templates`]:[],`all`,...r.INDICATOR_CATEGORIES.filter(t=>e.has(t))]}viewLabel(e){return e===`favorites`?this.t(`indicatorPicker.favorites`):e===`templates`?this.t(`toolbar.indicators.templates`):e===`all`?this.t(`toolbar.indicators.all`):this.t(`indicatorCategory.${e}`)}build(){let e=document.createElement(`div`);e.className=`tcw-modal-backdrop`,e.addEventListener(`click`,t=>{t.target===e&&this.close()});let t=document.createElement(`div`);t.className=`tcw-modal tcw-ip`,t.setAttribute(`role`,`dialog`),t.setAttribute(`aria-modal`,`true`),t.setAttribute(`aria-labelledby`,`tcw-ip-title-${this.uid}`),t.tabIndex=-1,t.addEventListener(`keydown`,e=>this.onKeyDown(e,t));let n=document.createElement(`div`);n.className=`tcw-visually-hidden`,n.setAttribute(`aria-live`,`polite`),t.append(this.buildHeader(),this.buildSearch(),this.buildBody(),n),e.appendChild(t),this.layer().appendChild(e),this.backdrop=e,this.liveEl=n,this.renderNav(),this.renderList()}buildHeader(){let e=document.createElement(`div`);e.className=`tcw-modal-header`;let n=document.createElement(`h3`);n.id=`tcw-ip-title-${this.uid}`,n.textContent=this.t(`toolbar.indicators`);let r=document.createElement(`button`);return r.type=`button`,r.className=`tcw-modal-close`,r.setAttribute(`aria-label`,this.t(`indicatorSettings.close`)),r.innerHTML=t.i(`x`,16),r.addEventListener(`click`,()=>this.close()),e.append(n,r),e}buildSearch(){let e=document.createElement(`label`);e.className=`tcw-ip-search`;let n=document.createElement(`input`);return n.type=`search`,n.id=`tcw-ip-search-${this.uid}`,n.placeholder=this.t(`indicatorPicker.search`),n.setAttribute(`aria-label`,this.t(`indicatorPicker.search`)),n.autocomplete=`off`,n.spellcheck=!1,n.addEventListener(`input`,()=>{this.query=n.value,this.renderList()}),n.addEventListener(`keydown`,e=>this.onSearchKey(e)),e.innerHTML=t.i(`search`,16),e.appendChild(n),this.searchEl=n,e}buildBody(){let e=document.createElement(`div`);e.className=`tcw-ip-body`;let t=document.createElement(`div`);t.className=`tcw-ip-nav`,t.setAttribute(`role`,`tablist`),t.setAttribute(`aria-orientation`,`vertical`),t.setAttribute(`aria-label`,this.t(`indicatorPicker.groups`)),t.addEventListener(`keydown`,e=>this.onNavKey(e));let n=document.createElement(`div`);return n.className=`tcw-ip-list`,n.id=`tcw-ip-list-${this.uid}`,n.setAttribute(`role`,`tabpanel`),n.addEventListener(`keydown`,e=>this.onListKey(e)),n.addEventListener(`focusin`,e=>this.rove(e.target)),e.append(t,n),this.navEl=t,this.listEl=n,e}renderNav(){let n=this.navEl;n&&(n.replaceChildren(...this.views().map(n=>{let r=document.createElement(`button`);return r.type=`button`,r.className=`tcw-ip-tab`,r.id=`tcw-ip-tab-${this.uid}-${n}`,r.dataset.view=n,r.setAttribute(`role`,`tab`),r.setAttribute(`aria-controls`,`tcw-ip-list-${this.uid}`),r.innerHTML=`${n===`favorites`?t.i(`star`,14):n===`templates`?t.i(`layers`,14):``}<span></span>`,r.querySelector(`span`).textContent=this.viewLabel(n),n===`templates`&&e.o(r,`indicatorTemplates`),r.addEventListener(`click`,()=>this.select(n)),r})),this.markNav())}markNav(){this.navEl?.querySelectorAll(`.tcw-ip-tab`).forEach(e=>{let t=e.dataset.view===this.view;e.setAttribute(`aria-selected`,String(t)),e.classList.toggle(`tcw-active`,t),e.tabIndex=t?0:-1}),this.labelList()}labelList(){let e=this.query.trim()!==``;this.listEl?.setAttribute(`aria-labelledby`,e?`tcw-ip-search-${this.uid}`:`tcw-ip-tab-${this.uid}-${this.view}`)}select(e,t=!1){this.view=this.lastView=e,this.searchEl&&this.query&&(this.searchEl.value=this.query=``),this.markNav(),this.renderList(),t&&this.navEl?.querySelector(`[data-view="${e}"]`)?.focus()}renderList(){let e=this.listEl;if(!e)return;e.replaceChildren();let t=this.content.items;if(this.navEl?.classList.toggle(`tcw-ip-nav--searching`,this.query.trim()!==``),this.labelList(),this.query.trim()){let r=s(t,this.query);r.length===0?e.appendChild(this.note(n.r(this.t(`indicatorPicker.noMatch`),{query:this.query.trim()}))):e.append(...r.map(e=>this.row(e)))}else if(this.view===`templates`)this.renderTemplates(e);else if(this.view===`favorites`){let n=t.filter(e=>this.favorites.has(e.id)).sort(l);n.length===0?e.appendChild(this.note(this.t(`indicatorPicker.noFavorites`))):e.append(...n.map(e=>this.row(e)))}else if(this.view===`all`)for(let n of r.INDICATOR_CATEGORIES){let r=t.filter(e=>e.category===n).sort(l);if(r.length===0)continue;let i=document.createElement(`div`);i.className=`tcw-ip-heading`,i.textContent=this.viewLabel(n),e.append(i,...r.map(e=>this.row(e)))}else e.append(...t.filter(e=>e.category===this.view).sort(l).map(e=>this.row(e)));e.querySelector(`.tcw-ip-add`)?.setAttribute(`tabindex`,`0`)}row(e){let n=document.createElement(`div`);n.className=`tcw-ip-row`,n.dataset.ind=e.id;let r=document.createElement(`button`);r.type=`button`,r.className=`tcw-ip-add`,r.dataset.ind=e.id,r.tabIndex=-1;let i=document.createElement(`span`);i.className=`tcw-ip-name`,i.textContent=e.name,r.appendChild(i),e.shortName&&e.shortName!==e.name&&r.appendChild(this.tag(`tcw-ip-short`,e.shortName)),r.appendChild(this.tag(`tcw-ip-kind`,this.t(`indicatorType.${e.placement}`))),r.addEventListener(`click`,()=>this.add(e));let a=document.createElement(`button`);return a.type=`button`,a.className=`tcw-ip-aside tcw-ip-star`,a.tabIndex=-1,a.title=this.t(`indicatorPicker.favorite`),a.setAttribute(`aria-label`,`${this.t(`indicatorPicker.favorite`)}: ${e.name}`),a.setAttribute(`aria-pressed`,String(this.favorites.has(e.id))),a.innerHTML=t.i(`star`,14),a.addEventListener(`click`,()=>this.toggleFavorite(e.id,a)),n.append(r,a),this.markCount(n),n}renderTemplates(e){let r=document.createElement(`button`);r.type=`button`,r.className=`tcw-ip-add tcw-ip-save`,r.tabIndex=-1,r.dataset.tplSave=``,r.innerHTML=`${t.i(`save`,14)}<span class="tcw-ip-name"></span>`,r.querySelector(`span`).textContent=this.t(`templates.save`),r.addEventListener(`click`,()=>{this.close(),this.callbacks.templates?.onSave()}),e.appendChild(r);let i=this.content.templates??[];i.length===0&&e.appendChild(this.note(this.t(`indicatorPicker.noTemplates`)));for(let r of i){let i=document.createElement(`div`);i.className=`tcw-ip-row`;let a=document.createElement(`button`);a.type=`button`,a.className=`tcw-ip-add`,a.tabIndex=-1,a.dataset.tplApply=r,a.innerHTML=`${t.i(`layers`,14)}<span class="tcw-ip-name"></span>`,a.querySelector(`span`).textContent=r,a.addEventListener(`click`,()=>{this.close(),this.callbacks.templates?.onApply(r)});let o=document.createElement(`button`);o.type=`button`,o.className=`tcw-ip-aside tcw-ip-remove`,o.tabIndex=-1,o.dataset.tplDelete=r;let s=n.r(this.t(`templates.delete`),{name:r});o.title=s,o.setAttribute(`aria-label`,s),o.innerHTML=t.i(`x`,12),o.addEventListener(`click`,()=>this.callbacks.templates?.onDelete(r)),i.append(a,o),e.appendChild(i)}}add(e){if(this.callbacks.onAdd(e.id)===!1||!this.liveEl)return;let t=n.r(this.t(`indicatorPicker.added`),{name:e.name});this.liveEl.textContent=this.liveEl.textContent===t?`${t}\u00a0`:t}toggleFavorite(e,t){let n=!this.favorites.has(e);if(n?this.favorites.add(e):this.favorites.delete(e),this.callbacks.onFavorite(e,n),t.setAttribute(`aria-pressed`,String(n)),this.view!==`favorites`||this.query||n)return;let r=[...this.listEl?.querySelectorAll(`.tcw-ip-star`)??[]].indexOf(t),i=document.activeElement===t;if(this.renderList(),!i)return;let a=[...this.listEl?.querySelectorAll(`.tcw-ip-star`)??[]];(a[r]??a[a.length-1]??this.searchEl)?.focus()}markCount(e){let t=this.counts.get(e.dataset.ind??``)??0,r=e.querySelector(`.tcw-ip-add`),i=e.querySelector(`.tcw-ip-count`);if(t===0){i?.remove();return}i||(i=this.tag(`tcw-ip-count`,``),r?.appendChild(i)),i.textContent=String(t),i.title=n.r(this.t(`indicatorPicker.onChart`),{count:String(t)})}tag(e,t){let n=document.createElement(`span`);return n.className=e,n.textContent=t,n}note(e){let t=document.createElement(`div`);return t.className=`tcw-ip-empty`,t.textContent=e,t}rove(e){!(e instanceof HTMLElement)||!e.classList.contains(`tcw-ip-add`)||(this.listEl?.querySelectorAll(`.tcw-ip-add[tabindex="0"]`).forEach(t=>{t!==e&&(t.tabIndex=-1)}),e.tabIndex=0)}onKeyDown(e,n){if(e.key===`Escape`){e.preventDefault(),e.stopPropagation(),this.close();return}t.n(e,n)}onSearchKey(e){let t=this.listEl?.querySelector(`.tcw-ip-add`);e.key===`ArrowDown`&&t?(e.preventDefault(),t.focus()):e.key===`Enter`&&this.query.trim()&&t&&(e.preventDefault(),t.click())}onNavKey(e){let t=this.views(),n=t.indexOf(this.view),r=e.key===`ArrowDown`?Math.min(t.length-1,n+1):e.key===`ArrowUp`?Math.max(0,n-1):e.key===`Home`?0:e.key===`End`?t.length-1:-1;r<0||(e.preventDefault(),this.select(t[r],!0))}onListKey(e){let t=e.target,n=t.closest(`.tcw-ip-row, .tcw-ip-save`),r=[...this.listEl?.querySelectorAll(`.tcw-ip-add`)??[]],i=n?.classList.contains(`tcw-ip-save`)?n:n?.querySelector(`.tcw-ip-add`),a=i?r.indexOf(i):-1,o=null;e.key===`ArrowDown`?o=r[a+1]:e.key===`ArrowUp`?o=a>0?r[a-1]:this.searchEl:e.key===`Home`?o=r[0]:e.key===`End`?o=r[r.length-1]:e.key===`ArrowRight`&&t===i?o=n?.querySelector(`.tcw-ip-aside`):e.key===`ArrowLeft`&&t!==i&&(o=i),o&&(e.preventDefault(),o.focus())}};exports.WidgetIndicatorPicker=u;
2
+ //# sourceMappingURL=WidgetIndicatorPicker-DuKJFPRC.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"WidgetIndicatorPicker-DuKJFPRC.cjs","names":[],"sources":["../src/widget/indicatorSearch.ts","../src/widget/WidgetIndicatorPicker.ts"],"sourcesContent":["/** What a search reads of an indicator. */\nexport interface SearchableIndicator {\n id: string;\n name: string;\n shortName: string;\n}\n\n/** Lower case without accents, so \"mômentum\" finds Momentum. */\nfunction fold(text: string): string {\n return text.normalize('NFD').replace(/[̀-ͯ]/g, '').toLowerCase();\n}\n\nconst WORD_BREAK = /[\\s/()%-]+/;\n\n/** How well an indicator matches `q` (folded): 0 best, null not at all. */\nfunction score(item: SearchableIndicator, q: string): number | null {\n const short = fold(item.shortName);\n const name = fold(item.name);\n if (short === q || fold(item.id) === q) return 0;\n if (short.startsWith(q) || name.startsWith(q)) return 1;\n if ([...name.split(WORD_BREAK), ...short.split(WORD_BREAK)].some((word) => word.startsWith(q))) return 2;\n if (name.includes(q) || short.includes(q)) return 3;\n return null;\n}\n\n/**\n * The indicators matching `query`, best first: the short name typed in full\n * (\"rsi\"), then names starting with it, then a word starting with it, then\n * any containing it. Ties go by name. Nothing typed finds nothing.\n */\nexport function searchIndicators<T extends SearchableIndicator>(items: readonly T[], query: string): T[] {\n const q = fold(query.trim());\n if (!q) return [];\n return items\n .map((item) => ({ item, rank: score(item, q) }))\n .filter((hit): hit is { item: T; rank: number } => hit.rank !== null)\n .sort((a, b) => a.rank - b.rank || a.item.name.localeCompare(b.item.name))\n .map((hit) => hit.item);\n}\n","import type { IndicatorCategory } from '@tradecanvas/commons';\nimport { INDICATOR_CATEGORIES } from '@tradecanvas/core';\nimport { createIcon } from './icons.js';\nimport { fill, type MessageKey, type Translator } from './i18n.js';\nimport { keepTabInside } from './focusTrap.js';\nimport { searchIndicators } from './indicatorSearch.js';\nimport { markPart } from './widgetFeatures.js';\n\n/** An indicator as the dialog lists it. */\nexport interface IndicatorPickerItem {\n id: string;\n name: string;\n shortName: string;\n placement: 'overlay' | 'panel';\n category: IndicatorCategory;\n}\n\nexport interface IndicatorPickerCallbacks {\n /** Add one; false when it wasn't added (nothing is announced then). */\n onAdd: (id: string) => boolean | void;\n onFavorite: (id: string, on: boolean) => void;\n /** The indicator templates; without them there is no Templates tab. */\n templates?: {\n onApply: (name: string) => void;\n onDelete: (name: string) => void;\n onSave: () => void;\n };\n onClose?: () => void;\n}\n\nexport interface IndicatorPickerContent {\n items: readonly IndicatorPickerItem[];\n favorites: readonly string[];\n /** The templates' names; null where they are switched off. */\n templates: readonly string[] | null;\n /** How many of each indicator are on the chart. */\n counts?: ReadonlyMap<string, number>;\n}\n\ntype View = 'favorites' | 'templates' | 'all' | IndicatorCategory;\n\nlet nextPickerId = 1;\n\nconst byName = (a: IndicatorPickerItem, b: IndicatorPickerItem) => a.name.localeCompare(b.name);\n\n/**\n * The indicators dialog: a search, the groups down the side (favourites,\n * templates, all, then each kind) and the list. A row adds its indicator and\n * the dialog stays open for the next; the star beside it keeps it in the\n * favourites. Mounts in the widget's overlay layer.\n */\nexport class WidgetIndicatorPicker {\n private backdrop: HTMLDivElement | null = null;\n private listEl: HTMLDivElement | null = null;\n private navEl: HTMLDivElement | null = null;\n private searchEl: HTMLInputElement | null = null;\n private liveEl: HTMLDivElement | null = null;\n private content: IndicatorPickerContent = { items: [], favorites: [], templates: [] };\n private favorites = new Set<string>();\n private counts: ReadonlyMap<string, number> = new Map();\n private view: View = 'all';\n /** The tab picked last, for the next opening. */\n private lastView: View | null = null;\n private query = '';\n private returnFocus: HTMLElement | null = null;\n private readonly uid = nextPickerId++;\n\n constructor(\n private readonly layer: () => HTMLElement,\n private readonly callbacks: IndicatorPickerCallbacks,\n private readonly t: Translator,\n ) {}\n\n open(content: IndicatorPickerContent): void {\n if (this.backdrop) this.close(false);\n this.returnFocus = document.activeElement instanceof HTMLElement ? document.activeElement : null;\n this.content = content;\n this.favorites = new Set(content.favorites);\n this.counts = content.counts ?? new Map();\n this.query = '';\n this.view = this.startView();\n this.build();\n this.searchEl?.focus();\n }\n\n close(giveFocusBack = true): void {\n if (!this.backdrop) return;\n this.backdrop.remove();\n this.backdrop = this.listEl = this.navEl = this.searchEl = this.liveEl = null;\n this.callbacks.onClose?.();\n if (giveFocusBack && this.returnFocus?.isConnected) this.returnFocus.focus();\n this.returnFocus = null;\n }\n\n isOpen(): boolean {\n return this.backdrop !== null;\n }\n\n /** The templates after one is saved or deleted; focus left on a deleted one goes to \"Save…\". */\n setTemplates(names: readonly string[]): void {\n this.content = { ...this.content, templates: [...names] };\n if (!this.listEl || this.view !== 'templates' || this.query) return;\n const hadFocus = this.listEl.contains(document.activeElement);\n this.renderList();\n if (hadFocus && !this.listEl.contains(document.activeElement)) {\n this.listEl.querySelector<HTMLButtonElement>('[data-tpl-save]')?.focus();\n }\n }\n\n /** How many of each indicator are on the chart, shown on their rows. */\n setCounts(counts: ReadonlyMap<string, number>): void {\n this.counts = counts;\n this.listEl?.querySelectorAll<HTMLElement>('.tcw-ip-row[data-ind]').forEach((row) => this.markCount(row));\n }\n\n destroy(): void {\n this.close(false);\n }\n\n private startView(): View {\n const views = this.views();\n if (this.lastView && views.includes(this.lastView) && !(this.lastView === 'favorites' && this.favorites.size === 0)) return this.lastView;\n return this.favorites.size > 0 ? 'favorites' : 'all';\n }\n\n /** The tabs down the side: favourites, templates, all, and each kind that has indicators. */\n private views(): View[] {\n const kinds = new Set(this.content.items.map((item) => item.category));\n return [\n 'favorites',\n ...(this.callbacks.templates && this.content.templates ? ['templates' as const] : []),\n 'all',\n ...INDICATOR_CATEGORIES.filter((category) => kinds.has(category)),\n ];\n }\n\n private viewLabel(view: View): string {\n if (view === 'favorites') return this.t('indicatorPicker.favorites');\n if (view === 'templates') return this.t('toolbar.indicators.templates');\n if (view === 'all') return this.t('toolbar.indicators.all');\n return this.t(`indicatorCategory.${view}` as MessageKey);\n }\n\n private build(): void {\n const backdrop = document.createElement('div');\n backdrop.className = 'tcw-modal-backdrop';\n backdrop.addEventListener('click', (e) => {\n if (e.target === backdrop) this.close();\n });\n const modal = document.createElement('div');\n modal.className = 'tcw-modal tcw-ip';\n modal.setAttribute('role', 'dialog');\n modal.setAttribute('aria-modal', 'true');\n modal.setAttribute('aria-labelledby', `tcw-ip-title-${this.uid}`);\n modal.tabIndex = -1;\n modal.addEventListener('keydown', (e) => this.onKeyDown(e, modal));\n\n const live = document.createElement('div');\n live.className = 'tcw-visually-hidden';\n live.setAttribute('aria-live', 'polite');\n modal.append(this.buildHeader(), this.buildSearch(), this.buildBody(), live);\n backdrop.appendChild(modal);\n this.layer().appendChild(backdrop);\n this.backdrop = backdrop;\n this.liveEl = live;\n this.renderNav();\n this.renderList();\n }\n\n private buildHeader(): HTMLDivElement {\n const header = document.createElement('div');\n header.className = 'tcw-modal-header';\n const title = document.createElement('h3');\n title.id = `tcw-ip-title-${this.uid}`;\n title.textContent = this.t('toolbar.indicators');\n const closeBtn = document.createElement('button');\n closeBtn.type = 'button';\n closeBtn.className = 'tcw-modal-close';\n closeBtn.setAttribute('aria-label', this.t('indicatorSettings.close'));\n closeBtn.innerHTML = createIcon('x', 16);\n closeBtn.addEventListener('click', () => this.close());\n header.append(title, closeBtn);\n return header;\n }\n\n private buildSearch(): HTMLLabelElement {\n const row = document.createElement('label');\n row.className = 'tcw-ip-search';\n const search = document.createElement('input');\n search.type = 'search';\n search.id = `tcw-ip-search-${this.uid}`;\n search.placeholder = this.t('indicatorPicker.search');\n search.setAttribute('aria-label', this.t('indicatorPicker.search'));\n search.autocomplete = 'off';\n search.spellcheck = false;\n search.addEventListener('input', () => {\n this.query = search.value;\n this.renderList();\n });\n search.addEventListener('keydown', (e) => this.onSearchKey(e));\n row.innerHTML = createIcon('search', 16);\n row.appendChild(search);\n this.searchEl = search;\n return row;\n }\n\n /** The groups down the side, and the list beside them. */\n private buildBody(): HTMLDivElement {\n const body = document.createElement('div');\n body.className = 'tcw-ip-body';\n const nav = document.createElement('div');\n nav.className = 'tcw-ip-nav';\n nav.setAttribute('role', 'tablist');\n nav.setAttribute('aria-orientation', 'vertical');\n nav.setAttribute('aria-label', this.t('indicatorPicker.groups'));\n nav.addEventListener('keydown', (e) => this.onNavKey(e));\n const list = document.createElement('div');\n list.className = 'tcw-ip-list';\n list.id = `tcw-ip-list-${this.uid}`;\n list.setAttribute('role', 'tabpanel');\n list.addEventListener('keydown', (e) => this.onListKey(e));\n list.addEventListener('focusin', (e) => this.rove(e.target));\n body.append(nav, list);\n this.navEl = nav;\n this.listEl = list;\n return body;\n }\n\n private renderNav(): void {\n const nav = this.navEl;\n if (!nav) return;\n nav.replaceChildren(...this.views().map((view) => {\n const tab = document.createElement('button');\n tab.type = 'button';\n tab.className = 'tcw-ip-tab';\n tab.id = `tcw-ip-tab-${this.uid}-${view}`;\n tab.dataset.view = view;\n tab.setAttribute('role', 'tab');\n tab.setAttribute('aria-controls', `tcw-ip-list-${this.uid}`);\n const icon = view === 'favorites' ? createIcon('star', 14) : view === 'templates' ? createIcon('layers', 14) : '';\n tab.innerHTML = `${icon}<span></span>`;\n tab.querySelector('span')!.textContent = this.viewLabel(view);\n if (view === 'templates') markPart(tab, 'indicatorTemplates');\n tab.addEventListener('click', () => this.select(view));\n return tab;\n }));\n this.markNav();\n }\n\n private markNav(): void {\n this.navEl?.querySelectorAll<HTMLButtonElement>('.tcw-ip-tab').forEach((tab) => {\n const on = tab.dataset.view === this.view;\n tab.setAttribute('aria-selected', String(on));\n tab.classList.toggle('tcw-active', on);\n tab.tabIndex = on ? 0 : -1;\n });\n this.labelList();\n }\n\n /** The list is named by its tab, or by the search while it shows the matches. */\n private labelList(): void {\n const searching = this.query.trim() !== '';\n this.listEl?.setAttribute('aria-labelledby', searching ? `tcw-ip-search-${this.uid}` : `tcw-ip-tab-${this.uid}-${this.view}`);\n }\n\n private select(view: View, focusTab = false): void {\n this.view = this.lastView = view;\n if (this.searchEl && this.query) this.searchEl.value = this.query = '';\n this.markNav();\n this.renderList();\n if (focusTab) this.navEl?.querySelector<HTMLButtonElement>(`[data-view=\"${view}\"]`)?.focus();\n }\n\n private renderList(): void {\n const list = this.listEl;\n if (!list) return;\n list.replaceChildren();\n const items = this.content.items;\n // While searching, the results come from every group: the one picked steps back.\n this.navEl?.classList.toggle('tcw-ip-nav--searching', this.query.trim() !== '');\n this.labelList();\n if (this.query.trim()) {\n const hits = searchIndicators(items, this.query);\n if (hits.length === 0) list.appendChild(this.note(fill(this.t('indicatorPicker.noMatch'), { query: this.query.trim() })));\n else list.append(...hits.map((item) => this.row(item)));\n } else if (this.view === 'templates') {\n this.renderTemplates(list);\n } else if (this.view === 'favorites') {\n const favorites = items.filter((item) => this.favorites.has(item.id)).sort(byName);\n if (favorites.length === 0) list.appendChild(this.note(this.t('indicatorPicker.noFavorites')));\n else list.append(...favorites.map((item) => this.row(item)));\n } else if (this.view === 'all') {\n for (const category of INDICATOR_CATEGORIES) {\n const group = items.filter((item) => item.category === category).sort(byName);\n if (group.length === 0) continue;\n const heading = document.createElement('div');\n heading.className = 'tcw-ip-heading';\n heading.textContent = this.viewLabel(category);\n list.append(heading, ...group.map((item) => this.row(item)));\n }\n } else {\n list.append(...items.filter((item) => item.category === this.view).sort(byName).map((item) => this.row(item)));\n }\n list.querySelector<HTMLButtonElement>('.tcw-ip-add')?.setAttribute('tabindex', '0');\n }\n\n private row(item: IndicatorPickerItem): HTMLDivElement {\n const row = document.createElement('div');\n row.className = 'tcw-ip-row';\n row.dataset.ind = item.id;\n const add = document.createElement('button');\n add.type = 'button';\n add.className = 'tcw-ip-add';\n add.dataset.ind = item.id;\n add.tabIndex = -1;\n const name = document.createElement('span');\n name.className = 'tcw-ip-name';\n name.textContent = item.name;\n add.appendChild(name);\n if (item.shortName && item.shortName !== item.name) add.appendChild(this.tag('tcw-ip-short', item.shortName));\n add.appendChild(this.tag('tcw-ip-kind', this.t(`indicatorType.${item.placement}`)));\n add.addEventListener('click', () => this.add(item));\n const star = document.createElement('button');\n star.type = 'button';\n star.className = 'tcw-ip-aside tcw-ip-star';\n star.tabIndex = -1;\n star.title = this.t('indicatorPicker.favorite');\n star.setAttribute('aria-label', `${this.t('indicatorPicker.favorite')}: ${item.name}`);\n star.setAttribute('aria-pressed', String(this.favorites.has(item.id)));\n star.innerHTML = createIcon('star', 14);\n star.addEventListener('click', () => this.toggleFavorite(item.id, star));\n row.append(add, star);\n this.markCount(row);\n return row;\n }\n\n private renderTemplates(list: HTMLElement): void {\n const save = document.createElement('button');\n save.type = 'button';\n save.className = 'tcw-ip-add tcw-ip-save';\n save.tabIndex = -1;\n save.dataset.tplSave = '';\n save.innerHTML = `${createIcon('save', 14)}<span class=\"tcw-ip-name\"></span>`;\n save.querySelector('span')!.textContent = this.t('templates.save');\n save.addEventListener('click', () => {\n this.close();\n this.callbacks.templates?.onSave();\n });\n list.appendChild(save);\n const names = this.content.templates ?? [];\n if (names.length === 0) list.appendChild(this.note(this.t('indicatorPicker.noTemplates')));\n for (const name of names) {\n const row = document.createElement('div');\n row.className = 'tcw-ip-row';\n const apply = document.createElement('button');\n apply.type = 'button';\n apply.className = 'tcw-ip-add';\n apply.tabIndex = -1;\n apply.dataset.tplApply = name;\n apply.innerHTML = `${createIcon('layers', 14)}<span class=\"tcw-ip-name\"></span>`;\n apply.querySelector('span')!.textContent = name;\n apply.addEventListener('click', () => {\n this.close();\n this.callbacks.templates?.onApply(name);\n });\n const remove = document.createElement('button');\n remove.type = 'button';\n remove.className = 'tcw-ip-aside tcw-ip-remove';\n remove.tabIndex = -1;\n remove.dataset.tplDelete = name;\n const label = fill(this.t('templates.delete'), { name });\n remove.title = label;\n remove.setAttribute('aria-label', label);\n remove.innerHTML = createIcon('x', 12);\n remove.addEventListener('click', () => this.callbacks.templates?.onDelete(name));\n row.append(apply, remove);\n list.appendChild(row);\n }\n }\n\n private add(item: IndicatorPickerItem): void {\n if (this.callbacks.onAdd(item.id) === false || !this.liveEl) return;\n const text = fill(this.t('indicatorPicker.added'), { name: item.name });\n // The same words again (a second RSI) are only read out when they change: a space tells them apart.\n this.liveEl.textContent = this.liveEl.textContent === text ? `${text}\\u00a0` : text;\n }\n\n private toggleFavorite(id: string, star: HTMLButtonElement): void {\n const on = !this.favorites.has(id);\n if (on) this.favorites.add(id);\n else this.favorites.delete(id);\n this.callbacks.onFavorite(id, on);\n star.setAttribute('aria-pressed', String(on));\n if (this.view !== 'favorites' || this.query || on) return;\n // Gone from the favourites: the keyboard moves to the next row's star.\n const stars = [...(this.listEl?.querySelectorAll<HTMLButtonElement>('.tcw-ip-star') ?? [])];\n const at = stars.indexOf(star);\n const hadFocus = document.activeElement === star;\n this.renderList();\n if (!hadFocus) return;\n const left = [...(this.listEl?.querySelectorAll<HTMLButtonElement>('.tcw-ip-star') ?? [])];\n (left[at] ?? left[left.length - 1] ?? this.searchEl)?.focus();\n }\n\n private markCount(row: HTMLElement): void {\n const n = this.counts.get(row.dataset.ind ?? '') ?? 0;\n const add = row.querySelector('.tcw-ip-add');\n let badge = row.querySelector<HTMLElement>('.tcw-ip-count');\n if (n === 0) {\n badge?.remove();\n return;\n }\n if (!badge) {\n badge = this.tag('tcw-ip-count', '');\n add?.appendChild(badge);\n }\n badge.textContent = String(n);\n badge.title = fill(this.t('indicatorPicker.onChart'), { count: String(n) });\n }\n\n private tag(className: string, text: string): HTMLSpanElement {\n const span = document.createElement('span');\n span.className = className;\n span.textContent = text;\n return span;\n }\n\n private note(text: string): HTMLDivElement {\n const el = document.createElement('div');\n el.className = 'tcw-ip-empty';\n el.textContent = text;\n return el;\n }\n\n /** One row in the list takes Tab: the one focused last. */\n private rove(target: EventTarget | null): void {\n if (!(target instanceof HTMLElement) || !target.classList.contains('tcw-ip-add')) return;\n this.listEl?.querySelectorAll<HTMLElement>('.tcw-ip-add[tabindex=\"0\"]').forEach((el) => {\n if (el !== target) el.tabIndex = -1;\n });\n target.tabIndex = 0;\n }\n\n private onKeyDown(e: KeyboardEvent, modal: HTMLElement): void {\n if (e.key === 'Escape') {\n e.preventDefault();\n e.stopPropagation();\n this.close();\n return;\n }\n keepTabInside(e, modal);\n }\n\n /** Down goes into the list; Enter adds the best match. */\n private onSearchKey(e: KeyboardEvent): void {\n const first = this.listEl?.querySelector<HTMLButtonElement>('.tcw-ip-add');\n if (e.key === 'ArrowDown' && first) {\n e.preventDefault();\n first.focus();\n } else if (e.key === 'Enter' && this.query.trim() && first) {\n e.preventDefault();\n first.click();\n }\n }\n\n /** Up and down through the tabs (Home, End), picking as they go. */\n private onNavKey(e: KeyboardEvent): void {\n const views = this.views();\n const at = views.indexOf(this.view);\n const next = e.key === 'ArrowDown' ? Math.min(views.length - 1, at + 1)\n : e.key === 'ArrowUp' ? Math.max(0, at - 1)\n : e.key === 'Home' ? 0\n : e.key === 'End' ? views.length - 1\n : -1;\n if (next < 0) return;\n e.preventDefault();\n this.select(views[next], true);\n }\n\n /** Up and down through the rows (from the top, back to the search); right to a row's star, left back. */\n private onListKey(e: KeyboardEvent): void {\n const target = e.target as HTMLElement;\n const row = target.closest('.tcw-ip-row, .tcw-ip-save');\n const adds = [...(this.listEl?.querySelectorAll<HTMLButtonElement>('.tcw-ip-add') ?? [])];\n const main = row?.classList.contains('tcw-ip-save') ? row as HTMLButtonElement : row?.querySelector<HTMLButtonElement>('.tcw-ip-add');\n const at = main ? adds.indexOf(main) : -1;\n let to: HTMLElement | null | undefined = null;\n if (e.key === 'ArrowDown') to = adds[at + 1];\n else if (e.key === 'ArrowUp') to = at > 0 ? adds[at - 1] : this.searchEl;\n else if (e.key === 'Home') to = adds[0];\n else if (e.key === 'End') to = adds[adds.length - 1];\n else if (e.key === 'ArrowRight' && target === main) to = row?.querySelector<HTMLButtonElement>('.tcw-ip-aside');\n else if (e.key === 'ArrowLeft' && target !== main) to = main;\n if (!to) return;\n e.preventDefault();\n to.focus();\n }\n}\n"],"mappings":"2JAQA,SAAS,EAAK,EAAsB,CAClC,OAAO,EAAK,UAAU,MAAM,CAAC,QAAQ,SAAU,GAAG,CAAC,aAAa,CAGlE,IAAM,EAAa,aAGnB,SAAS,EAAM,EAA2B,EAA0B,CAClE,IAAM,EAAQ,EAAK,EAAK,UAAU,CAC5B,EAAO,EAAK,EAAK,KAAK,CAK5B,OAJI,IAAU,GAAK,EAAK,EAAK,GAAG,GAAK,EAAU,EAC3C,EAAM,WAAW,EAAE,EAAI,EAAK,WAAW,EAAE,CAAS,EAClD,CAAC,GAAG,EAAK,MAAM,EAAW,CAAE,GAAG,EAAM,MAAM,EAAW,CAAC,CAAC,KAAM,GAAS,EAAK,WAAW,EAAE,CAAC,CAAS,EACnG,EAAK,SAAS,EAAE,EAAI,EAAM,SAAS,EAAE,CAAS,EAC3C,KAQT,SAAgB,EAAgD,EAAqB,EAAoB,CACvG,IAAM,EAAI,EAAK,EAAM,MAAM,CAAC,CAE5B,OADK,EACE,EACJ,IAAK,IAAU,CAAE,OAAM,KAAM,EAAM,EAAM,EAAE,CAAE,EAAE,CAC/C,OAAQ,GAA0C,EAAI,OAAS,KAAK,CACpE,MAAM,EAAG,IAAM,EAAE,KAAO,EAAE,MAAQ,EAAE,KAAK,KAAK,cAAc,EAAE,KAAK,KAAK,CAAC,CACzE,IAAK,GAAQ,EAAI,KAAK,CALV,EAAE,CCSnB,IAAI,EAAe,EAEb,GAAU,EAAwB,IAA2B,EAAE,KAAK,cAAc,EAAE,KAAK,CAQlF,EAAb,KAAmC,CACjC,SAA0C,KAC1C,OAAwC,KACxC,MAAuC,KACvC,SAA4C,KAC5C,OAAwC,KACxC,QAA0C,CAAE,MAAO,EAAE,CAAE,UAAW,EAAE,CAAE,UAAW,EAAE,CAAE,CACrF,UAAoB,IAAI,IACxB,OAA8C,IAAI,IAClD,KAAqB,MAErB,SAAgC,KAChC,MAAgB,GAChB,YAA0C,KAC1C,IAAuB,IAEvB,YACE,EACA,EACA,EACA,CAHiB,KAAA,MAAA,EACA,KAAA,UAAA,EACA,KAAA,EAAA,EAGnB,KAAK,EAAuC,CACtC,KAAK,UAAU,KAAK,MAAM,GAAM,CACpC,KAAK,YAAc,SAAS,yBAAyB,YAAc,SAAS,cAAgB,KAC5F,KAAK,QAAU,EACf,KAAK,UAAY,IAAI,IAAI,EAAQ,UAAU,CAC3C,KAAK,OAAS,EAAQ,QAAU,IAAI,IACpC,KAAK,MAAQ,GACb,KAAK,KAAO,KAAK,WAAW,CAC5B,KAAK,OAAO,CACZ,KAAK,UAAU,OAAO,CAGxB,MAAM,EAAgB,GAAY,CAC3B,KAAK,WACV,KAAK,SAAS,QAAQ,CACtB,KAAK,SAAW,KAAK,OAAS,KAAK,MAAQ,KAAK,SAAW,KAAK,OAAS,KACzE,KAAK,UAAU,WAAW,CACtB,GAAiB,KAAK,aAAa,aAAa,KAAK,YAAY,OAAO,CAC5E,KAAK,YAAc,MAGrB,QAAkB,CAChB,OAAO,KAAK,WAAa,KAI3B,aAAa,EAAgC,CAE3C,GADA,KAAK,QAAU,CAAE,GAAG,KAAK,QAAS,UAAW,CAAC,GAAG,EAAM,CAAE,CACrD,CAAC,KAAK,QAAU,KAAK,OAAS,aAAe,KAAK,MAAO,OAC7D,IAAM,EAAW,KAAK,OAAO,SAAS,SAAS,cAAc,CAC7D,KAAK,YAAY,CACb,GAAY,CAAC,KAAK,OAAO,SAAS,SAAS,cAAc,EAC3D,KAAK,OAAO,cAAiC,kBAAkB,EAAE,OAAO,CAK5E,UAAU,EAA2C,CACnD,KAAK,OAAS,EACd,KAAK,QAAQ,iBAA8B,wBAAwB,CAAC,QAAS,GAAQ,KAAK,UAAU,EAAI,CAAC,CAG3G,SAAgB,CACd,KAAK,MAAM,GAAM,CAGnB,WAA0B,CACxB,IAAM,EAAQ,KAAK,OAAO,CAE1B,OADI,KAAK,UAAY,EAAM,SAAS,KAAK,SAAS,EAAI,EAAE,KAAK,WAAa,aAAe,KAAK,UAAU,OAAS,GAAW,KAAK,SAC1H,KAAK,UAAU,KAAO,EAAI,YAAc,MAIjD,OAAwB,CACtB,IAAM,EAAQ,IAAI,IAAI,KAAK,QAAQ,MAAM,IAAK,GAAS,EAAK,SAAS,CAAC,CACtE,MAAO,CACL,YACA,GAAI,KAAK,UAAU,WAAa,KAAK,QAAQ,UAAY,CAAC,YAAqB,CAAG,EAAE,CACpF,MACA,GAAG,EAAA,qBAAqB,OAAQ,GAAa,EAAM,IAAI,EAAS,CAAC,CAClE,CAGH,UAAkB,EAAoB,CAIpC,OAHI,IAAS,YAAoB,KAAK,EAAE,4BAA4B,CAChE,IAAS,YAAoB,KAAK,EAAE,+BAA+B,CACnE,IAAS,MAAc,KAAK,EAAE,yBAAyB,CACpD,KAAK,EAAE,qBAAqB,IAAqB,CAG1D,OAAsB,CACpB,IAAM,EAAW,SAAS,cAAc,MAAM,CAC9C,EAAS,UAAY,qBACrB,EAAS,iBAAiB,QAAU,GAAM,CACpC,EAAE,SAAW,GAAU,KAAK,OAAO,EACvC,CACF,IAAM,EAAQ,SAAS,cAAc,MAAM,CAC3C,EAAM,UAAY,mBAClB,EAAM,aAAa,OAAQ,SAAS,CACpC,EAAM,aAAa,aAAc,OAAO,CACxC,EAAM,aAAa,kBAAmB,gBAAgB,KAAK,MAAM,CACjE,EAAM,SAAW,GACjB,EAAM,iBAAiB,UAAY,GAAM,KAAK,UAAU,EAAG,EAAM,CAAC,CAElE,IAAM,EAAO,SAAS,cAAc,MAAM,CAC1C,EAAK,UAAY,sBACjB,EAAK,aAAa,YAAa,SAAS,CACxC,EAAM,OAAO,KAAK,aAAa,CAAE,KAAK,aAAa,CAAE,KAAK,WAAW,CAAE,EAAK,CAC5E,EAAS,YAAY,EAAM,CAC3B,KAAK,OAAO,CAAC,YAAY,EAAS,CAClC,KAAK,SAAW,EAChB,KAAK,OAAS,EACd,KAAK,WAAW,CAChB,KAAK,YAAY,CAGnB,aAAsC,CACpC,IAAM,EAAS,SAAS,cAAc,MAAM,CAC5C,EAAO,UAAY,mBACnB,IAAM,EAAQ,SAAS,cAAc,KAAK,CAC1C,EAAM,GAAK,gBAAgB,KAAK,MAChC,EAAM,YAAc,KAAK,EAAE,qBAAqB,CAChD,IAAM,EAAW,SAAS,cAAc,SAAS,CAOjD,MANA,GAAS,KAAO,SAChB,EAAS,UAAY,kBACrB,EAAS,aAAa,aAAc,KAAK,EAAE,0BAA0B,CAAC,CACtE,EAAS,UAAY,EAAA,EAAW,IAAK,GAAG,CACxC,EAAS,iBAAiB,YAAe,KAAK,OAAO,CAAC,CACtD,EAAO,OAAO,EAAO,EAAS,CACvB,EAGT,aAAwC,CACtC,IAAM,EAAM,SAAS,cAAc,QAAQ,CAC3C,EAAI,UAAY,gBAChB,IAAM,EAAS,SAAS,cAAc,QAAQ,CAe9C,MAdA,GAAO,KAAO,SACd,EAAO,GAAK,iBAAiB,KAAK,MAClC,EAAO,YAAc,KAAK,EAAE,yBAAyB,CACrD,EAAO,aAAa,aAAc,KAAK,EAAE,yBAAyB,CAAC,CACnE,EAAO,aAAe,MACtB,EAAO,WAAa,GACpB,EAAO,iBAAiB,YAAe,CACrC,KAAK,MAAQ,EAAO,MACpB,KAAK,YAAY,EACjB,CACF,EAAO,iBAAiB,UAAY,GAAM,KAAK,YAAY,EAAE,CAAC,CAC9D,EAAI,UAAY,EAAA,EAAW,SAAU,GAAG,CACxC,EAAI,YAAY,EAAO,CACvB,KAAK,SAAW,EACT,EAIT,WAAoC,CAClC,IAAM,EAAO,SAAS,cAAc,MAAM,CAC1C,EAAK,UAAY,cACjB,IAAM,EAAM,SAAS,cAAc,MAAM,CACzC,EAAI,UAAY,aAChB,EAAI,aAAa,OAAQ,UAAU,CACnC,EAAI,aAAa,mBAAoB,WAAW,CAChD,EAAI,aAAa,aAAc,KAAK,EAAE,yBAAyB,CAAC,CAChE,EAAI,iBAAiB,UAAY,GAAM,KAAK,SAAS,EAAE,CAAC,CACxD,IAAM,EAAO,SAAS,cAAc,MAAM,CAS1C,MARA,GAAK,UAAY,cACjB,EAAK,GAAK,eAAe,KAAK,MAC9B,EAAK,aAAa,OAAQ,WAAW,CACrC,EAAK,iBAAiB,UAAY,GAAM,KAAK,UAAU,EAAE,CAAC,CAC1D,EAAK,iBAAiB,UAAY,GAAM,KAAK,KAAK,EAAE,OAAO,CAAC,CAC5D,EAAK,OAAO,EAAK,EAAK,CACtB,KAAK,MAAQ,EACb,KAAK,OAAS,EACP,EAGT,WAA0B,CACxB,IAAM,EAAM,KAAK,MACZ,IACL,EAAI,gBAAgB,GAAG,KAAK,OAAO,CAAC,IAAK,GAAS,CAChD,IAAM,EAAM,SAAS,cAAc,SAAS,CAY5C,MAXA,GAAI,KAAO,SACX,EAAI,UAAY,aAChB,EAAI,GAAK,cAAc,KAAK,IAAI,GAAG,IACnC,EAAI,QAAQ,KAAO,EACnB,EAAI,aAAa,OAAQ,MAAM,CAC/B,EAAI,aAAa,gBAAiB,eAAe,KAAK,MAAM,CAE5D,EAAI,UAAY,GADH,IAAS,YAAc,EAAA,EAAW,OAAQ,GAAG,CAAG,IAAS,YAAc,EAAA,EAAW,SAAU,GAAG,CAAG,GACvF,eACxB,EAAI,cAAc,OAAO,CAAE,YAAc,KAAK,UAAU,EAAK,CACzD,IAAS,aAAa,EAAA,EAAS,EAAK,qBAAqB,CAC7D,EAAI,iBAAiB,YAAe,KAAK,OAAO,EAAK,CAAC,CAC/C,GACP,CAAC,CACH,KAAK,SAAS,EAGhB,SAAwB,CACtB,KAAK,OAAO,iBAAoC,cAAc,CAAC,QAAS,GAAQ,CAC9E,IAAM,EAAK,EAAI,QAAQ,OAAS,KAAK,KACrC,EAAI,aAAa,gBAAiB,OAAO,EAAG,CAAC,CAC7C,EAAI,UAAU,OAAO,aAAc,EAAG,CACtC,EAAI,SAAW,EAAK,EAAI,IACxB,CACF,KAAK,WAAW,CAIlB,WAA0B,CACxB,IAAM,EAAY,KAAK,MAAM,MAAM,GAAK,GACxC,KAAK,QAAQ,aAAa,kBAAmB,EAAY,iBAAiB,KAAK,MAAQ,cAAc,KAAK,IAAI,GAAG,KAAK,OAAO,CAG/H,OAAe,EAAY,EAAW,GAAa,CACjD,KAAK,KAAO,KAAK,SAAW,EACxB,KAAK,UAAY,KAAK,QAAO,KAAK,SAAS,MAAQ,KAAK,MAAQ,IACpE,KAAK,SAAS,CACd,KAAK,YAAY,CACb,GAAU,KAAK,OAAO,cAAiC,eAAe,EAAK,IAAI,EAAE,OAAO,CAG9F,YAA2B,CACzB,IAAM,EAAO,KAAK,OAClB,GAAI,CAAC,EAAM,OACX,EAAK,iBAAiB,CACtB,IAAM,EAAQ,KAAK,QAAQ,MAI3B,GAFA,KAAK,OAAO,UAAU,OAAO,wBAAyB,KAAK,MAAM,MAAM,GAAK,GAAG,CAC/E,KAAK,WAAW,CACZ,KAAK,MAAM,MAAM,CAAE,CACrB,IAAM,EAAO,EAAiB,EAAO,KAAK,MAAM,CAC5C,EAAK,SAAW,EAAG,EAAK,YAAY,KAAK,KAAK,EAAA,EAAK,KAAK,EAAE,0BAA0B,CAAE,CAAE,MAAO,KAAK,MAAM,MAAM,CAAE,CAAC,CAAC,CAAC,CACpH,EAAK,OAAO,GAAG,EAAK,IAAK,GAAS,KAAK,IAAI,EAAK,CAAC,CAAC,SAC9C,KAAK,OAAS,YACvB,KAAK,gBAAgB,EAAK,SACjB,KAAK,OAAS,YAAa,CACpC,IAAM,EAAY,EAAM,OAAQ,GAAS,KAAK,UAAU,IAAI,EAAK,GAAG,CAAC,CAAC,KAAK,EAAO,CAC9E,EAAU,SAAW,EAAG,EAAK,YAAY,KAAK,KAAK,KAAK,EAAE,8BAA8B,CAAC,CAAC,CACzF,EAAK,OAAO,GAAG,EAAU,IAAK,GAAS,KAAK,IAAI,EAAK,CAAC,CAAC,SACnD,KAAK,OAAS,MACvB,IAAK,IAAM,KAAY,EAAA,qBAAsB,CAC3C,IAAM,EAAQ,EAAM,OAAQ,GAAS,EAAK,WAAa,EAAS,CAAC,KAAK,EAAO,CAC7E,GAAI,EAAM,SAAW,EAAG,SACxB,IAAM,EAAU,SAAS,cAAc,MAAM,CAC7C,EAAQ,UAAY,iBACpB,EAAQ,YAAc,KAAK,UAAU,EAAS,CAC9C,EAAK,OAAO,EAAS,GAAG,EAAM,IAAK,GAAS,KAAK,IAAI,EAAK,CAAC,CAAC,MAG9D,EAAK,OAAO,GAAG,EAAM,OAAQ,GAAS,EAAK,WAAa,KAAK,KAAK,CAAC,KAAK,EAAO,CAAC,IAAK,GAAS,KAAK,IAAI,EAAK,CAAC,CAAC,CAEhH,EAAK,cAAiC,cAAc,EAAE,aAAa,WAAY,IAAI,CAGrF,IAAY,EAA2C,CACrD,IAAM,EAAM,SAAS,cAAc,MAAM,CACzC,EAAI,UAAY,aAChB,EAAI,QAAQ,IAAM,EAAK,GACvB,IAAM,EAAM,SAAS,cAAc,SAAS,CAC5C,EAAI,KAAO,SACX,EAAI,UAAY,aAChB,EAAI,QAAQ,IAAM,EAAK,GACvB,EAAI,SAAW,GACf,IAAM,EAAO,SAAS,cAAc,OAAO,CAC3C,EAAK,UAAY,cACjB,EAAK,YAAc,EAAK,KACxB,EAAI,YAAY,EAAK,CACjB,EAAK,WAAa,EAAK,YAAc,EAAK,MAAM,EAAI,YAAY,KAAK,IAAI,eAAgB,EAAK,UAAU,CAAC,CAC7G,EAAI,YAAY,KAAK,IAAI,cAAe,KAAK,EAAE,iBAAiB,EAAK,YAAY,CAAC,CAAC,CACnF,EAAI,iBAAiB,YAAe,KAAK,IAAI,EAAK,CAAC,CACnD,IAAM,EAAO,SAAS,cAAc,SAAS,CAW7C,MAVA,GAAK,KAAO,SACZ,EAAK,UAAY,2BACjB,EAAK,SAAW,GAChB,EAAK,MAAQ,KAAK,EAAE,2BAA2B,CAC/C,EAAK,aAAa,aAAc,GAAG,KAAK,EAAE,2BAA2B,CAAC,IAAI,EAAK,OAAO,CACtF,EAAK,aAAa,eAAgB,OAAO,KAAK,UAAU,IAAI,EAAK,GAAG,CAAC,CAAC,CACtE,EAAK,UAAY,EAAA,EAAW,OAAQ,GAAG,CACvC,EAAK,iBAAiB,YAAe,KAAK,eAAe,EAAK,GAAI,EAAK,CAAC,CACxE,EAAI,OAAO,EAAK,EAAK,CACrB,KAAK,UAAU,EAAI,CACZ,EAGT,gBAAwB,EAAyB,CAC/C,IAAM,EAAO,SAAS,cAAc,SAAS,CAC7C,EAAK,KAAO,SACZ,EAAK,UAAY,yBACjB,EAAK,SAAW,GAChB,EAAK,QAAQ,QAAU,GACvB,EAAK,UAAY,GAAG,EAAA,EAAW,OAAQ,GAAG,CAAC,mCAC3C,EAAK,cAAc,OAAO,CAAE,YAAc,KAAK,EAAE,iBAAiB,CAClE,EAAK,iBAAiB,YAAe,CACnC,KAAK,OAAO,CACZ,KAAK,UAAU,WAAW,QAAQ,EAClC,CACF,EAAK,YAAY,EAAK,CACtB,IAAM,EAAQ,KAAK,QAAQ,WAAa,EAAE,CACtC,EAAM,SAAW,GAAG,EAAK,YAAY,KAAK,KAAK,KAAK,EAAE,8BAA8B,CAAC,CAAC,CAC1F,IAAK,IAAM,KAAQ,EAAO,CACxB,IAAM,EAAM,SAAS,cAAc,MAAM,CACzC,EAAI,UAAY,aAChB,IAAM,EAAQ,SAAS,cAAc,SAAS,CAC9C,EAAM,KAAO,SACb,EAAM,UAAY,aAClB,EAAM,SAAW,GACjB,EAAM,QAAQ,SAAW,EACzB,EAAM,UAAY,GAAG,EAAA,EAAW,SAAU,GAAG,CAAC,mCAC9C,EAAM,cAAc,OAAO,CAAE,YAAc,EAC3C,EAAM,iBAAiB,YAAe,CACpC,KAAK,OAAO,CACZ,KAAK,UAAU,WAAW,QAAQ,EAAK,EACvC,CACF,IAAM,EAAS,SAAS,cAAc,SAAS,CAC/C,EAAO,KAAO,SACd,EAAO,UAAY,6BACnB,EAAO,SAAW,GAClB,EAAO,QAAQ,UAAY,EAC3B,IAAM,EAAQ,EAAA,EAAK,KAAK,EAAE,mBAAmB,CAAE,CAAE,OAAM,CAAC,CACxD,EAAO,MAAQ,EACf,EAAO,aAAa,aAAc,EAAM,CACxC,EAAO,UAAY,EAAA,EAAW,IAAK,GAAG,CACtC,EAAO,iBAAiB,YAAe,KAAK,UAAU,WAAW,SAAS,EAAK,CAAC,CAChF,EAAI,OAAO,EAAO,EAAO,CACzB,EAAK,YAAY,EAAI,EAIzB,IAAY,EAAiC,CAC3C,GAAI,KAAK,UAAU,MAAM,EAAK,GAAG,GAAK,IAAS,CAAC,KAAK,OAAQ,OAC7D,IAAM,EAAO,EAAA,EAAK,KAAK,EAAE,wBAAwB,CAAE,CAAE,KAAM,EAAK,KAAM,CAAC,CAEvE,KAAK,OAAO,YAAc,KAAK,OAAO,cAAgB,EAAO,GAAG,EAAK,QAAU,EAGjF,eAAuB,EAAY,EAA+B,CAChE,IAAM,EAAK,CAAC,KAAK,UAAU,IAAI,EAAG,CAKlC,GAJI,EAAI,KAAK,UAAU,IAAI,EAAG,CACzB,KAAK,UAAU,OAAO,EAAG,CAC9B,KAAK,UAAU,WAAW,EAAI,EAAG,CACjC,EAAK,aAAa,eAAgB,OAAO,EAAG,CAAC,CACzC,KAAK,OAAS,aAAe,KAAK,OAAS,EAAI,OAGnD,IAAM,EAAK,CADI,GAAI,KAAK,QAAQ,iBAAoC,eAAe,EAAI,EAAE,CAC9E,CAAM,QAAQ,EAAK,CACxB,EAAW,SAAS,gBAAkB,EAE5C,GADA,KAAK,YAAY,CACb,CAAC,EAAU,OACf,IAAM,EAAO,CAAC,GAAI,KAAK,QAAQ,iBAAoC,eAAe,EAAI,EAAE,CAAE,EACzF,EAAK,IAAO,EAAK,EAAK,OAAS,IAAM,KAAK,WAAW,OAAO,CAG/D,UAAkB,EAAwB,CACxC,IAAM,EAAI,KAAK,OAAO,IAAI,EAAI,QAAQ,KAAO,GAAG,EAAI,EAC9C,EAAM,EAAI,cAAc,cAAc,CACxC,EAAQ,EAAI,cAA2B,gBAAgB,CAC3D,GAAI,IAAM,EAAG,CACX,GAAO,QAAQ,CACf,OAEG,IACH,EAAQ,KAAK,IAAI,eAAgB,GAAG,CACpC,GAAK,YAAY,EAAM,EAEzB,EAAM,YAAc,OAAO,EAAE,CAC7B,EAAM,MAAQ,EAAA,EAAK,KAAK,EAAE,0BAA0B,CAAE,CAAE,MAAO,OAAO,EAAE,CAAE,CAAC,CAG7E,IAAY,EAAmB,EAA+B,CAC5D,IAAM,EAAO,SAAS,cAAc,OAAO,CAG3C,MAFA,GAAK,UAAY,EACjB,EAAK,YAAc,EACZ,EAGT,KAAa,EAA8B,CACzC,IAAM,EAAK,SAAS,cAAc,MAAM,CAGxC,MAFA,GAAG,UAAY,eACf,EAAG,YAAc,EACV,EAIT,KAAa,EAAkC,CACzC,EAAE,aAAkB,cAAgB,CAAC,EAAO,UAAU,SAAS,aAAa,GAChF,KAAK,QAAQ,iBAA8B,4BAA4B,CAAC,QAAS,GAAO,CAClF,IAAO,IAAQ,EAAG,SAAW,KACjC,CACF,EAAO,SAAW,GAGpB,UAAkB,EAAkB,EAA0B,CAC5D,GAAI,EAAE,MAAQ,SAAU,CACtB,EAAE,gBAAgB,CAClB,EAAE,iBAAiB,CACnB,KAAK,OAAO,CACZ,OAEF,EAAA,EAAc,EAAG,EAAM,CAIzB,YAAoB,EAAwB,CAC1C,IAAM,EAAQ,KAAK,QAAQ,cAAiC,cAAc,CACtE,EAAE,MAAQ,aAAe,GAC3B,EAAE,gBAAgB,CAClB,EAAM,OAAO,EACJ,EAAE,MAAQ,SAAW,KAAK,MAAM,MAAM,EAAI,IACnD,EAAE,gBAAgB,CAClB,EAAM,OAAO,EAKjB,SAAiB,EAAwB,CACvC,IAAM,EAAQ,KAAK,OAAO,CACpB,EAAK,EAAM,QAAQ,KAAK,KAAK,CAC7B,EAAO,EAAE,MAAQ,YAAc,KAAK,IAAI,EAAM,OAAS,EAAG,EAAK,EAAE,CACnE,EAAE,MAAQ,UAAY,KAAK,IAAI,EAAG,EAAK,EAAE,CACzC,EAAE,MAAQ,OAAS,EACnB,EAAE,MAAQ,MAAQ,EAAM,OAAS,EACjC,GACA,EAAO,IACX,EAAE,gBAAgB,CAClB,KAAK,OAAO,EAAM,GAAO,GAAK,EAIhC,UAAkB,EAAwB,CACxC,IAAM,EAAS,EAAE,OACX,EAAM,EAAO,QAAQ,4BAA4B,CACjD,EAAO,CAAC,GAAI,KAAK,QAAQ,iBAAoC,cAAc,EAAI,EAAE,CAAE,CACnF,EAAO,GAAK,UAAU,SAAS,cAAc,CAAG,EAA2B,GAAK,cAAiC,cAAc,CAC/H,EAAK,EAAO,EAAK,QAAQ,EAAK,CAAG,GACnC,EAAqC,KACrC,EAAE,MAAQ,YAAa,EAAK,EAAK,EAAK,GACjC,EAAE,MAAQ,UAAW,EAAK,EAAK,EAAI,EAAK,EAAK,GAAK,KAAK,SACvD,EAAE,MAAQ,OAAQ,EAAK,EAAK,GAC5B,EAAE,MAAQ,MAAO,EAAK,EAAK,EAAK,OAAS,GACzC,EAAE,MAAQ,cAAgB,IAAW,EAAM,EAAK,GAAK,cAAiC,gBAAgB,CACtG,EAAE,MAAQ,aAAe,IAAW,IAAM,EAAK,GACnD,IACL,EAAE,gBAAgB,CAClB,EAAG,OAAO"}
@@ -0,0 +1,2 @@
1
+ const e=require(`./focusTrap-DrhhmG0V.cjs`),t=require(`./settingsControls-Tn8_vJJv.cjs`),n=require(`./i18n-C2nQBZTY.cjs`),r=require(`./indicatorInputWords-CTA4IcJR.cjs`),i=require(`./indicatorSettingsTabs-B0liE3ST.cjs`);let a=require(`@tradecanvas/commons`),o=require(`@tradecanvas/core`);var s=1,c=1.5;function l(e,t){return{visible:!0,lineStyle:e?.find(e=>e.key===t)?.lineStyle??`solid`}}var u=(e,t)=>JSON.stringify(e)===JSON.stringify(t),d=e=>JSON.parse(JSON.stringify(e));function f(e){return e.replace(/_/g,` `).replace(/([a-z0-9])([A-Z])/g,`$1 $2`).replace(/^./,e=>e.toUpperCase())}function p(e,t,n){return Object.fromEntries([...new Set([...Object.keys(e),...Object.keys(t)])].map(e=>[e,{...l(n,e),...t[e]}]))}var m=class{backdrop;modal;bodyEl;titleEl;tabsEl;defaultsBtn;menu=null;callbacks;target=null;draft={};initial=null;look={colors:[],lineWidth:c,plots:{},levels:[],intervals:null};tab=`inputs`;tabButtons=[];returnFocus=null;uid=s++;t;constructor(t,r,i){this.callbacks=r,this.t=i??(e=>n.c[e]),this.backdrop=document.createElement(`div`),this.backdrop.className=`tcw-modal-backdrop`,this.backdrop.hidden=!0;let a=!1;this.backdrop.addEventListener(`pointerdown`,e=>{a=e.target===this.backdrop}),this.backdrop.addEventListener(`click`,e=>{e.target===this.backdrop&&a&&this.finish(!1),a=!1}),this.modal=document.createElement(`div`),this.modal.className=`tcw-modal tcw-modal-narrow tcw-indi`,this.modal.setAttribute(`role`,`dialog`),this.modal.setAttribute(`aria-modal`,`true`),this.modal.setAttribute(`aria-labelledby`,`tcw-indi-title-${this.uid}`),this.modal.tabIndex=-1,this.modal.addEventListener(`keydown`,e=>this.onKeyDown(e));let o=document.createElement(`div`);o.className=`tcw-modal-header`,this.titleEl=document.createElement(`h3`),this.titleEl.id=`tcw-indi-title-${this.uid}`;let s=document.createElement(`button`);s.type=`button`,s.className=`tcw-modal-close`,s.setAttribute(`aria-label`,this.t(`indicatorSettings.close`)),s.innerHTML=e.i(`x`,16),s.addEventListener(`click`,()=>this.finish(!1)),o.append(this.titleEl,s),this.tabsEl=document.createElement(`div`),this.tabsEl.className=`tcw-modal-tabs`,this.tabsEl.setAttribute(`role`,`tablist`),this.tabsEl.addEventListener(`keydown`,e=>this.onTabKey(e)),this.bodyEl=document.createElement(`div`),this.bodyEl.className=`tcw-modal-body`,this.bodyEl.id=`tcw-indi-panel-${this.uid}`,this.bodyEl.setAttribute(`role`,`tabpanel`),this.defaultsBtn=this.footerButton(`defaults`,`tcw-footer-menu`,()=>this.openDefaults()),this.modal.append(o,this.tabsEl,this.bodyEl,this.buildFooter()),this.backdrop.appendChild(this.modal),t.appendChild(this.backdrop)}buildFooter(){this.defaultsBtn.setAttribute(`aria-haspopup`,`menu`),this.defaultsBtn.setAttribute(`aria-expanded`,`false`),this.defaultsBtn.innerHTML=`<span></span>${e.i(`chevronDown`,12)}`,this.defaultsBtn.querySelector(`span`).textContent=this.t(`indicatorSettings.defaults`);let t=document.createElement(`div`);t.className=`tcw-footer-end`,t.append(this.footerButton(`cancel`,`tcw-footer-cancel`,()=>this.finish(!1),this.t(`common.cancel`)),this.footerButton(`ok`,`tcw-done-btn`,()=>this.finish(!0),this.t(`drawingSettings.ok`)));let n=document.createElement(`div`);return n.className=`tcw-modal-footer`,n.append(this.defaultsBtn,t),n}open(e){this.target&&this.finish(!0),this.backdrop.hidden&&(this.returnFocus=document.activeElement instanceof HTMLElement?document.activeElement:null),this.target=e,this.titleEl.textContent=this.t(`indicatorSettings.title`).replace(`{name}`,e.name),this.draft={},this.look={colors:[...e.colors??[]],lineWidth:e.lineWidth??c,plots:Object.fromEntries(Object.entries(e.plotStyles??{}).map(([e,t])=>[e,{...t}])),levels:[...e.levels??[]],scale:e.scale,intervals:e.intervals??null},this.initial={params:this.currentParams(),look:d(this.look)},this.tab=`inputs`,this.renderTabs(),this.renderBody(),this.backdrop.hidden=!1;let t=this.tabsEl.hidden?null:this.tabButtons[0]??null;(this.bodyEl.querySelector(`button:not([disabled]), input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex="-1"])`)??t??this.modal).focus()}close(){this.finish(!0)}isOpen(){return!this.backdrop.hidden}destroy(){this.menu?.destroy(),this.backdrop.remove()}finish(e){if(this.target){if(e){let e=document.activeElement;(e instanceof HTMLInputElement||e instanceof HTMLSelectElement)&&this.modal.contains(e)&&e.dispatchEvent(new Event(`change`))}else this.revert();this.target=null,this.menu?.close(),this.backdrop.hidden=!0,this.callbacks.onClose(),this.returnFocus?.isConnected&&this.returnFocus.focus(),this.returnFocus=null}}revert(){let e=this.target,t=this.initial;if(!e||!t)return;let n=e.instanceId;u(this.currentParams(),t.params)||this.callbacks.onApply(n,{...t.params});let r=t.look,i={};u(this.look.colors,r.colors)||(i.colors=[...r.colors]),this.look.lineWidth!==r.lineWidth&&(i.lineWidths=[r.lineWidth]),u(this.look.plots,r.plots)||(i.plots=p(this.look.plots,r.plots,e.plots)),Object.keys(i).length>0&&this.callbacks.onStyle?.(n,i),u(this.look.levels,r.levels)||this.callbacks.onLevels?.(n,e.ownLevels?[...r.levels]:null),this.look.scale!==r.scale&&r.scale&&this.callbacks.onScale?.(n,r.scale),u(this.look.intervals,r.intervals)||this.callbacks.onIntervals?.(n,r.intervals)}currentParams(){let e=this.target;return e?{...e.defaults,...e.params,...this.draft}:{}}tabs(){let e=this.target;if(!e)return[];let t=!!(e.plots?.length&&e.colors&&this.callbacks.onStyle),n=!!(e.levels&&this.callbacks.onLevels);return[`inputs`,...t||n?[`style`]:[],...this.callbacks.onIntervals?[`visibility`]:[]]}renderTabs(){let e=this.tabs();this.tabsEl.hidden=e.length<2,this.tabButtons=e.map(e=>{let t=document.createElement(`button`);return t.type=`button`,t.className=`tcw-modal-tab`,t.id=`tcw-indi-tab-${this.uid}-${e}`,t.setAttribute(`role`,`tab`),t.setAttribute(`aria-controls`,this.bodyEl.id),t.dataset.tab=e,t.textContent=this.t(`indicatorSettings.tab.${e}`),t.addEventListener(`click`,()=>this.selectTab(e)),t}),this.tabsEl.replaceChildren(...this.tabButtons),this.markTabs()}markTabs(){for(let e of this.tabButtons){let t=e.dataset.tab===this.tab;e.classList.toggle(`tcw-active`,t),e.setAttribute(`aria-selected`,String(t)),e.tabIndex=t?0:-1,t&&this.bodyEl.setAttribute(`aria-labelledby`,e.id)}}selectTab(e,t=!1){this.tab=e,this.markTabs(),this.renderBody(),t&&this.tabButtons.find(t=>t.dataset.tab===e)?.focus()}onTabKey(e){let t=this.tabButtons.map(e=>e.dataset.tab),n=t.indexOf(this.tab),[r,i]=this.modal.closest(`[dir]`)?.getAttribute(`dir`)===`rtl`?[`ArrowLeft`,`ArrowRight`]:[`ArrowRight`,`ArrowLeft`],a=e.key===r?(n+1)%t.length:e.key===i?(n-1+t.length)%t.length:e.key===`Home`?0:e.key===`End`?t.length-1:-1;a<0||t.length===0||(e.preventDefault(),this.selectTab(t[a],!0))}onKeyDown(t){if(t.key===`Escape`){t.preventDefault(),t.stopPropagation(),this.finish(!1);return}e.n(t,this.modal)}renderBody(){this.bodyEl.replaceChildren();let e=this.target;e&&(this.tab===`style`?this.renderStyle(e):this.tab===`visibility`?i.n(this.bodyEl,this.look.intervals,e=>this.setIntervals(e),this.t):this.renderInputs())}currentValue(e){if(e in this.draft)return this.draft[e];let t=this.target?.params[e],n=this.target?.defaults[e];return t??n}setValue(e,t){this.target&&(this.draft[e]=t,this.callbacks.onApply(this.target.instanceId,{...this.draft}))}renderInputs(){let e=document.createElement(`div`);e.className=`tcw-settings-section`;let t=(0,o.describeInputs)({defaultConfig:this.target.defaults,inputs:this.target.inputs});t.length===0&&e.appendChild(this.note(this.t(`indicatorSettings.noInputs`)));for(let n of t)e.appendChild(this.fieldRow(n));this.bodyEl.appendChild(e)}fieldRow(e){let n=e.name,i=this.currentValue(n),a;switch(e.kind){case`source`:a=this.sourceControl(n,String(i??`close`));break;case`choice`:a=this.choiceControl(n,e.options??[],i);break;case`boolean`:a=t.u(!!i,e=>this.setValue(n,e));break;case`int`:case`number`:a=t.i(Number(i),{min:e.min,max:e.max,step:e.step,whole:e.kind===`int`},e=>this.setValue(n,e));break;case`color`:a=t.r(String(i),e=>this.setValue(n,e));break;default:a=this.textControl(n,String(i??``))}return t.s(e.label??r.n(n,this.t)??f(n),a)}sourceControl(e,t){let n=document.createElement(`select`);n.className=`tcw-indi-input tcw-indi-select`;let r=document.createElement(`optgroup`);r.label=this.t(`indicatorSettings.source.prices`);for(let e of a.PRICE_SOURCES)r.appendChild(this.option(e,e));n.appendChild(r);let i=this.target?.lineSources??[];if(i.length){let e=document.createElement(`optgroup`);e.label=this.t(`indicatorSettings.source.indicators`);for(let t of i)e.appendChild(this.option(t.value,t.label));n.appendChild(e)}return n.value=t,n.value!==t&&(n.value=`close`),n.addEventListener(`change`,()=>this.setValue(e,n.value)),n}choiceControl(e,t,n){let i=document.createElement(`select`);i.className=`tcw-indi-input tcw-indi-select`;for(let n of t)i.appendChild(this.option(String(n),r.t(e,n,this.t)??String(n)));return i.value=String(n),i.addEventListener(`change`,()=>{let n=t.find(e=>String(e)===i.value);n!==void 0&&this.setValue(e,n)}),i}textControl(e,t){let n=document.createElement(`input`);return n.type=`text`,n.className=`tcw-indi-input`,n.value=t,n.addEventListener(`change`,()=>this.setValue(e,n.value)),n}renderStyle(e){let t=e.instanceId,n=()=>this.target===e,r=e=>n()&&this.callbacks.onStyle?.(t,e),a=e=>n()&&this.callbacks.onLevels?.(t,e),o=e=>n()&&this.callbacks.onScale?.(t,e);i.t(this.bodyEl,{plots:e.colors&&this.callbacks.onStyle?e.plots??[]:[],colors:this.look.colors,plotStyles:this.look.plots,lineWidth:this.look.lineWidth,scale:e.scale&&this.callbacks.onScale?this.look.scale:void 0,levels:e.levels&&this.callbacks.onLevels?this.look.levels:void 0},{color:(e,t)=>{if(!n())return;let i=[...this.look.colors];for(;i.length<=e;)i.push(i[0]??t);i[e]=t,this.look.colors=i,r({colors:[...i]})},plot:(e,t)=>{n()&&(this.look.plots={...this.look.plots,[e]:{...this.look.plots[e],...t}},r({plots:{[e]:t}}))},lineWidth:e=>{n()&&(this.look.lineWidth=e,r({lineWidths:[e]}))},scale:e=>{n()&&(this.look.scale=e,o(e))},levels:e=>{n()&&(this.look.levels=e,a([...e]))}},this.t,this.uid)}setIntervals(e){this.target&&(this.look.intervals=e,this.callbacks.onIntervals?.(this.target.instanceId,e))}openDefaults(){if(this.menu?.isOpen()){this.menu.close();return}this.menu??=new t.f(this.modal,this.t(`indicatorSettings.defaults`));let e=this.defaultsBtn.getBoundingClientRect(),n=this.modal.getBoundingClientRect(),r=[{id:`reset`,label:this.t(`indicatorSettings.resetSettings`),icon:`reset`},...this.callbacks.onSaveDefault?[{id:`save`,label:this.t(`indicatorSettings.saveAsDefault`),icon:`save`}]:[]];this.menu.open(r,e.left-n.left,e.top-n.top,e=>{e===`reset`?this.resetAll():this.saveDefault()},this.defaultsBtn)}resetAll(){let e=this.target;if(!e)return;let t=e.instanceId,n={...e.defaults};this.draft={...n},this.callbacks.onApply(t,{...n}),Object.keys(this.look.plots).length>0&&(this.callbacks.onStyle?.(t,{plots:p(this.look.plots,{},e.plots)}),this.look.plots={}),e.levels&&this.callbacks.onLevels&&(this.callbacks.onLevels(t,null),this.look.levels=[...e.defaultLevels??[]]),this.look.intervals&&this.setIntervals(null),this.renderBody()}saveDefault(){let e=this.target;if(!e)return;let t=Object.fromEntries(Object.entries(this.currentParams()).filter(([t,n])=>!u(n,e.defaults[t])&&!(typeof n==`string`&&(0,a.parseIndicatorSource)(n)))),n={};(0,o.isSeriesPalette)(this.look.colors)||(n.colors=[...this.look.colors]),this.look.lineWidth!==c&&(n.lineWidths=[this.look.lineWidth]),Object.keys(this.look.plots).length>0&&(n.plots=d(this.look.plots)),this.callbacks.onSaveDefault?.(e.indicatorId,{params:t,style:n})}footerButton(e,t,n,r){let i=document.createElement(`button`);return i.type=`button`,i.className=t,i.dataset.act=e,r&&(i.textContent=r),i.addEventListener(`click`,n),i}note(e){let t=document.createElement(`div`);return t.className=`tcw-settings-label`,t.textContent=e,t}option(e,t){let n=document.createElement(`option`);return n.value=e,n.textContent=t,n}};exports.WidgetIndicatorSettings=m;
2
+ //# sourceMappingURL=WidgetIndicatorSettings-C7vpco6e.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"WidgetIndicatorSettings-C7vpco6e.cjs","names":[],"sources":["../src/widget/WidgetIndicatorSettings.ts"],"sourcesContent":["import type { IndicatorInputSpec, IndicatorIntervals, IndicatorPlot, IndicatorPlotStyle, IndicatorStyleConfig } from '@tradecanvas/commons';\nimport { PRICE_SOURCES, parseIndicatorSource } from '@tradecanvas/commons';\nimport { describeInputs, isSeriesPalette, type IndicatorInputInfo } from '@tradecanvas/core';\nimport { FOCUSABLE, keepTabInside } from './focusTrap.js';\nimport { createIcon } from './icons.js';\nimport { EN_MESSAGES, type Translator } from './i18n.js';\nimport { WidgetContextMenu } from './WidgetContextMenu.js';\nimport { colorInput, numberInput, settingsRow, toggleSwitch } from './settingsControls.js';\nimport { renderStyleTab, renderVisibilityTab } from './indicatorSettingsTabs.js';\nimport { choiceLabel, inputLabel } from './indicatorInputWords.js';\n\nexport type IndicatorParamValue = number | string | boolean;\n\nexport interface IndicatorSettingsTarget {\n instanceId: string;\n /** The kind of indicator (`'rsi'`): what \"Save as default\" is for. */\n indicatorId: string;\n name: string;\n /** Default config — defines the editable keys and their types. */\n defaults: Record<string, unknown>;\n /** Current values, overlaid on defaults. */\n params: Record<string, unknown>;\n /** How parameters are edited (the descriptor's `inputs`). */\n inputs?: Readonly<Record<string, IndicatorInputSpec>>;\n /** Other indicators' lines a source can be: value `ind:<instanceId>:<key>`. */\n lineSources?: readonly { value: string; label: string }[];\n /** What it draws; with `colors`, the lines of the Style tab. */\n plots?: readonly IndicatorPlot[];\n colors?: readonly string[];\n lineWidth?: number;\n /** Each line's own dash and visibility, by its key. */\n plotStyles?: Readonly<Record<string, IndicatorPlotStyle>>;\n /** Its levels and its indicator's defaults; with them, the levels under the lines. */\n levels?: readonly number[];\n defaultLevels?: readonly number[];\n /** Whether `levels` are the instance's own; not: its indicator's defaults, which Cancel leaves it on. */\n ownLevels?: boolean;\n /** The price scale an overlay is on; set only for price-pane overlays. */\n scale?: 'right' | 'left';\n /** The intervals it shows on; unset or null: all of them. */\n intervals?: IndicatorIntervals | null;\n}\n\n/** What \"Save as default\" keeps: new indicators of the kind start with it. */\nexport interface IndicatorDefault {\n params: Record<string, IndicatorParamValue>;\n style: IndicatorStyleConfig;\n}\n\nexport interface IndicatorSettingsCallbacks {\n onApply: (instanceId: string, params: Record<string, IndicatorParamValue>) => void;\n onStyle?: (instanceId: string, style: IndicatorStyleConfig) => void;\n /** `null` restores the indicator's default levels. */\n onLevels?: (instanceId: string, levels: number[] | null) => void;\n /** Move an overlay to the left price scale or back to the price scale. */\n onScale?: (instanceId: string, scale: 'right' | 'left') => void;\n /** The intervals it shows on (`null`: all); with it, the Visibility tab. */\n onIntervals?: (instanceId: string, intervals: IndicatorIntervals | null) => void;\n /** \"Save as default\" in the Defaults menu. */\n onSaveDefault?: (indicatorId: string, saved: IndicatorDefault) => void;\n onClose: () => void;\n}\n\ntype Tab = 'inputs' | 'style' | 'visibility';\n\n/** What the dialog changes besides the inputs: as it was on opening, and as it is. */\ninterface Look {\n colors: string[];\n lineWidth: number;\n plots: Record<string, IndicatorPlotStyle>;\n levels: number[];\n scale?: 'right' | 'left';\n intervals: IndicatorIntervals | null;\n}\n\n/** Ids for the dialog's labelling (several widgets may share a page). */\nlet nextDialogId = 1;\n\nconst DEFAULT_LINE_WIDTH = 1.5;\n/** A line as it is with no style of its own: shown, and dashed as its indicator declares it. */\nfunction plainPlot(plots: readonly IndicatorPlot[] | undefined, key: string): IndicatorPlotStyle {\n return { visible: true, lineStyle: plots?.find((p) => p.key === key)?.lineStyle ?? 'solid' };\n}\n\nconst same = (a: unknown, b: unknown) => JSON.stringify(a) === JSON.stringify(b);\n/** A copy of plain values (numbers, strings, flags, lists of them). */\nconst copy = <T>(value: T): T => JSON.parse(JSON.stringify(value)) as T;\n\nfunction titleCase(key: string): string {\n // camelCase / snake_case → \"Title Case\"\n return key\n .replace(/_/g, ' ')\n .replace(/([a-z0-9])([A-Z])/g, '$1 $2')\n .replace(/^./, (c) => c.toUpperCase());\n}\n\n/** Each line put back as `to` has it; one `to` has no style for goes back to plain. */\nfunction plotsBack(\n from: Record<string, IndicatorPlotStyle>,\n to: Record<string, IndicatorPlotStyle>,\n plots: readonly IndicatorPlot[] | undefined,\n): Record<string, IndicatorPlotStyle> {\n return Object.fromEntries([...new Set([...Object.keys(from), ...Object.keys(to)])].map((key) => [key, { ...plainPlot(plots, key), ...to[key] }]));\n}\n\n/**\n * Per-indicator settings: its inputs; its style (each line shown or not, its\n * colours and dash, the line width, the scale, the levels); and the\n * intervals it shows on. Every edit shows at once; Cancel (or Escape) puts\n * it all back as it was, Ok keeps it. The Defaults menu resets it, or keeps\n * it for the next ones of its kind.\n */\nexport class WidgetIndicatorSettings {\n private backdrop: HTMLDivElement;\n private modal: HTMLDivElement;\n private bodyEl: HTMLDivElement;\n private titleEl: HTMLHeadingElement;\n private tabsEl: HTMLDivElement;\n private defaultsBtn: HTMLButtonElement;\n private menu: WidgetContextMenu | null = null;\n private callbacks: IndicatorSettingsCallbacks;\n private target: IndicatorSettingsTarget | null = null;\n private draft: Record<string, IndicatorParamValue> = {};\n private initial: { params: Record<string, IndicatorParamValue>; look: Look } | null = null;\n private look: Look = { colors: [], lineWidth: DEFAULT_LINE_WIDTH, plots: {}, levels: [], intervals: null };\n private tab: Tab = 'inputs';\n private tabButtons: HTMLButtonElement[] = [];\n private returnFocus: HTMLElement | null = null;\n private readonly uid = nextDialogId++;\n private readonly t: Translator;\n\n constructor(host: HTMLElement, callbacks: IndicatorSettingsCallbacks, t?: Translator) {\n this.callbacks = callbacks;\n this.t = t ?? ((key) => EN_MESSAGES[key]);\n\n this.backdrop = document.createElement('div');\n this.backdrop.className = 'tcw-modal-backdrop';\n this.backdrop.hidden = true;\n // A press that starts in the dialog (a slider dragged, text selected) and ends on the backdrop doesn't cancel it.\n let pressedBackdrop = false;\n this.backdrop.addEventListener('pointerdown', (e) => {\n pressedBackdrop = e.target === this.backdrop;\n });\n this.backdrop.addEventListener('click', (e) => {\n if (e.target === this.backdrop && pressedBackdrop) this.finish(false);\n pressedBackdrop = false;\n });\n\n this.modal = document.createElement('div');\n this.modal.className = 'tcw-modal tcw-modal-narrow tcw-indi';\n this.modal.setAttribute('role', 'dialog');\n this.modal.setAttribute('aria-modal', 'true');\n this.modal.setAttribute('aria-labelledby', `tcw-indi-title-${this.uid}`);\n // Focusable, so keys still reach it after a click on its blank parts.\n this.modal.tabIndex = -1;\n this.modal.addEventListener('keydown', (e) => this.onKeyDown(e));\n\n const header = document.createElement('div');\n header.className = 'tcw-modal-header';\n this.titleEl = document.createElement('h3');\n this.titleEl.id = `tcw-indi-title-${this.uid}`;\n const closeBtn = document.createElement('button');\n closeBtn.type = 'button';\n closeBtn.className = 'tcw-modal-close';\n closeBtn.setAttribute('aria-label', this.t('indicatorSettings.close'));\n closeBtn.innerHTML = createIcon('x', 16);\n closeBtn.addEventListener('click', () => this.finish(false));\n header.append(this.titleEl, closeBtn);\n\n this.tabsEl = document.createElement('div');\n this.tabsEl.className = 'tcw-modal-tabs';\n this.tabsEl.setAttribute('role', 'tablist');\n this.tabsEl.addEventListener('keydown', (e) => this.onTabKey(e));\n\n this.bodyEl = document.createElement('div');\n this.bodyEl.className = 'tcw-modal-body';\n this.bodyEl.id = `tcw-indi-panel-${this.uid}`;\n this.bodyEl.setAttribute('role', 'tabpanel');\n\n this.defaultsBtn = this.footerButton('defaults', 'tcw-footer-menu', () => this.openDefaults());\n this.modal.append(header, this.tabsEl, this.bodyEl, this.buildFooter());\n this.backdrop.appendChild(this.modal);\n host.appendChild(this.backdrop);\n }\n\n /** The Defaults menu on the left; Cancel and OK on the right. */\n private buildFooter(): HTMLDivElement {\n this.defaultsBtn.setAttribute('aria-haspopup', 'menu');\n this.defaultsBtn.setAttribute('aria-expanded', 'false');\n this.defaultsBtn.innerHTML = `<span></span>${createIcon('chevronDown', 12)}`;\n this.defaultsBtn.querySelector('span')!.textContent = this.t('indicatorSettings.defaults');\n const end = document.createElement('div');\n end.className = 'tcw-footer-end';\n end.append(\n this.footerButton('cancel', 'tcw-footer-cancel', () => this.finish(false), this.t('common.cancel')),\n this.footerButton('ok', 'tcw-done-btn', () => this.finish(true), this.t('drawingSettings.ok')),\n );\n const footer = document.createElement('div');\n footer.className = 'tcw-modal-footer';\n footer.append(this.defaultsBtn, end);\n return footer;\n }\n\n open(target: IndicatorSettingsTarget): void {\n if (this.target) this.finish(true);\n if (this.backdrop.hidden) this.returnFocus = document.activeElement instanceof HTMLElement ? document.activeElement : null;\n this.target = target;\n this.titleEl.textContent = this.t('indicatorSettings.title').replace('{name}', target.name);\n this.draft = {};\n this.look = {\n colors: [...(target.colors ?? [])],\n lineWidth: target.lineWidth ?? DEFAULT_LINE_WIDTH,\n plots: Object.fromEntries(Object.entries(target.plotStyles ?? {}).map(([key, plot]) => [key, { ...plot }])),\n levels: [...(target.levels ?? [])],\n scale: target.scale,\n intervals: target.intervals ?? null,\n };\n this.initial = { params: this.currentParams(), look: copy(this.look) };\n this.tab = 'inputs';\n this.renderTabs();\n this.renderBody();\n this.backdrop.hidden = false;\n // Start on the first input, else the tabs, else the dialog itself.\n const firstTab = this.tabsEl.hidden ? null : this.tabButtons[0] ?? null;\n (this.bodyEl.querySelector<HTMLElement>(FOCUSABLE) ?? firstTab ?? this.modal).focus();\n }\n\n /** Close, keeping what was changed (as Ok does). */\n close(): void {\n this.finish(true);\n }\n\n isOpen(): boolean {\n return !this.backdrop.hidden;\n }\n\n destroy(): void {\n this.menu?.destroy();\n this.backdrop.remove();\n }\n\n /** Close with Ok (`keep`: a value still being typed applies) or Cancel (everything goes back). */\n private finish(keep: boolean): void {\n if (!this.target) return;\n if (keep) {\n const active = document.activeElement;\n if ((active instanceof HTMLInputElement || active instanceof HTMLSelectElement) && this.modal.contains(active)) {\n active.dispatchEvent(new Event('change'));\n }\n } else {\n this.revert();\n }\n // No target: a field's change on its way out (its blur) no longer applies.\n this.target = null;\n this.menu?.close();\n this.backdrop.hidden = true;\n this.callbacks.onClose();\n // Back to where the dialog was opened from (a legend row, the object tree).\n if (this.returnFocus?.isConnected) this.returnFocus.focus();\n this.returnFocus = null;\n }\n\n /** Everything as it was on opening: only what changed is sent back. */\n private revert(): void {\n const target = this.target;\n const initial = this.initial;\n if (!target || !initial) return;\n const id = target.instanceId;\n if (!same(this.currentParams(), initial.params)) this.callbacks.onApply(id, { ...initial.params });\n const was = initial.look;\n const style: IndicatorStyleConfig = {};\n if (!same(this.look.colors, was.colors)) style.colors = [...was.colors];\n if (this.look.lineWidth !== was.lineWidth) style.lineWidths = [was.lineWidth];\n if (!same(this.look.plots, was.plots)) style.plots = plotsBack(this.look.plots, was.plots, target.plots);\n if (Object.keys(style).length > 0) this.callbacks.onStyle?.(id, style);\n if (!same(this.look.levels, was.levels)) this.callbacks.onLevels?.(id, target.ownLevels ? [...was.levels] : null);\n if (this.look.scale !== was.scale && was.scale) this.callbacks.onScale?.(id, was.scale);\n if (!same(this.look.intervals, was.intervals)) this.callbacks.onIntervals?.(id, was.intervals);\n }\n\n private currentParams(): Record<string, IndicatorParamValue> {\n const target = this.target;\n if (!target) return {};\n return { ...target.defaults, ...target.params, ...this.draft } as Record<string, IndicatorParamValue>;\n }\n\n // --- Tabs ---\n\n private tabs(): Tab[] {\n const target = this.target;\n if (!target) return [];\n const lines = !!(target.plots?.length && target.colors && this.callbacks.onStyle);\n const levels = !!(target.levels && this.callbacks.onLevels);\n return [\n 'inputs',\n ...(lines || levels ? ['style' as const] : []),\n ...(this.callbacks.onIntervals ? ['visibility' as const] : []),\n ];\n }\n\n /** The tab buttons for this indicator; built on open, then only marked (focus stays put). */\n private renderTabs(): void {\n const tabs = this.tabs();\n this.tabsEl.hidden = tabs.length < 2;\n this.tabButtons = tabs.map((tab) => {\n const btn = document.createElement('button');\n btn.type = 'button';\n btn.className = 'tcw-modal-tab';\n btn.id = `tcw-indi-tab-${this.uid}-${tab}`;\n btn.setAttribute('role', 'tab');\n btn.setAttribute('aria-controls', this.bodyEl.id);\n btn.dataset.tab = tab;\n btn.textContent = this.t(`indicatorSettings.tab.${tab}`);\n btn.addEventListener('click', () => this.selectTab(tab));\n return btn;\n });\n this.tabsEl.replaceChildren(...this.tabButtons);\n this.markTabs();\n }\n\n private markTabs(): void {\n for (const btn of this.tabButtons) {\n const selected = btn.dataset.tab === this.tab;\n btn.classList.toggle('tcw-active', selected);\n btn.setAttribute('aria-selected', String(selected));\n btn.tabIndex = selected ? 0 : -1;\n if (selected) this.bodyEl.setAttribute('aria-labelledby', btn.id);\n }\n }\n\n private selectTab(tab: Tab, focus = false): void {\n this.tab = tab;\n this.markTabs();\n this.renderBody();\n if (focus) this.tabButtons.find((b) => b.dataset.tab === tab)?.focus();\n }\n\n /** Arrow keys (the other way right to left), Home and End move between tabs. */\n private onTabKey(e: KeyboardEvent): void {\n const tabs = this.tabButtons.map((b) => b.dataset.tab as Tab);\n const at = tabs.indexOf(this.tab);\n const rtl = this.modal.closest('[dir]')?.getAttribute('dir') === 'rtl';\n const [forward, back] = rtl ? ['ArrowLeft', 'ArrowRight'] : ['ArrowRight', 'ArrowLeft'];\n const next = e.key === forward ? (at + 1) % tabs.length\n : e.key === back ? (at - 1 + tabs.length) % tabs.length\n : e.key === 'Home' ? 0\n : e.key === 'End' ? tabs.length - 1\n : -1;\n if (next < 0 || tabs.length === 0) return;\n e.preventDefault();\n this.selectTab(tabs[next], true);\n }\n\n /** Escape cancels; Tab stays inside the dialog. */\n private onKeyDown(e: KeyboardEvent): void {\n if (e.key === 'Escape') {\n e.preventDefault();\n e.stopPropagation();\n this.finish(false);\n return;\n }\n keepTabInside(e, this.modal);\n }\n\n private renderBody(): void {\n this.bodyEl.replaceChildren();\n const target = this.target;\n if (!target) return;\n if (this.tab === 'style') this.renderStyle(target);\n else if (this.tab === 'visibility') renderVisibilityTab(this.bodyEl, this.look.intervals, (next) => this.setIntervals(next), this.t);\n else this.renderInputs();\n }\n\n // --- Inputs ---\n\n private currentValue(key: string): IndicatorParamValue {\n if (key in this.draft) return this.draft[key];\n const cur = this.target?.params[key];\n const def = this.target?.defaults[key];\n return (cur ?? def) as IndicatorParamValue;\n }\n\n private setValue(key: string, value: IndicatorParamValue): void {\n if (!this.target) return;\n this.draft[key] = value;\n this.callbacks.onApply(this.target.instanceId, { ...this.draft });\n }\n\n private renderInputs(): void {\n const section = document.createElement('div');\n section.className = 'tcw-settings-section';\n const inputs = describeInputs({ defaultConfig: this.target!.defaults, inputs: this.target!.inputs });\n if (inputs.length === 0) section.appendChild(this.note(this.t('indicatorSettings.noInputs')));\n for (const input of inputs) section.appendChild(this.fieldRow(input));\n this.bodyEl.appendChild(section);\n }\n\n /** One input's row: its control follows the kind the indicator declares, or the one its default implies. */\n private fieldRow(input: IndicatorInputInfo): HTMLLabelElement {\n const key = input.name;\n const value = this.currentValue(key);\n let control: HTMLElement;\n switch (input.kind) {\n case 'source': control = this.sourceControl(key, String(value ?? 'close')); break;\n case 'choice': control = this.choiceControl(key, input.options ?? [], value); break;\n case 'boolean': control = toggleSwitch(Boolean(value), (v) => this.setValue(key, v)); break;\n case 'int':\n case 'number':\n control = numberInput(Number(value), { min: input.min, max: input.max, step: input.step, whole: input.kind === 'int' }, (v) => this.setValue(key, v));\n break;\n case 'color': control = colorInput(String(value), (v) => this.setValue(key, v)); break;\n default: control = this.textControl(key, String(value ?? ''));\n }\n return settingsRow(input.label ?? inputLabel(key, this.t) ?? titleCase(key), control);\n }\n\n private sourceControl(key: string, value: string): HTMLSelectElement {\n const select = document.createElement('select');\n select.className = 'tcw-indi-input tcw-indi-select';\n const prices = document.createElement('optgroup');\n prices.label = this.t('indicatorSettings.source.prices');\n for (const source of PRICE_SOURCES) prices.appendChild(this.option(source, source));\n select.appendChild(prices);\n const lines = this.target?.lineSources ?? [];\n if (lines.length) {\n const group = document.createElement('optgroup');\n group.label = this.t('indicatorSettings.source.indicators');\n for (const line of lines) group.appendChild(this.option(line.value, line.label));\n select.appendChild(group);\n }\n select.value = value;\n if (select.value !== value) select.value = 'close'; // a line that is gone\n select.addEventListener('change', () => this.setValue(key, select.value));\n return select;\n }\n\n private choiceControl(key: string, options: readonly (string | number)[], value: IndicatorParamValue): HTMLSelectElement {\n const select = document.createElement('select');\n select.className = 'tcw-indi-input tcw-indi-select';\n for (const opt of options) select.appendChild(this.option(String(opt), choiceLabel(key, opt, this.t) ?? String(opt)));\n select.value = String(value);\n select.addEventListener('change', () => {\n const picked = options.find((o) => String(o) === select.value);\n if (picked !== undefined) this.setValue(key, picked);\n });\n return select;\n }\n\n private textControl(key: string, value: string): HTMLInputElement {\n const input = document.createElement('input');\n input.type = 'text';\n input.className = 'tcw-indi-input';\n input.value = value;\n input.addEventListener('change', () => this.setValue(key, input.value));\n return input;\n }\n\n // --- Style and visibility ---\n\n private renderStyle(target: IndicatorSettingsTarget): void {\n const id = target.instanceId;\n // Once closed, nothing: a field's change on its way out (its blur after a Cancel) no longer applies.\n const open = () => this.target === target;\n const onStyle = (style: IndicatorStyleConfig) => open() && this.callbacks.onStyle?.(id, style);\n const onLevels = (levels: number[]) => open() && this.callbacks.onLevels?.(id, levels);\n const onScale = (scale: 'right' | 'left') => open() && this.callbacks.onScale?.(id, scale);\n renderStyleTab(this.bodyEl, {\n plots: target.colors && this.callbacks.onStyle ? target.plots ?? [] : [],\n colors: this.look.colors,\n plotStyles: this.look.plots,\n lineWidth: this.look.lineWidth,\n scale: target.scale && this.callbacks.onScale ? this.look.scale : undefined,\n levels: target.levels && this.callbacks.onLevels ? this.look.levels : undefined,\n }, {\n color: (index, value) => {\n if (!open()) return;\n const next = [...this.look.colors];\n while (next.length <= index) next.push(next[0] ?? value);\n next[index] = value;\n this.look.colors = next;\n onStyle({ colors: [...next] });\n },\n plot: (key, patch) => {\n if (!open()) return;\n this.look.plots = { ...this.look.plots, [key]: { ...this.look.plots[key], ...patch } };\n onStyle({ plots: { [key]: patch } });\n },\n lineWidth: (width) => {\n if (!open()) return;\n this.look.lineWidth = width;\n onStyle({ lineWidths: [width] });\n },\n scale: (scale) => {\n if (!open()) return;\n this.look.scale = scale;\n onScale(scale);\n },\n levels: (levels) => {\n if (!open()) return;\n this.look.levels = levels;\n onLevels([...levels]);\n },\n }, this.t, this.uid);\n }\n\n private setIntervals(intervals: IndicatorIntervals | null): void {\n if (!this.target) return;\n this.look.intervals = intervals;\n this.callbacks.onIntervals?.(this.target.instanceId, intervals);\n }\n\n // --- Defaults ---\n\n private openDefaults(): void {\n if (this.menu?.isOpen()) {\n this.menu.close();\n return;\n }\n this.menu ??= new WidgetContextMenu(this.modal, this.t('indicatorSettings.defaults'));\n const at = this.defaultsBtn.getBoundingClientRect();\n const box = this.modal.getBoundingClientRect();\n const entries = [\n { id: 'reset', label: this.t('indicatorSettings.resetSettings'), icon: 'reset' },\n ...(this.callbacks.onSaveDefault ? [{ id: 'save', label: this.t('indicatorSettings.saveAsDefault'), icon: 'save' }] : []),\n ];\n // From the button's top: in the footer there is no room below, so it opens above the button.\n this.menu.open(entries, at.left - box.left, at.top - box.top, (pick) => {\n if (pick === 'reset') this.resetAll();\n else this.saveDefault();\n }, this.defaultsBtn);\n }\n\n /** The inputs, the lines' own styles, the levels and the intervals back to the indicator's own. */\n private resetAll(): void {\n const target = this.target;\n if (!target) return;\n const id = target.instanceId;\n const params = { ...target.defaults } as Record<string, IndicatorParamValue>;\n this.draft = { ...params };\n this.callbacks.onApply(id, { ...params });\n if (Object.keys(this.look.plots).length > 0) {\n this.callbacks.onStyle?.(id, { plots: plotsBack(this.look.plots, {}, target.plots) });\n this.look.plots = {};\n }\n if (target.levels && this.callbacks.onLevels) {\n this.callbacks.onLevels(id, null);\n this.look.levels = [...(target.defaultLevels ?? [])];\n }\n if (this.look.intervals) this.setIntervals(null);\n this.renderBody();\n }\n\n /**\n * Keep what was chosen for the next ones of the kind: the inputs that differ\n * from the indicator's own (not a line of another indicator, which the next\n * one won't have), and of the look only what someone picked. Colours the\n * chart gave stay its to give, so a second one still gets a colour of its own.\n */\n private saveDefault(): void {\n const target = this.target;\n if (!target) return;\n const params = Object.fromEntries(Object.entries(this.currentParams()).filter(([key, value]) =>\n !same(value, target.defaults[key]) && !(typeof value === 'string' && parseIndicatorSource(value)))) as Record<string, IndicatorParamValue>;\n const style: IndicatorStyleConfig = {};\n if (!isSeriesPalette(this.look.colors)) style.colors = [...this.look.colors];\n if (this.look.lineWidth !== DEFAULT_LINE_WIDTH) style.lineWidths = [this.look.lineWidth];\n if (Object.keys(this.look.plots).length > 0) style.plots = copy(this.look.plots);\n this.callbacks.onSaveDefault?.(target.indicatorId, { params, style });\n }\n\n // --- Pieces ---\n\n private footerButton(act: string, className: string, onClick: () => void, text?: string): HTMLButtonElement {\n const btn = document.createElement('button');\n btn.type = 'button';\n btn.className = className;\n btn.dataset.act = act;\n if (text) btn.textContent = text;\n btn.addEventListener('click', onClick);\n return btn;\n }\n\n private note(text: string): HTMLDivElement {\n const el = document.createElement('div');\n el.className = 'tcw-settings-label';\n el.textContent = text;\n return el;\n }\n\n private option(value: string, label: string): HTMLOptionElement {\n const opt = document.createElement('option');\n opt.value = value;\n opt.textContent = label;\n return opt;\n }\n}\n"],"mappings":"iSA4EA,IAAI,EAAe,EAEb,EAAqB,IAE3B,SAAS,EAAU,EAA6C,EAAiC,CAC/F,MAAO,CAAE,QAAS,GAAM,UAAW,GAAO,KAAM,GAAM,EAAE,MAAQ,EAAI,EAAE,WAAa,QAAS,CAG9F,IAAM,GAAQ,EAAY,IAAe,KAAK,UAAU,EAAE,GAAK,KAAK,UAAU,EAAE,CAE1E,EAAW,GAAgB,KAAK,MAAM,KAAK,UAAU,EAAM,CAAC,CAElE,SAAS,EAAU,EAAqB,CAEtC,OAAO,EACJ,QAAQ,KAAM,IAAI,CAClB,QAAQ,qBAAsB,QAAQ,CACtC,QAAQ,KAAO,GAAM,EAAE,aAAa,CAAC,CAI1C,SAAS,EACP,EACA,EACA,EACoC,CACpC,OAAO,OAAO,YAAY,CAAC,GAAG,IAAI,IAAI,CAAC,GAAG,OAAO,KAAK,EAAK,CAAE,GAAG,OAAO,KAAK,EAAG,CAAC,CAAC,CAAC,CAAC,IAAK,GAAQ,CAAC,EAAK,CAAE,GAAG,EAAU,EAAO,EAAI,CAAE,GAAG,EAAG,GAAM,CAAC,CAAC,CAAC,CAUnJ,IAAa,EAAb,KAAqC,CACnC,SACA,MACA,OACA,QACA,OACA,YACA,KAAyC,KACzC,UACA,OAAiD,KACjD,MAAqD,EAAE,CACvD,QAAsF,KACtF,KAAqB,CAAE,OAAQ,EAAE,CAAE,UAAW,EAAoB,MAAO,EAAE,CAAE,OAAQ,EAAE,CAAE,UAAW,KAAM,CAC1G,IAAmB,SACnB,WAA0C,EAAE,CAC5C,YAA0C,KAC1C,IAAuB,IACvB,EAEA,YAAY,EAAmB,EAAuC,EAAgB,CACpF,KAAK,UAAY,EACjB,KAAK,EAAI,IAAO,GAAQ,EAAA,EAAY,IAEpC,KAAK,SAAW,SAAS,cAAc,MAAM,CAC7C,KAAK,SAAS,UAAY,qBAC1B,KAAK,SAAS,OAAS,GAEvB,IAAI,EAAkB,GACtB,KAAK,SAAS,iBAAiB,cAAgB,GAAM,CACnD,EAAkB,EAAE,SAAW,KAAK,UACpC,CACF,KAAK,SAAS,iBAAiB,QAAU,GAAM,CACzC,EAAE,SAAW,KAAK,UAAY,GAAiB,KAAK,OAAO,GAAM,CACrE,EAAkB,IAClB,CAEF,KAAK,MAAQ,SAAS,cAAc,MAAM,CAC1C,KAAK,MAAM,UAAY,sCACvB,KAAK,MAAM,aAAa,OAAQ,SAAS,CACzC,KAAK,MAAM,aAAa,aAAc,OAAO,CAC7C,KAAK,MAAM,aAAa,kBAAmB,kBAAkB,KAAK,MAAM,CAExE,KAAK,MAAM,SAAW,GACtB,KAAK,MAAM,iBAAiB,UAAY,GAAM,KAAK,UAAU,EAAE,CAAC,CAEhE,IAAM,EAAS,SAAS,cAAc,MAAM,CAC5C,EAAO,UAAY,mBACnB,KAAK,QAAU,SAAS,cAAc,KAAK,CAC3C,KAAK,QAAQ,GAAK,kBAAkB,KAAK,MACzC,IAAM,EAAW,SAAS,cAAc,SAAS,CACjD,EAAS,KAAO,SAChB,EAAS,UAAY,kBACrB,EAAS,aAAa,aAAc,KAAK,EAAE,0BAA0B,CAAC,CACtE,EAAS,UAAY,EAAA,EAAW,IAAK,GAAG,CACxC,EAAS,iBAAiB,YAAe,KAAK,OAAO,GAAM,CAAC,CAC5D,EAAO,OAAO,KAAK,QAAS,EAAS,CAErC,KAAK,OAAS,SAAS,cAAc,MAAM,CAC3C,KAAK,OAAO,UAAY,iBACxB,KAAK,OAAO,aAAa,OAAQ,UAAU,CAC3C,KAAK,OAAO,iBAAiB,UAAY,GAAM,KAAK,SAAS,EAAE,CAAC,CAEhE,KAAK,OAAS,SAAS,cAAc,MAAM,CAC3C,KAAK,OAAO,UAAY,iBACxB,KAAK,OAAO,GAAK,kBAAkB,KAAK,MACxC,KAAK,OAAO,aAAa,OAAQ,WAAW,CAE5C,KAAK,YAAc,KAAK,aAAa,WAAY,sBAAyB,KAAK,cAAc,CAAC,CAC9F,KAAK,MAAM,OAAO,EAAQ,KAAK,OAAQ,KAAK,OAAQ,KAAK,aAAa,CAAC,CACvE,KAAK,SAAS,YAAY,KAAK,MAAM,CACrC,EAAK,YAAY,KAAK,SAAS,CAIjC,aAAsC,CACpC,KAAK,YAAY,aAAa,gBAAiB,OAAO,CACtD,KAAK,YAAY,aAAa,gBAAiB,QAAQ,CACvD,KAAK,YAAY,UAAY,gBAAgB,EAAA,EAAW,cAAe,GAAG,GAC1E,KAAK,YAAY,cAAc,OAAO,CAAE,YAAc,KAAK,EAAE,6BAA6B,CAC1F,IAAM,EAAM,SAAS,cAAc,MAAM,CACzC,EAAI,UAAY,iBAChB,EAAI,OACF,KAAK,aAAa,SAAU,wBAA2B,KAAK,OAAO,GAAM,CAAE,KAAK,EAAE,gBAAgB,CAAC,CACnG,KAAK,aAAa,KAAM,mBAAsB,KAAK,OAAO,GAAK,CAAE,KAAK,EAAE,qBAAqB,CAAC,CAC/F,CACD,IAAM,EAAS,SAAS,cAAc,MAAM,CAG5C,MAFA,GAAO,UAAY,mBACnB,EAAO,OAAO,KAAK,YAAa,EAAI,CAC7B,EAGT,KAAK,EAAuC,CACtC,KAAK,QAAQ,KAAK,OAAO,GAAK,CAC9B,KAAK,SAAS,SAAQ,KAAK,YAAc,SAAS,yBAAyB,YAAc,SAAS,cAAgB,MACtH,KAAK,OAAS,EACd,KAAK,QAAQ,YAAc,KAAK,EAAE,0BAA0B,CAAC,QAAQ,SAAU,EAAO,KAAK,CAC3F,KAAK,MAAQ,EAAE,CACf,KAAK,KAAO,CACV,OAAQ,CAAC,GAAI,EAAO,QAAU,EAAE,CAAE,CAClC,UAAW,EAAO,WAAa,EAC/B,MAAO,OAAO,YAAY,OAAO,QAAQ,EAAO,YAAc,EAAE,CAAC,CAAC,KAAK,CAAC,EAAK,KAAU,CAAC,EAAK,CAAE,GAAG,EAAM,CAAC,CAAC,CAAC,CAC3G,OAAQ,CAAC,GAAI,EAAO,QAAU,EAAE,CAAE,CAClC,MAAO,EAAO,MACd,UAAW,EAAO,WAAa,KAChC,CACD,KAAK,QAAU,CAAE,OAAQ,KAAK,eAAe,CAAE,KAAM,EAAK,KAAK,KAAK,CAAE,CACtE,KAAK,IAAM,SACX,KAAK,YAAY,CACjB,KAAK,YAAY,CACjB,KAAK,SAAS,OAAS,GAEvB,IAAM,EAAW,KAAK,OAAO,OAAS,KAAO,KAAK,WAAW,IAAM,MAClE,KAAK,OAAO,cAAA,mIAAqC,EAAI,GAAY,KAAK,OAAO,OAAO,CAIvF,OAAc,CACZ,KAAK,OAAO,GAAK,CAGnB,QAAkB,CAChB,MAAO,CAAC,KAAK,SAAS,OAGxB,SAAgB,CACd,KAAK,MAAM,SAAS,CACpB,KAAK,SAAS,QAAQ,CAIxB,OAAe,EAAqB,CAC7B,QAAK,OACV,IAAI,EAAM,CACR,IAAM,EAAS,SAAS,eACnB,aAAkB,kBAAoB,aAAkB,oBAAsB,KAAK,MAAM,SAAS,EAAO,EAC5G,EAAO,cAAc,IAAI,MAAM,SAAS,CAAC,MAG3C,KAAK,QAAQ,CAGf,KAAK,OAAS,KACd,KAAK,MAAM,OAAO,CAClB,KAAK,SAAS,OAAS,GACvB,KAAK,UAAU,SAAS,CAEpB,KAAK,aAAa,aAAa,KAAK,YAAY,OAAO,CAC3D,KAAK,YAAc,MAIrB,QAAuB,CACrB,IAAM,EAAS,KAAK,OACd,EAAU,KAAK,QACrB,GAAI,CAAC,GAAU,CAAC,EAAS,OACzB,IAAM,EAAK,EAAO,WACb,EAAK,KAAK,eAAe,CAAE,EAAQ,OAAO,EAAE,KAAK,UAAU,QAAQ,EAAI,CAAE,GAAG,EAAQ,OAAQ,CAAC,CAClG,IAAM,EAAM,EAAQ,KACd,EAA8B,EAAE,CACjC,EAAK,KAAK,KAAK,OAAQ,EAAI,OAAO,GAAE,EAAM,OAAS,CAAC,GAAG,EAAI,OAAO,EACnE,KAAK,KAAK,YAAc,EAAI,YAAW,EAAM,WAAa,CAAC,EAAI,UAAU,EACxE,EAAK,KAAK,KAAK,MAAO,EAAI,MAAM,GAAE,EAAM,MAAQ,EAAU,KAAK,KAAK,MAAO,EAAI,MAAO,EAAO,MAAM,EACpG,OAAO,KAAK,EAAM,CAAC,OAAS,GAAG,KAAK,UAAU,UAAU,EAAI,EAAM,CACjE,EAAK,KAAK,KAAK,OAAQ,EAAI,OAAO,EAAE,KAAK,UAAU,WAAW,EAAI,EAAO,UAAY,CAAC,GAAG,EAAI,OAAO,CAAG,KAAK,CAC7G,KAAK,KAAK,QAAU,EAAI,OAAS,EAAI,OAAO,KAAK,UAAU,UAAU,EAAI,EAAI,MAAM,CAClF,EAAK,KAAK,KAAK,UAAW,EAAI,UAAU,EAAE,KAAK,UAAU,cAAc,EAAI,EAAI,UAAU,CAGhG,eAA6D,CAC3D,IAAM,EAAS,KAAK,OAEpB,OADK,EACE,CAAE,GAAG,EAAO,SAAU,GAAG,EAAO,OAAQ,GAAG,KAAK,MAAO,CAD1C,EAAE,CAMxB,MAAsB,CACpB,IAAM,EAAS,KAAK,OACpB,GAAI,CAAC,EAAQ,MAAO,EAAE,CACtB,IAAM,EAAQ,CAAC,EAAE,EAAO,OAAO,QAAU,EAAO,QAAU,KAAK,UAAU,SACnE,EAAS,CAAC,EAAE,EAAO,QAAU,KAAK,UAAU,UAClD,MAAO,CACL,SACA,GAAI,GAAS,EAAS,CAAC,QAAiB,CAAG,EAAE,CAC7C,GAAI,KAAK,UAAU,YAAc,CAAC,aAAsB,CAAG,EAAE,CAC9D,CAIH,YAA2B,CACzB,IAAM,EAAO,KAAK,MAAM,CACxB,KAAK,OAAO,OAAS,EAAK,OAAS,EACnC,KAAK,WAAa,EAAK,IAAK,GAAQ,CAClC,IAAM,EAAM,SAAS,cAAc,SAAS,CAS5C,MARA,GAAI,KAAO,SACX,EAAI,UAAY,gBAChB,EAAI,GAAK,gBAAgB,KAAK,IAAI,GAAG,IACrC,EAAI,aAAa,OAAQ,MAAM,CAC/B,EAAI,aAAa,gBAAiB,KAAK,OAAO,GAAG,CACjD,EAAI,QAAQ,IAAM,EAClB,EAAI,YAAc,KAAK,EAAE,yBAAyB,IAAM,CACxD,EAAI,iBAAiB,YAAe,KAAK,UAAU,EAAI,CAAC,CACjD,GACP,CACF,KAAK,OAAO,gBAAgB,GAAG,KAAK,WAAW,CAC/C,KAAK,UAAU,CAGjB,UAAyB,CACvB,IAAK,IAAM,KAAO,KAAK,WAAY,CACjC,IAAM,EAAW,EAAI,QAAQ,MAAQ,KAAK,IAC1C,EAAI,UAAU,OAAO,aAAc,EAAS,CAC5C,EAAI,aAAa,gBAAiB,OAAO,EAAS,CAAC,CACnD,EAAI,SAAW,EAAW,EAAI,GAC1B,GAAU,KAAK,OAAO,aAAa,kBAAmB,EAAI,GAAG,EAIrE,UAAkB,EAAU,EAAQ,GAAa,CAC/C,KAAK,IAAM,EACX,KAAK,UAAU,CACf,KAAK,YAAY,CACb,GAAO,KAAK,WAAW,KAAM,GAAM,EAAE,QAAQ,MAAQ,EAAI,EAAE,OAAO,CAIxE,SAAiB,EAAwB,CACvC,IAAM,EAAO,KAAK,WAAW,IAAK,GAAM,EAAE,QAAQ,IAAW,CACvD,EAAK,EAAK,QAAQ,KAAK,IAAI,CAE3B,CAAC,EAAS,GADJ,KAAK,MAAM,QAAQ,QAAQ,EAAE,aAAa,MAAM,GAAK,MACnC,CAAC,YAAa,aAAa,CAAG,CAAC,aAAc,YAAY,CACjF,EAAO,EAAE,MAAQ,GAAW,EAAK,GAAK,EAAK,OAC7C,EAAE,MAAQ,GAAQ,EAAK,EAAI,EAAK,QAAU,EAAK,OAC/C,EAAE,MAAQ,OAAS,EACnB,EAAE,MAAQ,MAAQ,EAAK,OAAS,EAChC,GACA,EAAO,GAAK,EAAK,SAAW,IAChC,EAAE,gBAAgB,CAClB,KAAK,UAAU,EAAK,GAAO,GAAK,EAIlC,UAAkB,EAAwB,CACxC,GAAI,EAAE,MAAQ,SAAU,CACtB,EAAE,gBAAgB,CAClB,EAAE,iBAAiB,CACnB,KAAK,OAAO,GAAM,CAClB,OAEF,EAAA,EAAc,EAAG,KAAK,MAAM,CAG9B,YAA2B,CACzB,KAAK,OAAO,iBAAiB,CAC7B,IAAM,EAAS,KAAK,OACf,IACD,KAAK,MAAQ,QAAS,KAAK,YAAY,EAAO,CACzC,KAAK,MAAQ,aAAc,EAAA,EAAoB,KAAK,OAAQ,KAAK,KAAK,UAAY,GAAS,KAAK,aAAa,EAAK,CAAE,KAAK,EAAE,CAC/H,KAAK,cAAc,EAK1B,aAAqB,EAAkC,CACrD,GAAI,KAAO,KAAK,MAAO,OAAO,KAAK,MAAM,GACzC,IAAM,EAAM,KAAK,QAAQ,OAAO,GAC1B,EAAM,KAAK,QAAQ,SAAS,GAClC,OAAQ,GAAO,EAGjB,SAAiB,EAAa,EAAkC,CACzD,KAAK,SACV,KAAK,MAAM,GAAO,EAClB,KAAK,UAAU,QAAQ,KAAK,OAAO,WAAY,CAAE,GAAG,KAAK,MAAO,CAAC,EAGnE,cAA6B,CAC3B,IAAM,EAAU,SAAS,cAAc,MAAM,CAC7C,EAAQ,UAAY,uBACpB,IAAM,GAAA,EAAA,EAAA,gBAAwB,CAAE,cAAe,KAAK,OAAQ,SAAU,OAAQ,KAAK,OAAQ,OAAQ,CAAC,CAChG,EAAO,SAAW,GAAG,EAAQ,YAAY,KAAK,KAAK,KAAK,EAAE,6BAA6B,CAAC,CAAC,CAC7F,IAAK,IAAM,KAAS,EAAQ,EAAQ,YAAY,KAAK,SAAS,EAAM,CAAC,CACrE,KAAK,OAAO,YAAY,EAAQ,CAIlC,SAAiB,EAA6C,CAC5D,IAAM,EAAM,EAAM,KACZ,EAAQ,KAAK,aAAa,EAAI,CAChC,EACJ,OAAQ,EAAM,KAAd,CACE,IAAK,SAAU,EAAU,KAAK,cAAc,EAAK,OAAO,GAAS,QAAQ,CAAC,CAAE,MAC5E,IAAK,SAAU,EAAU,KAAK,cAAc,EAAK,EAAM,SAAW,EAAE,CAAE,EAAM,CAAE,MAC9E,IAAK,UAAW,EAAU,EAAA,EAAa,EAAQ,EAAS,GAAM,KAAK,SAAS,EAAK,EAAE,CAAC,CAAE,MACtF,IAAK,MACL,IAAK,SACH,EAAU,EAAA,EAAY,OAAO,EAAM,CAAE,CAAE,IAAK,EAAM,IAAK,IAAK,EAAM,IAAK,KAAM,EAAM,KAAM,MAAO,EAAM,OAAS,MAAO,CAAG,GAAM,KAAK,SAAS,EAAK,EAAE,CAAC,CACrJ,MACF,IAAK,QAAS,EAAU,EAAA,EAAW,OAAO,EAAM,CAAG,GAAM,KAAK,SAAS,EAAK,EAAE,CAAC,CAAE,MACjF,QAAS,EAAU,KAAK,YAAY,EAAK,OAAO,GAAS,GAAG,CAAC,CAE/D,OAAO,EAAA,EAAY,EAAM,OAAS,EAAA,EAAW,EAAK,KAAK,EAAE,EAAI,EAAU,EAAI,CAAE,EAAQ,CAGvF,cAAsB,EAAa,EAAkC,CACnE,IAAM,EAAS,SAAS,cAAc,SAAS,CAC/C,EAAO,UAAY,iCACnB,IAAM,EAAS,SAAS,cAAc,WAAW,CACjD,EAAO,MAAQ,KAAK,EAAE,kCAAkC,CACxD,IAAK,IAAM,KAAU,EAAA,cAAe,EAAO,YAAY,KAAK,OAAO,EAAQ,EAAO,CAAC,CACnF,EAAO,YAAY,EAAO,CAC1B,IAAM,EAAQ,KAAK,QAAQ,aAAe,EAAE,CAC5C,GAAI,EAAM,OAAQ,CAChB,IAAM,EAAQ,SAAS,cAAc,WAAW,CAChD,EAAM,MAAQ,KAAK,EAAE,sCAAsC,CAC3D,IAAK,IAAM,KAAQ,EAAO,EAAM,YAAY,KAAK,OAAO,EAAK,MAAO,EAAK,MAAM,CAAC,CAChF,EAAO,YAAY,EAAM,CAK3B,MAHA,GAAO,MAAQ,EACX,EAAO,QAAU,IAAO,EAAO,MAAQ,SAC3C,EAAO,iBAAiB,aAAgB,KAAK,SAAS,EAAK,EAAO,MAAM,CAAC,CAClE,EAGT,cAAsB,EAAa,EAAuC,EAA+C,CACvH,IAAM,EAAS,SAAS,cAAc,SAAS,CAC/C,EAAO,UAAY,iCACnB,IAAK,IAAM,KAAO,EAAS,EAAO,YAAY,KAAK,OAAO,OAAO,EAAI,CAAE,EAAA,EAAY,EAAK,EAAK,KAAK,EAAE,EAAI,OAAO,EAAI,CAAC,CAAC,CAMrH,MALA,GAAO,MAAQ,OAAO,EAAM,CAC5B,EAAO,iBAAiB,aAAgB,CACtC,IAAM,EAAS,EAAQ,KAAM,GAAM,OAAO,EAAE,GAAK,EAAO,MAAM,CAC1D,IAAW,IAAA,IAAW,KAAK,SAAS,EAAK,EAAO,EACpD,CACK,EAGT,YAAoB,EAAa,EAAiC,CAChE,IAAM,EAAQ,SAAS,cAAc,QAAQ,CAK7C,MAJA,GAAM,KAAO,OACb,EAAM,UAAY,iBAClB,EAAM,MAAQ,EACd,EAAM,iBAAiB,aAAgB,KAAK,SAAS,EAAK,EAAM,MAAM,CAAC,CAChE,EAKT,YAAoB,EAAuC,CACzD,IAAM,EAAK,EAAO,WAEZ,MAAa,KAAK,SAAW,EAC7B,EAAW,GAAgC,GAAM,EAAI,KAAK,UAAU,UAAU,EAAI,EAAM,CACxF,EAAY,GAAqB,GAAM,EAAI,KAAK,UAAU,WAAW,EAAI,EAAO,CAChF,EAAW,GAA4B,GAAM,EAAI,KAAK,UAAU,UAAU,EAAI,EAAM,CAC1F,EAAA,EAAe,KAAK,OAAQ,CAC1B,MAAO,EAAO,QAAU,KAAK,UAAU,QAAU,EAAO,OAAS,EAAE,CAAG,EAAE,CACxE,OAAQ,KAAK,KAAK,OAClB,WAAY,KAAK,KAAK,MACtB,UAAW,KAAK,KAAK,UACrB,MAAO,EAAO,OAAS,KAAK,UAAU,QAAU,KAAK,KAAK,MAAQ,IAAA,GAClE,OAAQ,EAAO,QAAU,KAAK,UAAU,SAAW,KAAK,KAAK,OAAS,IAAA,GACvE,CAAE,CACD,OAAQ,EAAO,IAAU,CACvB,GAAI,CAAC,GAAM,CAAE,OACb,IAAM,EAAO,CAAC,GAAG,KAAK,KAAK,OAAO,CAClC,KAAO,EAAK,QAAU,GAAO,EAAK,KAAK,EAAK,IAAM,EAAM,CACxD,EAAK,GAAS,EACd,KAAK,KAAK,OAAS,EACnB,EAAQ,CAAE,OAAQ,CAAC,GAAG,EAAK,CAAE,CAAC,EAEhC,MAAO,EAAK,IAAU,CACf,GAAM,GACX,KAAK,KAAK,MAAQ,CAAE,GAAG,KAAK,KAAK,OAAQ,GAAM,CAAE,GAAG,KAAK,KAAK,MAAM,GAAM,GAAG,EAAO,CAAE,CACtF,EAAQ,CAAE,MAAO,EAAG,GAAM,EAAO,CAAE,CAAC,GAEtC,UAAY,GAAU,CACf,GAAM,GACX,KAAK,KAAK,UAAY,EACtB,EAAQ,CAAE,WAAY,CAAC,EAAM,CAAE,CAAC,GAElC,MAAQ,GAAU,CACX,GAAM,GACX,KAAK,KAAK,MAAQ,EAClB,EAAQ,EAAM,GAEhB,OAAS,GAAW,CACb,GAAM,GACX,KAAK,KAAK,OAAS,EACnB,EAAS,CAAC,GAAG,EAAO,CAAC,GAExB,CAAE,KAAK,EAAG,KAAK,IAAI,CAGtB,aAAqB,EAA4C,CAC1D,KAAK,SACV,KAAK,KAAK,UAAY,EACtB,KAAK,UAAU,cAAc,KAAK,OAAO,WAAY,EAAU,EAKjE,cAA6B,CAC3B,GAAI,KAAK,MAAM,QAAQ,CAAE,CACvB,KAAK,KAAK,OAAO,CACjB,OAEF,KAAK,OAAS,IAAI,EAAA,EAAkB,KAAK,MAAO,KAAK,EAAE,6BAA6B,CAAC,CACrF,IAAM,EAAK,KAAK,YAAY,uBAAuB,CAC7C,EAAM,KAAK,MAAM,uBAAuB,CACxC,EAAU,CACd,CAAE,GAAI,QAAS,MAAO,KAAK,EAAE,kCAAkC,CAAE,KAAM,QAAS,CAChF,GAAI,KAAK,UAAU,cAAgB,CAAC,CAAE,GAAI,OAAQ,MAAO,KAAK,EAAE,kCAAkC,CAAE,KAAM,OAAQ,CAAC,CAAG,EAAE,CACzH,CAED,KAAK,KAAK,KAAK,EAAS,EAAG,KAAO,EAAI,KAAM,EAAG,IAAM,EAAI,IAAM,GAAS,CAClE,IAAS,QAAS,KAAK,UAAU,CAChC,KAAK,aAAa,EACtB,KAAK,YAAY,CAItB,UAAyB,CACvB,IAAM,EAAS,KAAK,OACpB,GAAI,CAAC,EAAQ,OACb,IAAM,EAAK,EAAO,WACZ,EAAS,CAAE,GAAG,EAAO,SAAU,CACrC,KAAK,MAAQ,CAAE,GAAG,EAAQ,CAC1B,KAAK,UAAU,QAAQ,EAAI,CAAE,GAAG,EAAQ,CAAC,CACrC,OAAO,KAAK,KAAK,KAAK,MAAM,CAAC,OAAS,IACxC,KAAK,UAAU,UAAU,EAAI,CAAE,MAAO,EAAU,KAAK,KAAK,MAAO,EAAE,CAAE,EAAO,MAAM,CAAE,CAAC,CACrF,KAAK,KAAK,MAAQ,EAAE,EAElB,EAAO,QAAU,KAAK,UAAU,WAClC,KAAK,UAAU,SAAS,EAAI,KAAK,CACjC,KAAK,KAAK,OAAS,CAAC,GAAI,EAAO,eAAiB,EAAE,CAAE,EAElD,KAAK,KAAK,WAAW,KAAK,aAAa,KAAK,CAChD,KAAK,YAAY,CASnB,aAA4B,CAC1B,IAAM,EAAS,KAAK,OACpB,GAAI,CAAC,EAAQ,OACb,IAAM,EAAS,OAAO,YAAY,OAAO,QAAQ,KAAK,eAAe,CAAC,CAAC,QAAQ,CAAC,EAAK,KACnF,CAAC,EAAK,EAAO,EAAO,SAAS,GAAK,EAAI,EAAE,OAAO,GAAU,WAAA,EAAA,EAAA,sBAAiC,EAAM,EAAE,CAAC,CAC/F,EAA8B,EAAE,EAClC,EAAA,EAAA,iBAAiB,KAAK,KAAK,OAAO,GAAE,EAAM,OAAS,CAAC,GAAG,KAAK,KAAK,OAAO,EACxE,KAAK,KAAK,YAAc,IAAoB,EAAM,WAAa,CAAC,KAAK,KAAK,UAAU,EACpF,OAAO,KAAK,KAAK,KAAK,MAAM,CAAC,OAAS,IAAG,EAAM,MAAQ,EAAK,KAAK,KAAK,MAAM,EAChF,KAAK,UAAU,gBAAgB,EAAO,YAAa,CAAE,SAAQ,QAAO,CAAC,CAKvE,aAAqB,EAAa,EAAmB,EAAqB,EAAkC,CAC1G,IAAM,EAAM,SAAS,cAAc,SAAS,CAM5C,MALA,GAAI,KAAO,SACX,EAAI,UAAY,EAChB,EAAI,QAAQ,IAAM,EACd,IAAM,EAAI,YAAc,GAC5B,EAAI,iBAAiB,QAAS,EAAQ,CAC/B,EAGT,KAAa,EAA8B,CACzC,IAAM,EAAK,SAAS,cAAc,MAAM,CAGxC,MAFA,GAAG,UAAY,qBACf,EAAG,YAAc,EACV,EAGT,OAAe,EAAe,EAAkC,CAC9D,IAAM,EAAM,SAAS,cAAc,SAAS,CAG5C,MAFA,GAAI,MAAQ,EACZ,EAAI,YAAc,EACX"}