@tradecanvas/chart 1.72.0 → 1.72.2

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 (125) hide show
  1. package/dist/{LayoutSession-r7Rgc5Sw.cjs → LayoutSession-BrXo314m.cjs} +2 -2
  2. package/dist/{LayoutSession-r7Rgc5Sw.cjs.map → LayoutSession-BrXo314m.cjs.map} +1 -1
  3. package/dist/{LayoutSession-DjHF7tmL.js → LayoutSession-BrazGVET.js} +2 -2
  4. package/dist/{LayoutSession-DjHF7tmL.js.map → LayoutSession-BrazGVET.js.map} +1 -1
  5. package/dist/WidgetCommandPalette-CeCLHPy1.js +177 -0
  6. package/dist/WidgetCommandPalette-CeCLHPy1.js.map +1 -0
  7. package/dist/WidgetCommandPalette-ZZNbWwCZ.cjs +2 -0
  8. package/dist/WidgetCommandPalette-ZZNbWwCZ.cjs.map +1 -0
  9. package/dist/{WidgetDrawingSettings-MSpeHFhV.js → WidgetDrawingSettings-BeW8fX7r.js} +5 -5
  10. package/dist/{WidgetDrawingSettings-MSpeHFhV.js.map → WidgetDrawingSettings-BeW8fX7r.js.map} +1 -1
  11. package/dist/{WidgetDrawingSettings-B3KAH9Ag.cjs → WidgetDrawingSettings-Cyl2u_u8.cjs} +2 -2
  12. package/dist/{WidgetDrawingSettings-B3KAH9Ag.cjs.map → WidgetDrawingSettings-Cyl2u_u8.cjs.map} +1 -1
  13. package/dist/WidgetGoToDate-BNdngHlg.cjs +2 -0
  14. package/dist/{WidgetGoToDate-CPzfStIm.cjs.map → WidgetGoToDate-BNdngHlg.cjs.map} +1 -1
  15. package/dist/{WidgetGoToDate-R2teTLpJ.js → WidgetGoToDate-hcDtezRm.js} +6 -5
  16. package/dist/{WidgetGoToDate-R2teTLpJ.js.map → WidgetGoToDate-hcDtezRm.js.map} +1 -1
  17. package/dist/{WidgetHotkeySheet-BE0glkVA.js → WidgetHotkeySheet-1lRFSdvt.js} +3 -3
  18. package/dist/{WidgetHotkeySheet-BE0glkVA.js.map → WidgetHotkeySheet-1lRFSdvt.js.map} +1 -1
  19. package/dist/{WidgetHotkeySheet-pnsXreK6.cjs → WidgetHotkeySheet-y5LWKSG6.cjs} +2 -2
  20. package/dist/{WidgetHotkeySheet-pnsXreK6.cjs.map → WidgetHotkeySheet-y5LWKSG6.cjs.map} +1 -1
  21. package/dist/WidgetIndicatorPicker-CGvZ5kYd.js +245 -0
  22. package/dist/WidgetIndicatorPicker-CGvZ5kYd.js.map +1 -0
  23. package/dist/WidgetIndicatorPicker-DuKJFPRC.cjs +2 -0
  24. package/dist/WidgetIndicatorPicker-DuKJFPRC.cjs.map +1 -0
  25. package/dist/{WidgetIndicatorSettings-BJUjACzj.cjs → WidgetIndicatorSettings-C7vpco6e.cjs} +2 -2
  26. package/dist/{WidgetIndicatorSettings-BJUjACzj.cjs.map → WidgetIndicatorSettings-C7vpco6e.cjs.map} +1 -1
  27. package/dist/{WidgetIndicatorSettings-DgbsZk0h.js → WidgetIndicatorSettings-dDkqzzYo.js} +6 -6
  28. package/dist/{WidgetIndicatorSettings-DgbsZk0h.js.map → WidgetIndicatorSettings-dDkqzzYo.js.map} +1 -1
  29. package/dist/{WidgetSettings-D35fHGXL.js → WidgetSettings--XzDx515.js} +24 -11
  30. package/dist/WidgetSettings--XzDx515.js.map +1 -0
  31. package/dist/WidgetSettings-C0fnWGdY.cjs +2 -0
  32. package/dist/WidgetSettings-C0fnWGdY.cjs.map +1 -0
  33. package/dist/WidgetSymbolSearch-C4OR42nZ.cjs +2 -0
  34. package/dist/WidgetSymbolSearch-C4OR42nZ.cjs.map +1 -0
  35. package/dist/WidgetSymbolSearch-CvPpjIwa.js +580 -0
  36. package/dist/WidgetSymbolSearch-CvPpjIwa.js.map +1 -0
  37. package/dist/{focusTrap-Hm7AUTBw.js → focusTrap-Br9Zfje0.js} +1 -1
  38. package/dist/{focusTrap-Hm7AUTBw.js.map → focusTrap-Br9Zfje0.js.map} +1 -1
  39. package/dist/{focusTrap-B8OoGwHb.cjs → focusTrap-DrhhmG0V.cjs} +1 -1
  40. package/dist/{focusTrap-B8OoGwHb.cjs.map → focusTrap-DrhhmG0V.cjs.map} +1 -1
  41. package/dist/{i18n-DFR1Oukq.cjs → i18n-C2nQBZTY.cjs} +1 -1
  42. package/dist/{i18n-DFR1Oukq.cjs.map → i18n-C2nQBZTY.cjs.map} +1 -1
  43. package/dist/{i18n-CvnQcfor.js → i18n-C5v6Tzp1.js} +1 -1
  44. package/dist/{i18n-CvnQcfor.js.map → i18n-C5v6Tzp1.js.map} +1 -1
  45. package/dist/index.cjs +1 -1
  46. package/dist/index.js +1 -1
  47. package/dist/{indicatorInputWords-D19kY9NO.js → indicatorInputWords-Bd9Pt6qp.js} +2 -2
  48. package/dist/{indicatorInputWords-D19kY9NO.js.map → indicatorInputWords-Bd9Pt6qp.js.map} +1 -1
  49. package/dist/{indicatorInputWords-DzrKBU2I.cjs → indicatorInputWords-CTA4IcJR.cjs} +2 -2
  50. package/dist/{indicatorInputWords-DzrKBU2I.cjs.map → indicatorInputWords-CTA4IcJR.cjs.map} +1 -1
  51. package/dist/{indicatorSettingsTabs-BLEULMJJ.cjs → indicatorSettingsTabs-B0liE3ST.cjs} +2 -2
  52. package/dist/{indicatorSettingsTabs-BLEULMJJ.cjs.map → indicatorSettingsTabs-B0liE3ST.cjs.map} +1 -1
  53. package/dist/{indicatorSettingsTabs-Wxx2v_y4.js → indicatorSettingsTabs-XcgMdbLp.js} +3 -3
  54. package/dist/{indicatorSettingsTabs-Wxx2v_y4.js.map → indicatorSettingsTabs-XcgMdbLp.js.map} +1 -1
  55. package/dist/monthView-B-CieFDJ.cjs +2 -0
  56. package/dist/monthView-B-CieFDJ.cjs.map +1 -0
  57. package/dist/monthView-N8yDaTqZ.js +198 -0
  58. package/dist/monthView-N8yDaTqZ.js.map +1 -0
  59. package/dist/{platformKeys-BAPueZ1I.cjs → platformKeys-B82-j1Fs.cjs} +1 -1
  60. package/dist/{platformKeys-BAPueZ1I.cjs.map → platformKeys-B82-j1Fs.cjs.map} +1 -1
  61. package/dist/{platformKeys-D_x_6ZrV.js → platformKeys-Dd1qOCjF.js} +1 -1
  62. package/dist/{platformKeys-D_x_6ZrV.js.map → platformKeys-Dd1qOCjF.js.map} +1 -1
  63. package/dist/{settingsControls-ef4j_clQ.js → settingsControls-DIjzCFHI.js} +2 -2
  64. package/dist/{settingsControls-ef4j_clQ.js.map → settingsControls-DIjzCFHI.js.map} +1 -1
  65. package/dist/{settingsControls-jNn0If4b.cjs → settingsControls-Tn8_vJJv.cjs} +2 -2
  66. package/dist/{settingsControls-jNn0If4b.cjs.map → settingsControls-Tn8_vJJv.cjs.map} +1 -1
  67. package/dist/symbolCatalog-CA1fpz3s.js +90 -0
  68. package/dist/symbolCatalog-CA1fpz3s.js.map +1 -0
  69. package/dist/symbolCatalog-Dx1YnZgm.cjs +2 -0
  70. package/dist/symbolCatalog-Dx1YnZgm.cjs.map +1 -0
  71. package/dist/widget/ChartWidget.d.ts +102 -580
  72. package/dist/widget/ChartWidget.d.ts.map +1 -1
  73. package/dist/widget/WidgetAlerts.d.ts +35 -0
  74. package/dist/widget/WidgetAlerts.d.ts.map +1 -0
  75. package/dist/widget/WidgetChartSettings.d.ts +211 -0
  76. package/dist/widget/WidgetChartSettings.d.ts.map +1 -0
  77. package/dist/widget/WidgetCommands.d.ts +54 -0
  78. package/dist/widget/WidgetCommands.d.ts.map +1 -0
  79. package/dist/widget/WidgetCompares.d.ts +140 -0
  80. package/dist/widget/WidgetCompares.d.ts.map +1 -0
  81. package/dist/widget/WidgetDockArea.d.ts +90 -0
  82. package/dist/widget/WidgetDockArea.d.ts.map +1 -0
  83. package/dist/widget/WidgetDrawingEdit.d.ts +73 -0
  84. package/dist/widget/WidgetDrawingEdit.d.ts.map +1 -0
  85. package/dist/widget/WidgetIntervals.d.ts +66 -0
  86. package/dist/widget/WidgetIntervals.d.ts.map +1 -0
  87. package/dist/widget/WidgetLayouts.d.ts +85 -0
  88. package/dist/widget/WidgetLayouts.d.ts.map +1 -0
  89. package/dist/widget/WidgetLegendView.d.ts +54 -0
  90. package/dist/widget/WidgetLegendView.d.ts.map +1 -0
  91. package/dist/widget/WidgetMenus.d.ts +79 -0
  92. package/dist/widget/WidgetMenus.d.ts.map +1 -0
  93. package/dist/widget/WidgetReplay.d.ts +199 -0
  94. package/dist/widget/WidgetReplay.d.ts.map +1 -0
  95. package/dist/widget/WidgetSettings.d.ts +2 -0
  96. package/dist/widget/WidgetSettings.d.ts.map +1 -1
  97. package/dist/widget/WidgetTrading.d.ts +48 -0
  98. package/dist/widget/WidgetTrading.d.ts.map +1 -0
  99. package/dist/widget/WidgetWatchlists.d.ts +77 -0
  100. package/dist/widget/WidgetWatchlists.d.ts.map +1 -0
  101. package/dist/widget/index.cjs +43 -29
  102. package/dist/widget/index.cjs.map +1 -1
  103. package/dist/widget/index.js +9628 -9812
  104. package/dist/widget/index.js.map +1 -1
  105. package/dist/widget/keyTarget.d.ts +4 -0
  106. package/dist/widget/keyTarget.d.ts.map +1 -1
  107. package/dist/widget/locales/index.cjs +1 -1
  108. package/dist/widget/locales/index.js +1 -1
  109. package/dist/widget/pageEnv.d.ts +5 -0
  110. package/dist/widget/pageEnv.d.ts.map +1 -0
  111. package/dist/widget/widgetDialogs.d.ts +3 -0
  112. package/dist/widget/widgetDialogs.d.ts.map +1 -1
  113. package/dist/widgetFeatures-BM6u0u74.js +186 -0
  114. package/dist/widgetFeatures-BM6u0u74.js.map +1 -0
  115. package/dist/widgetFeatures-BZ8OVPN2.cjs +3 -0
  116. package/dist/widgetFeatures-BZ8OVPN2.cjs.map +1 -0
  117. package/package.json +3 -3
  118. package/dist/WidgetGoToDate-CPzfStIm.cjs +0 -2
  119. package/dist/WidgetSettings-D35fHGXL.js.map +0 -1
  120. package/dist/WidgetSettings-Pgt3p6vv.cjs +0 -2
  121. package/dist/WidgetSettings-Pgt3p6vv.cjs.map +0 -1
  122. package/dist/widgetFeatures-BvkTbsQn.js +0 -378
  123. package/dist/widgetFeatures-BvkTbsQn.js.map +0 -1
  124. package/dist/widgetFeatures-Cy0fs3Nl.cjs +0 -3
  125. package/dist/widgetFeatures-Cy0fs3Nl.cjs.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"settingsControls-jNn0If4b.cjs","names":[],"sources":["../src/widget/WidgetContextMenu.ts","../src/widget/settingsControls.ts"],"sourcesContent":["import { createIcon } from './icons.js';\n\nexport type ContextMenuEntry =\n /**\n * `checked` makes it a switch (ticked when on); with `radio` it is one\n * choice among the radios beside it. `shortcut` (\"Alt+I\") is written at\n * the row's end. `submenu` opens more entries beside this one. `remove`\n * (its action's name, \"Delete template Swing\") gives it a remove button\n * and the Delete key; the menu's `onRemove` hears it.\n */\n | {\n id: string;\n label: string;\n icon?: string;\n danger?: boolean;\n checked?: boolean;\n radio?: boolean;\n shortcut?: string;\n /** How many things it acts on (\"Remove drawings 3\"), at the row's end and read with its name. */\n count?: number;\n submenu?: readonly ContextMenuEntry[];\n remove?: string;\n }\n | 'separator';\n\ntype MenuItem = Exclude<ContextMenuEntry, 'separator'>;\n\n/** Room kept between the menu and the edge it would touch. */\nconst MENU_MARGIN = 4;\n/** Room between a menu and the button it hangs under. */\nconst MENU_GAP = 2;\n/**\n * How long the pointer rests on an entry before its submenu opens, or the\n * open one closes: crossing an entry on the way into a submenu does neither.\n */\nconst HOVER_DELAY_MS = 150;\nconst ITEM_SELECTOR = '[role=menuitem], [role=menuitemcheckbox], [role=menuitemradio]';\n/** Where menus open as sheets from the bottom (the stylesheet's phone width): a submenu covers its menu. */\nconst SHEET_QUERY = '(max-width: 640px)';\n/** The way back from a submenu opened as a sheet. */\nconst BACK_ID = 'tcw:back';\n\n/**\n * The menus open now, on every widget of the page: a button that several\n * menus hang from (\"more\", for those picked from it) closes whichever is open.\n */\nconst openMenus = new Set<WidgetContextMenu>();\n\n/** Where a menu opens along each axis, in the host's coordinates. */\ninterface MenuPlace {\n x: number;\n y: number;\n /** Where it ends instead, when there is no room after `x` or below `y`. */\n xBack: number;\n yBack: number;\n /** Ending at `xBack` first, and starting at `x` only without room there (a submenu, right to left). */\n before?: boolean;\n}\n\n/**\n * Where a menu `size` long starts along one axis: at `at`, or ending at\n * `back` when it would pass `end`; with `before`, the other way round.\n */\nfunction place(at: number, size: number, end: number, back = at, before = false): number {\n const start = before\n ? (back - size >= 0 ? back - size : at)\n : (at + size > end ? back - size : at);\n return Math.max(0, Math.min(start, end - size));\n}\n\nfunction itemRole(entry: MenuItem): string {\n if (entry.radio) return 'menuitemradio';\n return entry.checked === undefined ? 'menuitem' : 'menuitemcheckbox';\n}\n\n/** What leads an entry: a tick on a switch that is on, a dot on the choice made, else its icon or room for one. */\nfunction leadMark(entry: MenuItem): string {\n const blank = '<span class=\"tcw-context-icon\"></span>';\n if (entry.radio) return entry.checked ? '<span class=\"tcw-context-icon tcw-context-dot\"></span>' : blank;\n // A switch that is off shows its icon, as an action does.\n if (entry.checked) return createIcon('check', 14);\n return (entry.icon && createIcon(entry.icon, 14)) || blank;\n}\n\n/**\n * A small menu opened at a point (a right-click on a drawing, say): arrow\n * keys, Home and End move through it, Enter picks, Escape, Tab or a click\n * outside close it. Focus returns to where it was. An entry with a submenu\n * opens it beside itself on a click, Enter, the right arrow (the left one\n * right to left) or a rest of the pointer; the other arrow or Escape closes\n * it again.\n */\nexport class WidgetContextMenu {\n private el: HTMLDivElement;\n private onPick: ((id: string) => void) | null = null;\n private onRemove: ((id: string) => void) | null = null;\n private returnFocus: HTMLElement | null = null;\n /** The button that opened it: pressing it again is its click's to handle (a toggle). */\n private anchor: HTMLElement | null = null;\n /** The submenu, made the first time an entry opens one. */\n private child: WidgetContextMenu | null = null;\n /** The menu this one is the submenu of. */\n private parent: WidgetContextMenu | null = null;\n private hoverTimer: ReturnType<typeof setTimeout> | null = null;\n /** The entry `hoverTimer` counts the rest on: moving on it counts on, not again. */\n private hoverItem: HTMLElement | null = null;\n private readonly onDocPointer = (e: Event) => {\n const target = e.target as Node;\n if (this.holds(target) || this.anchor?.contains(target)) return;\n this.close();\n };\n /** Scrolling a long menu keeps it; scrolling anywhere else moves the chart from under it. */\n private readonly onWheel = (e: Event) => {\n if (!this.holds(e.target as Node)) this.close();\n };\n /** Resizing or leaving the window moves the chart from under the menu. */\n private readonly onAway = () => this.close();\n\n /**\n * `backLabel` (\"Back\") names the first entry of a submenu on a phone, which\n * leads back to the menu under it.\n */\n constructor(private readonly host: HTMLElement, label: string, private readonly backLabel = '') {\n this.el = document.createElement('div');\n this.el.className = 'tcw-context-menu';\n this.el.setAttribute('role', 'menu');\n this.el.setAttribute('aria-label', label);\n this.el.hidden = true;\n this.el.addEventListener('keydown', (e) => this.onKeyDown(e));\n host.appendChild(this.el);\n }\n\n /**\n * Open at (`x`, `y`) in the host's coordinates. It opens up or to the left\n * of the point when there is no room below or to the right, ending at\n * `xBack` then (a menu under a button ends at the button's far edge), and\n * stays inside both the host and the window. With `before` it ends at\n * `xBack` first, and starts at `x` only without room before it.\n */\n open(\n entries: readonly ContextMenuEntry[],\n x: number,\n y: number,\n onPick: (id: string) => void,\n anchor?: HTMLElement,\n onRemove?: (id: string) => void,\n xBack = x,\n before = false,\n ): void {\n this.show(entries, { x, y, xBack, yBack: y, before }, onPick, anchor, onRemove, true);\n }\n\n /**\n * Open under `anchor`, `gap` px below it, lined up with it as the\n * toolbar's own menus are, mirrored right to left: from its start edge\n * (`'start'`), or ending at its end edge (`'end'`: \"more\", at the\n * toolbar's end); from its other edge when there is no room that way.\n */\n openUnder(\n entries: readonly ContextMenuEntry[],\n anchor: HTMLElement,\n onPick: (id: string) => void,\n align: 'start' | 'end' = 'start',\n gap = MENU_GAP,\n ): void {\n const at = anchor.getBoundingClientRect();\n const box = this.host.getBoundingClientRect();\n // Ending at the button's right edge first: its end left to right, its start right to left.\n const before = (align === 'end') !== this.rtl();\n this.open(entries, at.left - box.left, at.bottom - box.top + gap, onPick, anchor, undefined, at.right - box.left, before);\n }\n\n /**\n * Open beside `anchor` (a button of the drawing bar), level with its top:\n * past its end edge, or before its start edge without room there; ending\n * at its bottom when there is no room below.\n */\n openBeside(entries: readonly ContextMenuEntry[], anchor: HTMLElement, onPick: (id: string) => void): void {\n const at = anchor.getBoundingClientRect();\n const box = this.host.getBoundingClientRect();\n // Starting past its right edge, or ending before its left one: right to left, the left first.\n const right = at.right - box.left + MENU_GAP;\n const left = at.left - box.left - MENU_GAP;\n this.show(entries, { x: right, y: at.top - box.top, xBack: left, yBack: at.bottom - box.top, before: this.rtl() }, onPick, anchor, undefined, true);\n // Safari doesn't focus a pressed button: the keyboard goes back to it all the same.\n this.returnFocus = anchor;\n }\n\n /**\n * Close every menu open under `anchor`, and say whether one was. Its\n * button shows it open (aria-expanded), so a press on it closes it.\n */\n static closeAnchoredTo(anchor: HTMLElement): boolean {\n const under = [...openMenus].filter((menu) => menu.anchor === anchor);\n for (const menu of under) menu.close();\n return under.length > 0;\n }\n\n /** The button an open menu that holds the keyboard hangs from; null when none does. */\n static focusedAnchor(): HTMLElement | null {\n return [...openMenus].find((menu) => menu.anchor !== null && menu.hasFocus())?.anchor ?? null;\n }\n\n close(): void {\n this.clearHover();\n this.child?.close();\n if (this.el.hidden) return;\n openMenus.delete(this);\n // A submenu the pointer closes leaves the keyboard where it is, in the menu.\n const giveBack = !this.parent || this.el.contains(document.activeElement);\n this.el.hidden = true;\n this.onPick = null;\n this.onRemove = null;\n this.anchor?.setAttribute('aria-expanded', 'false');\n this.anchor = null;\n document.removeEventListener('pointerdown', this.onDocPointer, true);\n document.removeEventListener('wheel', this.onWheel, { capture: true });\n window.removeEventListener('resize', this.onAway);\n window.removeEventListener('blur', this.onAway);\n if (giveBack && this.returnFocus?.isConnected) this.returnFocus.focus({ preventScroll: true });\n this.returnFocus = null;\n }\n\n isOpen(): boolean {\n return !this.el.hidden;\n }\n\n /** Whether the keyboard is in the menu, or in a submenu of it. */\n hasFocus(): boolean {\n return document.activeElement !== null && this.holds(document.activeElement);\n }\n\n destroy(): void {\n this.close();\n this.child?.destroy();\n this.el.remove();\n }\n\n private show(\n entries: readonly ContextMenuEntry[],\n at: MenuPlace,\n onPick: (id: string) => void,\n anchor: HTMLElement | undefined,\n onRemove: ((id: string) => void) | undefined,\n focusFirst: boolean,\n ): void {\n this.clearHover();\n this.child?.close();\n this.returnFocus = document.activeElement instanceof HTMLElement ? document.activeElement : null;\n this.onPick = onPick;\n this.onRemove = onRemove ?? null;\n this.anchor = anchor ?? null;\n this.anchor?.setAttribute('aria-expanded', 'true');\n this.el.replaceChildren(...entries.map((entry) => this.entry(entry)));\n this.el.hidden = false;\n openMenus.add(this);\n const hostRect = this.host.getBoundingClientRect();\n const right = Math.min(this.host.clientWidth, window.innerWidth - hostRect.left) - MENU_MARGIN;\n const bottom = Math.min(this.host.clientHeight, window.innerHeight - hostRect.top) - MENU_MARGIN;\n // Taller than the room (a short widget, the time zones), it scrolls, its padding and border in the room too.\n this.el.style.maxHeight = `${Math.max(0, bottom - this.frameHeight())}px`;\n this.el.style.left = `${place(at.x, this.el.offsetWidth, right, at.xBack, at.before)}px`;\n this.el.style.top = `${place(at.y, this.el.offsetHeight, bottom, at.yBack)}px`;\n document.addEventListener('pointerdown', this.onDocPointer, true);\n document.addEventListener('wheel', this.onWheel, { capture: true, passive: true });\n window.addEventListener('resize', this.onAway);\n window.addEventListener('blur', this.onAway);\n const chosen = this.revealChoice();\n if (focusFirst) (chosen ?? this.items()[0])?.focus({ preventScroll: true });\n }\n\n /** What its padding and border add to a max height, which sizes only its rows unless its box counts them. */\n private frameHeight(): number {\n const style = getComputedStyle(this.el);\n if (style.boxSizing === 'border-box') return 0;\n const px = (value: string) => parseFloat(value) || 0;\n return px(style.paddingTop) + px(style.paddingBottom) + px(style.borderTopWidth) + px(style.borderBottomWidth);\n }\n\n /**\n * A long list of choices (the time zones) opens scrolled to the one made,\n * returned to take the keyboard; null when it is in sight already.\n */\n private revealChoice(): HTMLElement | null {\n const chosen = this.el.querySelector<HTMLElement>('[role=menuitemradio][aria-checked=true]');\n if (!chosen || chosen.offsetTop + chosen.offsetHeight <= this.el.clientHeight) return null;\n this.el.scrollTop = chosen.offsetTop - (this.el.clientHeight - chosen.offsetHeight) / 2;\n return chosen;\n }\n\n /** The widget reads right to left (`dir=\"rtl\"`): submenus open to the left, and the arrows swap. */\n private rtl(): boolean {\n return this.host.closest('[dir]')?.getAttribute('dir') === 'rtl';\n }\n\n /** Whether `node` is in this menu or its open submenu. */\n private holds(node: Node): boolean {\n return this.el.contains(node) || (this.child?.holds(node) ?? false);\n }\n\n private entry(entry: ContextMenuEntry): HTMLElement {\n if (entry === 'separator') {\n const sep = document.createElement('div');\n sep.className = 'tcw-context-sep';\n sep.setAttribute('role', 'separator');\n return sep;\n }\n const item = document.createElement('button');\n item.type = 'button';\n item.className = `tcw-context-item${entry.danger ? ' tcw-context-danger' : ''}`;\n item.setAttribute('role', itemRole(entry));\n if (entry.radio || entry.checked !== undefined) item.setAttribute('aria-checked', String(entry.checked ?? false));\n item.tabIndex = -1;\n item.dataset.id = entry.id;\n item.innerHTML = leadMark(entry);\n const text = document.createElement('span');\n text.className = 'tcw-context-label';\n text.textContent = entry.label;\n item.appendChild(text);\n this.appendEnd(item, entry);\n const submenu = entry.submenu;\n // A finger's tap is a click: only a pointer resting on an entry opens its submenu. A pointer that\n // moved there, not one that entered: a menu that opens under a still pointer (a right-click near\n // a phone's foot, where it opens as a sheet) is entered by it, and opened a submenu unasked.\n item.addEventListener('pointermove', (e) => {\n if ((e as PointerEvent).pointerType !== 'touch') this.hover(item, entry.label, submenu);\n });\n item.addEventListener('click', () => {\n if (submenu) this.openSubmenu(item, entry.label, submenu, true);\n else this.pick(entry.id);\n });\n return entry.remove ? this.removableRow(item, entry.remove) : item;\n }\n\n /** The count, the shortcut, or the mark of a submenu, at the row's end. */\n private appendEnd(item: HTMLButtonElement, entry: MenuItem): void {\n if (entry.count !== undefined) {\n const count = document.createElement('span');\n count.className = 'tcw-context-count';\n count.textContent = String(entry.count);\n item.appendChild(count);\n }\n if (entry.shortcut) {\n // Named once, by aria-keyshortcuts: the key is for the eye.\n item.setAttribute('aria-keyshortcuts', entry.shortcut);\n const key = document.createElement('kbd');\n key.className = 'tcw-context-key';\n key.setAttribute('aria-hidden', 'true');\n key.textContent = entry.shortcut;\n item.appendChild(key);\n }\n if (entry.submenu) {\n item.setAttribute('aria-haspopup', 'menu');\n item.setAttribute('aria-expanded', 'false');\n item.insertAdjacentHTML('beforeend', createIcon('chevronRight', 12));\n item.lastElementChild?.classList.add('tcw-context-more');\n }\n }\n\n /** The keyboard removes it with Delete; the button is for a pointer. */\n private removableRow(item: HTMLButtonElement, action: string): HTMLElement {\n item.setAttribute('aria-keyshortcuts', 'Delete');\n const remove = document.createElement('button');\n remove.type = 'button';\n remove.className = 'tcw-context-remove';\n remove.tabIndex = -1;\n remove.setAttribute('aria-hidden', 'true');\n remove.title = action;\n remove.innerHTML = createIcon('x', 12);\n remove.addEventListener('click', () => this.remove(item));\n const row = document.createElement('div');\n row.className = 'tcw-context-row';\n row.setAttribute('role', 'none');\n row.append(item, remove);\n return row;\n }\n\n /** Open `entries` beside `item`, after it (to its right, or left in RTL) or, without room there, before it. */\n private openSubmenu(item: HTMLButtonElement, label: string, entries: readonly ContextMenuEntry[], focusFirst: boolean): void {\n this.clearHover();\n const child = this.child ?? this.makeChild();\n if (child.isOpen() && child.anchor === item) {\n if (focusFirst) child.items()[0]?.focus({ preventScroll: true });\n return;\n }\n child.close();\n child.el.setAttribute('aria-label', label);\n const hostRect = this.host.getBoundingClientRect();\n const r = item.getBoundingClientRect();\n const at: MenuPlace = {\n x: r.right - hostRect.left,\n y: r.top - hostRect.top,\n xBack: r.left - hostRect.left,\n yBack: r.bottom - hostRect.top,\n before: this.rtl(),\n };\n // As a sheet it hides the menu under it: its first entry leads back there.\n const sheet = window.matchMedia?.(SHEET_QUERY).matches ?? false;\n const list: readonly ContextMenuEntry[] = sheet\n ? [{ id: BACK_ID, label, icon: 'chevronLeft' }, 'separator', ...entries]\n : entries;\n child.show(list, at, (id) => {\n if (id !== BACK_ID) this.pick(id);\n }, item, (id) => this.onRemove?.(id), focusFirst);\n const back = sheet ? child.el.querySelector<HTMLElement>(`[data-id=\"${BACK_ID}\"]`) : null;\n back?.classList.add('tcw-context-back');\n if (back && this.backLabel) back.setAttribute('aria-label', `${this.backLabel}: ${label}`);\n // Closed, it gives the keyboard back to its entry (a click on a button doesn't focus it everywhere).\n child.returnFocus = item;\n }\n\n private makeChild(): WidgetContextMenu {\n const child = new WidgetContextMenu(this.host, '', this.backLabel);\n child.parent = this;\n // Reaching the submenu keeps it, whatever the pointer crossed on the way.\n child.el.addEventListener('pointerenter', () => this.clearHover());\n this.child = child;\n return child;\n }\n\n /** After a rest on `item`: open its submenu, or close the one open for another entry. */\n private hover(item: HTMLButtonElement, label: string, submenu?: readonly ContextMenuEntry[]): void {\n if (this.hoverTimer !== null && this.hoverItem === item) return;\n this.clearHover();\n const open = this.child?.isOpen() ? this.child.anchor : null;\n if (submenu ? open === item : open === null) return;\n this.hoverItem = item;\n this.hoverTimer = setTimeout(() => {\n this.hoverTimer = null;\n this.hoverItem = null;\n if (submenu) this.openSubmenu(item, label, submenu, false);\n else this.child?.close();\n }, HOVER_DELAY_MS);\n }\n\n private clearHover(): void {\n this.hoverItem = null;\n if (this.hoverTimer === null) return;\n clearTimeout(this.hoverTimer);\n this.hoverTimer = null;\n }\n\n /** Take an entry out (its `onRemove` told), the keyboard on the one after it; the menu closes when none is left. */\n private remove(item: HTMLButtonElement): void {\n const id = item.dataset.id;\n const row = item.closest('.tcw-context-row');\n if (!id || !row) return;\n const items = this.items();\n const at = items.indexOf(item);\n this.onRemove?.(id);\n row.remove();\n // A separator left first, last or next to another goes too.\n const seps = [...this.el.children].filter((el, i, all) => el.classList.contains('tcw-context-sep')\n && (i === 0 || i === all.length - 1 || all[i - 1].classList.contains('tcw-context-sep')));\n for (const sep of seps) sep.remove();\n const left = this.items();\n if (left.length === 0) {\n this.close();\n return;\n }\n left[Math.min(at, left.length - 1)].focus({ preventScroll: true });\n }\n\n private pick(id: string): void {\n const onPick = this.onPick;\n this.close();\n onPick?.(id);\n }\n\n private items(): HTMLButtonElement[] {\n return [...this.el.querySelectorAll<HTMLButtonElement>(ITEM_SELECTOR)];\n }\n\n private onKeyDown(e: KeyboardEvent): void {\n const items = this.items();\n const at = items.indexOf(document.activeElement as HTMLButtonElement);\n const [openKey, backKey] = this.rtl() ? ['ArrowLeft', 'ArrowRight'] : ['ArrowRight', 'ArrowLeft'];\n let next = -1;\n if (e.key === 'ArrowDown') next = (at + 1) % items.length;\n else if (e.key === 'ArrowUp') next = (at - 1 + items.length) % items.length;\n else if (e.key === 'Home') next = 0;\n else if (e.key === 'End') next = items.length - 1;\n else if (e.key === openKey && items[at]?.hasAttribute('aria-haspopup')) {\n e.preventDefault();\n items[at].click();\n return;\n } else if (e.key === backKey && this.parent) {\n e.preventDefault();\n this.close();\n return;\n } else if (e.key === 'Delete' && at >= 0) {\n e.preventDefault();\n this.remove(items[at]);\n return;\n } else if (e.key === 'Escape' || e.key === 'Tab') {\n e.preventDefault();\n e.stopPropagation();\n // Escape steps back out of a submenu; Tab leaves the menus altogether.\n let menu: WidgetContextMenu = this;\n while (e.key === 'Tab' && menu.parent) menu = menu.parent;\n menu.close();\n return;\n } else return;\n e.preventDefault();\n // Moving on from the entry a resting pointer opened a submenu for closes it.\n this.child?.close();\n items[next]?.focus({ preventScroll: true });\n }\n}\n","import { stepDecimals } from '@tradecanvas/commons';\nimport { createIcon } from './icons.js';\n\n/**\n * Form controls shared by the settings dialogs and the order ticket: a\n * labelled row, a ticked row, a switch, a number field, a colour picker, a\n * select, a row of choices and a number between − and +. Each calls back on\n * change.\n */\n\n/** A labelled control: clicking the label reaches the control. */\nexport function settingsRow(label: string, control: HTMLElement): HTMLLabelElement {\n const row = document.createElement('label');\n row.className = 'tcw-settings-row';\n const text = document.createElement('span');\n text.className = 'tcw-settings-label';\n text.textContent = label;\n row.append(text, control);\n return row;\n}\n\n/** A box ticked or not, its label after it: the whole row ticks it. */\nexport function checkRow(label: string, value: boolean, onChange: (value: boolean) => void): HTMLLabelElement {\n const row = document.createElement('label');\n row.className = 'tcw-settings-row tcw-check-row';\n const box = document.createElement('input');\n box.type = 'checkbox';\n box.checked = value;\n box.addEventListener('change', () => onChange(box.checked));\n const text = document.createElement('span');\n text.className = 'tcw-settings-label';\n text.textContent = label;\n row.append(box, text);\n return row;\n}\n\nexport function toggleSwitch(value: boolean, onChange: (value: boolean) => void): HTMLButtonElement {\n const toggle = document.createElement('button');\n toggle.type = 'button';\n toggle.className = `tcw-toggle${value ? ' tcw-on' : ''}`;\n toggle.setAttribute('role', 'switch');\n toggle.setAttribute('aria-checked', String(value));\n toggle.addEventListener('click', () => {\n const next = !toggle.classList.contains('tcw-on');\n toggle.classList.toggle('tcw-on', next);\n toggle.setAttribute('aria-checked', String(next));\n onChange(next);\n });\n return toggle;\n}\n\nexport function numberInput(\n value: number,\n range: { min?: number; max?: number; step?: number | 'any'; whole?: boolean },\n onChange: (value: number) => void,\n): HTMLInputElement {\n const input = document.createElement('input');\n input.type = 'number';\n input.className = 'tcw-indi-input';\n input.value = String(value);\n input.step = String(range.step ?? (Number.isInteger(value) ? 1 : 0.001));\n if (range.min !== undefined) input.min = String(range.min);\n if (range.max !== undefined) input.max = String(range.max);\n input.addEventListener('change', () => {\n if (input.value.trim() === '') return;\n let n = Number(input.value);\n if (!Number.isFinite(n)) return;\n // A whole number (a period): what is typed is rounded, so the field shows the value used.\n if (range.whole) n = Math.round(n);\n if (range.min !== undefined) n = Math.max(range.min, n);\n if (range.max !== undefined) n = Math.min(range.max, n);\n input.value = String(n);\n onChange(n);\n });\n return input;\n}\n\nexport function colorInput(value: string, onInput: (value: string) => void): HTMLInputElement {\n const input = document.createElement('input');\n input.type = 'color';\n input.className = 'tcw-indi-color';\n input.value = toHex(value);\n input.addEventListener('input', () => onInput(input.value));\n return input;\n}\n\nexport function selectInput(\n choices: readonly { value: string; label: string }[],\n value: string,\n onChange: (value: string) => void,\n): HTMLSelectElement {\n const select = document.createElement('select');\n select.className = 'tcw-indi-input tcw-indi-select';\n for (const choice of choices) {\n const opt = document.createElement('option');\n opt.value = choice.value;\n opt.textContent = choice.label;\n select.appendChild(opt);\n }\n select.value = value;\n select.addEventListener('change', () => onChange(select.value));\n return select;\n}\n\n/** A colour as `#rrggbb` (what a colour input takes); black if unreadable. */\nexport function toHex(color: unknown): string {\n if (typeof color !== 'string') return '#000000';\n const c = color.trim();\n if (/^#[0-9a-f]{6}$/i.test(c)) return c.toLowerCase();\n if (/^#[0-9a-f]{8}$/i.test(c)) return c.slice(0, 7).toLowerCase();\n if (/^#[0-9a-f]{3,4}$/i.test(c)) return `#${c[1]}${c[1]}${c[2]}${c[2]}${c[3]}${c[3]}`.toLowerCase();\n const m = /^rgba?\\(\\s*(\\d+)[\\s,]+(\\d+)[\\s,]+(\\d+)/i.exec(c);\n if (m) return `#${[m[1], m[2], m[3]].map((v) => Math.min(255, Number(v)).toString(16).padStart(2, '0')).join('')}`;\n // hsl(), oklch(), a colour's name…: as the browser reads it.\n const read = readByBrowser(c);\n return read !== null && read !== c ? toHex(read) : '#000000';\n}\n\nlet colourReader: CanvasRenderingContext2D | null | undefined;\n\n/** A colour as a canvas reads it (`#rrggbb` or `rgba(…)`); null where there is no canvas or it isn't a colour. */\nfunction readByBrowser(color: string): string | null {\n if (colourReader === undefined) {\n try {\n colourReader = typeof document !== 'undefined' ? document.createElement('canvas').getContext('2d') : null;\n } catch {\n colourReader = null;\n }\n }\n if (!colourReader) return null;\n colourReader.fillStyle = '#010203';\n colourReader.fillStyle = color;\n const out = String(colourReader.fillStyle);\n return out === '#010203' ? null : out;\n}\n\n/** The alpha of a colour (1 for `#rgb` / `#rrggbb`). */\nexport function colorAlpha(color: unknown): number {\n if (typeof color !== 'string') return 1;\n const m = /^rgba\\([^)]*,\\s*([\\d.]+)\\s*\\)$/i.exec(color.trim());\n return m ? Math.max(0, Math.min(1, Number(m[1]))) : 1;\n}\n\n/** `#rrggbb` with an alpha, as `rgba(…)`. */\nexport function withAlpha(hex: string, alpha: number): string {\n const h = toHex(hex);\n const [r, g, b] = [1, 3, 5].map((i) => parseInt(h.slice(i, i + 2), 16));\n return `rgba(${r}, ${g}, ${b}, ${Math.round(alpha * 100) / 100})`;\n}\n\n/** Arrow keys and where they move a row of choices, as it reads left to right. */\nconst SEGMENT_MOVES: Record<string, 1 | -1> = { ArrowRight: 1, ArrowDown: 1, ArrowLeft: -1, ArrowUp: -1 };\n\n/**\n * One choice of a few, as a row of buttons (a radio group). The arrow keys\n * move the choice and the focus with it, as a radio group's do (left and\n * right the way the row reads, so mirrored right to left), wrapping at the\n * ends; Home and End go to the first and the last. With a modifier held the\n * keys stay the browser's (Alt+Left goes back).\n */\nexport function segmentedControl<T extends string>(\n choices: readonly { value: T; label: string }[],\n value: T | string,\n onChange: (value: T) => void,\n label: string,\n): HTMLDivElement {\n const group = document.createElement('div');\n group.className = 'tcw-segmented';\n group.setAttribute('role', 'radiogroup');\n group.setAttribute('aria-label', label);\n const buttons = choices.map((choice) => {\n const btn = document.createElement('button');\n btn.type = 'button';\n btn.className = 'tcw-segment';\n btn.setAttribute('role', 'radio');\n btn.dataset.value = choice.value;\n btn.textContent = choice.label;\n btn.addEventListener('click', () => choose(choice.value));\n return btn;\n });\n const mark = (current: string) => {\n const at = Math.max(0, buttons.findIndex((btn) => btn.dataset.value === current));\n buttons.forEach((btn, i) => {\n btn.setAttribute('aria-checked', String(btn.dataset.value === current));\n // One Tab stop: the chosen button, or the first when none is.\n btn.tabIndex = i === at ? 0 : -1;\n });\n };\n const choose = (next: T) => {\n mark(next);\n onChange(next);\n };\n group.addEventListener('keydown', (e) => {\n const at = buttons.findIndex((btn) => btn === e.target);\n if (at < 0 || e.altKey || e.ctrlKey || e.metaKey) return;\n const last = buttons.length - 1;\n let to: number;\n if (e.key === 'Home') to = 0;\n else if (e.key === 'End') to = last;\n else if (e.key in SEGMENT_MOVES) {\n const rtl = group.closest('[dir]')?.getAttribute('dir') === 'rtl';\n const horizontal = e.key === 'ArrowLeft' || e.key === 'ArrowRight';\n const by = rtl && horizontal ? -SEGMENT_MOVES[e.key] : SEGMENT_MOVES[e.key];\n to = (at + by + buttons.length) % buttons.length;\n } else return;\n e.preventDefault();\n buttons[to].focus();\n choose(choices[to].value);\n });\n mark(value);\n group.append(...buttons);\n return group;\n}\n\n/**\n * `value` moved one `step` up (`dir` 1) or down (-1) on the step's grid: to\n * the next multiple of the step that way, so a value between steps lands on\n * one. Never below one step: there it stays. Written to the step's decimals,\n * so 0.1 + 0.2 is 0.3 and a 1/32 point is exact.\n */\nexport function stepValue(value: number, dir: 1 | -1, step: number): number {\n if (!(step > 0) || !Number.isFinite(value)) return value;\n const units = value / step;\n const near = Math.round(units);\n const onGrid = Math.abs(units - near) < 1e-9 * Math.max(1, Math.abs(units));\n const k = dir > 0 ? (onGrid ? near + 1 : Math.ceil(units)) : (onGrid ? near - 1 : Math.floor(units));\n if (k < 1) return value;\n return Number((k * step).toFixed(stepDecimals(step)));\n}\n\n/** What a stepper needs: its step each way, its buttons' names, how it writes and where an empty field starts. */\nexport interface StepperOptions {\n /** The step from `value` going `dir`: a price band's own below its edge. */\n step: (value: number, dir: 1 | -1) => number;\n labels: { down: string; up: string };\n /** How a stepped value is written; unset, as a plain number. */\n format?: (value: number) => string;\n /** Where an empty or unreadable field starts from. */\n fallback?: () => number;\n}\n\n/**\n * A number field between a − and a +, each moving it by a step on the step's\n * grid (`stepValue`) and telling the field as typing would (an `input`\n * event); the arrow keys in the field do the same. The buttons are no Tab\n * stops of their own: the field is. Figures read left to right in any widget.\n */\nexport function stepperField(input: HTMLInputElement, options: StepperOptions): { element: HTMLDivElement; down: HTMLButtonElement; up: HTMLButtonElement } {\n const wrap = document.createElement('div');\n wrap.className = 'tcw-stepper';\n wrap.dir = 'ltr';\n const move = (dir: 1 | -1) => {\n const typed = Number(input.value);\n const from = input.value.trim() !== '' && Number.isFinite(typed) ? typed : options.fallback?.() ?? 0;\n const next = stepValue(from, dir, options.step(from, dir));\n input.value = options.format ? options.format(next) : String(next);\n input.dispatchEvent(new Event('input'));\n };\n const button = (dir: 1 | -1, text: string) => {\n const btn = document.createElement('button');\n btn.type = 'button';\n btn.className = 'tcw-stepper-btn';\n btn.tabIndex = -1;\n btn.setAttribute('aria-label', text);\n btn.innerHTML = createIcon(dir > 0 ? 'plus' : 'minus', 14);\n btn.addEventListener('click', () => move(dir));\n return btn;\n };\n input.addEventListener('keydown', (e) => {\n if (e.key !== 'ArrowUp' && e.key !== 'ArrowDown') return;\n e.preventDefault();\n move(e.key === 'ArrowUp' ? 1 : -1);\n });\n const down = button(-1, options.labels.down);\n const up = button(1, options.labels.up);\n wrap.append(down, input, up);\n return { element: wrap, down, up };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"settingsControls-Tn8_vJJv.cjs","names":[],"sources":["../src/widget/WidgetContextMenu.ts","../src/widget/settingsControls.ts"],"sourcesContent":["import { createIcon } from './icons.js';\n\nexport type ContextMenuEntry =\n /**\n * `checked` makes it a switch (ticked when on); with `radio` it is one\n * choice among the radios beside it. `shortcut` (\"Alt+I\") is written at\n * the row's end. `submenu` opens more entries beside this one. `remove`\n * (its action's name, \"Delete template Swing\") gives it a remove button\n * and the Delete key; the menu's `onRemove` hears it.\n */\n | {\n id: string;\n label: string;\n icon?: string;\n danger?: boolean;\n checked?: boolean;\n radio?: boolean;\n shortcut?: string;\n /** How many things it acts on (\"Remove drawings 3\"), at the row's end and read with its name. */\n count?: number;\n submenu?: readonly ContextMenuEntry[];\n remove?: string;\n }\n | 'separator';\n\ntype MenuItem = Exclude<ContextMenuEntry, 'separator'>;\n\n/** Room kept between the menu and the edge it would touch. */\nconst MENU_MARGIN = 4;\n/** Room between a menu and the button it hangs under. */\nconst MENU_GAP = 2;\n/**\n * How long the pointer rests on an entry before its submenu opens, or the\n * open one closes: crossing an entry on the way into a submenu does neither.\n */\nconst HOVER_DELAY_MS = 150;\nconst ITEM_SELECTOR = '[role=menuitem], [role=menuitemcheckbox], [role=menuitemradio]';\n/** Where menus open as sheets from the bottom (the stylesheet's phone width): a submenu covers its menu. */\nconst SHEET_QUERY = '(max-width: 640px)';\n/** The way back from a submenu opened as a sheet. */\nconst BACK_ID = 'tcw:back';\n\n/**\n * The menus open now, on every widget of the page: a button that several\n * menus hang from (\"more\", for those picked from it) closes whichever is open.\n */\nconst openMenus = new Set<WidgetContextMenu>();\n\n/** Where a menu opens along each axis, in the host's coordinates. */\ninterface MenuPlace {\n x: number;\n y: number;\n /** Where it ends instead, when there is no room after `x` or below `y`. */\n xBack: number;\n yBack: number;\n /** Ending at `xBack` first, and starting at `x` only without room there (a submenu, right to left). */\n before?: boolean;\n}\n\n/**\n * Where a menu `size` long starts along one axis: at `at`, or ending at\n * `back` when it would pass `end`; with `before`, the other way round.\n */\nfunction place(at: number, size: number, end: number, back = at, before = false): number {\n const start = before\n ? (back - size >= 0 ? back - size : at)\n : (at + size > end ? back - size : at);\n return Math.max(0, Math.min(start, end - size));\n}\n\nfunction itemRole(entry: MenuItem): string {\n if (entry.radio) return 'menuitemradio';\n return entry.checked === undefined ? 'menuitem' : 'menuitemcheckbox';\n}\n\n/** What leads an entry: a tick on a switch that is on, a dot on the choice made, else its icon or room for one. */\nfunction leadMark(entry: MenuItem): string {\n const blank = '<span class=\"tcw-context-icon\"></span>';\n if (entry.radio) return entry.checked ? '<span class=\"tcw-context-icon tcw-context-dot\"></span>' : blank;\n // A switch that is off shows its icon, as an action does.\n if (entry.checked) return createIcon('check', 14);\n return (entry.icon && createIcon(entry.icon, 14)) || blank;\n}\n\n/**\n * A small menu opened at a point (a right-click on a drawing, say): arrow\n * keys, Home and End move through it, Enter picks, Escape, Tab or a click\n * outside close it. Focus returns to where it was. An entry with a submenu\n * opens it beside itself on a click, Enter, the right arrow (the left one\n * right to left) or a rest of the pointer; the other arrow or Escape closes\n * it again.\n */\nexport class WidgetContextMenu {\n private el: HTMLDivElement;\n private onPick: ((id: string) => void) | null = null;\n private onRemove: ((id: string) => void) | null = null;\n private returnFocus: HTMLElement | null = null;\n /** The button that opened it: pressing it again is its click's to handle (a toggle). */\n private anchor: HTMLElement | null = null;\n /** The submenu, made the first time an entry opens one. */\n private child: WidgetContextMenu | null = null;\n /** The menu this one is the submenu of. */\n private parent: WidgetContextMenu | null = null;\n private hoverTimer: ReturnType<typeof setTimeout> | null = null;\n /** The entry `hoverTimer` counts the rest on: moving on it counts on, not again. */\n private hoverItem: HTMLElement | null = null;\n private readonly onDocPointer = (e: Event) => {\n const target = e.target as Node;\n if (this.holds(target) || this.anchor?.contains(target)) return;\n this.close();\n };\n /** Scrolling a long menu keeps it; scrolling anywhere else moves the chart from under it. */\n private readonly onWheel = (e: Event) => {\n if (!this.holds(e.target as Node)) this.close();\n };\n /** Resizing or leaving the window moves the chart from under the menu. */\n private readonly onAway = () => this.close();\n\n /**\n * `backLabel` (\"Back\") names the first entry of a submenu on a phone, which\n * leads back to the menu under it.\n */\n constructor(private readonly host: HTMLElement, label: string, private readonly backLabel = '') {\n this.el = document.createElement('div');\n this.el.className = 'tcw-context-menu';\n this.el.setAttribute('role', 'menu');\n this.el.setAttribute('aria-label', label);\n this.el.hidden = true;\n this.el.addEventListener('keydown', (e) => this.onKeyDown(e));\n host.appendChild(this.el);\n }\n\n /**\n * Open at (`x`, `y`) in the host's coordinates. It opens up or to the left\n * of the point when there is no room below or to the right, ending at\n * `xBack` then (a menu under a button ends at the button's far edge), and\n * stays inside both the host and the window. With `before` it ends at\n * `xBack` first, and starts at `x` only without room before it.\n */\n open(\n entries: readonly ContextMenuEntry[],\n x: number,\n y: number,\n onPick: (id: string) => void,\n anchor?: HTMLElement,\n onRemove?: (id: string) => void,\n xBack = x,\n before = false,\n ): void {\n this.show(entries, { x, y, xBack, yBack: y, before }, onPick, anchor, onRemove, true);\n }\n\n /**\n * Open under `anchor`, `gap` px below it, lined up with it as the\n * toolbar's own menus are, mirrored right to left: from its start edge\n * (`'start'`), or ending at its end edge (`'end'`: \"more\", at the\n * toolbar's end); from its other edge when there is no room that way.\n */\n openUnder(\n entries: readonly ContextMenuEntry[],\n anchor: HTMLElement,\n onPick: (id: string) => void,\n align: 'start' | 'end' = 'start',\n gap = MENU_GAP,\n ): void {\n const at = anchor.getBoundingClientRect();\n const box = this.host.getBoundingClientRect();\n // Ending at the button's right edge first: its end left to right, its start right to left.\n const before = (align === 'end') !== this.rtl();\n this.open(entries, at.left - box.left, at.bottom - box.top + gap, onPick, anchor, undefined, at.right - box.left, before);\n }\n\n /**\n * Open beside `anchor` (a button of the drawing bar), level with its top:\n * past its end edge, or before its start edge without room there; ending\n * at its bottom when there is no room below.\n */\n openBeside(entries: readonly ContextMenuEntry[], anchor: HTMLElement, onPick: (id: string) => void): void {\n const at = anchor.getBoundingClientRect();\n const box = this.host.getBoundingClientRect();\n // Starting past its right edge, or ending before its left one: right to left, the left first.\n const right = at.right - box.left + MENU_GAP;\n const left = at.left - box.left - MENU_GAP;\n this.show(entries, { x: right, y: at.top - box.top, xBack: left, yBack: at.bottom - box.top, before: this.rtl() }, onPick, anchor, undefined, true);\n // Safari doesn't focus a pressed button: the keyboard goes back to it all the same.\n this.returnFocus = anchor;\n }\n\n /**\n * Close every menu open under `anchor`, and say whether one was. Its\n * button shows it open (aria-expanded), so a press on it closes it.\n */\n static closeAnchoredTo(anchor: HTMLElement): boolean {\n const under = [...openMenus].filter((menu) => menu.anchor === anchor);\n for (const menu of under) menu.close();\n return under.length > 0;\n }\n\n /** The button an open menu that holds the keyboard hangs from; null when none does. */\n static focusedAnchor(): HTMLElement | null {\n return [...openMenus].find((menu) => menu.anchor !== null && menu.hasFocus())?.anchor ?? null;\n }\n\n close(): void {\n this.clearHover();\n this.child?.close();\n if (this.el.hidden) return;\n openMenus.delete(this);\n // A submenu the pointer closes leaves the keyboard where it is, in the menu.\n const giveBack = !this.parent || this.el.contains(document.activeElement);\n this.el.hidden = true;\n this.onPick = null;\n this.onRemove = null;\n this.anchor?.setAttribute('aria-expanded', 'false');\n this.anchor = null;\n document.removeEventListener('pointerdown', this.onDocPointer, true);\n document.removeEventListener('wheel', this.onWheel, { capture: true });\n window.removeEventListener('resize', this.onAway);\n window.removeEventListener('blur', this.onAway);\n if (giveBack && this.returnFocus?.isConnected) this.returnFocus.focus({ preventScroll: true });\n this.returnFocus = null;\n }\n\n isOpen(): boolean {\n return !this.el.hidden;\n }\n\n /** Whether the keyboard is in the menu, or in a submenu of it. */\n hasFocus(): boolean {\n return document.activeElement !== null && this.holds(document.activeElement);\n }\n\n destroy(): void {\n this.close();\n this.child?.destroy();\n this.el.remove();\n }\n\n private show(\n entries: readonly ContextMenuEntry[],\n at: MenuPlace,\n onPick: (id: string) => void,\n anchor: HTMLElement | undefined,\n onRemove: ((id: string) => void) | undefined,\n focusFirst: boolean,\n ): void {\n this.clearHover();\n this.child?.close();\n this.returnFocus = document.activeElement instanceof HTMLElement ? document.activeElement : null;\n this.onPick = onPick;\n this.onRemove = onRemove ?? null;\n this.anchor = anchor ?? null;\n this.anchor?.setAttribute('aria-expanded', 'true');\n this.el.replaceChildren(...entries.map((entry) => this.entry(entry)));\n this.el.hidden = false;\n openMenus.add(this);\n const hostRect = this.host.getBoundingClientRect();\n const right = Math.min(this.host.clientWidth, window.innerWidth - hostRect.left) - MENU_MARGIN;\n const bottom = Math.min(this.host.clientHeight, window.innerHeight - hostRect.top) - MENU_MARGIN;\n // Taller than the room (a short widget, the time zones), it scrolls, its padding and border in the room too.\n this.el.style.maxHeight = `${Math.max(0, bottom - this.frameHeight())}px`;\n this.el.style.left = `${place(at.x, this.el.offsetWidth, right, at.xBack, at.before)}px`;\n this.el.style.top = `${place(at.y, this.el.offsetHeight, bottom, at.yBack)}px`;\n document.addEventListener('pointerdown', this.onDocPointer, true);\n document.addEventListener('wheel', this.onWheel, { capture: true, passive: true });\n window.addEventListener('resize', this.onAway);\n window.addEventListener('blur', this.onAway);\n const chosen = this.revealChoice();\n if (focusFirst) (chosen ?? this.items()[0])?.focus({ preventScroll: true });\n }\n\n /** What its padding and border add to a max height, which sizes only its rows unless its box counts them. */\n private frameHeight(): number {\n const style = getComputedStyle(this.el);\n if (style.boxSizing === 'border-box') return 0;\n const px = (value: string) => parseFloat(value) || 0;\n return px(style.paddingTop) + px(style.paddingBottom) + px(style.borderTopWidth) + px(style.borderBottomWidth);\n }\n\n /**\n * A long list of choices (the time zones) opens scrolled to the one made,\n * returned to take the keyboard; null when it is in sight already.\n */\n private revealChoice(): HTMLElement | null {\n const chosen = this.el.querySelector<HTMLElement>('[role=menuitemradio][aria-checked=true]');\n if (!chosen || chosen.offsetTop + chosen.offsetHeight <= this.el.clientHeight) return null;\n this.el.scrollTop = chosen.offsetTop - (this.el.clientHeight - chosen.offsetHeight) / 2;\n return chosen;\n }\n\n /** The widget reads right to left (`dir=\"rtl\"`): submenus open to the left, and the arrows swap. */\n private rtl(): boolean {\n return this.host.closest('[dir]')?.getAttribute('dir') === 'rtl';\n }\n\n /** Whether `node` is in this menu or its open submenu. */\n private holds(node: Node): boolean {\n return this.el.contains(node) || (this.child?.holds(node) ?? false);\n }\n\n private entry(entry: ContextMenuEntry): HTMLElement {\n if (entry === 'separator') {\n const sep = document.createElement('div');\n sep.className = 'tcw-context-sep';\n sep.setAttribute('role', 'separator');\n return sep;\n }\n const item = document.createElement('button');\n item.type = 'button';\n item.className = `tcw-context-item${entry.danger ? ' tcw-context-danger' : ''}`;\n item.setAttribute('role', itemRole(entry));\n if (entry.radio || entry.checked !== undefined) item.setAttribute('aria-checked', String(entry.checked ?? false));\n item.tabIndex = -1;\n item.dataset.id = entry.id;\n item.innerHTML = leadMark(entry);\n const text = document.createElement('span');\n text.className = 'tcw-context-label';\n text.textContent = entry.label;\n item.appendChild(text);\n this.appendEnd(item, entry);\n const submenu = entry.submenu;\n // A finger's tap is a click: only a pointer resting on an entry opens its submenu. A pointer that\n // moved there, not one that entered: a menu that opens under a still pointer (a right-click near\n // a phone's foot, where it opens as a sheet) is entered by it, and opened a submenu unasked.\n item.addEventListener('pointermove', (e) => {\n if ((e as PointerEvent).pointerType !== 'touch') this.hover(item, entry.label, submenu);\n });\n item.addEventListener('click', () => {\n if (submenu) this.openSubmenu(item, entry.label, submenu, true);\n else this.pick(entry.id);\n });\n return entry.remove ? this.removableRow(item, entry.remove) : item;\n }\n\n /** The count, the shortcut, or the mark of a submenu, at the row's end. */\n private appendEnd(item: HTMLButtonElement, entry: MenuItem): void {\n if (entry.count !== undefined) {\n const count = document.createElement('span');\n count.className = 'tcw-context-count';\n count.textContent = String(entry.count);\n item.appendChild(count);\n }\n if (entry.shortcut) {\n // Named once, by aria-keyshortcuts: the key is for the eye.\n item.setAttribute('aria-keyshortcuts', entry.shortcut);\n const key = document.createElement('kbd');\n key.className = 'tcw-context-key';\n key.setAttribute('aria-hidden', 'true');\n key.textContent = entry.shortcut;\n item.appendChild(key);\n }\n if (entry.submenu) {\n item.setAttribute('aria-haspopup', 'menu');\n item.setAttribute('aria-expanded', 'false');\n item.insertAdjacentHTML('beforeend', createIcon('chevronRight', 12));\n item.lastElementChild?.classList.add('tcw-context-more');\n }\n }\n\n /** The keyboard removes it with Delete; the button is for a pointer. */\n private removableRow(item: HTMLButtonElement, action: string): HTMLElement {\n item.setAttribute('aria-keyshortcuts', 'Delete');\n const remove = document.createElement('button');\n remove.type = 'button';\n remove.className = 'tcw-context-remove';\n remove.tabIndex = -1;\n remove.setAttribute('aria-hidden', 'true');\n remove.title = action;\n remove.innerHTML = createIcon('x', 12);\n remove.addEventListener('click', () => this.remove(item));\n const row = document.createElement('div');\n row.className = 'tcw-context-row';\n row.setAttribute('role', 'none');\n row.append(item, remove);\n return row;\n }\n\n /** Open `entries` beside `item`, after it (to its right, or left in RTL) or, without room there, before it. */\n private openSubmenu(item: HTMLButtonElement, label: string, entries: readonly ContextMenuEntry[], focusFirst: boolean): void {\n this.clearHover();\n const child = this.child ?? this.makeChild();\n if (child.isOpen() && child.anchor === item) {\n if (focusFirst) child.items()[0]?.focus({ preventScroll: true });\n return;\n }\n child.close();\n child.el.setAttribute('aria-label', label);\n const hostRect = this.host.getBoundingClientRect();\n const r = item.getBoundingClientRect();\n const at: MenuPlace = {\n x: r.right - hostRect.left,\n y: r.top - hostRect.top,\n xBack: r.left - hostRect.left,\n yBack: r.bottom - hostRect.top,\n before: this.rtl(),\n };\n // As a sheet it hides the menu under it: its first entry leads back there.\n const sheet = window.matchMedia?.(SHEET_QUERY).matches ?? false;\n const list: readonly ContextMenuEntry[] = sheet\n ? [{ id: BACK_ID, label, icon: 'chevronLeft' }, 'separator', ...entries]\n : entries;\n child.show(list, at, (id) => {\n if (id !== BACK_ID) this.pick(id);\n }, item, (id) => this.onRemove?.(id), focusFirst);\n const back = sheet ? child.el.querySelector<HTMLElement>(`[data-id=\"${BACK_ID}\"]`) : null;\n back?.classList.add('tcw-context-back');\n if (back && this.backLabel) back.setAttribute('aria-label', `${this.backLabel}: ${label}`);\n // Closed, it gives the keyboard back to its entry (a click on a button doesn't focus it everywhere).\n child.returnFocus = item;\n }\n\n private makeChild(): WidgetContextMenu {\n const child = new WidgetContextMenu(this.host, '', this.backLabel);\n child.parent = this;\n // Reaching the submenu keeps it, whatever the pointer crossed on the way.\n child.el.addEventListener('pointerenter', () => this.clearHover());\n this.child = child;\n return child;\n }\n\n /** After a rest on `item`: open its submenu, or close the one open for another entry. */\n private hover(item: HTMLButtonElement, label: string, submenu?: readonly ContextMenuEntry[]): void {\n if (this.hoverTimer !== null && this.hoverItem === item) return;\n this.clearHover();\n const open = this.child?.isOpen() ? this.child.anchor : null;\n if (submenu ? open === item : open === null) return;\n this.hoverItem = item;\n this.hoverTimer = setTimeout(() => {\n this.hoverTimer = null;\n this.hoverItem = null;\n if (submenu) this.openSubmenu(item, label, submenu, false);\n else this.child?.close();\n }, HOVER_DELAY_MS);\n }\n\n private clearHover(): void {\n this.hoverItem = null;\n if (this.hoverTimer === null) return;\n clearTimeout(this.hoverTimer);\n this.hoverTimer = null;\n }\n\n /** Take an entry out (its `onRemove` told), the keyboard on the one after it; the menu closes when none is left. */\n private remove(item: HTMLButtonElement): void {\n const id = item.dataset.id;\n const row = item.closest('.tcw-context-row');\n if (!id || !row) return;\n const items = this.items();\n const at = items.indexOf(item);\n this.onRemove?.(id);\n row.remove();\n // A separator left first, last or next to another goes too.\n const seps = [...this.el.children].filter((el, i, all) => el.classList.contains('tcw-context-sep')\n && (i === 0 || i === all.length - 1 || all[i - 1].classList.contains('tcw-context-sep')));\n for (const sep of seps) sep.remove();\n const left = this.items();\n if (left.length === 0) {\n this.close();\n return;\n }\n left[Math.min(at, left.length - 1)].focus({ preventScroll: true });\n }\n\n private pick(id: string): void {\n const onPick = this.onPick;\n this.close();\n onPick?.(id);\n }\n\n private items(): HTMLButtonElement[] {\n return [...this.el.querySelectorAll<HTMLButtonElement>(ITEM_SELECTOR)];\n }\n\n private onKeyDown(e: KeyboardEvent): void {\n const items = this.items();\n const at = items.indexOf(document.activeElement as HTMLButtonElement);\n const [openKey, backKey] = this.rtl() ? ['ArrowLeft', 'ArrowRight'] : ['ArrowRight', 'ArrowLeft'];\n let next = -1;\n if (e.key === 'ArrowDown') next = (at + 1) % items.length;\n else if (e.key === 'ArrowUp') next = (at - 1 + items.length) % items.length;\n else if (e.key === 'Home') next = 0;\n else if (e.key === 'End') next = items.length - 1;\n else if (e.key === openKey && items[at]?.hasAttribute('aria-haspopup')) {\n e.preventDefault();\n items[at].click();\n return;\n } else if (e.key === backKey && this.parent) {\n e.preventDefault();\n this.close();\n return;\n } else if (e.key === 'Delete' && at >= 0) {\n e.preventDefault();\n this.remove(items[at]);\n return;\n } else if (e.key === 'Escape' || e.key === 'Tab') {\n e.preventDefault();\n e.stopPropagation();\n // Escape steps back out of a submenu; Tab leaves the menus altogether.\n let menu: WidgetContextMenu = this;\n while (e.key === 'Tab' && menu.parent) menu = menu.parent;\n menu.close();\n return;\n } else return;\n e.preventDefault();\n // Moving on from the entry a resting pointer opened a submenu for closes it.\n this.child?.close();\n items[next]?.focus({ preventScroll: true });\n }\n}\n","import { stepDecimals } from '@tradecanvas/commons';\nimport { createIcon } from './icons.js';\n\n/**\n * Form controls shared by the settings dialogs and the order ticket: a\n * labelled row, a ticked row, a switch, a number field, a colour picker, a\n * select, a row of choices and a number between − and +. Each calls back on\n * change.\n */\n\n/** A labelled control: clicking the label reaches the control. */\nexport function settingsRow(label: string, control: HTMLElement): HTMLLabelElement {\n const row = document.createElement('label');\n row.className = 'tcw-settings-row';\n const text = document.createElement('span');\n text.className = 'tcw-settings-label';\n text.textContent = label;\n row.append(text, control);\n return row;\n}\n\n/** A box ticked or not, its label after it: the whole row ticks it. */\nexport function checkRow(label: string, value: boolean, onChange: (value: boolean) => void): HTMLLabelElement {\n const row = document.createElement('label');\n row.className = 'tcw-settings-row tcw-check-row';\n const box = document.createElement('input');\n box.type = 'checkbox';\n box.checked = value;\n box.addEventListener('change', () => onChange(box.checked));\n const text = document.createElement('span');\n text.className = 'tcw-settings-label';\n text.textContent = label;\n row.append(box, text);\n return row;\n}\n\nexport function toggleSwitch(value: boolean, onChange: (value: boolean) => void): HTMLButtonElement {\n const toggle = document.createElement('button');\n toggle.type = 'button';\n toggle.className = `tcw-toggle${value ? ' tcw-on' : ''}`;\n toggle.setAttribute('role', 'switch');\n toggle.setAttribute('aria-checked', String(value));\n toggle.addEventListener('click', () => {\n const next = !toggle.classList.contains('tcw-on');\n toggle.classList.toggle('tcw-on', next);\n toggle.setAttribute('aria-checked', String(next));\n onChange(next);\n });\n return toggle;\n}\n\nexport function numberInput(\n value: number,\n range: { min?: number; max?: number; step?: number | 'any'; whole?: boolean },\n onChange: (value: number) => void,\n): HTMLInputElement {\n const input = document.createElement('input');\n input.type = 'number';\n input.className = 'tcw-indi-input';\n input.value = String(value);\n input.step = String(range.step ?? (Number.isInteger(value) ? 1 : 0.001));\n if (range.min !== undefined) input.min = String(range.min);\n if (range.max !== undefined) input.max = String(range.max);\n input.addEventListener('change', () => {\n if (input.value.trim() === '') return;\n let n = Number(input.value);\n if (!Number.isFinite(n)) return;\n // A whole number (a period): what is typed is rounded, so the field shows the value used.\n if (range.whole) n = Math.round(n);\n if (range.min !== undefined) n = Math.max(range.min, n);\n if (range.max !== undefined) n = Math.min(range.max, n);\n input.value = String(n);\n onChange(n);\n });\n return input;\n}\n\nexport function colorInput(value: string, onInput: (value: string) => void): HTMLInputElement {\n const input = document.createElement('input');\n input.type = 'color';\n input.className = 'tcw-indi-color';\n input.value = toHex(value);\n input.addEventListener('input', () => onInput(input.value));\n return input;\n}\n\nexport function selectInput(\n choices: readonly { value: string; label: string }[],\n value: string,\n onChange: (value: string) => void,\n): HTMLSelectElement {\n const select = document.createElement('select');\n select.className = 'tcw-indi-input tcw-indi-select';\n for (const choice of choices) {\n const opt = document.createElement('option');\n opt.value = choice.value;\n opt.textContent = choice.label;\n select.appendChild(opt);\n }\n select.value = value;\n select.addEventListener('change', () => onChange(select.value));\n return select;\n}\n\n/** A colour as `#rrggbb` (what a colour input takes); black if unreadable. */\nexport function toHex(color: unknown): string {\n if (typeof color !== 'string') return '#000000';\n const c = color.trim();\n if (/^#[0-9a-f]{6}$/i.test(c)) return c.toLowerCase();\n if (/^#[0-9a-f]{8}$/i.test(c)) return c.slice(0, 7).toLowerCase();\n if (/^#[0-9a-f]{3,4}$/i.test(c)) return `#${c[1]}${c[1]}${c[2]}${c[2]}${c[3]}${c[3]}`.toLowerCase();\n const m = /^rgba?\\(\\s*(\\d+)[\\s,]+(\\d+)[\\s,]+(\\d+)/i.exec(c);\n if (m) return `#${[m[1], m[2], m[3]].map((v) => Math.min(255, Number(v)).toString(16).padStart(2, '0')).join('')}`;\n // hsl(), oklch(), a colour's name…: as the browser reads it.\n const read = readByBrowser(c);\n return read !== null && read !== c ? toHex(read) : '#000000';\n}\n\nlet colourReader: CanvasRenderingContext2D | null | undefined;\n\n/** A colour as a canvas reads it (`#rrggbb` or `rgba(…)`); null where there is no canvas or it isn't a colour. */\nfunction readByBrowser(color: string): string | null {\n if (colourReader === undefined) {\n try {\n colourReader = typeof document !== 'undefined' ? document.createElement('canvas').getContext('2d') : null;\n } catch {\n colourReader = null;\n }\n }\n if (!colourReader) return null;\n colourReader.fillStyle = '#010203';\n colourReader.fillStyle = color;\n const out = String(colourReader.fillStyle);\n return out === '#010203' ? null : out;\n}\n\n/** The alpha of a colour (1 for `#rgb` / `#rrggbb`). */\nexport function colorAlpha(color: unknown): number {\n if (typeof color !== 'string') return 1;\n const m = /^rgba\\([^)]*,\\s*([\\d.]+)\\s*\\)$/i.exec(color.trim());\n return m ? Math.max(0, Math.min(1, Number(m[1]))) : 1;\n}\n\n/** `#rrggbb` with an alpha, as `rgba(…)`. */\nexport function withAlpha(hex: string, alpha: number): string {\n const h = toHex(hex);\n const [r, g, b] = [1, 3, 5].map((i) => parseInt(h.slice(i, i + 2), 16));\n return `rgba(${r}, ${g}, ${b}, ${Math.round(alpha * 100) / 100})`;\n}\n\n/** Arrow keys and where they move a row of choices, as it reads left to right. */\nconst SEGMENT_MOVES: Record<string, 1 | -1> = { ArrowRight: 1, ArrowDown: 1, ArrowLeft: -1, ArrowUp: -1 };\n\n/**\n * One choice of a few, as a row of buttons (a radio group). The arrow keys\n * move the choice and the focus with it, as a radio group's do (left and\n * right the way the row reads, so mirrored right to left), wrapping at the\n * ends; Home and End go to the first and the last. With a modifier held the\n * keys stay the browser's (Alt+Left goes back).\n */\nexport function segmentedControl<T extends string>(\n choices: readonly { value: T; label: string }[],\n value: T | string,\n onChange: (value: T) => void,\n label: string,\n): HTMLDivElement {\n const group = document.createElement('div');\n group.className = 'tcw-segmented';\n group.setAttribute('role', 'radiogroup');\n group.setAttribute('aria-label', label);\n const buttons = choices.map((choice) => {\n const btn = document.createElement('button');\n btn.type = 'button';\n btn.className = 'tcw-segment';\n btn.setAttribute('role', 'radio');\n btn.dataset.value = choice.value;\n btn.textContent = choice.label;\n btn.addEventListener('click', () => choose(choice.value));\n return btn;\n });\n const mark = (current: string) => {\n const at = Math.max(0, buttons.findIndex((btn) => btn.dataset.value === current));\n buttons.forEach((btn, i) => {\n btn.setAttribute('aria-checked', String(btn.dataset.value === current));\n // One Tab stop: the chosen button, or the first when none is.\n btn.tabIndex = i === at ? 0 : -1;\n });\n };\n const choose = (next: T) => {\n mark(next);\n onChange(next);\n };\n group.addEventListener('keydown', (e) => {\n const at = buttons.findIndex((btn) => btn === e.target);\n if (at < 0 || e.altKey || e.ctrlKey || e.metaKey) return;\n const last = buttons.length - 1;\n let to: number;\n if (e.key === 'Home') to = 0;\n else if (e.key === 'End') to = last;\n else if (e.key in SEGMENT_MOVES) {\n const rtl = group.closest('[dir]')?.getAttribute('dir') === 'rtl';\n const horizontal = e.key === 'ArrowLeft' || e.key === 'ArrowRight';\n const by = rtl && horizontal ? -SEGMENT_MOVES[e.key] : SEGMENT_MOVES[e.key];\n to = (at + by + buttons.length) % buttons.length;\n } else return;\n e.preventDefault();\n buttons[to].focus();\n choose(choices[to].value);\n });\n mark(value);\n group.append(...buttons);\n return group;\n}\n\n/**\n * `value` moved one `step` up (`dir` 1) or down (-1) on the step's grid: to\n * the next multiple of the step that way, so a value between steps lands on\n * one. Never below one step: there it stays. Written to the step's decimals,\n * so 0.1 + 0.2 is 0.3 and a 1/32 point is exact.\n */\nexport function stepValue(value: number, dir: 1 | -1, step: number): number {\n if (!(step > 0) || !Number.isFinite(value)) return value;\n const units = value / step;\n const near = Math.round(units);\n const onGrid = Math.abs(units - near) < 1e-9 * Math.max(1, Math.abs(units));\n const k = dir > 0 ? (onGrid ? near + 1 : Math.ceil(units)) : (onGrid ? near - 1 : Math.floor(units));\n if (k < 1) return value;\n return Number((k * step).toFixed(stepDecimals(step)));\n}\n\n/** What a stepper needs: its step each way, its buttons' names, how it writes and where an empty field starts. */\nexport interface StepperOptions {\n /** The step from `value` going `dir`: a price band's own below its edge. */\n step: (value: number, dir: 1 | -1) => number;\n labels: { down: string; up: string };\n /** How a stepped value is written; unset, as a plain number. */\n format?: (value: number) => string;\n /** Where an empty or unreadable field starts from. */\n fallback?: () => number;\n}\n\n/**\n * A number field between a − and a +, each moving it by a step on the step's\n * grid (`stepValue`) and telling the field as typing would (an `input`\n * event); the arrow keys in the field do the same. The buttons are no Tab\n * stops of their own: the field is. Figures read left to right in any widget.\n */\nexport function stepperField(input: HTMLInputElement, options: StepperOptions): { element: HTMLDivElement; down: HTMLButtonElement; up: HTMLButtonElement } {\n const wrap = document.createElement('div');\n wrap.className = 'tcw-stepper';\n wrap.dir = 'ltr';\n const move = (dir: 1 | -1) => {\n const typed = Number(input.value);\n const from = input.value.trim() !== '' && Number.isFinite(typed) ? typed : options.fallback?.() ?? 0;\n const next = stepValue(from, dir, options.step(from, dir));\n input.value = options.format ? options.format(next) : String(next);\n input.dispatchEvent(new Event('input'));\n };\n const button = (dir: 1 | -1, text: string) => {\n const btn = document.createElement('button');\n btn.type = 'button';\n btn.className = 'tcw-stepper-btn';\n btn.tabIndex = -1;\n btn.setAttribute('aria-label', text);\n btn.innerHTML = createIcon(dir > 0 ? 'plus' : 'minus', 14);\n btn.addEventListener('click', () => move(dir));\n return btn;\n };\n input.addEventListener('keydown', (e) => {\n if (e.key !== 'ArrowUp' && e.key !== 'ArrowDown') return;\n e.preventDefault();\n move(e.key === 'ArrowUp' ? 1 : -1);\n });\n const down = button(-1, options.labels.down);\n const up = button(1, options.labels.up);\n wrap.append(down, input, up);\n return { element: wrap, down, up };\n}\n"],"mappings":"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"}
@@ -0,0 +1,90 @@
1
+ //#region src/widget/escapeHtml.ts
2
+ function e(e) {
3
+ return e.replace(/[&<>"']/g, (e) => ({
4
+ "&": "&amp;",
5
+ "<": "&lt;",
6
+ ">": "&gt;",
7
+ "\"": "&quot;",
8
+ "'": "&#39;"
9
+ })[e]);
10
+ }
11
+ var t = 2048, n = 65536, r = /^data:image\/(png|jpeg|gif|webp|avif|svg\+xml)[;,]/i, i = new Set([
12
+ "https:",
13
+ "http:",
14
+ "blob:"
15
+ ]), a = new Set([
16
+ "description",
17
+ "exchange",
18
+ "type",
19
+ "logo"
20
+ ]);
21
+ function o(e) {
22
+ if (typeof e != "string") return null;
23
+ let t = e.trim();
24
+ return t && t.length <= 64 ? t : null;
25
+ }
26
+ function s(e) {
27
+ let t = [], n = /* @__PURE__ */ new Map();
28
+ if (!Array.isArray(e)) return {
29
+ names: t,
30
+ info: n
31
+ };
32
+ let r = /* @__PURE__ */ new Set();
33
+ for (let i of e) {
34
+ let e = typeof i == "object" && !!i, a = o(e ? i.symbol : i);
35
+ !a || r.has(a) || (r.add(a), t.push(a), e && n.set(a, c(i, a)));
36
+ }
37
+ return {
38
+ names: t,
39
+ info: n
40
+ };
41
+ }
42
+ function c(e, t) {
43
+ return {
44
+ ...Object.fromEntries(Object.entries(e).filter(([e, t]) => e !== "__proto__" && (!a.has(e) || typeof t == "string" && t.trim() !== ""))),
45
+ symbol: t
46
+ };
47
+ }
48
+ function l(e) {
49
+ return Array.isArray(e) ? e.filter((e) => typeof e == "object" && !!e && typeof e.symbol == "string" && e.symbol !== "").map((e) => c(e, e.symbol)) : [];
50
+ }
51
+ function u(e, t) {
52
+ if (!t) return e;
53
+ if (!e) return t;
54
+ let n = Object.entries(t).filter(([, e]) => e != null);
55
+ return Object.fromEntries([...Object.entries(e), ...n]);
56
+ }
57
+ function d(e) {
58
+ let t = e.lastIndexOf(":");
59
+ return t <= 0 || t === e.length - 1 ? {
60
+ venue: "",
61
+ ticker: e
62
+ } : {
63
+ venue: e.slice(0, t + 1),
64
+ ticker: e.slice(t + 1)
65
+ };
66
+ }
67
+ var f = (e) => d(e).ticker.toUpperCase();
68
+ function p(e, t) {
69
+ return !t || typeof t.symbol != "string" ? null : t.symbol.toUpperCase() === e.toUpperCase() || t.symbol.includes(":") !== e.includes(":") && f(t.symbol) === f(e) ? t : null;
70
+ }
71
+ function m(e) {
72
+ return [e?.description, e?.exchange].filter(Boolean).join(" · ") || null;
73
+ }
74
+ var h = () => typeof document > "u" ? void 0 : document.baseURI;
75
+ function g(e, a = h()) {
76
+ if (typeof e != "string") return null;
77
+ let o = e.trim();
78
+ if (!o || o.length > n) return null;
79
+ let s;
80
+ try {
81
+ s = new URL(o, a);
82
+ } catch {
83
+ return null;
84
+ }
85
+ return s.protocol === "data:" ? r.test(s.href) && s.href.length <= n ? s.href : null : i.has(s.protocol) && s.href.length <= t ? s.href : null;
86
+ }
87
+ //#endregion
88
+ export { u as a, d as c, g as i, m as l, o as n, s as o, c as r, l as s, p as t, e as u };
89
+
90
+ //# sourceMappingURL=symbolCatalog-CA1fpz3s.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"symbolCatalog-CA1fpz3s.js","names":[],"sources":["../src/widget/escapeHtml.ts","../src/widget/symbolCatalog.ts"],"sourcesContent":["/** Text made safe to put in HTML markup, attribute values included. */\nexport function escapeHtml(text: string): string {\n return text.replace(/[&<>\"']/g, (c) => ({ '&': '&amp;', '<': '&lt;', '>': '&gt;', '\"': '&quot;', \"'\": '&#39;' })[c]!);\n}\n","import type { SymbolInfo } from '@tradecanvas/commons';\n\n/** The longest ticker the widget keeps, in a watchlist or a list of symbols. */\nexport const MAX_SYMBOL_LENGTH = 64;\n/** The longest logo address taken, and the longest image written in a `data:` one. */\nconst MAX_LOGO_URL = 2048;\nconst MAX_LOGO_DATA = 65_536;\n/** The images a `data:` logo may be; a page written in `data:` is none of them. */\nconst DATA_IMAGE = /^data:image\\/(png|jpeg|gif|webp|avif|svg\\+xml)[;,]/i;\nconst LOGO_PROTOCOLS = new Set(['https:', 'http:', 'blob:']);\n/** The fields a row writes as words, or loads (the logo): anything but text there says nothing. */\nconst TEXT_FIELDS: ReadonlySet<string> = new Set(['description', 'exchange', 'type', 'logo']);\n\n/** A ticker trimmed, or null when it isn't one a list can keep. */\nexport function cleanSymbol(v: unknown): string | null {\n if (typeof v !== 'string') return null;\n const symbol = v.trim();\n return symbol && symbol.length <= MAX_SYMBOL_LENGTH ? symbol : null;\n}\n\n/** The symbols on offer: their tickers in order, and the info given with some. */\nexport interface SymbolCatalog {\n names: string[];\n info: Map<string, SymbolInfo>;\n}\n\n/**\n * Symbols from untrusted input (a host's `symbols`, plain JavaScript): a\n * ticker, or a SymbolInfo with one. Tickers are cleaned as a watchlist cleans\n * them, so both key a symbol alike; a repeat keeps the first; anything else is\n * skipped, and anything but a list is no symbols.\n */\nexport function readSymbolCatalog(raw: unknown): SymbolCatalog {\n const names: string[] = [];\n const info = new Map<string, SymbolInfo>();\n if (!Array.isArray(raw)) return { names, info };\n const seen = new Set<string>();\n for (const entry of raw) {\n const isInfo = typeof entry === 'object' && entry !== null;\n const symbol = cleanSymbol(isInfo ? (entry as { symbol?: unknown }).symbol : entry);\n if (!symbol || seen.has(symbol)) continue;\n seen.add(symbol);\n names.push(symbol);\n if (isInfo) info.set(symbol, cleanSymbolInfo(entry, symbol));\n }\n return { names, info };\n}\n\n/**\n * A copy of `info` named `symbol`, its words kept only where they are text:\n * a JSON feed's numeric exchange code, a null or a blank says nothing, so it\n * neither shows nor erases what is known. A \"__proto__\" key, which JSON.parse\n * keeps as a field, is dropped: copied onto another object by assignment, it\n * would hand that object fields no one cleaned.\n */\nexport function cleanSymbolInfo(info: object, symbol: string): SymbolInfo {\n const isWords = ([key, value]: [string, unknown]) =>\n key !== '__proto__' && (!TEXT_FIELDS.has(key) || (typeof value === 'string' && value.trim() !== ''));\n return { ...Object.fromEntries(Object.entries(info).filter(isWords)), symbol } as SymbolInfo;\n}\n\n/**\n * A feed's search results, from untrusted input: those that name a symbol,\n * in order, their words cleaned as the symbols given are; anything but a list\n * is none.\n */\nexport function readSymbolResults(found: unknown): SymbolInfo[] {\n if (!Array.isArray(found)) return [];\n const named = (info: unknown): info is SymbolInfo =>\n typeof info === 'object' && info !== null && typeof (info as { symbol?: unknown }).symbol === 'string' && (info as SymbolInfo).symbol !== '';\n return found.filter(named).map((info) => cleanSymbolInfo(info, info.symbol));\n}\n\n/**\n * `over`'s fields where it has them, `base`'s for the rest: a field left\n * undefined or null (as a JSON feed writes one it has no value for) never\n * erases one. Neither is changed.\n */\nexport function mergeSymbolInfo(base: SymbolInfo | undefined, over: SymbolInfo | null | undefined): SymbolInfo | undefined {\n if (!over) return base;\n if (!base) return over;\n // Defined, not assigned: a \"__proto__\" field is copied as a field and never swaps the copy's prototype.\n const given = Object.entries(over).filter(([, value]) => value !== undefined && value !== null);\n return Object.fromEntries([...Object.entries(base), ...given]) as unknown as SymbolInfo;\n}\n\n/**\n * A symbol's venue, up to its last colon (\"BINANCE:\"), and the ticker after\n * it (\"BTCUSDT\"). No venue when there is no colon, or one at either end: the\n * ticker would be left with nothing.\n */\nexport function splitVenue(symbol: string): { venue: string; ticker: string } {\n const at = symbol.lastIndexOf(':');\n if (at <= 0 || at === symbol.length - 1) return { venue: '', ticker: symbol };\n return { venue: symbol.slice(0, at + 1), ticker: symbol.slice(at + 1) };\n}\n\n/** A ticker without its venue (\"KBS:VCB\" is VCB), in upper case. */\nconst bareTicker = (symbol: string): string => splitVenue(symbol).ticker.toUpperCase();\n\n/**\n * What the chart knows (`getSymbolInfo()`), when it is of `symbol`: in any\n * case, or with a venue before one of the two and not the other. Null when it\n * is still another symbol's, as it stays with no feed to replace it.\n */\nexport function chartInfoFor(symbol: string, info: SymbolInfo | null | undefined): SymbolInfo | null {\n if (!info || typeof info.symbol !== 'string') return null;\n if (info.symbol.toUpperCase() === symbol.toUpperCase()) return info;\n const oneHasVenue = info.symbol.includes(':') !== symbol.includes(':');\n return oneHasVenue && bareTicker(info.symbol) === bareTicker(symbol) ? info : null;\n}\n\n/** A symbol's name and exchange on a line, for a tooltip (\"Vinamilk · HOSE\"); null when neither is known. */\nexport function symbolTitle(info: SymbolInfo | null | undefined): string | null {\n return [info?.description, info?.exchange].filter(Boolean).join(' · ') || null;\n}\n\nconst pageBase = (): string | undefined => (typeof document === 'undefined' ? undefined : document.baseURI);\n\n/**\n * The address to load a logo from, or null for one it must not load. It is\n * only ever an image's `src`, so markup in it is a path that finds nothing;\n * the scheme still has to be one for images, which keeps out `javascript:`\n * and pages written in `data:`.\n */\nexport function logoSrc(raw: unknown, base: string | undefined = pageBase()): string | null {\n if (typeof raw !== 'string') return null;\n const text = raw.trim();\n // One longer than any taken is refused before it is parsed: a row's badge is made again on every keystroke.\n if (!text || text.length > MAX_LOGO_DATA) return null;\n let url: URL;\n try {\n url = new URL(text, base);\n } catch {\n return null;\n }\n if (url.protocol === 'data:') return DATA_IMAGE.test(url.href) && url.href.length <= MAX_LOGO_DATA ? url.href : null;\n return LOGO_PROTOCOLS.has(url.protocol) && url.href.length <= MAX_LOGO_URL ? url.href : null;\n}\n"],"mappings":";AACA,SAAgB,EAAW,GAAsB;AAC/C,QAAO,EAAK,QAAQ,aAAa,OAAO;EAAE,KAAK;EAAS,KAAK;EAAQ,KAAK;EAAQ,MAAK;EAAU,KAAK;EAAS,EAAE,GAAI;;ACGvH,IAAM,IAAe,MACf,IAAgB,OAEhB,IAAa,uDACb,IAAiB,IAAI,IAAI;CAAC;CAAU;CAAS;CAAQ,CAAC,EAEtD,IAAmC,IAAI,IAAI;CAAC;CAAe;CAAY;CAAQ;CAAO,CAAC;AAG7F,SAAgB,EAAY,GAA2B;AACrD,KAAI,OAAO,KAAM,SAAU,QAAO;CAClC,IAAM,IAAS,EAAE,MAAM;AACvB,QAAO,KAAU,EAAO,UAAA,KAA8B,IAAS;;AAejE,SAAgB,EAAkB,GAA6B;CAC7D,IAAM,IAAkB,EAAE,EACpB,oBAAO,IAAI,KAAyB;AAC1C,KAAI,CAAC,MAAM,QAAQ,EAAI,CAAE,QAAO;EAAE;EAAO;EAAM;CAC/C,IAAM,oBAAO,IAAI,KAAa;AAC9B,MAAK,IAAM,KAAS,GAAK;EACvB,IAAM,IAAS,OAAO,KAAU,cAAY,GACtC,IAAS,EAAY,IAAU,EAA+B,SAAS,EAAM;AAC/E,GAAC,KAAU,EAAK,IAAI,EAAO,KAC/B,EAAK,IAAI,EAAO,EAChB,EAAM,KAAK,EAAO,EACd,KAAQ,EAAK,IAAI,GAAQ,EAAgB,GAAO,EAAO,CAAC;;AAE9D,QAAO;EAAE;EAAO;EAAM;;AAUxB,SAAgB,EAAgB,GAAc,GAA4B;AAGxE,QAAO;EAAE,GAAG,OAAO,YAAY,OAAO,QAAQ,EAAK,CAAC,QAFnC,CAAC,GAAK,OACrB,MAAQ,gBAAgB,CAAC,EAAY,IAAI,EAAI,IAAK,OAAO,KAAU,YAAY,EAAM,MAAM,KAAK,IAC/B,CAAC;EAAE;EAAQ;;AAQhF,SAAgB,EAAkB,GAA8B;AAI9D,QAHK,MAAM,QAAQ,EAAM,GAGlB,EAAM,QAFE,MACb,OAAO,KAAS,cAAY,KAAiB,OAAQ,EAA8B,UAAW,YAAa,EAAoB,WAAW,GAClH,CAAC,KAAK,MAAS,EAAgB,GAAM,EAAK,OAAO,CAAC,GAH1C,EAAE;;AAWtC,SAAgB,EAAgB,GAA8B,GAA6D;AACzH,KAAI,CAAC,EAAM,QAAO;AAClB,KAAI,CAAC,EAAM,QAAO;CAElB,IAAM,IAAQ,OAAO,QAAQ,EAAK,CAAC,QAAQ,GAAG,OAAW,KAAiC,KAAK;AAC/F,QAAO,OAAO,YAAY,CAAC,GAAG,OAAO,QAAQ,EAAK,EAAE,GAAG,EAAM,CAAC;;AAQhE,SAAgB,EAAW,GAAmD;CAC5E,IAAM,IAAK,EAAO,YAAY,IAAI;AAElC,QADI,KAAM,KAAK,MAAO,EAAO,SAAS,IAAU;EAAE,OAAO;EAAI,QAAQ;EAAQ,GACtE;EAAE,OAAO,EAAO,MAAM,GAAG,IAAK,EAAE;EAAE,QAAQ,EAAO,MAAM,IAAK,EAAE;EAAE;;AAIzE,IAAM,KAAc,MAA2B,EAAW,EAAO,CAAC,OAAO,aAAa;AAOtF,SAAgB,EAAa,GAAgB,GAAwD;AAInG,QAHI,CAAC,KAAQ,OAAO,EAAK,UAAW,WAAiB,OACjD,EAAK,OAAO,aAAa,KAAK,EAAO,aAAa,IAClC,EAAK,OAAO,SAAS,IAAI,KAAK,EAAO,SAAS,IAAI,IAChD,EAAW,EAAK,OAAO,KAAK,EAAW,EAAO,GAFL,IAEe;;AAIhF,SAAgB,EAAY,GAAoD;AAC9E,QAAO,CAAC,GAAM,aAAa,GAAM,SAAS,CAAC,OAAO,QAAQ,CAAC,KAAK,MAAM,IAAI;;AAG5E,IAAM,UAAsC,OAAO,WAAa,MAAc,KAAA,IAAY,SAAS;AAQnG,SAAgB,EAAQ,GAAc,IAA2B,GAAU,EAAiB;AAC1F,KAAI,OAAO,KAAQ,SAAU,QAAO;CACpC,IAAM,IAAO,EAAI,MAAM;AAEvB,KAAI,CAAC,KAAQ,EAAK,SAAS,EAAe,QAAO;CACjD,IAAI;AACJ,KAAI;AACF,MAAM,IAAI,IAAI,GAAM,EAAK;SACnB;AACN,SAAO;;AAGT,QADI,EAAI,aAAa,UAAgB,EAAW,KAAK,EAAI,KAAK,IAAI,EAAI,KAAK,UAAU,IAAgB,EAAI,OAAO,OACzG,EAAe,IAAI,EAAI,SAAS,IAAI,EAAI,KAAK,UAAU,IAAe,EAAI,OAAO"}
@@ -0,0 +1,2 @@
1
+ function e(e){return e.replace(/[&<>"']/g,e=>({"&":`&amp;`,"<":`&lt;`,">":`&gt;`,'"':`&quot;`,"'":`&#39;`})[e])}var t=2048,n=65536,r=/^data:image\/(png|jpeg|gif|webp|avif|svg\+xml)[;,]/i,i=new Set([`https:`,`http:`,`blob:`]),a=new Set([`description`,`exchange`,`type`,`logo`]);function o(e){if(typeof e!=`string`)return null;let t=e.trim();return t&&t.length<=64?t:null}function s(e){let t=[],n=new Map;if(!Array.isArray(e))return{names:t,info:n};let r=new Set;for(let i of e){let e=typeof i==`object`&&!!i,a=o(e?i.symbol:i);!a||r.has(a)||(r.add(a),t.push(a),e&&n.set(a,c(i,a)))}return{names:t,info:n}}function c(e,t){return{...Object.fromEntries(Object.entries(e).filter(([e,t])=>e!==`__proto__`&&(!a.has(e)||typeof t==`string`&&t.trim()!==``))),symbol:t}}function l(e){return Array.isArray(e)?e.filter(e=>typeof e==`object`&&!!e&&typeof e.symbol==`string`&&e.symbol!==``).map(e=>c(e,e.symbol)):[]}function u(e,t){if(!t)return e;if(!e)return t;let n=Object.entries(t).filter(([,e])=>e!=null);return Object.fromEntries([...Object.entries(e),...n])}function d(e){let t=e.lastIndexOf(`:`);return t<=0||t===e.length-1?{venue:``,ticker:e}:{venue:e.slice(0,t+1),ticker:e.slice(t+1)}}var f=e=>d(e).ticker.toUpperCase();function p(e,t){return!t||typeof t.symbol!=`string`?null:t.symbol.toUpperCase()===e.toUpperCase()||t.symbol.includes(`:`)!==e.includes(`:`)&&f(t.symbol)===f(e)?t:null}function m(e){return[e?.description,e?.exchange].filter(Boolean).join(` · `)||null}var h=()=>typeof document>`u`?void 0:document.baseURI;function g(e,a=h()){if(typeof e!=`string`)return null;let o=e.trim();if(!o||o.length>n)return null;let s;try{s=new URL(o,a)}catch{return null}return s.protocol===`data:`?r.test(s.href)&&s.href.length<=n?s.href:null:i.has(s.protocol)&&s.href.length<=t?s.href:null}Object.defineProperty(exports,`a`,{enumerable:!0,get:function(){return u}}),Object.defineProperty(exports,`c`,{enumerable:!0,get:function(){return d}}),Object.defineProperty(exports,`i`,{enumerable:!0,get:function(){return g}}),Object.defineProperty(exports,`l`,{enumerable:!0,get:function(){return m}}),Object.defineProperty(exports,`n`,{enumerable:!0,get:function(){return o}}),Object.defineProperty(exports,`o`,{enumerable:!0,get:function(){return s}}),Object.defineProperty(exports,`r`,{enumerable:!0,get:function(){return c}}),Object.defineProperty(exports,`s`,{enumerable:!0,get:function(){return l}}),Object.defineProperty(exports,`t`,{enumerable:!0,get:function(){return p}}),Object.defineProperty(exports,`u`,{enumerable:!0,get:function(){return e}});
2
+ //# sourceMappingURL=symbolCatalog-Dx1YnZgm.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"symbolCatalog-Dx1YnZgm.cjs","names":[],"sources":["../src/widget/escapeHtml.ts","../src/widget/symbolCatalog.ts"],"sourcesContent":["/** Text made safe to put in HTML markup, attribute values included. */\nexport function escapeHtml(text: string): string {\n return text.replace(/[&<>\"']/g, (c) => ({ '&': '&amp;', '<': '&lt;', '>': '&gt;', '\"': '&quot;', \"'\": '&#39;' })[c]!);\n}\n","import type { SymbolInfo } from '@tradecanvas/commons';\n\n/** The longest ticker the widget keeps, in a watchlist or a list of symbols. */\nexport const MAX_SYMBOL_LENGTH = 64;\n/** The longest logo address taken, and the longest image written in a `data:` one. */\nconst MAX_LOGO_URL = 2048;\nconst MAX_LOGO_DATA = 65_536;\n/** The images a `data:` logo may be; a page written in `data:` is none of them. */\nconst DATA_IMAGE = /^data:image\\/(png|jpeg|gif|webp|avif|svg\\+xml)[;,]/i;\nconst LOGO_PROTOCOLS = new Set(['https:', 'http:', 'blob:']);\n/** The fields a row writes as words, or loads (the logo): anything but text there says nothing. */\nconst TEXT_FIELDS: ReadonlySet<string> = new Set(['description', 'exchange', 'type', 'logo']);\n\n/** A ticker trimmed, or null when it isn't one a list can keep. */\nexport function cleanSymbol(v: unknown): string | null {\n if (typeof v !== 'string') return null;\n const symbol = v.trim();\n return symbol && symbol.length <= MAX_SYMBOL_LENGTH ? symbol : null;\n}\n\n/** The symbols on offer: their tickers in order, and the info given with some. */\nexport interface SymbolCatalog {\n names: string[];\n info: Map<string, SymbolInfo>;\n}\n\n/**\n * Symbols from untrusted input (a host's `symbols`, plain JavaScript): a\n * ticker, or a SymbolInfo with one. Tickers are cleaned as a watchlist cleans\n * them, so both key a symbol alike; a repeat keeps the first; anything else is\n * skipped, and anything but a list is no symbols.\n */\nexport function readSymbolCatalog(raw: unknown): SymbolCatalog {\n const names: string[] = [];\n const info = new Map<string, SymbolInfo>();\n if (!Array.isArray(raw)) return { names, info };\n const seen = new Set<string>();\n for (const entry of raw) {\n const isInfo = typeof entry === 'object' && entry !== null;\n const symbol = cleanSymbol(isInfo ? (entry as { symbol?: unknown }).symbol : entry);\n if (!symbol || seen.has(symbol)) continue;\n seen.add(symbol);\n names.push(symbol);\n if (isInfo) info.set(symbol, cleanSymbolInfo(entry, symbol));\n }\n return { names, info };\n}\n\n/**\n * A copy of `info` named `symbol`, its words kept only where they are text:\n * a JSON feed's numeric exchange code, a null or a blank says nothing, so it\n * neither shows nor erases what is known. A \"__proto__\" key, which JSON.parse\n * keeps as a field, is dropped: copied onto another object by assignment, it\n * would hand that object fields no one cleaned.\n */\nexport function cleanSymbolInfo(info: object, symbol: string): SymbolInfo {\n const isWords = ([key, value]: [string, unknown]) =>\n key !== '__proto__' && (!TEXT_FIELDS.has(key) || (typeof value === 'string' && value.trim() !== ''));\n return { ...Object.fromEntries(Object.entries(info).filter(isWords)), symbol } as SymbolInfo;\n}\n\n/**\n * A feed's search results, from untrusted input: those that name a symbol,\n * in order, their words cleaned as the symbols given are; anything but a list\n * is none.\n */\nexport function readSymbolResults(found: unknown): SymbolInfo[] {\n if (!Array.isArray(found)) return [];\n const named = (info: unknown): info is SymbolInfo =>\n typeof info === 'object' && info !== null && typeof (info as { symbol?: unknown }).symbol === 'string' && (info as SymbolInfo).symbol !== '';\n return found.filter(named).map((info) => cleanSymbolInfo(info, info.symbol));\n}\n\n/**\n * `over`'s fields where it has them, `base`'s for the rest: a field left\n * undefined or null (as a JSON feed writes one it has no value for) never\n * erases one. Neither is changed.\n */\nexport function mergeSymbolInfo(base: SymbolInfo | undefined, over: SymbolInfo | null | undefined): SymbolInfo | undefined {\n if (!over) return base;\n if (!base) return over;\n // Defined, not assigned: a \"__proto__\" field is copied as a field and never swaps the copy's prototype.\n const given = Object.entries(over).filter(([, value]) => value !== undefined && value !== null);\n return Object.fromEntries([...Object.entries(base), ...given]) as unknown as SymbolInfo;\n}\n\n/**\n * A symbol's venue, up to its last colon (\"BINANCE:\"), and the ticker after\n * it (\"BTCUSDT\"). No venue when there is no colon, or one at either end: the\n * ticker would be left with nothing.\n */\nexport function splitVenue(symbol: string): { venue: string; ticker: string } {\n const at = symbol.lastIndexOf(':');\n if (at <= 0 || at === symbol.length - 1) return { venue: '', ticker: symbol };\n return { venue: symbol.slice(0, at + 1), ticker: symbol.slice(at + 1) };\n}\n\n/** A ticker without its venue (\"KBS:VCB\" is VCB), in upper case. */\nconst bareTicker = (symbol: string): string => splitVenue(symbol).ticker.toUpperCase();\n\n/**\n * What the chart knows (`getSymbolInfo()`), when it is of `symbol`: in any\n * case, or with a venue before one of the two and not the other. Null when it\n * is still another symbol's, as it stays with no feed to replace it.\n */\nexport function chartInfoFor(symbol: string, info: SymbolInfo | null | undefined): SymbolInfo | null {\n if (!info || typeof info.symbol !== 'string') return null;\n if (info.symbol.toUpperCase() === symbol.toUpperCase()) return info;\n const oneHasVenue = info.symbol.includes(':') !== symbol.includes(':');\n return oneHasVenue && bareTicker(info.symbol) === bareTicker(symbol) ? info : null;\n}\n\n/** A symbol's name and exchange on a line, for a tooltip (\"Vinamilk · HOSE\"); null when neither is known. */\nexport function symbolTitle(info: SymbolInfo | null | undefined): string | null {\n return [info?.description, info?.exchange].filter(Boolean).join(' · ') || null;\n}\n\nconst pageBase = (): string | undefined => (typeof document === 'undefined' ? undefined : document.baseURI);\n\n/**\n * The address to load a logo from, or null for one it must not load. It is\n * only ever an image's `src`, so markup in it is a path that finds nothing;\n * the scheme still has to be one for images, which keeps out `javascript:`\n * and pages written in `data:`.\n */\nexport function logoSrc(raw: unknown, base: string | undefined = pageBase()): string | null {\n if (typeof raw !== 'string') return null;\n const text = raw.trim();\n // One longer than any taken is refused before it is parsed: a row's badge is made again on every keystroke.\n if (!text || text.length > MAX_LOGO_DATA) return null;\n let url: URL;\n try {\n url = new URL(text, base);\n } catch {\n return null;\n }\n if (url.protocol === 'data:') return DATA_IMAGE.test(url.href) && url.href.length <= MAX_LOGO_DATA ? url.href : null;\n return LOGO_PROTOCOLS.has(url.protocol) && url.href.length <= MAX_LOGO_URL ? url.href : null;\n}\n"],"mappings":"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"}