@tradecanvas/chart 2.5.0 → 2.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (116) hide show
  1. package/README.md +2 -2
  2. package/dist/Chart.d.ts +6 -0
  3. package/dist/Chart.d.ts.map +1 -1
  4. package/dist/{LayoutSession-CE8S2GCG.js → LayoutSession-CO51QKwZ.js} +10 -3
  5. package/dist/LayoutSession-CO51QKwZ.js.map +1 -0
  6. package/dist/LayoutSession-DIPjmNVe.cjs +2 -0
  7. package/dist/LayoutSession-DIPjmNVe.cjs.map +1 -0
  8. package/dist/{WidgetCommandPalette-B1jLC9oE.js → WidgetCommandPalette-CSIKnTca.js} +2 -2
  9. package/dist/{WidgetCommandPalette-B1jLC9oE.js.map → WidgetCommandPalette-CSIKnTca.js.map} +1 -1
  10. package/dist/{WidgetCommandPalette-Cx-UQ9ft.cjs → WidgetCommandPalette-CZ3GhE1O.cjs} +2 -2
  11. package/dist/{WidgetCommandPalette-Cx-UQ9ft.cjs.map → WidgetCommandPalette-CZ3GhE1O.cjs.map} +1 -1
  12. package/dist/{WidgetDrawingSettings-ezbzlGqD.js → WidgetDrawingSettings-Cc8PeqEZ.js} +2 -2
  13. package/dist/{WidgetDrawingSettings-ezbzlGqD.js.map → WidgetDrawingSettings-Cc8PeqEZ.js.map} +1 -1
  14. package/dist/{WidgetDrawingSettings-BjPR5Wyq.cjs → WidgetDrawingSettings-DutEdVyV.cjs} +2 -2
  15. package/dist/{WidgetDrawingSettings-BjPR5Wyq.cjs.map → WidgetDrawingSettings-DutEdVyV.cjs.map} +1 -1
  16. package/dist/{WidgetGoToDate-DJyCaP4H.js → WidgetGoToDate-DnQxxvf1.js} +2 -2
  17. package/dist/{WidgetGoToDate-DJyCaP4H.js.map → WidgetGoToDate-DnQxxvf1.js.map} +1 -1
  18. package/dist/{WidgetGoToDate-Vfaztduf.cjs → WidgetGoToDate-hNVxz-Zb.cjs} +2 -2
  19. package/dist/{WidgetGoToDate-Vfaztduf.cjs.map → WidgetGoToDate-hNVxz-Zb.cjs.map} +1 -1
  20. package/dist/{WidgetIndicatorPicker-Cw7pFl_l.js → WidgetIndicatorPicker-DNjcuBp6.js} +3 -3
  21. package/dist/{WidgetIndicatorPicker-Cw7pFl_l.js.map → WidgetIndicatorPicker-DNjcuBp6.js.map} +1 -1
  22. package/dist/{WidgetIndicatorPicker-Nc-1Rbm_.cjs → WidgetIndicatorPicker-GCesxFMn.cjs} +2 -2
  23. package/dist/{WidgetIndicatorPicker-Nc-1Rbm_.cjs.map → WidgetIndicatorPicker-GCesxFMn.cjs.map} +1 -1
  24. package/dist/{WidgetIndicatorSettings-Da_T6bmX.cjs → WidgetIndicatorSettings-CBHWj5z5.cjs} +2 -2
  25. package/dist/{WidgetIndicatorSettings-Da_T6bmX.cjs.map → WidgetIndicatorSettings-CBHWj5z5.cjs.map} +1 -1
  26. package/dist/{WidgetIndicatorSettings-DR8Z9RfF.js → WidgetIndicatorSettings-XhHlqyvn.js} +3 -3
  27. package/dist/{WidgetIndicatorSettings-DR8Z9RfF.js.map → WidgetIndicatorSettings-XhHlqyvn.js.map} +1 -1
  28. package/dist/{WidgetSettings-DCGCymVY.js → WidgetSettings-B1ay0W6I.js} +3 -3
  29. package/dist/{WidgetSettings-DCGCymVY.js.map → WidgetSettings-B1ay0W6I.js.map} +1 -1
  30. package/dist/{WidgetSettings-COljE3G_.cjs → WidgetSettings-CBf6hUSV.cjs} +2 -2
  31. package/dist/{WidgetSettings-COljE3G_.cjs.map → WidgetSettings-CBf6hUSV.cjs.map} +1 -1
  32. package/dist/{WidgetSymbolSearch-B8iDwVy8.js → WidgetSymbolSearch-BlQWH3Md.js} +3 -3
  33. package/dist/{WidgetSymbolSearch-B8iDwVy8.js.map → WidgetSymbolSearch-BlQWH3Md.js.map} +1 -1
  34. package/dist/{WidgetSymbolSearch-DVIRTJuT.cjs → WidgetSymbolSearch-CxeLjy63.cjs} +2 -2
  35. package/dist/{WidgetSymbolSearch-DVIRTJuT.cjs.map → WidgetSymbolSearch-CxeLjy63.cjs.map} +1 -1
  36. package/dist/i18n-BFGknfTa.cjs +2 -0
  37. package/dist/i18n-BFGknfTa.cjs.map +1 -0
  38. package/dist/{i18n-DWLKoOBG.js → i18n-BZdkj80c.js} +7 -1
  39. package/dist/i18n-BZdkj80c.js.map +1 -0
  40. package/dist/index.cjs +1 -1
  41. package/dist/index.js +1 -1
  42. package/dist/{indicatorInputWords-VWMPRfzM.js → indicatorInputWords-B8lf8reQ.js} +2 -2
  43. package/dist/{indicatorInputWords-VWMPRfzM.js.map → indicatorInputWords-B8lf8reQ.js.map} +1 -1
  44. package/dist/{indicatorInputWords-C4Mefhve.cjs → indicatorInputWords-_kxwKob4.cjs} +2 -2
  45. package/dist/{indicatorInputWords-C4Mefhve.cjs.map → indicatorInputWords-_kxwKob4.cjs.map} +1 -1
  46. package/dist/widget/ChartWidget.d.ts.map +1 -1
  47. package/dist/widget/WidgetDrawingEdit.d.ts +13 -0
  48. package/dist/widget/WidgetDrawingEdit.d.ts.map +1 -1
  49. package/dist/widget/WidgetMenus.d.ts +2 -0
  50. package/dist/widget/WidgetMenus.d.ts.map +1 -1
  51. package/dist/widget/WidgetObjectTree.d.ts.map +1 -1
  52. package/dist/widget/WidgetSelectionToolbar.d.ts +4 -0
  53. package/dist/widget/WidgetSelectionToolbar.d.ts.map +1 -1
  54. package/dist/widget/drawingMenu.d.ts +3 -1
  55. package/dist/widget/drawingMenu.d.ts.map +1 -1
  56. package/dist/widget/index.cjs +4 -3
  57. package/dist/widget/index.cjs.map +1 -1
  58. package/dist/widget/index.js +179 -118
  59. package/dist/widget/index.js.map +1 -1
  60. package/dist/widget/locales/ar.d.ts.map +1 -1
  61. package/dist/widget/locales/cs.d.ts.map +1 -1
  62. package/dist/widget/locales/da.d.ts.map +1 -1
  63. package/dist/widget/locales/de.d.ts.map +1 -1
  64. package/dist/widget/locales/el.d.ts.map +1 -1
  65. package/dist/widget/locales/en.d.ts +3 -0
  66. package/dist/widget/locales/en.d.ts.map +1 -1
  67. package/dist/widget/locales/es.d.ts.map +1 -1
  68. package/dist/widget/locales/et.d.ts.map +1 -1
  69. package/dist/widget/locales/fa.d.ts.map +1 -1
  70. package/dist/widget/locales/fr.d.ts.map +1 -1
  71. package/dist/widget/locales/he.d.ts.map +1 -1
  72. package/dist/widget/locales/hu.d.ts.map +1 -1
  73. package/dist/widget/locales/id.d.ts.map +1 -1
  74. package/dist/widget/locales/index.cjs +1 -1
  75. package/dist/widget/locales/index.cjs.map +1 -1
  76. package/dist/widget/locales/index.js +85 -1
  77. package/dist/widget/locales/index.js.map +1 -1
  78. package/dist/widget/locales/it.d.ts.map +1 -1
  79. package/dist/widget/locales/ja.d.ts.map +1 -1
  80. package/dist/widget/locales/ko.d.ts.map +1 -1
  81. package/dist/widget/locales/ms.d.ts.map +1 -1
  82. package/dist/widget/locales/nb.d.ts.map +1 -1
  83. package/dist/widget/locales/nl.d.ts.map +1 -1
  84. package/dist/widget/locales/pl.d.ts.map +1 -1
  85. package/dist/widget/locales/pt.d.ts.map +1 -1
  86. package/dist/widget/locales/ro.d.ts.map +1 -1
  87. package/dist/widget/locales/ru.d.ts.map +1 -1
  88. package/dist/widget/locales/sk.d.ts.map +1 -1
  89. package/dist/widget/locales/sv.d.ts.map +1 -1
  90. package/dist/widget/locales/th.d.ts.map +1 -1
  91. package/dist/widget/locales/tr.d.ts.map +1 -1
  92. package/dist/widget/locales/vi.d.ts.map +1 -1
  93. package/dist/widget/locales/zh.d.ts.map +1 -1
  94. package/dist/widget/locales/zhHant.d.ts.map +1 -1
  95. package/dist/widget/widgetConfig.d.ts.map +1 -1
  96. package/dist/widget/widgetFeatures.d.ts.map +1 -1
  97. package/dist/{widgetFeatures-B-el8UA3.cjs → widgetFeatures-DpBWfQ8D.cjs} +2 -2
  98. package/dist/widgetFeatures-DpBWfQ8D.cjs.map +1 -0
  99. package/dist/{widgetFeatures-DsbSG6Pq.js → widgetFeatures-sU-9JOSB.js} +2 -1
  100. package/dist/widgetFeatures-sU-9JOSB.js.map +1 -0
  101. package/dist/widgetLocales-BdRQu8wS.cjs +2 -0
  102. package/dist/widgetLocales-BdRQu8wS.cjs.map +1 -0
  103. package/dist/{widgetLocales-BX7VRdl5.js → widgetLocales-Bgm5-BB5.js} +13 -1
  104. package/dist/widgetLocales-Bgm5-BB5.js.map +1 -0
  105. package/package.json +3 -3
  106. package/dist/LayoutSession-C9bQsnH2.cjs +0 -2
  107. package/dist/LayoutSession-C9bQsnH2.cjs.map +0 -1
  108. package/dist/LayoutSession-CE8S2GCG.js.map +0 -1
  109. package/dist/i18n-DWLKoOBG.js.map +0 -1
  110. package/dist/i18n-fgAwciRT.cjs +0 -2
  111. package/dist/i18n-fgAwciRT.cjs.map +0 -1
  112. package/dist/widgetFeatures-B-el8UA3.cjs.map +0 -1
  113. package/dist/widgetFeatures-DsbSG6Pq.js.map +0 -1
  114. package/dist/widgetLocales-BX7VRdl5.js.map +0 -1
  115. package/dist/widgetLocales-C_XTkni5.cjs +0 -2
  116. package/dist/widgetLocales-C_XTkni5.cjs.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"WidgetGoToDate-Vfaztduf.cjs","names":[],"sources":["../src/widget/WidgetGoToDate.ts"],"sourcesContent":["import { createIcon } from './icons.js';\nimport { keepTabInside } from './focusTrap.js';\nimport { MonthView, type CalendarLabels } from './monthView.js';\nimport { markPart } from './widgetFeatures.js';\nimport type { WallTime } from './wallTime.js';\n\nexport interface GoToLabels {\n title: string;\n /** The tabs. */\n date: string;\n range: string;\n /** The range's ends. */\n from: string;\n to: string;\n time: string;\n submit: string;\n cancel: string;\n close: string;\n calendar: CalendarLabels;\n}\n\n/** Where to go: a moment to centre, or a span to show edge to edge. */\nexport type GoToTarget =\n | ({ kind: 'date' } & WallTime)\n | { kind: 'range'; from: WallTime; to: WallTime };\n\nexport interface GoToStart {\n /** The Date tab's moment. */\n date: WallTime;\n /** The Custom range tab's ends. */\n range: { from: WallTime; to: WallTime };\n /** Whether bars have a time of day (not on daily bars and longer). */\n withTime: boolean;\n /** The last day that can be picked, YYYY-MM-DD. */\n max?: string;\n /** Today in the time zone the dates are read in; the browser's when left out. */\n today?: string;\n}\n\ntype Tab = 'date' | 'range';\ntype Field = 'date' | 'time' | 'fromDate' | 'fromTime' | 'toDate' | 'toTime';\n\nconst TABS: readonly Tab[] = ['date', 'range'];\n\nlet nextGoToId = 1;\n\n/**\n * The Go to dialog. Date: a day and a time to centre the chart on. Custom\n * range: two of them to show edge to edge. The month under the fields fills\n * the date field in hand; after a range's start it moves on to its end.\n * Mounts in the widget's overlay layer; focus goes back to what opened it.\n */\nexport class WidgetGoToDate {\n private backdrop: HTMLDivElement | null = null;\n private fieldsEl: HTMLDivElement | null = null;\n private tabButtons: HTMLButtonElement[] = [];\n private month: MonthView | null = null;\n /** The date field the month fills. */\n private active: HTMLInputElement | null = null;\n private values: Record<Field, string> = { date: '', time: '', fromDate: '', fromTime: '', toDate: '', toTime: '' };\n private start: GoToStart | null = null;\n private tab: Tab = 'date';\n private returnFocus: HTMLElement | null = null;\n private readonly uid = nextGoToId++;\n\n constructor(\n private readonly layer: () => HTMLElement,\n private readonly labels: () => GoToLabels,\n private readonly locale: () => string,\n private readonly onSubmit: (target: GoToTarget) => void,\n ) {}\n\n isOpen(): boolean {\n return this.backdrop !== null;\n }\n\n open(start: GoToStart): void {\n this.close(false);\n this.returnFocus = document.activeElement instanceof HTMLElement ? document.activeElement : null;\n this.start = start;\n this.values = {\n date: start.date.date,\n time: start.date.time,\n fromDate: start.range.from.date,\n fromTime: start.range.from.time,\n toDate: start.range.to.date,\n toTime: start.range.to.time,\n };\n this.build();\n this.renderTab(true);\n }\n\n /** Close without going anywhere; focus goes back to what opened it unless `giveFocusBack` is false. */\n close(giveFocusBack = true): void {\n if (!this.backdrop) return;\n this.backdrop.remove();\n this.backdrop = this.fieldsEl = this.month = this.active = this.start = null;\n this.tabButtons = [];\n const back = this.returnFocus;\n this.returnFocus = null;\n if (giveFocusBack && back?.isConnected) back.focus();\n }\n\n destroy(): void {\n this.close(false);\n }\n\n private build(): void {\n const labels = this.labels();\n const backdrop = document.createElement('div');\n backdrop.className = 'tcw-modal-backdrop';\n // A press that starts in the dialog and ends on the backdrop doesn't close it.\n let pressedBackdrop = false;\n backdrop.addEventListener('pointerdown', (e) => {\n pressedBackdrop = e.target === backdrop;\n });\n backdrop.addEventListener('click', (e) => {\n if (e.target === backdrop && pressedBackdrop) this.close();\n pressedBackdrop = false;\n });\n\n const modal = document.createElement('div');\n modal.className = 'tcw-modal tcw-goto';\n markPart(modal, 'goToDate');\n modal.setAttribute('role', 'dialog');\n modal.setAttribute('aria-modal', 'true');\n modal.setAttribute('aria-labelledby', `tcw-goto-title-${this.uid}`);\n modal.tabIndex = -1;\n modal.addEventListener('keydown', (e) => {\n e.stopPropagation(); // typing here must not drive chart shortcuts\n if (e.key === 'Escape') {\n e.preventDefault();\n this.close();\n return;\n }\n keepTabInside(e, modal);\n });\n\n const header = document.createElement('div');\n header.className = 'tcw-modal-header';\n const title = document.createElement('h3');\n title.id = `tcw-goto-title-${this.uid}`;\n title.textContent = labels.title;\n const closeBtn = document.createElement('button');\n closeBtn.type = 'button';\n closeBtn.className = 'tcw-modal-close';\n closeBtn.setAttribute('aria-label', labels.close);\n closeBtn.innerHTML = createIcon('x', 16);\n closeBtn.addEventListener('click', () => this.close());\n header.append(title, closeBtn);\n\n const form = document.createElement('form');\n form.className = 'tcw-goto-form';\n form.noValidate = true;\n form.addEventListener('submit', (e) => {\n e.preventDefault();\n this.submit();\n });\n const fields = document.createElement('div');\n fields.className = 'tcw-goto-fields';\n fields.id = `tcw-goto-panel-${this.uid}`;\n fields.setAttribute('role', 'tabpanel');\n const month = new MonthView(labels.calendar, this.locale(), (value) => this.pickDay(value));\n month.element.classList.add('tcw-goto-calendar');\n form.append(fields, month.element, this.buildFooter(labels));\n\n modal.append(header, this.buildTabs(labels), form);\n backdrop.appendChild(modal);\n this.layer().appendChild(backdrop);\n this.backdrop = backdrop;\n this.fieldsEl = fields;\n this.month = month;\n }\n\n private buildTabs(labels: GoToLabels): HTMLDivElement {\n const tabs = document.createElement('div');\n tabs.className = 'tcw-modal-tabs';\n tabs.setAttribute('role', 'tablist');\n tabs.addEventListener('keydown', (e) => this.onTabKey(e));\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-goto-tab-${this.uid}-${tab}`;\n btn.dataset.tab = tab;\n btn.setAttribute('role', 'tab');\n btn.setAttribute('aria-controls', `tcw-goto-panel-${this.uid}`);\n btn.textContent = tab === 'date' ? labels.date : labels.range;\n btn.addEventListener('click', () => this.select(tab, false));\n return btn;\n });\n tabs.append(...this.tabButtons);\n return tabs;\n }\n\n private buildFooter(labels: GoToLabels): HTMLDivElement {\n const cancel = document.createElement('button');\n cancel.type = 'button';\n cancel.className = 'tcw-footer-cancel';\n cancel.dataset.act = 'cancel';\n cancel.textContent = labels.cancel;\n cancel.addEventListener('click', () => this.close());\n const go = document.createElement('button');\n go.type = 'submit';\n go.className = 'tcw-done-btn';\n go.dataset.act = 'go';\n go.textContent = labels.submit;\n const end = document.createElement('div');\n end.className = 'tcw-footer-end';\n end.append(cancel, go);\n const footer = document.createElement('div');\n footer.className = 'tcw-modal-footer';\n footer.appendChild(end);\n return footer;\n }\n\n private select(tab: Tab, focusTab: boolean): void {\n this.tab = tab;\n this.renderTab(!focusTab);\n if (focusTab) this.tabButtons.find((b) => b.dataset.tab === tab)?.focus();\n }\n\n /** The tab's fields, the month on its first date; `focusField` puts the keyboard there. */\n private renderTab(focusField: boolean): void {\n const fields = this.fieldsEl;\n const start = this.start;\n if (!fields || !start) return;\n for (const btn of this.tabButtons) {\n const on = btn.dataset.tab === this.tab;\n btn.setAttribute('aria-selected', String(on));\n btn.classList.toggle('tcw-active', on);\n btn.tabIndex = on ? 0 : -1;\n }\n fields.setAttribute('aria-labelledby', `tcw-goto-tab-${this.uid}-${this.tab}`);\n const labels = this.labels();\n fields.replaceChildren(...(this.tab === 'date'\n ? [this.row(null, 'date', 'time', labels)]\n : [this.row(labels.from, 'fromDate', 'fromTime', labels), this.row(labels.to, 'toDate', 'toTime', labels)]));\n const first = fields.querySelector<HTMLInputElement>('input[type=\"date\"]');\n this.setActive(first);\n if (focusField) first?.focus();\n }\n\n /** A date and, on intraday bars, a time, after the name of the range's end they set. */\n private row(name: string | null, dateField: Field, timeField: Field, labels: GoToLabels): HTMLDivElement {\n const row = document.createElement('div');\n row.className = 'tcw-goto-row';\n if (name) {\n const label = document.createElement('span');\n label.className = 'tcw-goto-label';\n label.textContent = name;\n row.appendChild(label);\n }\n const prefix = name ? `${name} · ` : '';\n const date = this.input('date', dateField, `${prefix}${labels.date}`);\n date.required = true;\n // The month below is this field's calendar.\n date.dataset.tcwCalendar = 'inline';\n if (this.start?.max) date.max = this.start.max;\n date.addEventListener('focus', () => this.setActive(date));\n date.addEventListener('input', () => {\n date.removeAttribute('aria-invalid');\n if (this.active === date) this.showMonth();\n });\n row.appendChild(date);\n if (this.start?.withTime) row.appendChild(this.input('time', timeField, `${prefix}${labels.time}`));\n return row;\n }\n\n private input(type: 'date' | 'time', field: Field, label: string): HTMLInputElement {\n const input = document.createElement('input');\n input.type = type;\n input.className = 'tcw-goto-input';\n input.dataset.field = field;\n input.value = this.values[field];\n input.setAttribute('aria-label', label);\n input.addEventListener('input', () => {\n this.values[field] = input.value;\n });\n return input;\n }\n\n private setActive(input: HTMLInputElement | null): void {\n this.active = input;\n this.showMonth();\n }\n\n private showMonth(): void {\n if (!this.active) return;\n this.month?.show(this.active.value, { max: this.start?.max, today: this.start?.today });\n }\n\n /** A day from the month: into the field in hand; a range's start then moves on to its end. */\n private pickDay(value: string): void {\n const input = this.active;\n if (!input) return;\n const fromGrid = this.month?.element.contains(document.activeElement) ?? false;\n input.value = value;\n input.dispatchEvent(new Event('input', { bubbles: true }));\n input.dispatchEvent(new Event('change', { bubbles: true }));\n const next = input.dataset.field === 'fromDate'\n ? this.fieldsEl?.querySelector<HTMLInputElement>('[data-field=\"toDate\"]')\n : null;\n if (next) {\n next.focus();\n this.setActive(next);\n return;\n }\n this.showMonth();\n if (fromGrid) this.month?.focusGrid();\n }\n\n private submit(): void {\n // The fields as they are now, whether or not they said they changed (autofill).\n const inputs = [...(this.fieldsEl?.querySelectorAll<HTMLInputElement>('[data-field]') ?? [])];\n for (const input of inputs) this.values[input.dataset.field as Field] = input.value;\n // A date not given (or not whole), or after the last day: marked, the keyboard on it, and nowhere to go.\n const max = this.start?.max;\n const wrong = inputs.find((input) => input.type === 'date' && (!input.value || (max !== undefined && input.value > max)));\n if (wrong) {\n wrong.setAttribute('aria-invalid', 'true');\n wrong.focus();\n return;\n }\n const v = this.values;\n const withTime = this.start?.withTime ?? false;\n const time = (t: string) => (withTime ? t : '');\n let target: GoToTarget;\n if (this.tab === 'date') {\n if (!v.date) return;\n target = { kind: 'date', date: v.date, time: time(v.time) };\n } else {\n if (!v.fromDate || !v.toDate) return;\n target = { kind: 'range', from: { date: v.fromDate, time: time(v.fromTime) }, to: { date: v.toDate, time: time(v.toTime) } };\n }\n this.close();\n this.onSubmit(target);\n }\n\n /** Arrow keys (the other way right to left), Home and End move between the tabs. */\n private onTabKey(e: KeyboardEvent): void {\n const at = TABS.indexOf(this.tab);\n const rtl = this.backdrop?.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) return;\n e.preventDefault();\n this.select(TABS[next], true);\n }\n}\n"],"mappings":"6HA0CA,IAAM,EAAuB,CAAC,OAAQ,QAAQ,CAE1C,EAAa,EAQJ,EAAb,KAA4B,CAC1B,SAA0C,KAC1C,SAA0C,KAC1C,WAA0C,EAAE,CAC5C,MAAkC,KAElC,OAA0C,KAC1C,OAAwC,CAAE,KAAM,GAAI,KAAM,GAAI,SAAU,GAAI,SAAU,GAAI,OAAQ,GAAI,OAAQ,GAAI,CAClH,MAAkC,KAClC,IAAmB,OACnB,YAA0C,KAC1C,IAAuB,IAEvB,YACE,EACA,EACA,EACA,EACA,CAJiB,KAAA,MAAA,EACA,KAAA,OAAA,EACA,KAAA,OAAA,EACA,KAAA,SAAA,EAGnB,QAAkB,CAChB,OAAO,KAAK,WAAa,KAG3B,KAAK,EAAwB,CAC3B,KAAK,MAAM,GAAM,CACjB,KAAK,YAAc,SAAS,yBAAyB,YAAc,SAAS,cAAgB,KAC5F,KAAK,MAAQ,EACb,KAAK,OAAS,CACZ,KAAM,EAAM,KAAK,KACjB,KAAM,EAAM,KAAK,KACjB,SAAU,EAAM,MAAM,KAAK,KAC3B,SAAU,EAAM,MAAM,KAAK,KAC3B,OAAQ,EAAM,MAAM,GAAG,KACvB,OAAQ,EAAM,MAAM,GAAG,KACxB,CACD,KAAK,OAAO,CACZ,KAAK,UAAU,GAAK,CAItB,MAAM,EAAgB,GAAY,CAChC,GAAI,CAAC,KAAK,SAAU,OACpB,KAAK,SAAS,QAAQ,CACtB,KAAK,SAAW,KAAK,SAAW,KAAK,MAAQ,KAAK,OAAS,KAAK,MAAQ,KACxE,KAAK,WAAa,EAAE,CACpB,IAAM,EAAO,KAAK,YAClB,KAAK,YAAc,KACf,GAAiB,GAAM,aAAa,EAAK,OAAO,CAGtD,SAAgB,CACd,KAAK,MAAM,GAAM,CAGnB,OAAsB,CACpB,IAAM,EAAS,KAAK,QAAQ,CACtB,EAAW,SAAS,cAAc,MAAM,CAC9C,EAAS,UAAY,qBAErB,IAAI,EAAkB,GACtB,EAAS,iBAAiB,cAAgB,GAAM,CAC9C,EAAkB,EAAE,SAAW,GAC/B,CACF,EAAS,iBAAiB,QAAU,GAAM,CACpC,EAAE,SAAW,GAAY,GAAiB,KAAK,OAAO,CAC1D,EAAkB,IAClB,CAEF,IAAM,EAAQ,SAAS,cAAc,MAAM,CAC3C,EAAM,UAAY,qBAClB,EAAA,EAAS,EAAO,WAAW,CAC3B,EAAM,aAAa,OAAQ,SAAS,CACpC,EAAM,aAAa,aAAc,OAAO,CACxC,EAAM,aAAa,kBAAmB,kBAAkB,KAAK,MAAM,CACnE,EAAM,SAAW,GACjB,EAAM,iBAAiB,UAAY,GAAM,CAEvC,GADA,EAAE,iBAAiB,CACf,EAAE,MAAQ,SAAU,CACtB,EAAE,gBAAgB,CAClB,KAAK,OAAO,CACZ,OAEF,EAAA,EAAc,EAAG,EAAM,EACvB,CAEF,IAAM,EAAS,SAAS,cAAc,MAAM,CAC5C,EAAO,UAAY,mBACnB,IAAM,EAAQ,SAAS,cAAc,KAAK,CAC1C,EAAM,GAAK,kBAAkB,KAAK,MAClC,EAAM,YAAc,EAAO,MAC3B,IAAM,EAAW,SAAS,cAAc,SAAS,CACjD,EAAS,KAAO,SAChB,EAAS,UAAY,kBACrB,EAAS,aAAa,aAAc,EAAO,MAAM,CACjD,EAAS,UAAY,EAAA,EAAW,IAAK,GAAG,CACxC,EAAS,iBAAiB,YAAe,KAAK,OAAO,CAAC,CACtD,EAAO,OAAO,EAAO,EAAS,CAE9B,IAAM,EAAO,SAAS,cAAc,OAAO,CAC3C,EAAK,UAAY,gBACjB,EAAK,WAAa,GAClB,EAAK,iBAAiB,SAAW,GAAM,CACrC,EAAE,gBAAgB,CAClB,KAAK,QAAQ,EACb,CACF,IAAM,EAAS,SAAS,cAAc,MAAM,CAC5C,EAAO,UAAY,kBACnB,EAAO,GAAK,kBAAkB,KAAK,MACnC,EAAO,aAAa,OAAQ,WAAW,CACvC,IAAM,EAAQ,IAAI,EAAA,EAAU,EAAO,SAAU,KAAK,QAAQ,CAAG,GAAU,KAAK,QAAQ,EAAM,CAAC,CAC3F,EAAM,QAAQ,UAAU,IAAI,oBAAoB,CAChD,EAAK,OAAO,EAAQ,EAAM,QAAS,KAAK,YAAY,EAAO,CAAC,CAE5D,EAAM,OAAO,EAAQ,KAAK,UAAU,EAAO,CAAE,EAAK,CAClD,EAAS,YAAY,EAAM,CAC3B,KAAK,OAAO,CAAC,YAAY,EAAS,CAClC,KAAK,SAAW,EAChB,KAAK,SAAW,EAChB,KAAK,MAAQ,EAGf,UAAkB,EAAoC,CACpD,IAAM,EAAO,SAAS,cAAc,MAAM,CAiB1C,MAhBA,GAAK,UAAY,iBACjB,EAAK,aAAa,OAAQ,UAAU,CACpC,EAAK,iBAAiB,UAAY,GAAM,KAAK,SAAS,EAAE,CAAC,CACzD,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,QAAQ,IAAM,EAClB,EAAI,aAAa,OAAQ,MAAM,CAC/B,EAAI,aAAa,gBAAiB,kBAAkB,KAAK,MAAM,CAC/D,EAAI,YAAc,IAAQ,OAAS,EAAO,KAAO,EAAO,MACxD,EAAI,iBAAiB,YAAe,KAAK,OAAO,EAAK,GAAM,CAAC,CACrD,GACP,CACF,EAAK,OAAO,GAAG,KAAK,WAAW,CACxB,EAGT,YAAoB,EAAoC,CACtD,IAAM,EAAS,SAAS,cAAc,SAAS,CAC/C,EAAO,KAAO,SACd,EAAO,UAAY,oBACnB,EAAO,QAAQ,IAAM,SACrB,EAAO,YAAc,EAAO,OAC5B,EAAO,iBAAiB,YAAe,KAAK,OAAO,CAAC,CACpD,IAAM,EAAK,SAAS,cAAc,SAAS,CAC3C,EAAG,KAAO,SACV,EAAG,UAAY,eACf,EAAG,QAAQ,IAAM,KACjB,EAAG,YAAc,EAAO,OACxB,IAAM,EAAM,SAAS,cAAc,MAAM,CACzC,EAAI,UAAY,iBAChB,EAAI,OAAO,EAAQ,EAAG,CACtB,IAAM,EAAS,SAAS,cAAc,MAAM,CAG5C,MAFA,GAAO,UAAY,mBACnB,EAAO,YAAY,EAAI,CAChB,EAGT,OAAe,EAAU,EAAyB,CAChD,KAAK,IAAM,EACX,KAAK,UAAU,CAAC,EAAS,CACrB,GAAU,KAAK,WAAW,KAAM,GAAM,EAAE,QAAQ,MAAQ,EAAI,EAAE,OAAO,CAI3E,UAAkB,EAA2B,CAC3C,IAAM,EAAS,KAAK,SACd,EAAQ,KAAK,MACnB,GAAI,CAAC,GAAU,CAAC,EAAO,OACvB,IAAK,IAAM,KAAO,KAAK,WAAY,CACjC,IAAM,EAAK,EAAI,QAAQ,MAAQ,KAAK,IACpC,EAAI,aAAa,gBAAiB,OAAO,EAAG,CAAC,CAC7C,EAAI,UAAU,OAAO,aAAc,EAAG,CACtC,EAAI,SAAW,EAAK,EAAI,GAE1B,EAAO,aAAa,kBAAmB,gBAAgB,KAAK,IAAI,GAAG,KAAK,MAAM,CAC9E,IAAM,EAAS,KAAK,QAAQ,CAC5B,EAAO,gBAAgB,GAAI,KAAK,MAAQ,OACpC,CAAC,KAAK,IAAI,KAAM,OAAQ,OAAQ,EAAO,CAAC,CACxC,CAAC,KAAK,IAAI,EAAO,KAAM,WAAY,WAAY,EAAO,CAAE,KAAK,IAAI,EAAO,GAAI,SAAU,SAAU,EAAO,CAAC,CAAE,CAC9G,IAAM,EAAQ,EAAO,cAAgC,qBAAqB,CAC1E,KAAK,UAAU,EAAM,CACjB,GAAY,GAAO,OAAO,CAIhC,IAAY,EAAqB,EAAkB,EAAkB,EAAoC,CACvG,IAAM,EAAM,SAAS,cAAc,MAAM,CAEzC,GADA,EAAI,UAAY,eACZ,EAAM,CACR,IAAM,EAAQ,SAAS,cAAc,OAAO,CAC5C,EAAM,UAAY,iBAClB,EAAM,YAAc,EACpB,EAAI,YAAY,EAAM,CAExB,IAAM,EAAS,EAAO,GAAG,EAAK,KAAO,GAC/B,EAAO,KAAK,MAAM,OAAQ,EAAW,GAAG,IAAS,EAAO,OAAO,CAYrE,MAXA,GAAK,SAAW,GAEhB,EAAK,QAAQ,YAAc,SACvB,KAAK,OAAO,MAAK,EAAK,IAAM,KAAK,MAAM,KAC3C,EAAK,iBAAiB,YAAe,KAAK,UAAU,EAAK,CAAC,CAC1D,EAAK,iBAAiB,YAAe,CACnC,EAAK,gBAAgB,eAAe,CAChC,KAAK,SAAW,GAAM,KAAK,WAAW,EAC1C,CACF,EAAI,YAAY,EAAK,CACjB,KAAK,OAAO,UAAU,EAAI,YAAY,KAAK,MAAM,OAAQ,EAAW,GAAG,IAAS,EAAO,OAAO,CAAC,CAC5F,EAGT,MAAc,EAAuB,EAAc,EAAiC,CAClF,IAAM,EAAQ,SAAS,cAAc,QAAQ,CAS7C,MARA,GAAM,KAAO,EACb,EAAM,UAAY,iBAClB,EAAM,QAAQ,MAAQ,EACtB,EAAM,MAAQ,KAAK,OAAO,GAC1B,EAAM,aAAa,aAAc,EAAM,CACvC,EAAM,iBAAiB,YAAe,CACpC,KAAK,OAAO,GAAS,EAAM,OAC3B,CACK,EAGT,UAAkB,EAAsC,CACtD,KAAK,OAAS,EACd,KAAK,WAAW,CAGlB,WAA0B,CACnB,KAAK,QACV,KAAK,OAAO,KAAK,KAAK,OAAO,MAAO,CAAE,IAAK,KAAK,OAAO,IAAK,MAAO,KAAK,OAAO,MAAO,CAAC,CAIzF,QAAgB,EAAqB,CACnC,IAAM,EAAQ,KAAK,OACnB,GAAI,CAAC,EAAO,OACZ,IAAM,EAAW,KAAK,OAAO,QAAQ,SAAS,SAAS,cAAc,EAAI,GACzE,EAAM,MAAQ,EACd,EAAM,cAAc,IAAI,MAAM,QAAS,CAAE,QAAS,GAAM,CAAC,CAAC,CAC1D,EAAM,cAAc,IAAI,MAAM,SAAU,CAAE,QAAS,GAAM,CAAC,CAAC,CAC3D,IAAM,EAAO,EAAM,QAAQ,QAAU,WACjC,KAAK,UAAU,cAAgC,wBAAwB,CACvE,KACJ,GAAI,EAAM,CACR,EAAK,OAAO,CACZ,KAAK,UAAU,EAAK,CACpB,OAEF,KAAK,WAAW,CACZ,GAAU,KAAK,OAAO,WAAW,CAGvC,QAAuB,CAErB,IAAM,EAAS,CAAC,GAAI,KAAK,UAAU,iBAAmC,eAAe,EAAI,EAAE,CAAE,CAC7F,IAAK,IAAM,KAAS,EAAQ,KAAK,OAAO,EAAM,QAAQ,OAAkB,EAAM,MAE9E,IAAM,EAAM,KAAK,OAAO,IAClB,EAAQ,EAAO,KAAM,GAAU,EAAM,OAAS,SAAW,CAAC,EAAM,OAAU,IAAQ,IAAA,IAAa,EAAM,MAAQ,GAAM,CACzH,GAAI,EAAO,CACT,EAAM,aAAa,eAAgB,OAAO,CAC1C,EAAM,OAAO,CACb,OAEF,IAAM,EAAI,KAAK,OACT,EAAW,KAAK,OAAO,UAAY,GACnC,EAAQ,GAAe,EAAW,EAAI,GACxC,EACJ,GAAI,KAAK,MAAQ,OAAQ,CACvB,GAAI,CAAC,EAAE,KAAM,OACb,EAAS,CAAE,KAAM,OAAQ,KAAM,EAAE,KAAM,KAAM,EAAK,EAAE,KAAK,CAAE,KACtD,CACL,GAAI,CAAC,EAAE,UAAY,CAAC,EAAE,OAAQ,OAC9B,EAAS,CAAE,KAAM,QAAS,KAAM,CAAE,KAAM,EAAE,SAAU,KAAM,EAAK,EAAE,SAAS,CAAE,CAAE,GAAI,CAAE,KAAM,EAAE,OAAQ,KAAM,EAAK,EAAE,OAAO,CAAE,CAAE,CAE9H,KAAK,OAAO,CACZ,KAAK,SAAS,EAAO,CAIvB,SAAiB,EAAwB,CACvC,IAAM,EAAK,EAAK,QAAQ,KAAK,IAAI,CAE3B,CAAC,EAAS,GADJ,KAAK,UAAU,QAAQ,QAAQ,EAAE,aAAa,MAAM,GAAK,MACvC,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,IACX,EAAE,gBAAgB,CAClB,KAAK,OAAO,EAAK,GAAO,GAAK"}
