@363045841yyt/klinechart 0.7.1 → 0.7.3-alpha.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (235) hide show
  1. package/README.md +10 -6
  2. package/dist/components/DrawingStyleToolbar.vue.d.ts +28 -0
  3. package/dist/components/DrawingStyleToolbar.vue.d.ts.map +1 -0
  4. package/dist/{src/components → components}/IndicatorParams.vue.d.ts +3 -3
  5. package/dist/components/IndicatorParams.vue.d.ts.map +1 -0
  6. package/dist/{src/components → components}/IndicatorSelector.vue.d.ts +4 -4
  7. package/dist/components/IndicatorSelector.vue.d.ts.map +1 -0
  8. package/dist/components/KLineChart.vue.d.ts +63 -0
  9. package/dist/components/KLineChart.vue.d.ts.map +1 -0
  10. package/dist/components/KLineTooltip.vue.d.ts +30 -0
  11. package/dist/components/KLineTooltip.vue.d.ts.map +1 -0
  12. package/dist/{src/components → components}/LeftToolbar.vue.d.ts +3 -2
  13. package/dist/components/LeftToolbar.vue.d.ts.map +1 -0
  14. package/dist/components/MarkerTooltip.vue.d.ts +26 -0
  15. package/dist/components/MarkerTooltip.vue.d.ts.map +1 -0
  16. package/dist/components/index.d.ts +8 -0
  17. package/dist/components/index.d.ts.map +1 -0
  18. package/dist/{src/composables → composables}/useFullscreenTeleportTarget.d.ts +1 -0
  19. package/dist/composables/useFullscreenTeleportTarget.d.ts.map +1 -0
  20. package/dist/{src/debug → debug}/canvasProfiler.d.ts +1 -0
  21. package/dist/debug/canvasProfiler.d.ts.map +1 -0
  22. package/dist/index.cjs +2 -49
  23. package/dist/index.d.cts +124 -0
  24. package/dist/index.d.ts +124 -2
  25. package/dist/index.d.ts.map +1 -0
  26. package/dist/index.js +1390 -23809
  27. package/dist/klinechart.css +1 -1
  28. package/dist/version.d.ts +3 -0
  29. package/dist/version.d.ts.map +1 -0
  30. package/package.json +49 -85
  31. package/src/__tests__/_mockController.ts +192 -0
  32. package/src/__tests__/contract.test.ts +132 -0
  33. package/src/components/DrawingStyleToolbar.vue +199 -0
  34. package/src/components/IndicatorParams.vue +570 -0
  35. package/src/components/IndicatorSelector.vue +1042 -0
  36. package/src/components/KLineChart.vue +1570 -0
  37. package/src/components/KLineTooltip.vue +200 -0
  38. package/src/components/LeftToolbar.vue +844 -0
  39. package/src/components/MarkerTooltip.vue +155 -0
  40. package/src/components/index.ts +7 -0
  41. package/src/composables/useFullscreenTeleportTarget.ts +18 -0
  42. package/src/debug/canvasProfiler.ts +296 -0
  43. package/src/index.ts +402 -0
  44. package/src/version.ts +3 -0
  45. package/dist/favicon.ico +0 -0
  46. package/dist/mock-stock-data.json +0 -1
  47. package/dist/schema-CzmPW09E.cjs +0 -1
  48. package/dist/schema-DBMGp6Af.js +0 -437
  49. package/dist/src/App.vue.d.ts +0 -4
  50. package/dist/src/api/data/baostock.d.ts +0 -90
  51. package/dist/src/api/data/baostock.integration.test.d.ts +0 -1
  52. package/dist/src/api/data/index.d.ts +0 -26
  53. package/dist/src/api/data/kLine.d.ts +0 -11
  54. package/dist/src/api/data/types.d.ts +0 -33
  55. package/dist/src/api/data/unified.d.ts +0 -37
  56. package/dist/src/components/DrawingStyleToolbar.vue.d.ts +0 -12
  57. package/dist/src/components/KLineChart.vue.d.ts +0 -100
  58. package/dist/src/components/KLineTooltip.vue.d.ts +0 -17
  59. package/dist/src/components/MarkerTooltip.vue.d.ts +0 -13
  60. package/dist/src/components/index.d.ts +0 -2
  61. package/dist/src/config/chartSettings.d.ts +0 -69
  62. package/dist/src/core/chart-store.d.ts +0 -74
  63. package/dist/src/core/chart.d.ts +0 -617
  64. package/dist/src/core/controller/interaction.d.ts +0 -167
  65. package/dist/src/core/controller/markerInteraction.d.ts +0 -28
  66. package/dist/src/core/controller/pinchTracker.d.ts +0 -18
  67. package/dist/src/core/controller/tooltipPosition.d.ts +0 -21
  68. package/dist/src/core/draw/pixelAlign.d.ts +0 -114
  69. package/dist/src/core/drawing/index.d.ts +0 -47
  70. package/dist/src/core/drawing/interaction.d.ts +0 -75
  71. package/dist/src/core/drawing/plugin.d.ts +0 -27
  72. package/dist/src/core/indicators/atrState.d.ts +0 -16
  73. package/dist/src/core/indicators/bollState.d.ts +0 -34
  74. package/dist/src/core/indicators/calculators.d.ts +0 -465
  75. package/dist/src/core/indicators/cciState.d.ts +0 -15
  76. package/dist/src/core/indicators/chaikinVolState.d.ts +0 -18
  77. package/dist/src/core/indicators/cmfState.d.ts +0 -16
  78. package/dist/src/core/indicators/demaState.d.ts +0 -16
  79. package/dist/src/core/indicators/donchianState.d.ts +0 -23
  80. package/dist/src/core/indicators/eneState.d.ts +0 -30
  81. package/dist/src/core/indicators/expmaState.d.ts +0 -30
  82. package/dist/src/core/indicators/fastkState.d.ts +0 -15
  83. package/dist/src/core/indicators/fibState.d.ts +0 -26
  84. package/dist/src/core/indicators/hmaState.d.ts +0 -16
  85. package/dist/src/core/indicators/hvState.d.ts +0 -18
  86. package/dist/src/core/indicators/ichimokuState.d.ts +0 -44
  87. package/dist/src/core/indicators/indicator.worker.d.ts +0 -5
  88. package/dist/src/core/indicators/indicatorDefinitionRegistry.d.ts +0 -30
  89. package/dist/src/core/indicators/indicatorMetadata.d.ts +0 -81
  90. package/dist/src/core/indicators/indicatorRegistry.d.ts +0 -57
  91. package/dist/src/core/indicators/indicatorRuntime.d.ts +0 -126
  92. package/dist/src/core/indicators/kamaState.d.ts +0 -20
  93. package/dist/src/core/indicators/keltnerState.d.ts +0 -27
  94. package/dist/src/core/indicators/kstState.d.ts +0 -21
  95. package/dist/src/core/indicators/maState.d.ts +0 -26
  96. package/dist/src/core/indicators/macdState.d.ts +0 -58
  97. package/dist/src/core/indicators/mfiState.d.ts +0 -16
  98. package/dist/src/core/indicators/momState.d.ts +0 -15
  99. package/dist/src/core/indicators/obvState.d.ts +0 -14
  100. package/dist/src/core/indicators/parkinsonState.d.ts +0 -18
  101. package/dist/src/core/indicators/pivotState.d.ts +0 -29
  102. package/dist/src/core/indicators/pvtState.d.ts +0 -14
  103. package/dist/src/core/indicators/rocState.d.ts +0 -16
  104. package/dist/src/core/indicators/rsiState.d.ts +0 -43
  105. package/dist/src/core/indicators/sarState.d.ts +0 -26
  106. package/dist/src/core/indicators/scheduler.d.ts +0 -262
  107. package/dist/src/core/indicators/soa.d.ts +0 -115
  108. package/dist/src/core/indicators/stateComposer.d.ts +0 -146
  109. package/dist/src/core/indicators/stochState.d.ts +0 -18
  110. package/dist/src/core/indicators/structureState.d.ts +0 -43
  111. package/dist/src/core/indicators/supertrendState.d.ts +0 -22
  112. package/dist/src/core/indicators/temaState.d.ts +0 -16
  113. package/dist/src/core/indicators/trixState.d.ts +0 -20
  114. package/dist/src/core/indicators/vmaState.d.ts +0 -16
  115. package/dist/src/core/indicators/volumeProfileState.d.ts +0 -34
  116. package/dist/src/core/indicators/vwapState.d.ts +0 -16
  117. package/dist/src/core/indicators/wmaState.d.ts +0 -16
  118. package/dist/src/core/indicators/wmsrState.d.ts +0 -15
  119. package/dist/src/core/indicators/workerProtocol.d.ts +0 -496
  120. package/dist/src/core/indicators/zonesState.d.ts +0 -26
  121. package/dist/src/core/layout/pane.d.ts +0 -103
  122. package/dist/src/core/marker/registry.d.ts +0 -174
  123. package/dist/src/core/paneRenderer.d.ts +0 -45
  124. package/dist/src/core/renderers/Indicator/atr.d.ts +0 -17
  125. package/dist/src/core/renderers/Indicator/boll.d.ts +0 -2
  126. package/dist/src/core/renderers/Indicator/cci.d.ts +0 -22
  127. package/dist/src/core/renderers/Indicator/chaikinVol.d.ts +0 -4
  128. package/dist/src/core/renderers/Indicator/cmf.d.ts +0 -4
  129. package/dist/src/core/renderers/Indicator/dema.d.ts +0 -5
  130. package/dist/src/core/renderers/Indicator/donchian.d.ts +0 -5
  131. package/dist/src/core/renderers/Indicator/ene.d.ts +0 -2
  132. package/dist/src/core/renderers/Indicator/expma.d.ts +0 -2
  133. package/dist/src/core/renderers/Indicator/fastk.d.ts +0 -22
  134. package/dist/src/core/renderers/Indicator/fib.d.ts +0 -4
  135. package/dist/src/core/renderers/Indicator/hma.d.ts +0 -5
  136. package/dist/src/core/renderers/Indicator/hv.d.ts +0 -4
  137. package/dist/src/core/renderers/Indicator/ichimoku.d.ts +0 -5
  138. package/dist/src/core/renderers/Indicator/index.d.ts +0 -59
  139. package/dist/src/core/renderers/Indicator/indicatorData.d.ts +0 -13
  140. package/dist/src/core/renderers/Indicator/kama.d.ts +0 -5
  141. package/dist/src/core/renderers/Indicator/keltner.d.ts +0 -5
  142. package/dist/src/core/renderers/Indicator/kst.d.ts +0 -22
  143. package/dist/src/core/renderers/Indicator/ma.d.ts +0 -3
  144. package/dist/src/core/renderers/Indicator/macd.d.ts +0 -50
  145. package/dist/src/core/renderers/Indicator/macdLegend.d.ts +0 -12
  146. package/dist/src/core/renderers/Indicator/mainIndicatorLegend.d.ts +0 -10
  147. package/dist/src/core/renderers/Indicator/mfi.d.ts +0 -4
  148. package/dist/src/core/renderers/Indicator/mom.d.ts +0 -22
  149. package/dist/src/core/renderers/Indicator/obv.d.ts +0 -4
  150. package/dist/src/core/renderers/Indicator/parkinson.d.ts +0 -4
  151. package/dist/src/core/renderers/Indicator/pivot.d.ts +0 -4
  152. package/dist/src/core/renderers/Indicator/pvt.d.ts +0 -4
  153. package/dist/src/core/renderers/Indicator/roc.d.ts +0 -5
  154. package/dist/src/core/renderers/Indicator/rsi.d.ts +0 -33
  155. package/dist/src/core/renderers/Indicator/sar.d.ts +0 -5
  156. package/dist/src/core/renderers/Indicator/scale/atr_scale.d.ts +0 -11
  157. package/dist/src/core/renderers/Indicator/scale/cci_scale.d.ts +0 -11
  158. package/dist/src/core/renderers/Indicator/scale/fastk_scale.d.ts +0 -11
  159. package/dist/src/core/renderers/Indicator/scale/indicator_scale.d.ts +0 -38
  160. package/dist/src/core/renderers/Indicator/scale/kst_scale.d.ts +0 -11
  161. package/dist/src/core/renderers/Indicator/scale/macd_scale.d.ts +0 -14
  162. package/dist/src/core/renderers/Indicator/scale/mom_scale.d.ts +0 -11
  163. package/dist/src/core/renderers/Indicator/scale/rsi_scale.d.ts +0 -11
  164. package/dist/src/core/renderers/Indicator/scale/stoch_scale.d.ts +0 -11
  165. package/dist/src/core/renderers/Indicator/scale/volume_scale.d.ts +0 -14
  166. package/dist/src/core/renderers/Indicator/scale/wmsr_scale.d.ts +0 -11
  167. package/dist/src/core/renderers/Indicator/stoch.d.ts +0 -22
  168. package/dist/src/core/renderers/Indicator/structure.d.ts +0 -4
  169. package/dist/src/core/renderers/Indicator/subPaneConfig.d.ts +0 -9
  170. package/dist/src/core/renderers/Indicator/supertrend.d.ts +0 -5
  171. package/dist/src/core/renderers/Indicator/tema.d.ts +0 -5
  172. package/dist/src/core/renderers/Indicator/trix.d.ts +0 -5
  173. package/dist/src/core/renderers/Indicator/vma.d.ts +0 -4
  174. package/dist/src/core/renderers/Indicator/volumeProfile.d.ts +0 -4
  175. package/dist/src/core/renderers/Indicator/vwap.d.ts +0 -4
  176. package/dist/src/core/renderers/Indicator/wma.d.ts +0 -5
  177. package/dist/src/core/renderers/Indicator/wmsr.d.ts +0 -22
  178. package/dist/src/core/renderers/Indicator/zones.d.ts +0 -4
  179. package/dist/src/core/renderers/candle.d.ts +0 -21
  180. package/dist/src/core/renderers/crosshair.d.ts +0 -17
  181. package/dist/src/core/renderers/customMarkers.d.ts +0 -6
  182. package/dist/src/core/renderers/extremaMarkers.d.ts +0 -5
  183. package/dist/src/core/renderers/gridLines.d.ts +0 -7
  184. package/dist/src/core/renderers/lastPrice.d.ts +0 -9
  185. package/dist/src/core/renderers/paneTitle.d.ts +0 -40
  186. package/dist/src/core/renderers/subVolume.d.ts +0 -13
  187. package/dist/src/core/renderers/timeAxis.d.ts +0 -14
  188. package/dist/src/core/renderers/webgl/candleSurface.d.ts +0 -80
  189. package/dist/src/core/renderers/webgl/sharedWebGLSurface.d.ts +0 -33
  190. package/dist/src/core/renderers/yAxis.d.ts +0 -14
  191. package/dist/src/core/scale/logFormula.d.ts +0 -66
  192. package/dist/src/core/scale/price.d.ts +0 -11
  193. package/dist/src/core/scale/priceScale.d.ts +0 -87
  194. package/dist/src/core/subPaneManager.d.ts +0 -22
  195. package/dist/src/core/theme/colors.d.ts +0 -222
  196. package/dist/src/core/theme/fonts.d.ts +0 -12
  197. package/dist/src/core/utils/klineConfig.d.ts +0 -28
  198. package/dist/src/core/utils/tickCount.d.ts +0 -8
  199. package/dist/src/core/utils/tickPosition.d.ts +0 -24
  200. package/dist/src/core/utils/zoom.d.ts +0 -30
  201. package/dist/src/core/viewport/viewport.d.ts +0 -31
  202. package/dist/src/index.d.ts +0 -8
  203. package/dist/src/main.d.ts +0 -0
  204. package/dist/src/plugin/ConfigManager.d.ts +0 -31
  205. package/dist/src/plugin/EventBus.d.ts +0 -34
  206. package/dist/src/plugin/HookSystem.d.ts +0 -28
  207. package/dist/src/plugin/PluginHost.d.ts +0 -57
  208. package/dist/src/plugin/PluginRegistry.d.ts +0 -40
  209. package/dist/src/plugin/StateStore.d.ts +0 -37
  210. package/dist/src/plugin/index.d.ts +0 -11
  211. package/dist/src/plugin/rendererPluginManager.d.ts +0 -77
  212. package/dist/src/plugin/stateKeys.d.ts +0 -6
  213. package/dist/src/plugin/types.d.ts +0 -445
  214. package/dist/src/semantic/controller.d.ts +0 -35
  215. package/dist/src/semantic/drawShape.d.ts +0 -14
  216. package/dist/src/semantic/index.d.ts +0 -8
  217. package/dist/src/semantic/types.d.ts +0 -298
  218. package/dist/src/semantic/validator.d.ts +0 -43
  219. package/dist/src/test-setup.d.ts +0 -6
  220. package/dist/src/types/kLine.d.ts +0 -3
  221. package/dist/src/types/price.d.ts +0 -31
  222. package/dist/src/types/volumePrice.d.ts +0 -26
  223. package/dist/src/utils/cache.d.ts +0 -33
  224. package/dist/src/utils/dateFormat.d.ts +0 -83
  225. package/dist/src/utils/http.d.ts +0 -14
  226. package/dist/src/utils/kLineDraw/MA.d.ts +0 -7
  227. package/dist/src/utils/kLineDraw/axis.d.ts +0 -150
  228. package/dist/src/utils/kLineDraw/grid.d.ts +0 -30
  229. package/dist/src/utils/kLineDraw/kLine.d.ts +0 -15
  230. package/dist/src/utils/kline/format.d.ts +0 -11
  231. package/dist/src/utils/kline/viewport.d.ts +0 -10
  232. package/dist/src/utils/logger.d.ts +0 -5
  233. package/dist/src/utils/mock/genRandomPriceData.d.ts +0 -3
  234. package/dist/src/utils/priceToY.d.ts +0 -7
  235. package/dist/src/utils/volumePrice.d.ts +0 -54