1
+ {"version":3,"file":"WidgetGoToDate-hNVxz-Zb.cjs","names":[],"sources":["../src/widget/WidgetGoToDate.ts"],"sourcesContent":["import { createIcon } from './icons.js';\nimport { keepTabInside } from './focusTrap.js';\nimport { MonthView, type CalendarLabels } from './monthView.js';\nimport { markPart } from './widgetFeatures.js';\nimport type { WallTime } from './wallTime.js';\n\nexport interface GoToLabels {\n title: string;\n /** The tabs. */\n date: string;\n range: string;\n /** The range's ends. */\n from: string;\n to: string;\n time: string;\n submit: string;\n cancel: string;\n close: string;\n calendar: CalendarLabels;\n}\n\n/** Where to go: a moment to centre, or a span to show edge to edge. */\nexport type GoToTarget =\n | ({ kind: 'date' } & WallTime)\n | { kind: 'range'; from: WallTime; to: WallTime };\n\nexport interface GoToStart {\n /** The Date tab's moment. */\n date: WallTime;\n /** The Custom range tab's ends. */\n range: { from: WallTime; to: WallTime };\n /** Whether bars have a time of day (not on daily bars and longer). */\n withTime: boolean;\n /** The last day that can be picked, YYYY-MM-DD. */\n max?: string;\n /** Today in the time zone the dates are read in; the browser's when left out. */\n today?: string;\n}\n\ntype Tab = 'date' | 'range';\ntype Field = 'date' | 'time' | 'fromDate' | 'fromTime' | 'toDate' | 'toTime';\n\nconst TABS: readonly Tab[] = ['date', 'range'];\n\nlet nextGoToId = 1;\n\n/**\n * The Go to dialog. Date: a day and a time to centre the chart on. Custom\n * range: two of them to show edge to edge. The month under the fields fills\n * the date field in hand; after a range's start it moves on to its end.\n * Mounts in the widget's overlay layer; focus goes back to what opened it.\n */\nexport class WidgetGoToDate {\n private backdrop: HTMLDivElement | null = null;\n private fieldsEl: HTMLDivElement | null = null;\n private tabButtons: HTMLButtonElement[] = [];\n private month: MonthView | null = null;\n /** The date field the month fills. */\n private active: HTMLInputElement | null = null;\n private values: Record<Field, string> = { date: '', time: '', fromDate: '', fromTime: '', toDate: '', toTime: '' };\n private start: GoToStart | null = null;\n private tab: Tab = 'date';\n private returnFocus: HTMLElement | null = null;\n private readonly uid = nextGoToId++;\n\n constructor(\n private readonly layer: () => HTMLElement,\n private readonly labels: () => GoToLabels,\n private readonly locale: () => string,\n private readonly onSubmit: (target: GoToTarget) => void,\n ) {}\n\n isOpen(): boolean {\n return this.backdrop !== null;\n }\n\n open(start: GoToStart): void {\n this.close(false);\n this.returnFocus = document.activeElement instanceof HTMLElement ? document.activeElement : null;\n this.start = start;\n this.values = {\n date: start.date.date,\n time: start.date.time,\n fromDate: start.range.from.date,\n fromTime: start.range.from.time,\n toDate: start.range.to.date,\n toTime: start.range.to.time,\n };\n this.build();\n this.renderTab(true);\n }\n\n /** Close without going anywhere; focus goes back to what opened it unless `giveFocusBack` is false. */\n close(giveFocusBack = true): void {\n if (!this.backdrop) return;\n this.backdrop.remove();\n this.backdrop = this.fieldsEl = this.month = this.active = this.start = null;\n this.tabButtons = [];\n const back = this.returnFocus;\n this.returnFocus = null;\n if (giveFocusBack && back?.isConnected) back.focus();\n }\n\n destroy(): void {\n this.close(false);\n }\n\n private build(): void {\n const labels = this.labels();\n const backdrop = document.createElement('div');\n backdrop.className = 'tcw-modal-backdrop';\n // A press that starts in the dialog and ends on the backdrop doesn't close it.\n let pressedBackdrop = false;\n backdrop.addEventListener('pointerdown', (e) => {\n pressedBackdrop = e.target === backdrop;\n });\n backdrop.addEventListener('click', (e) => {\n if (e.target === backdrop && pressedBackdrop) this.close();\n pressedBackdrop = false;\n });\n\n const modal = document.createElement('div');\n modal.className = 'tcw-modal tcw-goto';\n markPart(modal, 'goToDate');\n modal.setAttribute('role', 'dialog');\n modal.setAttribute('aria-modal', 'true');\n modal.setAttribute('aria-labelledby', `tcw-goto-title-${this.uid}`);\n modal.tabIndex = -1;\n modal.addEventListener('keydown', (e) => {\n e.stopPropagation(); // typing here must not drive chart shortcuts\n if (e.key === 'Escape') {\n e.preventDefault();\n this.close();\n return;\n }\n keepTabInside(e, modal);\n });\n\n const header = document.createElement('div');\n header.className = 'tcw-modal-header';\n const title = document.createElement('h3');\n title.id = `tcw-goto-title-${this.uid}`;\n title.textContent = labels.title;\n const closeBtn = document.createElement('button');\n closeBtn.type = 'button';\n closeBtn.className = 'tcw-modal-close';\n closeBtn.setAttribute('aria-label', labels.close);\n closeBtn.innerHTML = createIcon('x', 16);\n closeBtn.addEventListener('click', () => this.close());\n header.append(title, closeBtn);\n\n const form = document.createElement('form');\n form.className = 'tcw-goto-form';\n form.noValidate = true;\n form.addEventListener('submit', (e) => {\n e.preventDefault();\n this.submit();\n });\n const fields = document.createElement('div');\n fields.className = 'tcw-goto-fields';\n fields.id = `tcw-goto-panel-${this.uid}`;\n fields.setAttribute('role', 'tabpanel');\n const month = new MonthView(labels.calendar, this.locale(), (value) => this.pickDay(value));\n month.element.classList.add('tcw-goto-calendar');\n form.append(fields, month.element, this.buildFooter(labels));\n\n modal.append(header, this.buildTabs(labels), form);\n backdrop.appendChild(modal);\n this.layer().appendChild(backdrop);\n this.backdrop = backdrop;\n this.fieldsEl = fields;\n this.month = month;\n }\n\n private buildTabs(labels: GoToLabels): HTMLDivElement {\n const tabs = document.createElement('div');\n tabs.className = 'tcw-modal-tabs';\n tabs.setAttribute('role', 'tablist');\n tabs.addEventListener('keydown', (e) => this.onTabKey(e));\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-goto-tab-${this.uid}-${tab}`;\n btn.dataset.tab = tab;\n btn.setAttribute('role', 'tab');\n btn.setAttribute('aria-controls', `tcw-goto-panel-${this.uid}`);\n btn.textContent = tab === 'date' ? labels.date : labels.range;\n btn.addEventListener('click', () => this.select(tab, false));\n return btn;\n });\n tabs.append(...this.tabButtons);\n return tabs;\n }\n\n private buildFooter(labels: GoToLabels): HTMLDivElement {\n const cancel = document.createElement('button');\n cancel.type = 'button';\n cancel.className = 'tcw-footer-cancel';\n cancel.dataset.act = 'cancel';\n cancel.textContent = labels.cancel;\n cancel.addEventListener('click', () => this.close());\n const go = document.createElement('button');\n go.type = 'submit';\n go.className = 'tcw-done-btn';\n go.dataset.act = 'go';\n go.textContent = labels.submit;\n const end = document.createElement('div');\n end.className = 'tcw-footer-end';\n end.append(cancel, go);\n const footer = document.createElement('div');\n footer.className = 'tcw-modal-footer';\n footer.appendChild(end);\n return footer;\n }\n\n private select(tab: Tab, focusTab: boolean): void {\n this.tab = tab;\n this.renderTab(!focusTab);\n if (focusTab) this.tabButtons.find((b) => b.dataset.tab === tab)?.focus();\n }\n\n /** The tab's fields, the month on its first date; `focusField` puts the keyboard there. */\n private renderTab(focusField: boolean): void {\n const fields = this.fieldsEl;\n const start = this.start;\n if (!fields || !start) return;\n for (const btn of this.tabButtons) {\n const on = btn.dataset.tab === this.tab;\n btn.setAttribute('aria-selected', String(on));\n btn.classList.toggle('tcw-active', on);\n btn.tabIndex = on ? 0 : -1;\n }\n fields.setAttribute('aria-labelledby', `tcw-goto-tab-${this.uid}-${this.tab}`);\n const labels = this.labels();\n fields.replaceChildren(...(this.tab === 'date'\n ? [this.row(null, 'date', 'time', labels)]\n : [this.row(labels.from, 'fromDate', 'fromTime', labels), this.row(labels.to, 'toDate', 'toTime', labels)]));\n const first = fields.querySelector<HTMLInputElement>('input[type=\"date\"]');\n this.setActive(first);\n if (focusField) first?.focus();\n }\n\n /** A date and, on intraday bars, a time, after the name of the range's end they set. */\n private row(name: string | null, dateField: Field, timeField: Field, labels: GoToLabels): HTMLDivElement {\n const row = document.createElement('div');\n row.className = 'tcw-goto-row';\n if (name) {\n const label = document.createElement('span');\n label.className = 'tcw-goto-label';\n label.textContent = name;\n row.appendChild(label);\n }\n const prefix = name ? `${name} · ` : '';\n const date = this.input('date', dateField, `${prefix}${labels.date}`);\n date.required = true;\n // The month below is this field's calendar.\n date.dataset.tcwCalendar = 'inline';\n if (this.start?.max) date.max = this.start.max;\n date.addEventListener('focus', () => this.setActive(date));\n date.addEventListener('input', () => {\n date.removeAttribute('aria-invalid');\n if (this.active === date) this.showMonth();\n });\n row.appendChild(date);\n if (this.start?.withTime) row.appendChild(this.input('time', timeField, `${prefix}${labels.time}`));\n return row;\n }\n\n private input(type: 'date' | 'time', field: Field, label: string): HTMLInputElement {\n const input = document.createElement('input');\n input.type = type;\n input.className = 'tcw-goto-input';\n input.dataset.field = field;\n input.value = this.values[field];\n input.setAttribute('aria-label', label);\n input.addEventListener('input', () => {\n this.values[field] = input.value;\n });\n return input;\n }\n\n private setActive(input: HTMLInputElement | null): void {\n this.active = input;\n this.showMonth();\n }\n\n private showMonth(): void {\n if (!this.active) return;\n this.month?.show(this.active.value, { max: this.start?.max, today: this.start?.today });\n }\n\n /** A day from the month: into the field in hand; a range's start then moves on to its end. */\n private pickDay(value: string): void {\n const input = this.active;\n if (!input) return;\n const fromGrid = this.month?.element.contains(document.activeElement) ?? false;\n input.value = value;\n input.dispatchEvent(new Event('input', { bubbles: true }));\n input.dispatchEvent(new Event('change', { bubbles: true }));\n const next = input.dataset.field === 'fromDate'\n ? this.fieldsEl?.querySelector<HTMLInputElement>('[data-field=\"toDate\"]')\n : null;\n if (next) {\n next.focus();\n this.setActive(next);\n return;\n }\n this.showMonth();\n if (fromGrid) this.month?.focusGrid();\n }\n\n private submit(): void {\n // The fields as they are now, whether or not they said they changed (autofill).\n const inputs = [...(this.fieldsEl?.querySelectorAll<HTMLInputElement>('[data-field]') ?? [])];\n for (const input of inputs) this.values[input.dataset.field as Field] = input.value;\n // A date not given (or not whole), or after the last day: marked, the keyboard on it, and nowhere to go.\n const max = this.start?.max;\n const wrong = inputs.find((input) => input.type === 'date' && (!input.value || (max !== undefined && input.value > max)));\n if (wrong) {\n wrong.setAttribute('aria-invalid', 'true');\n wrong.focus();\n return;\n }\n const v = this.values;\n const withTime = this.start?.withTime ?? false;\n const time = (t: string) => (withTime ? t : '');\n let target: GoToTarget;\n if (this.tab === 'date') {\n if (!v.date) return;\n target = { kind: 'date', date: v.date, time: time(v.time) };\n } else {\n if (!v.fromDate || !v.toDate) return;\n target = { kind: 'range', from: { date: v.fromDate, time: time(v.fromTime) }, to: { date: v.toDate, time: time(v.toTime) } };\n }\n this.close();\n this.onSubmit(target);\n }\n\n /** Arrow keys (the other way right to left), Home and End move between the tabs. */\n private onTabKey(e: KeyboardEvent): void {\n const at = TABS.indexOf(this.tab);\n const rtl = this.backdrop?.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) return;\n e.preventDefault();\n this.select(TABS[next], true);\n }\n}\n"],"mappings":"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"}
@@ -1,6 +1,6 @@
1
- import { o as e } from "./widgetFeatures-DsbSG6Pq.js";
1
+ import { o as e } from "./widgetFeatures-sU-9JOSB.js";
2
2
  import { i as t, n } from "./focusTrap-Br9Zfje0.js";
3
- import { r } from "./i18n-DWLKoOBG.js";
3
+ import { r } from "./i18n-BZdkj80c.js";
4
4
  import { INDICATOR_CATEGORIES as i } from "@tradecanvas/core";
5
5
  //#region src/widget/indicatorSearch.ts
6
6
  function a(e) {
@@ -242,4 +242,4 @@ var l = 1, u = (e, t) => e.name.localeCompare(t.name), d = class {
242
242
  //#endregion
243
243
  export { d as WidgetIndicatorPicker };
244
244
 
245
- //# sourceMappingURL=WidgetIndicatorPicker-Cw7pFl_l.js.map
245
+ //# sourceMappingURL=WidgetIndicatorPicker-DNjcuBp6.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"WidgetIndicatorPicker-Cw7pFl_l.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":";;;;;AAQA,SAAS,EAAK,GAAsB;AAClC,QAAO,EAAK,UAAU,MAAM,CAAC,QAAQ,UAAU,GAAG,CAAC,aAAa;;AAGlE,IAAM,IAAa;AAGnB,SAAS,EAAM,GAA2B,GAA0B;CAClE,IAAM,IAAQ,EAAK,EAAK,UAAU,EAC5B,IAAO,EAAK,EAAK,KAAK;AAK5B,QAJI,MAAU,KAAK,EAAK,EAAK,GAAG,KAAK,IAAU,IAC3C,EAAM,WAAW,EAAE,IAAI,EAAK,WAAW,EAAE,GAAS,IAClD,CAAC,GAAG,EAAK,MAAM,EAAW,EAAE,GAAG,EAAM,MAAM,EAAW,CAAC,CAAC,MAAM,MAAS,EAAK,WAAW,EAAE,CAAC,GAAS,IACnG,EAAK,SAAS,EAAE,IAAI,EAAM,SAAS,EAAE,GAAS,IAC3C;;AAQT,SAAgB,EAAgD,GAAqB,GAAoB;CACvG,IAAM,IAAI,EAAK,EAAM,MAAM,CAAC;AAE5B,QADK,IACE,EACJ,KAAK,OAAU;EAAE;EAAM,MAAM,EAAM,GAAM,EAAE;EAAE,EAAE,CAC/C,QAAQ,MAA0C,EAAI,SAAS,KAAK,CACpE,MAAM,GAAG,MAAM,EAAE,OAAO,EAAE,QAAQ,EAAE,KAAK,KAAK,cAAc,EAAE,KAAK,KAAK,CAAC,CACzE,KAAK,MAAQ,EAAI,KAAK,GALV,EAAE;;;;ACSnB,IAAI,IAAe,GAEb,KAAU,GAAwB,MAA2B,EAAE,KAAK,cAAc,EAAE,KAAK,EAQlF,IAAb,MAAmC;CACjC,WAA0C;CAC1C,SAAwC;CACxC,QAAuC;CACvC,WAA4C;CAC5C,SAAwC;CACxC,UAA0C;EAAE,OAAO,EAAE;EAAE,WAAW,EAAE;EAAE,WAAW,EAAE;EAAE;CACrF,4BAAoB,IAAI,KAAa;CACrC,yBAA8C,IAAI,KAAK;CACvD,OAAqB;CAErB,WAAgC;CAChC,QAAgB;CAChB,cAA0C;CAC1C,MAAuB;CAEvB,YACE,GACA,GACA,GACA;AADiB,EAFA,KAAA,QAAA,GACA,KAAA,YAAA,GACA,KAAA,IAAA;;CAGnB,KAAK,GAAuC;AAS1C,EARI,KAAK,YAAU,KAAK,MAAM,GAAM,EACpC,KAAK,cAAc,SAAS,yBAAyB,cAAc,SAAS,gBAAgB,MAC5F,KAAK,UAAU,GACf,KAAK,YAAY,IAAI,IAAI,EAAQ,UAAU,EAC3C,KAAK,SAAS,EAAQ,0BAAU,IAAI,KAAK,EACzC,KAAK,QAAQ,IACb,KAAK,OAAO,KAAK,WAAW,EAC5B,KAAK,OAAO,EACZ,KAAK,UAAU,OAAO;;CAGxB,MAAM,IAAgB,IAAY;AAC3B,OAAK,aACV,KAAK,SAAS,QAAQ,EACtB,KAAK,WAAW,KAAK,SAAS,KAAK,QAAQ,KAAK,WAAW,KAAK,SAAS,MACzE,KAAK,UAAU,WAAW,EACtB,KAAiB,KAAK,aAAa,eAAa,KAAK,YAAY,OAAO,EAC5E,KAAK,cAAc;;CAGrB,SAAkB;AAChB,SAAO,KAAK,aAAa;;CAI3B,aAAa,GAAgC;AAE3C,MADA,KAAK,UAAU;GAAE,GAAG,KAAK;GAAS,WAAW,CAAC,GAAG,EAAM;GAAE,EACrD,CAAC,KAAK,UAAU,KAAK,SAAS,eAAe,KAAK,MAAO;EAC7D,IAAM,IAAW,KAAK,OAAO,SAAS,SAAS,cAAc;AAE7D,EADA,KAAK,YAAY,EACb,KAAY,CAAC,KAAK,OAAO,SAAS,SAAS,cAAc,IAC3D,KAAK,OAAO,cAAiC,kBAAkB,EAAE,OAAO;;CAK5E,UAAU,GAA2C;AAEnD,EADA,KAAK,SAAS,GACd,KAAK,QAAQ,iBAA8B,wBAAwB,CAAC,SAAS,MAAQ,KAAK,UAAU,EAAI,CAAC;;CAG3G,UAAgB;AACd,OAAK,MAAM,GAAM;;CAGnB,YAA0B;EACxB,IAAM,IAAQ,KAAK,OAAO;AAE1B,SADI,KAAK,YAAY,EAAM,SAAS,KAAK,SAAS,IAAI,EAAE,KAAK,aAAa,eAAe,KAAK,UAAU,SAAS,KAAW,KAAK,WAC1H,KAAK,UAAU,OAAO,IAAI,cAAc;;CAIjD,QAAwB;EACtB,IAAM,IAAQ,IAAI,IAAI,KAAK,QAAQ,MAAM,KAAK,MAAS,EAAK,SAAS,CAAC;AACtE,SAAO;GACL;GACA,GAAI,KAAK,UAAU,aAAa,KAAK,QAAQ,YAAY,CAAC,YAAqB,GAAG,EAAE;GACpF;GACA,GAAG,EAAqB,QAAQ,MAAa,EAAM,IAAI,EAAS,CAAC;GAClE;;CAGH,UAAkB,GAAoB;AAIpC,SAHI,MAAS,cAAoB,KAAK,EAAE,4BAA4B,GAChE,MAAS,cAAoB,KAAK,EAAE,+BAA+B,GACnE,MAAS,QAAc,KAAK,EAAE,yBAAyB,GACpD,KAAK,EAAE,qBAAqB,IAAqB;;CAG1D,QAAsB;EACpB,IAAM,IAAW,SAAS,cAAc,MAAM;AAE9C,EADA,EAAS,YAAY,sBACrB,EAAS,iBAAiB,UAAU,MAAM;AACxC,GAAI,EAAE,WAAW,KAAU,KAAK,OAAO;IACvC;EACF,IAAM,IAAQ,SAAS,cAAc,MAAM;AAM3C,EALA,EAAM,YAAY,oBAClB,EAAM,aAAa,QAAQ,SAAS,EACpC,EAAM,aAAa,cAAc,OAAO,EACxC,EAAM,aAAa,mBAAmB,gBAAgB,KAAK,MAAM,EACjE,EAAM,WAAW,IACjB,EAAM,iBAAiB,YAAY,MAAM,KAAK,UAAU,GAAG,EAAM,CAAC;EAElE,IAAM,IAAO,SAAS,cAAc,MAAM;AAS1C,EARA,EAAK,YAAY,uBACjB,EAAK,aAAa,aAAa,SAAS,EACxC,EAAM,OAAO,KAAK,aAAa,EAAE,KAAK,aAAa,EAAE,KAAK,WAAW,EAAE,EAAK,EAC5E,EAAS,YAAY,EAAM,EAC3B,KAAK,OAAO,CAAC,YAAY,EAAS,EAClC,KAAK,WAAW,GAChB,KAAK,SAAS,GACd,KAAK,WAAW,EAChB,KAAK,YAAY;;CAGnB,cAAsC;EACpC,IAAM,IAAS,SAAS,cAAc,MAAM;AAC5C,IAAO,YAAY;EACnB,IAAM,IAAQ,SAAS,cAAc,KAAK;AAE1C,EADA,EAAM,KAAK,gBAAgB,KAAK,OAChC,EAAM,cAAc,KAAK,EAAE,qBAAqB;EAChD,IAAM,IAAW,SAAS,cAAc,SAAS;AAOjD,SANA,EAAS,OAAO,UAChB,EAAS,YAAY,mBACrB,EAAS,aAAa,cAAc,KAAK,EAAE,0BAA0B,CAAC,EACtE,EAAS,YAAY,EAAW,KAAK,GAAG,EACxC,EAAS,iBAAiB,eAAe,KAAK,OAAO,CAAC,EACtD,EAAO,OAAO,GAAO,EAAS,EACvB;;CAGT,cAAwC;EACtC,IAAM,IAAM,SAAS,cAAc,QAAQ;AAC3C,IAAI,YAAY;EAChB,IAAM,IAAS,SAAS,cAAc,QAAQ;AAe9C,SAdA,EAAO,OAAO,UACd,EAAO,KAAK,iBAAiB,KAAK,OAClC,EAAO,cAAc,KAAK,EAAE,yBAAyB,EACrD,EAAO,aAAa,cAAc,KAAK,EAAE,yBAAyB,CAAC,EACnE,EAAO,eAAe,OACtB,EAAO,aAAa,IACpB,EAAO,iBAAiB,eAAe;AAErC,GADA,KAAK,QAAQ,EAAO,OACpB,KAAK,YAAY;IACjB,EACF,EAAO,iBAAiB,YAAY,MAAM,KAAK,YAAY,EAAE,CAAC,EAC9D,EAAI,YAAY,EAAW,UAAU,GAAG,EACxC,EAAI,YAAY,EAAO,EACvB,KAAK,WAAW,GACT;;CAIT,YAAoC;EAClC,IAAM,IAAO,SAAS,cAAc,MAAM;AAC1C,IAAK,YAAY;EACjB,IAAM,IAAM,SAAS,cAAc,MAAM;AAKzC,EAJA,EAAI,YAAY,cAChB,EAAI,aAAa,QAAQ,UAAU,EACnC,EAAI,aAAa,oBAAoB,WAAW,EAChD,EAAI,aAAa,cAAc,KAAK,EAAE,yBAAyB,CAAC,EAChE,EAAI,iBAAiB,YAAY,MAAM,KAAK,SAAS,EAAE,CAAC;EACxD,IAAM,IAAO,SAAS,cAAc,MAAM;AAS1C,SARA,EAAK,YAAY,eACjB,EAAK,KAAK,eAAe,KAAK,OAC9B,EAAK,aAAa,QAAQ,WAAW,EACrC,EAAK,iBAAiB,YAAY,MAAM,KAAK,UAAU,EAAE,CAAC,EAC1D,EAAK,iBAAiB,YAAY,MAAM,KAAK,KAAK,EAAE,OAAO,CAAC,EAC5D,EAAK,OAAO,GAAK,EAAK,EACtB,KAAK,QAAQ,GACb,KAAK,SAAS,GACP;;CAGT,YAA0B;EACxB,IAAM,IAAM,KAAK;AACZ,QACL,EAAI,gBAAgB,GAAG,KAAK,OAAO,CAAC,KAAK,MAAS;GAChD,IAAM,IAAM,SAAS,cAAc,SAAS;AAY5C,UAXA,EAAI,OAAO,UACX,EAAI,YAAY,cAChB,EAAI,KAAK,cAAc,KAAK,IAAI,GAAG,KACnC,EAAI,QAAQ,OAAO,GACnB,EAAI,aAAa,QAAQ,MAAM,EAC/B,EAAI,aAAa,iBAAiB,eAAe,KAAK,MAAM,EAE5D,EAAI,YAAY,GADH,MAAS,cAAc,EAAW,QAAQ,GAAG,GAAG,MAAS,cAAc,EAAW,UAAU,GAAG,GAAG,GACvF,gBACxB,EAAI,cAAc,OAAO,CAAE,cAAc,KAAK,UAAU,EAAK,EACzD,MAAS,eAAa,EAAS,GAAK,qBAAqB,EAC7D,EAAI,iBAAiB,eAAe,KAAK,OAAO,EAAK,CAAC,EAC/C;IACP,CAAC,EACH,KAAK,SAAS;;CAGhB,UAAwB;AAOtB,EANA,KAAK,OAAO,iBAAoC,cAAc,CAAC,SAAS,MAAQ;GAC9E,IAAM,IAAK,EAAI,QAAQ,SAAS,KAAK;AAGrC,GAFA,EAAI,aAAa,iBAAiB,OAAO,EAAG,CAAC,EAC7C,EAAI,UAAU,OAAO,cAAc,EAAG,EACtC,EAAI,WAAW,IAAK,IAAI;IACxB,EACF,KAAK,WAAW;;CAIlB,YAA0B;EACxB,IAAM,IAAY,KAAK,MAAM,MAAM,KAAK;AACxC,OAAK,QAAQ,aAAa,mBAAmB,IAAY,iBAAiB,KAAK,QAAQ,cAAc,KAAK,IAAI,GAAG,KAAK,OAAO;;CAG/H,OAAe,GAAY,IAAW,IAAa;AAKjD,EAJA,KAAK,OAAO,KAAK,WAAW,GACxB,KAAK,YAAY,KAAK,UAAO,KAAK,SAAS,QAAQ,KAAK,QAAQ,KACpE,KAAK,SAAS,EACd,KAAK,YAAY,EACb,KAAU,KAAK,OAAO,cAAiC,eAAe,EAAK,IAAI,EAAE,OAAO;;CAG9F,aAA2B;EACzB,IAAM,IAAO,KAAK;AAClB,MAAI,CAAC,EAAM;AACX,IAAK,iBAAiB;EACtB,IAAM,IAAQ,KAAK,QAAQ;AAI3B,MAFA,KAAK,OAAO,UAAU,OAAO,yBAAyB,KAAK,MAAM,MAAM,KAAK,GAAG,EAC/E,KAAK,WAAW,EACZ,KAAK,MAAM,MAAM,EAAE;GACrB,IAAM,IAAO,EAAiB,GAAO,KAAK,MAAM;AAChD,GAAI,EAAK,WAAW,IAAG,EAAK,YAAY,KAAK,KAAK,EAAK,KAAK,EAAE,0BAA0B,EAAE,EAAE,OAAO,KAAK,MAAM,MAAM,EAAE,CAAC,CAAC,CAAC,GACpH,EAAK,OAAO,GAAG,EAAK,KAAK,MAAS,KAAK,IAAI,EAAK,CAAC,CAAC;aAC9C,KAAK,SAAS,YACvB,MAAK,gBAAgB,EAAK;WACjB,KAAK,SAAS,aAAa;GACpC,IAAM,IAAY,EAAM,QAAQ,MAAS,KAAK,UAAU,IAAI,EAAK,GAAG,CAAC,CAAC,KAAK,EAAO;AAClF,GAAI,EAAU,WAAW,IAAG,EAAK,YAAY,KAAK,KAAK,KAAK,EAAE,8BAA8B,CAAC,CAAC,GACzF,EAAK,OAAO,GAAG,EAAU,KAAK,MAAS,KAAK,IAAI,EAAK,CAAC,CAAC;aACnD,KAAK,SAAS,MACvB,MAAK,IAAM,KAAY,GAAsB;GAC3C,IAAM,IAAQ,EAAM,QAAQ,MAAS,EAAK,aAAa,EAAS,CAAC,KAAK,EAAO;AAC7E,OAAI,EAAM,WAAW,EAAG;GACxB,IAAM,IAAU,SAAS,cAAc,MAAM;AAG7C,GAFA,EAAQ,YAAY,kBACpB,EAAQ,cAAc,KAAK,UAAU,EAAS,EAC9C,EAAK,OAAO,GAAS,GAAG,EAAM,KAAK,MAAS,KAAK,IAAI,EAAK,CAAC,CAAC;;MAG9D,GAAK,OAAO,GAAG,EAAM,QAAQ,MAAS,EAAK,aAAa,KAAK,KAAK,CAAC,KAAK,EAAO,CAAC,KAAK,MAAS,KAAK,IAAI,EAAK,CAAC,CAAC;AAEhH,IAAK,cAAiC,cAAc,EAAE,aAAa,YAAY,IAAI;;CAGrF,IAAY,GAA2C;EACrD,IAAM,IAAM,SAAS,cAAc,MAAM;AAEzC,EADA,EAAI,YAAY,cAChB,EAAI,QAAQ,MAAM,EAAK;EACvB,IAAM,IAAM,SAAS,cAAc,SAAS;AAI5C,EAHA,EAAI,OAAO,UACX,EAAI,YAAY,cAChB,EAAI,QAAQ,MAAM,EAAK,IACvB,EAAI,WAAW;EACf,IAAM,IAAO,SAAS,cAAc,OAAO;AAM3C,EALA,EAAK,YAAY,eACjB,EAAK,cAAc,EAAK,MACxB,EAAI,YAAY,EAAK,EACjB,EAAK,aAAa,EAAK,cAAc,EAAK,QAAM,EAAI,YAAY,KAAK,IAAI,gBAAgB,EAAK,UAAU,CAAC,EAC7G,EAAI,YAAY,KAAK,IAAI,eAAe,KAAK,EAAE,iBAAiB,EAAK,YAAY,CAAC,CAAC,EACnF,EAAI,iBAAiB,eAAe,KAAK,IAAI,EAAK,CAAC;EACnD,IAAM,IAAO,SAAS,cAAc,SAAS;AAW7C,SAVA,EAAK,OAAO,UACZ,EAAK,YAAY,4BACjB,EAAK,WAAW,IAChB,EAAK,QAAQ,KAAK,EAAE,2BAA2B,EAC/C,EAAK,aAAa,cAAc,GAAG,KAAK,EAAE,2BAA2B,CAAC,IAAI,EAAK,OAAO,EACtF,EAAK,aAAa,gBAAgB,OAAO,KAAK,UAAU,IAAI,EAAK,GAAG,CAAC,CAAC,EACtE,EAAK,YAAY,EAAW,QAAQ,GAAG,EACvC,EAAK,iBAAiB,eAAe,KAAK,eAAe,EAAK,IAAI,EAAK,CAAC,EACxE,EAAI,OAAO,GAAK,EAAK,EACrB,KAAK,UAAU,EAAI,EACZ;;CAGT,gBAAwB,GAAyB;EAC/C,IAAM,IAAO,SAAS,cAAc,SAAS;AAW7C,EAVA,EAAK,OAAO,UACZ,EAAK,YAAY,0BACjB,EAAK,WAAW,IAChB,EAAK,QAAQ,UAAU,IACvB,EAAK,YAAY,GAAG,EAAW,QAAQ,GAAG,CAAC,oCAC3C,EAAK,cAAc,OAAO,CAAE,cAAc,KAAK,EAAE,iBAAiB,EAClE,EAAK,iBAAiB,eAAe;AAEnC,GADA,KAAK,OAAO,EACZ,KAAK,UAAU,WAAW,QAAQ;IAClC,EACF,EAAK,YAAY,EAAK;EACtB,IAAM,IAAQ,KAAK,QAAQ,aAAa,EAAE;AAC1C,EAAI,EAAM,WAAW,KAAG,EAAK,YAAY,KAAK,KAAK,KAAK,EAAE,8BAA8B,CAAC,CAAC;AAC1F,OAAK,IAAM,KAAQ,GAAO;GACxB,IAAM,IAAM,SAAS,cAAc,MAAM;AACzC,KAAI,YAAY;GAChB,IAAM,IAAQ,SAAS,cAAc,SAAS;AAO9C,GANA,EAAM,OAAO,UACb,EAAM,YAAY,cAClB,EAAM,WAAW,IACjB,EAAM,QAAQ,WAAW,GACzB,EAAM,YAAY,GAAG,EAAW,UAAU,GAAG,CAAC,oCAC9C,EAAM,cAAc,OAAO,CAAE,cAAc,GAC3C,EAAM,iBAAiB,eAAe;AAEpC,IADA,KAAK,OAAO,EACZ,KAAK,UAAU,WAAW,QAAQ,EAAK;KACvC;GACF,IAAM,IAAS,SAAS,cAAc,SAAS;AAI/C,GAHA,EAAO,OAAO,UACd,EAAO,YAAY,8BACnB,EAAO,WAAW,IAClB,EAAO,QAAQ,YAAY;GAC3B,IAAM,IAAQ,EAAK,KAAK,EAAE,mBAAmB,EAAE,EAAE,SAAM,CAAC;AAMxD,GALA,EAAO,QAAQ,GACf,EAAO,aAAa,cAAc,EAAM,EACxC,EAAO,YAAY,EAAW,KAAK,GAAG,EACtC,EAAO,iBAAiB,eAAe,KAAK,UAAU,WAAW,SAAS,EAAK,CAAC,EAChF,EAAI,OAAO,GAAO,EAAO,EACzB,EAAK,YAAY,EAAI;;;CAIzB,IAAY,GAAiC;AAC3C,MAAI,KAAK,UAAU,MAAM,EAAK,GAAG,KAAK,MAAS,CAAC,KAAK,OAAQ;EAC7D,IAAM,IAAO,EAAK,KAAK,EAAE,wBAAwB,EAAE,EAAE,MAAM,EAAK,MAAM,CAAC;AAEvE,OAAK,OAAO,cAAc,KAAK,OAAO,gBAAgB,IAAO,GAAG,EAAK,UAAU;;CAGjF,eAAuB,GAAY,GAA+B;EAChE,IAAM,IAAK,CAAC,KAAK,UAAU,IAAI,EAAG;AAKlC,MAJI,IAAI,KAAK,UAAU,IAAI,EAAG,GACzB,KAAK,UAAU,OAAO,EAAG,EAC9B,KAAK,UAAU,WAAW,GAAI,EAAG,EACjC,EAAK,aAAa,gBAAgB,OAAO,EAAG,CAAC,EACzC,KAAK,SAAS,eAAe,KAAK,SAAS,EAAI;EAGnD,IAAM,IAAK,CADI,GAAI,KAAK,QAAQ,iBAAoC,eAAe,IAAI,EAAE,CAC9E,CAAM,QAAQ,EAAK,EACxB,IAAW,SAAS,kBAAkB;AAE5C,MADA,KAAK,YAAY,EACb,CAAC,EAAU;EACf,IAAM,IAAO,CAAC,GAAI,KAAK,QAAQ,iBAAoC,eAAe,IAAI,EAAE,CAAE;AAC1F,GAAC,EAAK,MAAO,EAAK,EAAK,SAAS,MAAM,KAAK,WAAW,OAAO;;CAG/D,UAAkB,GAAwB;EACxC,IAAM,IAAI,KAAK,OAAO,IAAI,EAAI,QAAQ,OAAO,GAAG,IAAI,GAC9C,IAAM,EAAI,cAAc,cAAc,EACxC,IAAQ,EAAI,cAA2B,gBAAgB;AAC3D,MAAI,MAAM,GAAG;AACX,MAAO,QAAQ;AACf;;AAOF,EALK,MACH,IAAQ,KAAK,IAAI,gBAAgB,GAAG,EACpC,GAAK,YAAY,EAAM,GAEzB,EAAM,cAAc,OAAO,EAAE,EAC7B,EAAM,QAAQ,EAAK,KAAK,EAAE,0BAA0B,EAAE,EAAE,OAAO,OAAO,EAAE,EAAE,CAAC;;CAG7E,IAAY,GAAmB,GAA+B;EAC5D,IAAM,IAAO,SAAS,cAAc,OAAO;AAG3C,SAFA,EAAK,YAAY,GACjB,EAAK,cAAc,GACZ;;CAGT,KAAa,GAA8B;EACzC,IAAM,IAAK,SAAS,cAAc,MAAM;AAGxC,SAFA,EAAG,YAAY,gBACf,EAAG,cAAc,GACV;;CAIT,KAAa,GAAkC;AACzC,IAAE,aAAkB,gBAAgB,CAAC,EAAO,UAAU,SAAS,aAAa,KAChF,KAAK,QAAQ,iBAA8B,8BAA4B,CAAC,SAAS,MAAO;AACtF,GAAI,MAAO,MAAQ,EAAG,WAAW;IACjC,EACF,EAAO,WAAW;;CAGpB,UAAkB,GAAkB,GAA0B;AAC5D,MAAI,EAAE,QAAQ,UAAU;AAGtB,GAFA,EAAE,gBAAgB,EAClB,EAAE,iBAAiB,EACnB,KAAK,OAAO;AACZ;;AAEF,IAAc,GAAG,EAAM;;CAIzB,YAAoB,GAAwB;EAC1C,IAAM,IAAQ,KAAK,QAAQ,cAAiC,cAAc;AAC1E,EAAI,EAAE,QAAQ,eAAe,KAC3B,EAAE,gBAAgB,EAClB,EAAM,OAAO,IACJ,EAAE,QAAQ,WAAW,KAAK,MAAM,MAAM,IAAI,MACnD,EAAE,gBAAgB,EAClB,EAAM,OAAO;;CAKjB,SAAiB,GAAwB;EACvC,IAAM,IAAQ,KAAK,OAAO,EACpB,IAAK,EAAM,QAAQ,KAAK,KAAK,EAC7B,IAAO,EAAE,QAAQ,cAAc,KAAK,IAAI,EAAM,SAAS,GAAG,IAAK,EAAE,GACnE,EAAE,QAAQ,YAAY,KAAK,IAAI,GAAG,IAAK,EAAE,GACzC,EAAE,QAAQ,SAAS,IACnB,EAAE,QAAQ,QAAQ,EAAM,SAAS,IACjC;AACA,MAAO,MACX,EAAE,gBAAgB,EAClB,KAAK,OAAO,EAAM,IAAO,GAAK;;CAIhC,UAAkB,GAAwB;EACxC,IAAM,IAAS,EAAE,QACX,IAAM,EAAO,QAAQ,4BAA4B,EACjD,IAAO,CAAC,GAAI,KAAK,QAAQ,iBAAoC,cAAc,IAAI,EAAE,CAAE,EACnF,IAAO,GAAK,UAAU,SAAS,cAAc,GAAG,IAA2B,GAAK,cAAiC,cAAc,EAC/H,IAAK,IAAO,EAAK,QAAQ,EAAK,GAAG,IACnC,IAAqC;AACzC,EAAI,EAAE,QAAQ,cAAa,IAAK,EAAK,IAAK,KACjC,EAAE,QAAQ,YAAW,IAAK,IAAK,IAAI,EAAK,IAAK,KAAK,KAAK,WACvD,EAAE,QAAQ,SAAQ,IAAK,EAAK,KAC5B,EAAE,QAAQ,QAAO,IAAK,EAAK,EAAK,SAAS,KACzC,EAAE,QAAQ,gBAAgB,MAAW,IAAM,IAAK,GAAK,cAAiC,gBAAgB,GACtG,EAAE,QAAQ,eAAe,MAAW,MAAM,IAAK,IACnD,MACL,EAAE,gBAAgB,EAClB,EAAG,OAAO"}
1
+ {"version":3,"file":"WidgetIndicatorPicker-DNjcuBp6.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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"}
@@ -1,2 +1,2 @@
1
- const e=require(`./widgetFeatures-B-el8UA3.cjs`),t=require(`./focusTrap-DrhhmG0V.cjs`),n=require(`./i18n-fgAwciRT.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-Nc-1Rbm_.cjs.map
1
+ const e=require(`./widgetFeatures-DpBWfQ8D.cjs`),t=require(`./focusTrap-DrhhmG0V.cjs`),n=require(`./i18n-BFGknfTa.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-GCesxFMn.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"WidgetIndicatorPicker-Nc-1Rbm_.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":"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"}
1
+ {"version":3,"file":"WidgetIndicatorPicker-GCesxFMn.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":"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"}
@@ -1,2 +1,2 @@
1
- const e=require(`./focusTrap-DrhhmG0V.cjs`),t=require(`./settingsControls-Tn8_vJJv.cjs`),n=require(`./i18n-fgAwciRT.cjs`),r=require(`./indicatorInputWords-C4Mefhve.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-Da_T6bmX.cjs.map
1
+ const e=require(`./focusTrap-DrhhmG0V.cjs`),t=require(`./settingsControls-Tn8_vJJv.cjs`),n=require(`./i18n-BFGknfTa.cjs`),r=require(`./indicatorInputWords-_kxwKob4.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-CBHWj5z5.cjs.map