@@ -0,0 +1,1042 @@
1
+ <template>
2
+ <div class="indicator-selector">
3
+ <div class="indicator-scroll-container">
4
+ <div class="indicator-list">
5
+ <!-- ?????? -->
6
+ <template v-for="indicator in activeIndicatorsList" :key="indicator.id">
7
+ <div
8
+ v-if="indicator.id === firstActiveSubIndicatorId"
9
+ class="indicator-divider"
10
+ aria-hidden="true"
11
+ ></div>
12
+
13
+ <div
14
+ class="indicator-item"
15
+ :class="{
16
+ draggable: isSubIndicatorId(indicator.id),
17
+ 'drag-over': dragOverIndicatorId === indicator.id,
18
+ 'is-dragging': draggingIndicatorId === indicator.id,
19
+ }"
20
+ :draggable="isSubIndicatorId(indicator.id)"
21
+ @dragstart="onDragStart($event, indicator.id)"
22
+ @dragover.prevent="onDragOver($event, indicator.id)"
23
+ @drop.prevent="onDrop($event, indicator.id)"
24
+ @dragend="onDragEnd"
25
+ >
26
+ <div
27
+ class="indicator-btn-wrapper"
28
+ @mouseenter="hoveredIndicator = indicator.id"
29
+ @mouseleave="hoveredIndicator = null"
30
+ >
31
+ <button
32
+ class="indicator-btn"
33
+ :class="{ active: true, hovering: hoveredIndicator === indicator.id }"
34
+ >
35
+ <span class="btn-content">
36
+ {{ indicator.label }}
37
+ <span v-if="indicator.params?.length" class="param-hint">
38
+ ({{ getParamDisplay(indicator) }})
39
+ </span>
40
+ </span>
41
+ <!-- ????? -->
42
+ <Transition name="fade">
43
+ <div v-if="hoveredIndicator === indicator.id" class="hover-overlay">
44
+ <button
45
+ v-if="indicator.params?.length"
46
+ class="action-btn settings-btn"
47
+ @click.stop="showParams(indicator.id)"
48
+ title="????"
49
+ >
50
+ <svg viewBox="0 0 24 24" width="14" height="14" fill="currentColor">
51
+ <path
52
+ d="M19.14 12.94c.04-.31.06-.63.06-.94 0-.31-.02-.63-.06-.94l2.03-1.58c.18-.14.23-.41.12-.61l-1.92-3.32c-.12-.22-.37-.29-.59-.22l-2.39.96c-.5-.38-1.03-.7-1.62-.94l-.36-2.54c-.04-.24-.24-.41-.48-.41h-3.84c-.24 0-.43.17-.47.41l-.36 2.54c-.59.24-1.13.57-1.62.94l-2.39-.96c-.22-.08-.47 0-.59.22L2.74 8.87c-.12.21-.08.47.12.61l2.03 1.58c-.04.31-.06.63-.06.94s.02.63.06.94l-2.03 1.58c-.18.14-.23.41-.12.61l1.92 3.32c.12.22.37.29.59.22l2.39-.96c.5.38 1.03.7 1.62.94l.36 2.54c.05.24.24.41.48.41h3.84c.24 0 .44-.17.47-.41l.36-2.54c.59-.24 1.13-.56 1.62-.94l2.39.96c.22.08.47 0 .59-.22l1.92-3.32c.12-.22.07-.47-.12-.61l-2.01-1.58zM12 15.6c-1.98 0-3.6-1.62-3.6-3.6s1.62-3.6 3.6-3.6 3.6 1.62 3.6 3.6-1.62 3.6-3.6 3.6z"
53
+ />
54
+ </svg>
55
+ </button>
56
+ <span v-if="indicator.params?.length" class="divider"></span>
57
+ <button
58
+ class="action-btn remove-btn"
59
+ @click.stop="removeIndicator(indicator.id)"
60
+ title="????"
61
+ >
62
+ <svg viewBox="0 0 24 24" width="14" height="14" fill="currentColor">
63
+ <path
64
+ d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z"
65
+ />
66
+ </svg>
67
+ </button>
68
+ </div>
69
+ </Transition>
70
+ </button>
71
+ </div>
72
+ </div>
73
+ </template>
74
+
75
+ <!-- ???? -->
76
+ <div class="indicator-item">
77
+ <button ref="addBtnRef" class="add-btn" @click="toggleAddMenu" title="????">
78
+ <svg viewBox="0 0 24 24" width="16" height="16" fill="currentColor">
79
+ <path d="M19 13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z" />
80
+ </svg>
81
+ </button>
82
+ </div>
83
+ </div>
84
+ </div>
85
+
86
+ <!-- ?????? -->
87
+ <Teleport :to="teleportTarget">
88
+ <Transition name="overlay">
89
+ <div v-if="showAddMenu" class="selector-overlay" @click="closeAddMenu">
90
+ <Transition name="modal">
91
+ <div v-if="showAddMenu" class="selector-modal" @click.stop>
92
+ <!-- ???? -->
93
+ <div class="modal-header">
94
+ <div class="header-title">
95
+ <span class="title-text">????</span>
96
+ <span class="title-sub">{{ totalIndicatorsCount }} ?????</span>
97
+ </div>
98
+ <div class="header-actions">
99
+ <button
100
+ class="view-toggle-btn"
101
+ :class="{ active: isCompactView }"
102
+ @click="isCompactView = !isCompactView"
103
+ title="????"
104
+ >
105
+ <svg
106
+ v-if="!isCompactView"
107
+ viewBox="0 0 24 24"
108
+ width="16"
109
+ height="16"
110
+ fill="currentColor"
111
+ >
112
+ <path d="M4 6h16v2H4zm0 5h16v2H4zm0 5h16v2H4z" />
113
+ </svg>
114
+ <svg v-else viewBox="0 0 24 24" width="16" height="16" fill="currentColor">
115
+ <path
116
+ d="M3 3h18v18H3V3zm16 16V5H5v14h14zM7 7h4v4H7V7zm0 6h4v4H7v-4zm6-6h4v4h-4V7zm0 6h4v4h-4v-4z"
117
+ />
118
+ </svg>
119
+ </button>
120
+ <button class="modal-close" @click="closeAddMenu" title="??">
121
+ <svg viewBox="0 0 24 24" width="14" height="14" fill="currentColor">
122
+ <path
123
+ d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z"
124
+ />
125
+ </svg>
126
+ </button>
127
+ </div>
128
+ </div>
129
+
130
+ <!-- ???? -->
131
+ <div class="modal-body">
132
+ <!-- ?????? -->
133
+ <div class="indicator-section">
134
+ <div class="section-header">
135
+ <span class="section-title">????</span>
136
+ <span class="section-count">{{ mainIndicators.length }}</span>
137
+ </div>
138
+ <div class="indicator-grid" :class="{ compact: isCompactView }">
139
+ <button
140
+ v-for="indicator in mainIndicators"
141
+ :key="indicator.id"
142
+ class="indicator-card"
143
+ :class="{ active: isActive(indicator.id), compact: isCompactView }"
144
+ @click="
145
+ isActive(indicator.id)
146
+ ? removeIndicator(indicator.id)
147
+ : addIndicator(indicator.id)
148
+ "
149
+ >
150
+ <template v-if="isCompactView">
151
+ <span class="card-label">{{ indicator.label }}</span>
152
+ <span class="card-tooltip">{{ indicator.name }}</span>
153
+ </template>
154
+ <template v-else>
155
+ <div class="card-header">
156
+ <span class="card-label">{{ indicator.label }}</span>
157
+ <div class="card-header-actions">
158
+ <button
159
+ v-if="indicator.params?.length"
160
+ class="card-settings-btn"
161
+ @click.stop="showParams(indicator.id)"
162
+ title="????"
163
+ >
164
+ <svg viewBox="0 0 24 24" width="14" height="14" fill="currentColor">
165
+ <path
166
+ d="M19.14 12.94c.04-.31.06-.63.06-.94 0-.31-.02-.63-.06-.94l2.03-1.58c.18-.14.23-.41.12-.61l-1.92-3.32c-.12-.22-.37-.29-.59-.22l-2.39.96c-.5-.38-1.03-.7-1.62-.94l-.36-2.54c-.04-.24-.24-.41-.48-.41h-3.84c-.24 0-.43.17-.47.41l-.36 2.54c-.59.24-1.13.57-1.62.94l-2.39-.96c-.22-.08-.47 0-.59.22L2.74 8.87c-.12.21-.08.47.12.61l2.03 1.58c-.04.31-.06.63-.06.94s.02.63.06.94l-2.03 1.58c-.18.14-.23.41-.12.61l1.92 3.32c.12.22.37.29.59.22l2.39-.96c.5.38 1.03.7 1.62.94l.36 2.54c.05.24.24.41.48.41h3.84c.24 0 .44-.17.47-.41l.36-2.54c.59-.24 1.13-.56 1.62-.94l2.39.96c.22.08.47 0 .59-.22l1.92-3.32c.12-.22.07-.47-.12-.61l-2.01-1.58zM12 15.6c-1.98 0-3.6-1.62-3.6-3.6s1.62-3.6 3.6-3.6 3.6 1.62 3.6 3.6-1.62 3.6-3.6 3.6z"
167
+ />
168
+ </svg>
169
+ </button>
170
+ </div>
171
+ </div>
172
+ <div class="card-name">{{ indicator.name }}</div>
173
+ </template>
174
+ </button>
175
+ </div>
176
+ </div>
177
+
178
+ <!-- ??? -->
179
+ <div class="section-divider"></div>
180
+
181
+ <!-- ?????? -->
182
+ <div class="indicator-section">
183
+ <div class="section-header">
184
+ <span class="section-title">????</span>
185
+ <span class="section-count">{{ subIndicators.length }}</span>
186
+ </div>
187
+ <div class="indicator-grid" :class="{ compact: isCompactView }">
188
+ <button
189
+ v-for="indicator in subIndicators"
190
+ :key="indicator.id"
191
+ class="indicator-card"
192
+ :class="{ active: isActive(indicator.id), compact: isCompactView }"
193
+ @click="
194
+ isActive(indicator.id)
195
+ ? removeIndicator(indicator.id)
196
+ : addIndicator(indicator.id)
197
+ "
198
+ >
199
+ <template v-if="isCompactView">
200
+ <span class="card-label">{{ indicator.label }}</span>
201
+ <span class="card-tooltip">{{ indicator.name }}</span>
202
+ </template>
203
+ <template v-else>
204
+ <div class="card-header">
205
+ <span class="card-label">{{ indicator.label }}</span>
206
+ <div class="card-header-actions">
207
+ <button
208
+ v-if="indicator.params?.length"
209
+ class="card-settings-btn"
210
+ @click.stop="showParams(indicator.id)"
211
+ title="????"
212
+ >
213
+ <svg viewBox="0 0 24 24" width="14" height="14" fill="currentColor">
214
+ <path
215
+ d="M19.14 12.94c.04-.31.06-.63.06-.94 0-.31-.02-.63-.06-.94l2.03-1.58c.18-.14.23-.41.12-.61l-1.92-3.32c-.12-.22-.37-.29-.59-.22l-2.39.96c-.5-.38-1.03-.7-1.62-.94l-.36-2.54c-.04-.24-.24-.41-.48-.41h-3.84c-.24 0-.43.17-.47.41l-.36 2.54c-.59.24-1.13.57-1.62.94l-2.39-.96c-.22-.08-.47 0-.59.22L2.74 8.87c-.12.21-.08.47.12.61l2.03 1.58c-.04.31-.06.63-.06.94s.02.63.06.94l-2.03 1.58c-.18.14-.23.41-.12.61l1.92 3.32c.12.22.37.29.59.22l2.39-.96c.5.38 1.03.7 1.62.94l.36 2.54c.05.24.24.41.48.41h3.84c.24 0 .44-.17.47-.41l.36-2.54c.59-.24 1.13-.56 1.62-.94l2.39.96c.22.08.47 0 .59-.22l1.92-3.32c.12-.22.07-.47-.12-.61l-2.01-1.58zM12 15.6c-1.98 0-3.6-1.62-3.6-3.6s1.62-3.6 3.6-3.6 3.6 1.62 3.6 3.6-1.62 3.6-3.6 3.6z"
216
+ />
217
+ </svg>
218
+ </button>
219
+ </div>
220
+ </div>
221
+ <div class="card-name">{{ indicator.name }}</div>
222
+ </template>
223
+ </button>
224
+ </div>
225
+ </div>
226
+ </div>
227
+
228
+ <!-- ???? -->
229
+ <div class="modal-footer">
230
+ <div class="footer-info">
231
+ <span class="info-text">??? {{ activeCount }} ???</span>
232
+ </div>
233
+ <button class="btn btn-confirm" @click="closeAddMenu">??</button>
234
+ </div>
235
+ </div>
236
+ </Transition>
237
+ </div>
238
+ </Transition>
239
+ </Teleport>
240
+
241
+ <!-- ?????? -->
242
+ <IndicatorParams
243
+ v-if="currentIndicator"
244
+ :visible="paramsVisible"
245
+ :indicator-id="currentIndicator.id"
246
+ :indicator-name="currentIndicator.name"
247
+ :indicator-description="currentIndicator.description"
248
+ :params="currentIndicator.params || []"
249
+ :values="getParamValues(currentIndicator.id)"
250
+ @close="paramsVisible = false"
251
+ @confirm="onParamsConfirm"
252
+ />
253
+ </div>
254
+ </template>
255
+
256
+ <script setup lang="ts">
257
+ import { ref, computed, onMounted, onUnmounted } from 'vue'
258
+ import IndicatorParams from './IndicatorParams.vue'
259
+ import { useFullscreenTeleportTarget } from '../composables/useFullscreenTeleportTarget'
260
+ import {
261
+ mainIndicators,
262
+ subIndicators,
263
+ findIndicator,
264
+ isSubIndicatorId,
265
+ } from '@363045841yyt/klinechart-core/engine/renderers/Indicator/indicatorData'
266
+ import type { Indicator } from '@363045841yyt/klinechart-core/engine/renderers/Indicator/indicatorData'
267
+
268
+ const props = defineProps<{
269
+ activeIndicators?: string[]
270
+ indicatorParams?: Record<string, Record<string, unknown>>
271
+ }>()
272
+
273
+ const emit = defineEmits<{
274
+ toggle: [indicatorId: string, active: boolean]
275
+ updateParams: [indicatorId: string, params: Record<string, number>]
276
+ reorderSubIndicators: [orderedIndicatorIds: string[]]
277
+ }>()
278
+
279
+ const addBtnRef = ref<HTMLButtonElement | null>(null)
280
+ const paramsVisible = ref(false)
281
+ const currentIndicatorId = ref<string | null>(null)
282
+ const hoveredIndicator = ref<string | null>(null)
283
+ const showAddMenu = ref(false)
284
+ const dragOverIndicatorId = ref<string | null>(null)
285
+ const draggingIndicatorId = ref<string | null>(null)
286
+ const isCompactView = ref(false)
287
+
288
+ const teleportTarget = useFullscreenTeleportTarget()
289
+
290
+ const activeIndicatorsList = computed(() => {
291
+ if (!props.activeIndicators?.length) return []
292
+ return props.activeIndicators
293
+ .map((id) => findIndicator(id))
294
+ .filter((i): i is Indicator => i !== undefined)
295
+ .sort((a, b) => {
296
+ if (a.pane === b.pane) return 0
297
+ return a.pane === 'main' ? -1 : 1
298
+ })
299
+ })
300
+
301
+ const firstActiveSubIndicatorId = computed(() => {
302
+ const hasMain = activeIndicatorsList.value.some((indicator) => indicator.pane === 'main')
303
+ if (!hasMain) return null
304
+ const firstSub = activeIndicatorsList.value.find((indicator) => indicator.pane === 'sub')
305
+ return firstSub?.id ?? null
306
+ })
307
+
308
+ const currentIndicator = computed(() => {
309
+ if (!currentIndicatorId.value) return null
310
+ return findIndicator(currentIndicatorId.value)
311
+ })
312
+
313
+ const totalIndicatorsCount = computed(() => mainIndicators.length + subIndicators.length)
314
+
315
+ const activeCount = computed(() => props.activeIndicators?.length ?? 0)
316
+
317
+ function isActive(indicatorId: string): boolean {
318
+ return props.activeIndicators?.includes(indicatorId) ?? false
319
+ }
320
+
321
+ function addIndicator(indicatorId: string) {
322
+ if (isActive(indicatorId)) return
323
+
324
+ const indicator = findIndicator(indicatorId)
325
+ if (!indicator) return
326
+
327
+ if (indicator.pane === 'main') {
328
+ mainIndicators
329
+ .filter((i) => i.id !== indicatorId && isActive(i.id))
330
+ .forEach((i) => emit('toggle', i.id, false))
331
+ }
332
+
333
+ emit('toggle', indicatorId, true)
334
+ }
335
+
336
+ function removeIndicator(indicatorId: string) {
337
+ emit('toggle', indicatorId, false)
338
+ }
339
+
340
+ function showParams(indicatorId: string) {
341
+ currentIndicatorId.value = indicatorId
342
+ paramsVisible.value = true
343
+ }
344
+
345
+ function closeAddMenu() {
346
+ showAddMenu.value = false
347
+ }
348
+
349
+ function getParamValues(indicatorId: string): Record<string, number> {
350
+ const indicator = findIndicator(indicatorId)
351
+ if (!indicator?.params) return {}
352
+
353
+ const defaultParams: Record<string, number> = {}
354
+ for (const p of indicator.params) {
355
+ defaultParams[p.key] = p.default ?? p.min ?? 1
356
+ }
357
+
358
+ const userParams = props.indicatorParams?.[indicatorId] || {}
359
+ const result: Record<string, number> = { ...defaultParams }
360
+
361
+ for (const [key, value] of Object.entries(userParams)) {
362
+ if (typeof value === 'number') {
363
+ result[key] = value
364
+ }
365
+ }
366
+
367
+ return result
368
+ }
369
+
370
+ function getParamDisplay(indicator: Indicator): string {
371
+ const values = getParamValues(indicator.id)
372
+ if (!indicator.params) return ''
373
+ return indicator.params.map((p) => values[p.key] ?? '').join(',')
374
+ }
375
+
376
+ function onParamsConfirm(values: Record<string, number>) {
377
+ if (currentIndicatorId.value) {
378
+ emit('updateParams', currentIndicatorId.value, values)
379
+ }
380
+ paramsVisible.value = false
381
+ }
382
+
383
+ function onDragStart(event: DragEvent, indicatorId: string) {
384
+ if (!isSubIndicatorId(indicatorId)) {
385
+ event.preventDefault()
386
+ return
387
+ }
388
+ draggingIndicatorId.value = indicatorId
389
+ dragOverIndicatorId.value = null
390
+ event.dataTransfer?.setData('text/plain', indicatorId)
391
+ if (event.dataTransfer) {
392
+ event.dataTransfer.effectAllowed = 'move'
393
+ }
394
+ }
395
+
396
+ function onDragOver(event: DragEvent, indicatorId: string) {
397
+ if (
398
+ !draggingIndicatorId.value ||
399
+ !isSubIndicatorId(indicatorId) ||
400
+ draggingIndicatorId.value === indicatorId
401
+ )
402
+ return
403
+ dragOverIndicatorId.value = indicatorId
404
+ if (event.dataTransfer) {
405
+ event.dataTransfer.dropEffect = 'move'
406
+ }
407
+ }
408
+
409
+ function onDrop(event: DragEvent, targetIndicatorId: string) {
410
+ const sourceIndicatorId =
411
+ draggingIndicatorId.value || event.dataTransfer?.getData('text/plain') || ''
412
+ if (!sourceIndicatorId || sourceIndicatorId === targetIndicatorId) {
413
+ onDragEnd()
414
+ return
415
+ }
416
+ if (!isSubIndicatorId(sourceIndicatorId) || !isSubIndicatorId(targetIndicatorId)) {
417
+ onDragEnd()
418
+ return
419
+ }
420
+
421
+ const sourceIndex = activeIndicatorsList.value.findIndex((i) => i.id === sourceIndicatorId)
422
+ const targetIndex = activeIndicatorsList.value.findIndex((i) => i.id === targetIndicatorId)
423
+ if (sourceIndex < 0 || targetIndex < 0) {
424
+ onDragEnd()
425
+ return
426
+ }
427
+
428
+ const next = [...activeIndicatorsList.value.map((i) => i.id)]
429
+ const [moved] = next.splice(sourceIndex, 1)
430
+ if (!moved) {
431
+ onDragEnd()
432
+ return
433
+ }
434
+ next.splice(targetIndex, 0, moved)
435
+
436
+ emit(
437
+ 'reorderSubIndicators',
438
+ next.filter((id) => isSubIndicatorId(id)),
439
+ )
440
+ onDragEnd()
441
+ }
442
+
443
+ function onDragEnd() {
444
+ dragOverIndicatorId.value = null
445
+ draggingIndicatorId.value = null
446
+ }
447
+
448
+ function toggleAddMenu() {
449
+ showAddMenu.value = !showAddMenu.value
450
+ }
451
+
452
+ function handleKeydown(event: KeyboardEvent) {
453
+ if (event.key === 'Escape' && showAddMenu.value) {
454
+ showAddMenu.value = false
455
+ }
456
+ }
457
+
458
+ onMounted(() => {
459
+ document.addEventListener('keydown', handleKeydown)
460
+ })
461
+
462
+ onUnmounted(() => {
463
+ document.removeEventListener('keydown', handleKeydown)
464
+ })
465
+ </script>
466
+
467
+ <style scoped>
468
+ .indicator-selector {
469
+ margin: 20px;
470
+ width: 80%;
471
+ position: relative;
472
+ }
473
+
474
+ .indicator-scroll-container {
475
+ width: 100%;
476
+ overflow-x: auto;
477
+ overflow-y: hidden;
478
+ scrollbar-width: none;
479
+ -webkit-overflow-scrolling: touch;
480
+ text-align: center;
481
+ }
482
+
483
+ .indicator-scroll-container::-webkit-scrollbar {
484
+ display: none;
485
+ }
486
+
487
+ .indicator-list {
488
+ display: inline-flex;
489
+ gap: 8px;
490
+ padding: 2px;
491
+ margin: 0 auto;
492
+ }
493
+
494
+ .indicator-divider {
495
+ width: 1px;
496
+ height: 20px;
497
+ align-self: center;
498
+ background: #d9d9d9;
499
+ }
500
+
501
+ .indicator-item {
502
+ display: flex;
503
+ align-items: center;
504
+ gap: 4px;
505
+ }
506
+
507
+ .indicator-item.draggable,
508
+ .indicator-item.draggable .indicator-btn,
509
+ .indicator-item.draggable:hover,
510
+ .indicator-item.draggable:hover .indicator-btn {
511
+ cursor: move;
512
+ }
513
+
514
+ .indicator-item.is-dragging {
515
+ opacity: 0.6;
516
+ }
517
+
518
+ .indicator-item.drag-over .indicator-btn {
519
+ border-color: #1a1a1a;
520
+ box-shadow: 0 0 0 2px rgba(26, 26, 26, 0.12);
521
+ }
522
+
523
+ .indicator-btn-wrapper {
524
+ position: relative;
525
+ }
526
+
527
+ .indicator-btn {
528
+ position: relative;
529
+ flex-shrink: 0;
530
+ padding: 6px 16px;
531
+ border: 1px solid #e0e0e0;
532
+ border-radius: 16px;
533
+ background: #ffffff;
534
+ color: #666;
535
+ font-size: 13px;
536
+ font-weight: 500;
537
+ cursor: pointer;
538
+ transition: all 0.3s ease;
539
+ white-space: nowrap;
540
+ display: flex;
541
+ align-items: center;
542
+ justify-content: center;
543
+ gap: 4px;
544
+ overflow: hidden;
545
+ }
546
+
547
+ .indicator-btn:hover:not(.hovering) {
548
+ background: #f8f8f8;
549
+ border-color: #ccc;
550
+ color: #333;
551
+ }
552
+
553
+ .indicator-btn.active {
554
+ background: #f8f8f8;
555
+ border-color: #1a1a1a;
556
+ color: #1a1a1a;
557
+ }
558
+
559
+ .indicator-btn.active:hover:not(.hovering) {
560
+ background: #f0f0f0;
561
+ border-color: #333;
562
+ }
563
+
564
+ .btn-content {
565
+ position: relative;
566
+ z-index: 1;
567
+ }
568
+
569
+ .param-hint {
570
+ font-size: 11px;
571
+ opacity: 0.85;
572
+ }
573
+
574
+ /* ????? */
575
+ .hover-overlay {
576
+ position: absolute;
577
+ top: 0;
578
+ left: 0;
579
+ right: 0;
580
+ bottom: 0;
581
+ display: flex;
582
+ align-items: center;
583
+ justify-content: center;
584
+ gap: 4px;
585
+ background: rgba(255, 255, 255, 0.85);
586
+ backdrop-filter: blur(4px);
587
+ border-radius: 16px;
588
+ z-index: 2;
589
+ }
590
+
591
+ .action-btn {
592
+ width: 24px;
593
+ height: 24px;
594
+ padding: 0;
595
+ border: none;
596
+ border-radius: 50%;
597
+ background: transparent;
598
+ color: #666;
599
+ cursor: pointer;
600
+ display: flex;
601
+ align-items: center;
602
+ justify-content: center;
603
+ transition: all 0.2s;
604
+ }
605
+
606
+ .action-btn:hover {
607
+ background: rgba(0, 0, 0, 0.06);
608
+ color: #333;
609
+ }
610
+
611
+ .settings-btn:hover {
612
+ color: #1a1a1a;
613
+ }
614
+
615
+ .remove-btn:hover {
616
+ color: #ff4d4f;
617
+ }
618
+
619
+ .divider {
620
+ width: 1px;
621
+ height: 14px;
622
+ background: #e0e0e0;
623
+ }
624
+
625
+ /* ???? */
626
+ .add-btn {
627
+ flex-shrink: 0;
628
+ width: 32px;
629
+ height: 32px;
630
+ padding: 0;
631
+ border: 1px dashed #d9d9d9;
632
+ border-radius: 50%;
633
+ background: transparent;
634
+ color: #999;
635
+ cursor: pointer;
636
+ display: flex;
637
+ align-items: center;
638
+ justify-content: center;
639
+ transition: all 0.3s ease;
640
+ }
641
+
642
+ .add-btn:hover {
643
+ border-color: #1a1a1a;
644
+ color: #1a1a1a;
645
+ background: rgba(26, 26, 26, 0.04);
646
+ }
647
+
648
+ /* ?????????????????????????????????????????????????????????????????
649
+ ???? - ?????????
650
+ ????????????????????????????????????????????????????????????????? */
651
+
652
+ /* ??? */
653
+ .selector-overlay {
654
+ position: fixed;
655
+ inset: 0;
656
+ background: rgba(0, 0, 0, 0.3);
657
+ backdrop-filter: blur(4px);
658
+ display: flex;
659
+ align-items: center;
660
+ justify-content: center;
661
+ z-index: 1000;
662
+ }
663
+
664
+ /* ???? */
665
+ .selector-modal {
666
+ background: #ffffff;
667
+ border: 1px solid #e0e0e0;
668
+ border-radius: 12px;
669
+ box-shadow: 0 8px 40px rgba(0, 0, 0, 0.15);
670
+ width: 90vw;
671
+ max-width: 860px;
672
+ max-height: 85vh;
673
+ overflow: hidden;
674
+ display: flex;
675
+ flex-direction: column;
676
+ }
677
+
678
+ /* ???? */
679
+ .modal-header {
680
+ display: flex;
681
+ justify-content: space-between;
682
+ align-items: center;
683
+ padding: 16px 20px;
684
+ background: #f8f8f8;
685
+ border-bottom: 1px solid #e8e8e8;
686
+ flex-shrink: 0;
687
+ }
688
+
689
+ .header-title {
690
+ display: flex;
691
+ flex-direction: column;
692
+ gap: 2px;
693
+ }
694
+
695
+ .title-text {
696
+ font-size: 14px;
697
+ font-weight: 600;
698
+ color: #1a1a1a;
699
+ letter-spacing: 0.2px;
700
+ }
701
+
702
+ .title-sub {
703
+ font-size: 11px;
704
+ color: #999;
705
+ }
706
+
707
+ .modal-close {
708
+ background: #fff;
709
+ border: 1px solid #e0e0e0;
710
+ border-radius: 6px;
711
+ width: 28px;
712
+ height: 28px;
713
+ display: flex;
714
+ align-items: center;
715
+ justify-content: center;
716
+ cursor: pointer;
717
+ color: #888;
718
+ transition: all 0.15s;
719
+ padding: 0;
720
+ }
721
+
722
+ .modal-close:hover {
723
+ background: #f0f0f0;
724
+ color: #333;
725
+ border-color: #ccc;
726
+ }
727
+
728
+ .modal-close svg {
729
+ width: 14px;
730
+ height: 14px;
731
+ }
732
+
733
+ .header-actions {
734
+ display: flex;
735
+ align-items: center;
736
+ gap: 8px;
737
+ }
738
+
739
+ .view-toggle-btn {
740
+ background: #fff;
741
+ border: 1px solid #e0e0e0;
742
+ border-radius: 6px;
743
+ width: 28px;
744
+ height: 28px;
745
+ display: flex;
746
+ align-items: center;
747
+ justify-content: center;
748
+ cursor: pointer;
749
+ color: #888;
750
+ transition: all 0.15s;
751
+ padding: 0;
752
+ }
753
+
754
+ .view-toggle-btn:hover {
755
+ background: #f0f0f0;
756
+ color: #333;
757
+ border-color: #ccc;
758
+ }
759
+
760
+ .view-toggle-btn.active {
761
+ background: #1a1a1a;
762
+ border-color: #1a1a1a;
763
+ color: #fff;
764
+ }
765
+
766
+ /* ???? */
767
+ .modal-body {
768
+ padding: 20px;
769
+ overflow-y: auto;
770
+ flex: 1;
771
+ display: flex;
772
+ flex-direction: column;
773
+ gap: 20px;
774
+ }
775
+
776
+ /* ???? */
777
+ .indicator-section {
778
+ display: flex;
779
+ flex-direction: column;
780
+ gap: 12px;
781
+ }
782
+
783
+ .section-header {
784
+ display: flex;
785
+ align-items: center;
786
+ gap: 8px;
787
+ }
788
+
789
+ .section-title {
790
+ font-size: 13px;
791
+ font-weight: 600;
792
+ color: #1a1a1a;
793
+ }
794
+
795
+ .section-count {
796
+ font-size: 11px;
797
+ color: #999;
798
+ background: #f0f0f0;
799
+ padding: 2px 8px;
800
+ border-radius: 10px;
801
+ }
802
+
803
+ /* ??????? */
804
+ .indicator-grid {
805
+ display: grid;
806
+ grid-template-columns: repeat(auto-fill, minmax(195px, 1fr));
807
+ gap: 10px;
808
+ }
809
+
810
+ /* ???? - ???? */
811
+ .indicator-grid.compact {
812
+ display: flex;
813
+ flex-wrap: wrap;
814
+ gap: 8px;
815
+ }
816
+
817
+ .indicator-grid.compact .indicator-card {
818
+ display: inline-flex;
819
+ align-items: center;
820
+ justify-content: center;
821
+ padding: 6px 14px;
822
+ border-radius: 16px;
823
+ min-height: 32px;
824
+ white-space: nowrap;
825
+ position: relative;
826
+ }
827
+
828
+ .indicator-grid.compact .indicator-card .card-tooltip {
829
+ position: absolute;
830
+ bottom: calc(100% + 6px);
831
+ left: 50%;
832
+ transform: translateX(-50%);
833
+ padding: 4px 10px;
834
+ border-radius: 6px;
835
+ background: #333;
836
+ color: #fff;
837
+ font-size: 12px;
838
+ white-space: nowrap;
839
+ pointer-events: none;
840
+ opacity: 0;
841
+ transition: opacity 0.15s ease;
842
+ z-index: 10;
843
+ }
844
+
845
+ .indicator-grid.compact .indicator-card:hover .card-tooltip {
846
+ opacity: 1;
847
+ }
848
+
849
+ .indicator-grid.compact .indicator-card .card-label {
850
+ font-size: 12px;
851
+ font-weight: 500;
852
+ }
853
+
854
+ /* ???? */
855
+ .indicator-card {
856
+ display: flex;
857
+ flex-direction: column;
858
+ gap: 4px;
859
+ padding: 12px 14px;
860
+ border: 1px solid #e8e8e8;
861
+ border-radius: 8px;
862
+ background: #ffffff;
863
+ cursor: pointer;
864
+ transition: all 0.15s;
865
+ text-align: left;
866
+ }
867
+
868
+ .indicator-card:hover:not(.disabled) {
869
+ border-color: #1a1a1a;
870
+ background: #fafafa;
871
+ transform: translateY(-1px);
872
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
873
+ }
874
+
875
+ .indicator-card.active {
876
+ border-color: #1a1a1a;
877
+ background: #f8f8f8;
878
+ }
879
+
880
+ .card-header {
881
+ display: flex;
882
+ align-items: center;
883
+ justify-content: space-between;
884
+ gap: 8px;
885
+ }
886
+
887
+ .card-label {
888
+ font-size: 13px;
889
+ font-weight: 600;
890
+ color: #1a1a1a;
891
+ }
892
+
893
+ .card-header-actions {
894
+ display: flex;
895
+ align-items: center;
896
+ gap: 4px;
897
+ }
898
+
899
+ .card-settings-btn {
900
+ display: flex;
901
+ align-items: center;
902
+ justify-content: center;
903
+ width: 20px;
904
+ height: 20px;
905
+ padding: 0;
906
+ border: none;
907
+ border-radius: 4px;
908
+ background: transparent;
909
+ color: #bbb;
910
+ cursor: pointer;
911
+ transition: all 0.15s;
912
+ }
913
+
914
+ .card-settings-btn:hover {
915
+ background: #f0f0f0;
916
+ color: #555;
917
+ }
918
+
919
+ .card-name {
920
+ font-size: 11px;
921
+ color: #666;
922
+ line-height: 1.4;
923
+ }
924
+
925
+ .card-params {
926
+ font-size: 10px;
927
+ color: #999;
928
+ margin-top: 2px;
929
+ }
930
+
931
+ /* ????? */
932
+ .section-divider {
933
+ height: 1px;
934
+ background: linear-gradient(90deg, transparent, #e0e0e0, transparent);
935
+ margin: 4px 0;
936
+ }
937
+
938
+ /* ???? */
939
+ .modal-footer {
940
+ display: flex;
941
+ align-items: center;
942
+ justify-content: space-between;
943
+ padding: 12px 20px;
944
+ background: #f8f8f8;
945
+ border-top: 1px solid #e8e8e8;
946
+ flex-shrink: 0;
947
+ }
948
+
949
+ .footer-info {
950
+ font-size: 12px;
951
+ color: #666;
952
+ }
953
+
954
+ .info-text {
955
+ color: #999;
956
+ }
957
+
958
+ /* ???? */
959
+ .btn {
960
+ display: flex;
961
+ align-items: center;
962
+ gap: 5px;
963
+ padding: 6px 16px;
964
+ border-radius: 7px;
965
+ font-size: 13px;
966
+ font-weight: 500;
967
+ cursor: pointer;
968
+ border: 1px solid transparent;
969
+ transition: all 0.15s;
970
+ line-height: 1.4;
971
+ }
972
+
973
+ .btn-confirm {
974
+ background: #1a1a1a;
975
+ border-color: #1a1a1a;
976
+ color: #fff;
977
+ }
978
+
979
+ .btn-confirm:hover {
980
+ background: #333;
981
+ border-color: #333;
982
+ box-shadow: 0 2px 10px rgba(0, 0, 0, 0.15);
983
+ transform: translateY(-1px);
984
+ }
985
+
986
+ /* ???? */
987
+ .fade-enter-active,
988
+ .fade-leave-active {
989
+ transition: opacity 0.2s ease;
990
+ }
991
+
992
+ .fade-enter-from,
993
+ .fade-leave-to {
994
+ opacity: 0;
995
+ }
996
+
997
+ /* ????? */
998
+ .overlay-enter-active,
999
+ .overlay-leave-active {
1000
+ transition: opacity 0.2s ease;
1001
+ }
1002
+
1003
+ .overlay-enter-from,
1004
+ .overlay-leave-to {
1005
+ opacity: 0;
1006
+ }
1007
+
1008
+ /* ???? */
1009
+ .modal-enter-active {
1010
+ transition: all 0.22s cubic-bezier(0.34, 1.56, 0.64, 1);
1011
+ }
1012
+
1013
+ .modal-leave-active {
1014
+ transition: all 0.16s ease-in;
1015
+ }
1016
+
1017
+ .modal-enter-from {
1018
+ opacity: 0;
1019
+ transform: scale(0.88) translateY(-16px);
1020
+ }
1021
+
1022
+ .modal-leave-to {
1023
+ opacity: 0;
1024
+ transform: scale(0.94) translateY(8px);
1025
+ }
1026
+
1027
+ /* ????? */
1028
+ @media (max-width: 640px) {
1029
+ .selector-modal {
1030
+ width: 95vw;
1031
+ max-height: 90vh;
1032
+ }
1033
+
1034
+ .indicator-grid {
1035
+ grid-template-columns: 1fr;
1036
+ }
1037
+
1038
+ .modal-body {
1039
+ padding: 16px;
1040
+ }
1041
+ }
1042
+ </style>