@363045841yyt/klinechart 0.8.4 → 0.8.6

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 (69) hide show
  1. package/README.md +6 -1
  2. package/dist/components/BaseModal.vue.d.ts +54 -0
  3. package/dist/components/BaseModal.vue.d.ts.map +1 -0
  4. package/dist/components/BatchStockDialog.vue.d.ts +13 -0
  5. package/dist/components/BatchStockDialog.vue.d.ts.map +1 -0
  6. package/dist/components/ChartSettingsDialog.vue.d.ts.map +1 -1
  7. package/dist/components/ColorPresetPanel.vue.d.ts +4 -1
  8. package/dist/components/ColorPresetPanel.vue.d.ts.map +1 -1
  9. package/dist/components/CompareSymbolSelector.vue.d.ts.map +1 -1
  10. package/dist/components/DrawingStyleToolbar.vue.d.ts.map +1 -1
  11. package/dist/components/Dropdown.vue.d.ts.map +1 -1
  12. package/dist/components/ExportProgressDialog.vue.d.ts +15 -0
  13. package/dist/components/ExportProgressDialog.vue.d.ts.map +1 -0
  14. package/dist/components/IndicatorParams.vue.d.ts.map +1 -1
  15. package/dist/components/IndicatorSelector.vue.d.ts.map +1 -1
  16. package/dist/components/KLineChart.vue.d.ts +5 -9
  17. package/dist/components/KLineChart.vue.d.ts.map +1 -1
  18. package/dist/components/LeftToolbar.vue.d.ts.map +1 -1
  19. package/dist/components/RangeSelectionExport.vue.d.ts +23 -0
  20. package/dist/components/RangeSelectionExport.vue.d.ts.map +1 -0
  21. package/dist/components/SymbolSelector.vue.d.ts.map +1 -1
  22. package/dist/components/TopToolbar.vue.d.ts.map +1 -1
  23. package/dist/components/common/CanvasToolbar.vue.d.ts +14 -0
  24. package/dist/components/common/CanvasToolbar.vue.d.ts.map +1 -0
  25. package/dist/components/common/CanvasToolbarStack.vue.d.ts +14 -0
  26. package/dist/components/common/CanvasToolbarStack.vue.d.ts.map +1 -0
  27. package/dist/composables/chart/useChartTheme.d.ts +329 -0
  28. package/dist/composables/chart/useChartTheme.d.ts.map +1 -0
  29. package/dist/composables/chart/useDrawingManager.d.ts +86 -0
  30. package/dist/composables/chart/useDrawingManager.d.ts.map +1 -0
  31. package/dist/composables/chart/useIndicatorManager.d.ts +38 -0
  32. package/dist/composables/chart/useIndicatorManager.d.ts.map +1 -0
  33. package/dist/composables/chart/useRangeSelection.d.ts +66 -0
  34. package/dist/composables/chart/useRangeSelection.d.ts.map +1 -0
  35. package/dist/composables/useTeleportedPopup.d.ts +8 -0
  36. package/dist/composables/useTeleportedPopup.d.ts.map +1 -0
  37. package/dist/index.cjs +9 -2
  38. package/dist/index.css +1 -1
  39. package/dist/index.js +2149 -1409
  40. package/dist/tools/calcRangeOverlayPixel.d.ts +15 -0
  41. package/dist/tools/calcRangeOverlayPixel.d.ts.map +1 -0
  42. package/dist/tools/getKLineIndexByTimestamp.d.ts +4 -0
  43. package/dist/tools/getKLineIndexByTimestamp.d.ts.map +1 -0
  44. package/dist/web-component.d.ts.map +1 -1
  45. package/package.json +1 -1
  46. package/src/components/BaseModal.vue +292 -0
  47. package/src/components/BatchStockDialog.vue +128 -0
  48. package/src/components/ChartSettingsDialog.vue +248 -405
  49. package/src/components/ColorPresetPanel.vue +58 -106
  50. package/src/components/CompareSymbolSelector.vue +37 -10
  51. package/src/components/DrawingStyleToolbar.vue +33 -72
  52. package/src/components/Dropdown.vue +42 -19
  53. package/src/components/ExportProgressDialog.vue +118 -0
  54. package/src/components/IndicatorParams.vue +194 -321
  55. package/src/components/IndicatorSelector.vue +188 -405
  56. package/src/components/KLineChart.vue +228 -403
  57. package/src/components/LeftToolbar.vue +3 -2
  58. package/src/components/RangeSelectionExport.vue +117 -0
  59. package/src/components/SymbolSelector.vue +37 -10
  60. package/src/components/TopToolbar.vue +55 -2
  61. package/src/components/common/CanvasToolbar.vue +70 -0
  62. package/src/components/common/CanvasToolbarStack.vue +32 -0
  63. package/src/composables/chart/useChartTheme.ts +86 -0
  64. package/src/composables/chart/useDrawingManager.ts +67 -0
  65. package/src/composables/chart/useIndicatorManager.ts +307 -0
  66. package/src/composables/chart/useRangeSelection.ts +424 -0
  67. package/src/composables/useTeleportedPopup.ts +46 -0
  68. package/src/tools/calcRangeOverlayPixel.ts +28 -0
  69. package/src/tools/getKLineIndexByTimestamp.ts +40 -0
@@ -139,6 +139,7 @@ import IconTablerShape from '~icons/tabler/shape'
139
139
  import IconTablerChartDots3 from '~icons/tabler/chart-dots-3'
140
140
  import IconTablerCaretUpDown from '~icons/tabler/caret-up-down'
141
141
  import IconTablerBrackets from '~icons/tabler/brackets'
142
+ import IconTablerArrowsHorizontal from '~icons/tabler/arrows-horizontal'
142
143
  import {
143
144
  DEFAULT_SETTINGS,
144
145
  SETTINGS_STORAGE_KEY,
@@ -181,6 +182,7 @@ const primaryTools: ToolDef[] = [
181
182
  { id: 'disjoint-channel', title: '不相交通道', icon: IconTablerBrackets },
182
183
  ],
183
184
  },
185
+ { id: 'range-select', title: '导出区间数据', icon: IconTablerArrowsHorizontal },
184
186
  ]
185
187
 
186
188
  defineProps<{
@@ -416,7 +418,7 @@ onUnmounted(() => {
416
418
  gap: 4px;
417
419
  padding: 0 5px;
418
420
  height: 40px;
419
- background: var(--klc-color-tag-bg-white);
421
+ background: var(--klc-color-background);
420
422
  backdrop-filter: blur(8px);
421
423
  -webkit-backdrop-filter: blur(8px);
422
424
  border: 1px solid var(--klc-color-border-chart);
@@ -482,5 +484,4 @@ onUnmounted(() => {
482
484
  height: 36px;
483
485
  }
484
486
  }
485
-
486
487
  </style>
@@ -0,0 +1,117 @@
1
+ <template>
2
+ <CanvasToolbar>
3
+ <input
4
+ class="range-input"
5
+ :value="startDate"
6
+ @input="$emit('update:startDate', ($event.target as HTMLInputElement).value)"
7
+ :placeholder="startLabel"
8
+ />
9
+ <span class="range-sep">~</span>
10
+ <input
11
+ class="range-input"
12
+ :value="endDate"
13
+ @input="$emit('update:endDate', ($event.target as HTMLInputElement).value)"
14
+ :placeholder="endLabel"
15
+ />
16
+ <span class="range-count">共 {{ count }} 条</span>
17
+ <button type="button" class="toolbar-btn" title="批量设置" @click="$emit('batchSetting')">
18
+ 批量设置
19
+ </button>
20
+ <button type="button" class="toolbar-btn" title="导出" @click="$emit('export')">导出</button>
21
+ <button
22
+ type="button"
23
+ class="toolbar-btn toolbar-btn--delete"
24
+ title="取消选区"
25
+ @click="$emit('clear')"
26
+ >
27
+ <svg
28
+ class="delete-icon"
29
+ viewBox="0 0 24 24"
30
+ fill="none"
31
+ stroke="currentColor"
32
+ stroke-width="2"
33
+ stroke-linecap="round"
34
+ stroke-linejoin="round"
35
+ aria-hidden="true"
36
+ >
37
+ <path d="M3 6h18" />
38
+ <path d="M19 6v14c0 1-1 2-2 2H7c-1 0-2-1-2-2V6" />
39
+ <path d="M8 6V4c0-1 1-2 2-2h4c1 0 2 1 2 2v2" />
40
+ </svg>
41
+ </button>
42
+ </CanvasToolbar>
43
+ </template>
44
+
45
+ <script setup lang="ts">
46
+ import CanvasToolbar from './common/CanvasToolbar.vue'
47
+
48
+ defineProps<{
49
+ startDate: string
50
+ endDate: string
51
+ startLabel: string
52
+ endLabel: string
53
+ count: number
54
+ }>()
55
+
56
+ defineEmits<{
57
+ 'update:startDate': [value: string]
58
+ 'update:endDate': [value: string]
59
+ export: []
60
+ clear: []
61
+ batchSetting: []
62
+ }>()
63
+ </script>
64
+
65
+ <style scoped>
66
+ .range-input {
67
+ color: var(--klc-color-axis-text);
68
+ font-size: 12px;
69
+ white-space: nowrap;
70
+ border: none;
71
+ background: transparent;
72
+ outline: none;
73
+ padding: 0 8px;
74
+ width: auto;
75
+ field-sizing: content;
76
+ min-width: 60px;
77
+ height: 26px;
78
+ box-sizing: border-box;
79
+ font-family: inherit;
80
+ border-radius: 4px;
81
+ text-align: center;
82
+ transition:
83
+ background 0.15s ease,
84
+ color 0.15s ease;
85
+ }
86
+
87
+ .range-input::placeholder {
88
+ color: var(--klc-color-axis-text);
89
+ opacity: 0.6;
90
+ }
91
+
92
+ .range-input:hover,
93
+ .range-input:focus {
94
+ background: var(--klc-color-grid-minor);
95
+ color: var(--klc-color-foreground);
96
+ }
97
+
98
+ .range-sep {
99
+ color: var(--klc-color-axis-text);
100
+ font-size: 12px;
101
+ opacity: 0.6;
102
+ user-select: none;
103
+ }
104
+
105
+ .range-count {
106
+ color: var(--klc-color-axis-text);
107
+ font-size: 12px;
108
+ white-space: nowrap;
109
+ user-select: none;
110
+ padding: 0 8px;
111
+ margin-right: 4px;
112
+ display: flex;
113
+ align-items: center;
114
+ height: 18px;
115
+ border-right: 1px solid var(--klc-color-border-button);
116
+ }
117
+ </style>
@@ -13,8 +13,16 @@
13
13
  <span v-if="loading" class="symbol-chip__spinner" aria-hidden="true" />
14
14
  <IconTablerAlertTriangle v-else-if="error" class="symbol-chip__warn" aria-hidden="true" />
15
15
  </button>
16
- <Transition name="symbol-popover">
17
- <div v-if="showPopup" class="symbol-popover" role="dialog" aria-label="切换合约">
16
+ <Teleport :to="teleportTarget">
17
+ <Transition name="symbol-popover">
18
+ <div
19
+ v-if="showPopup"
20
+ ref="popupRef"
21
+ class="symbol-popover"
22
+ :style="popupStyle"
23
+ role="dialog"
24
+ aria-label="切换合约"
25
+ >
18
26
  <div class="symbol-search">
19
27
  <span class="symbol-search__icon" aria-hidden="true">
20
28
  <svg width="14" height="14" viewBox="0 0 16 16" fill="none">
@@ -95,14 +103,17 @@
95
103
  <span class="symbol-list__exchange">{{ item.exchange }}</span>
96
104
  </button>
97
105
  </div>
98
- </div>
99
- </Transition>
106
+ </div>
107
+ </Transition>
108
+ </Teleport>
100
109
  </div>
101
110
  </template>
102
111
 
103
112
  <script setup lang="ts">
104
113
  import { ref, computed, watch, nextTick, onMounted, onBeforeUnmount } from 'vue'
105
114
  import IconTablerAlertTriangle from '~icons/tabler/alert-triangle'
115
+ import { useTeleportedPopup } from '../composables/useTeleportedPopup'
116
+ import { useFullscreenTeleportTarget } from '../composables/useFullscreenTeleportTarget'
106
117
 
107
118
  export interface SymbolItem {
108
119
  code: string
@@ -126,6 +137,15 @@ const showPopup = ref(false)
126
137
  const searchQuery = ref('')
127
138
  const searchInputRef = ref<HTMLInputElement | null>(null)
128
139
  const chipWrapRef = ref<HTMLElement | null>(null)
140
+ const popupRef = ref<HTMLElement | null>(null)
141
+
142
+ const teleportTarget = useFullscreenTeleportTarget()
143
+
144
+ const { popupStyle, startPositionSync, stopPositionSync } = useTeleportedPopup(
145
+ chipWrapRef,
146
+ popupRef,
147
+ 8,
148
+ )
129
149
 
130
150
  const currentSymbol = computed<SymbolItem | undefined>(() =>
131
151
  props.symbols.find((s) => s.code === props.symbol),
@@ -155,6 +175,14 @@ function togglePopup() {
155
175
  }
156
176
  }
157
177
 
178
+ watch(showPopup, (val) => {
179
+ if (val) {
180
+ startPositionSync()
181
+ } else {
182
+ stopPositionSync()
183
+ }
184
+ })
185
+
158
186
  function clearSearch() {
159
187
  searchQuery.value = ''
160
188
  searchInputRef.value?.focus()
@@ -170,7 +198,9 @@ function selectSymbol(item: SymbolItem) {
170
198
  }
171
199
 
172
200
  function onDocumentClick(e: MouseEvent) {
173
- if (chipWrapRef.value && !chipWrapRef.value.contains(e.target as Node)) {
201
+ const chip = chipWrapRef.value
202
+ const popup = popupRef.value
203
+ if (chip && !chip.contains(e.target as Node) && !popup?.contains(e.target as Node)) {
174
204
  showPopup.value = false
175
205
  }
176
206
  }
@@ -238,15 +268,12 @@ watch(() => props.symbol, () => {
238
268
  }
239
269
 
240
270
  .symbol-popover {
241
- position: absolute;
242
- top: calc(100% + 8px);
243
- left: 0;
244
- z-index: 20;
271
+ z-index: 110;
245
272
  width: min(320px, calc(100vw - 24px));
246
273
  padding: 14px;
247
274
  border: 1px solid var(--klc-color-border-button);
248
275
  border-radius: 3px;
249
- background: var(--klc-color-tag-bg-white);
276
+ background: var(--klc-color-background);
250
277
  color: var(--klc-color-foreground);
251
278
 
252
279
  box-sizing: border-box;
@@ -1,5 +1,12 @@
1
1
  <template>
2
- <div class="top-toolbar">
2
+ <div
3
+ ref="toolbarRef"
4
+ class="top-toolbar"
5
+ @mousedown="onMouseDown"
6
+ @mousemove="onMouseMove"
7
+ @mouseup="onMouseUp"
8
+ @mouseleave="onMouseUp"
9
+ >
3
10
  <SymbolSelector
4
11
  v-if="displaySymbol"
5
12
  :symbol="displaySymbol"
@@ -38,7 +45,7 @@
38
45
  </template>
39
46
 
40
47
  <script setup lang="ts">
41
- import { computed } from 'vue'
48
+ import { computed, ref } from 'vue'
42
49
  import KLineLevelDropdown, { type KLineLevel } from './KLineLevelDropdown.vue'
43
50
  import KLineAdjustmentDropdown, { type KLineAdjustment } from './KLineAdjustmentDropdown.vue'
44
51
  import SymbolSelector from './SymbolSelector.vue'
@@ -47,6 +54,41 @@ import type { SymbolItem } from './SymbolSelector.vue'
47
54
 
48
55
  export type { SymbolItem }
49
56
 
57
+ const toolbarRef = ref<HTMLElement | null>(null)
58
+
59
+ let isDown = false
60
+ let startX = 0
61
+ let scrollLeft = 0
62
+
63
+ function onMouseDown(e: MouseEvent) {
64
+ const el = toolbarRef.value
65
+ if (!el) return
66
+ isDown = true
67
+ startX = e.pageX - el.getBoundingClientRect().left
68
+ scrollLeft = el.scrollLeft
69
+ el.style.cursor = 'grabbing'
70
+ el.style.userSelect = 'none'
71
+ }
72
+
73
+ function onMouseMove(e: MouseEvent) {
74
+ if (!isDown) return
75
+ const el = toolbarRef.value
76
+ if (!el) return
77
+ e.preventDefault()
78
+ const x = e.pageX - el.getBoundingClientRect().left
79
+ const walk = x - startX
80
+ el.scrollLeft = scrollLeft - walk
81
+ }
82
+
83
+ function onMouseUp() {
84
+ if (!isDown) return
85
+ isDown = false
86
+ const el = toolbarRef.value
87
+ if (!el) return
88
+ el.style.cursor = ''
89
+ el.style.userSelect = ''
90
+ }
91
+
50
92
  const props = defineProps<{
51
93
  symbol?: string
52
94
  kLineLevel?: string
@@ -115,6 +157,14 @@ function onSymbolSelectorChange(item: SymbolItem) {
115
157
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.06);
116
158
  box-sizing: border-box;
117
159
  user-select: none;
160
+ overflow-x: auto;
161
+ overflow-y: hidden;
162
+ scrollbar-width: none;
163
+ -ms-overflow-style: none;
164
+ }
165
+
166
+ .top-toolbar::-webkit-scrollbar {
167
+ display: none;
118
168
  }
119
169
 
120
170
  .indicator-button {
@@ -168,5 +218,8 @@ function onSymbolSelectorChange(item: SymbolItem) {
168
218
  .indicator-button__text {
169
219
  display: none;
170
220
  }
221
+ .indicator-button {
222
+ height: 26px;
223
+ }
171
224
  }
172
225
  </style>
@@ -0,0 +1,70 @@
1
+ <template>
2
+ <div class="canvas-toolbar" @pointerdown.stop @pointermove.stop @pointerup.stop>
3
+ <slot />
4
+ </div>
5
+ </template>
6
+
7
+ <style scoped>
8
+ .canvas-toolbar {
9
+ display: flex;
10
+ align-items: center;
11
+ gap: 4px;
12
+ padding: 4px;
13
+ background: color-mix(in srgb, var(--klc-color-background) 92%, transparent);
14
+ backdrop-filter: blur(12px);
15
+ -webkit-backdrop-filter: blur(12px);
16
+ border: 1px solid var(--klc-color-border-button);
17
+ border-radius: 8px;
18
+ box-shadow:
19
+ 0 2px 8px rgba(0, 0, 0, 0.08),
20
+ 0 1px 2px rgba(0, 0, 0, 0.04);
21
+ user-select: none;
22
+ pointer-events: auto;
23
+ overflow-x: auto;
24
+ overflow-y: hidden;
25
+ scrollbar-width: none;
26
+ -ms-overflow-style: none;
27
+ }
28
+
29
+ .canvas-toolbar::-webkit-scrollbar {
30
+ display: none;
31
+ }
32
+
33
+ .canvas-toolbar :deep(.toolbar-btn) {
34
+ display: inline-flex;
35
+ align-items: center;
36
+ justify-content: center;
37
+ height: 26px;
38
+ padding: 0 10px;
39
+ border: none;
40
+ border-radius: 4px;
41
+ background: transparent;
42
+ color: var(--klc-color-axis-text);
43
+ font-size: 12px;
44
+ cursor: pointer;
45
+ white-space: nowrap;
46
+ transition:
47
+ background 0.15s ease,
48
+ color 0.15s ease;
49
+ }
50
+
51
+ .canvas-toolbar :deep(.toolbar-btn:hover) {
52
+ background: var(--klc-color-grid-minor);
53
+ color: var(--klc-color-foreground);
54
+ }
55
+
56
+ .canvas-toolbar :deep(.toolbar-btn--delete) {
57
+ width: 26px;
58
+ padding: 0;
59
+ }
60
+
61
+ .canvas-toolbar :deep(.toolbar-btn--delete:hover) {
62
+ color: #dc2626;
63
+ background: color-mix(in srgb, #dc2626 10%, transparent);
64
+ }
65
+
66
+ .canvas-toolbar :deep(.delete-icon) {
67
+ width: 14px;
68
+ height: 14px;
69
+ }
70
+ </style>
@@ -0,0 +1,32 @@
1
+ <template>
2
+ <div
3
+ class="canvas-toolbar-stack"
4
+ @pointerdown.stop
5
+ @pointermove.stop
6
+ @pointerup.stop
7
+ >
8
+ <slot />
9
+ </div>
10
+ </template>
11
+
12
+ <style scoped>
13
+ .canvas-toolbar-stack {
14
+ position: absolute;
15
+ left: 50%;
16
+ top: 10px;
17
+ transform: translateX(-50%);
18
+ display: flex;
19
+ flex-direction: column;
20
+ align-items: stretch;
21
+ gap: 8px;
22
+ z-index: 100;
23
+ pointer-events: none;
24
+ max-width: calc(100% - 20px);
25
+ }
26
+
27
+ @media (max-width: 768px) {
28
+ .canvas-toolbar-stack {
29
+ max-width: 90%;
30
+ }
31
+ }
32
+ </style>
@@ -0,0 +1,86 @@
1
+ /**
2
+ * Manages chart theme state (light/dark), computed CSS vars for theming,
3
+ * tooltip up/down colors, and auto theme detection via prefers-color-scheme.
4
+ * Handles settings persistence through ChartController.updateSettingsFacade.
5
+ */
6
+ import { ref, computed, onUnmounted } from 'vue'
7
+ import type { Ref } from 'vue'
8
+ import {
9
+ resolveThemeColors,
10
+ themeToCssVars,
11
+ lightTheme,
12
+ darkTheme,
13
+ type ColorPresetSettings,
14
+ } from '@363045841yyt/klinechart-core'
15
+ import type { ChartSettings } from '@363045841yyt/klinechart-core/config'
16
+ import type { ChartController } from '@363045841yyt/klinechart-core/controllers'
17
+
18
+ export function useChartTheme(ctrl: Ref<ChartController | null>) {
19
+ const chartTheme = ref<'light' | 'dark'>('light')
20
+ const chartSettings = ref<ChartSettings>({})
21
+
22
+ const tooltipColors = computed(() => {
23
+ const isAsiaMarket = chartSettings.value.isAsiaMarket ?? false
24
+ const colors = resolveThemeColors(chartTheme.value, isAsiaMarket as boolean | undefined)
25
+ return {
26
+ upColor: colors.candleUpBody,
27
+ downColor: colors.candleDownBody,
28
+ }
29
+ })
30
+
31
+ const themeCssVars = computed(() => {
32
+ const theme = chartTheme.value === 'dark' ? darkTheme : lightTheme
33
+ const overrides = (chartSettings.value.colorPresetSettings as ColorPresetSettings | undefined)?.[
34
+ chartTheme.value
35
+ ]
36
+ if (overrides && Object.keys(overrides).length > 0) {
37
+ return themeToCssVars({ ...theme, colors: { ...theme.colors, ...overrides } })
38
+ }
39
+ return themeToCssVars(theme)
40
+ })
41
+
42
+ let autoThemeMediaQuery: MediaQueryList | null = null
43
+
44
+ function onSystemThemeChange(e: MediaQueryListEvent) {
45
+ ctrl.value?.setTheme(e.matches ? 'dark' : 'light')
46
+ }
47
+
48
+ function applyThemeFromSettings(themeSetting: string | undefined) {
49
+ const chartCtrl = ctrl.value
50
+ if (!chartCtrl || !themeSetting) return
51
+
52
+ if (themeSetting === 'auto') {
53
+ const mq = window.matchMedia('(prefers-color-scheme: dark)')
54
+ chartCtrl.setTheme(mq.matches ? 'dark' : 'light')
55
+ if (autoThemeMediaQuery !== mq) {
56
+ autoThemeMediaQuery?.removeEventListener('change', onSystemThemeChange)
57
+ autoThemeMediaQuery = mq
58
+ mq.addEventListener('change', onSystemThemeChange)
59
+ }
60
+ } else {
61
+ autoThemeMediaQuery?.removeEventListener('change', onSystemThemeChange)
62
+ autoThemeMediaQuery = null
63
+ chartCtrl.setTheme(themeSetting as 'light' | 'dark')
64
+ }
65
+ }
66
+
67
+ function handleSettingsChange(settings: ChartSettings) {
68
+ chartSettings.value = settings
69
+ applyThemeFromSettings(settings.theme as string)
70
+ ctrl.value?.updateSettingsFacade(settings)
71
+ }
72
+
73
+ onUnmounted(() => {
74
+ autoThemeMediaQuery?.removeEventListener('change', onSystemThemeChange)
75
+ autoThemeMediaQuery = null
76
+ })
77
+
78
+ return {
79
+ chartTheme,
80
+ chartSettings,
81
+ tooltipColors,
82
+ themeCssVars,
83
+ handleSettingsChange,
84
+ applyThemeFromSettings,
85
+ }
86
+ }
@@ -0,0 +1,67 @@
1
+ /**
2
+ * Manages drawing interaction state (selected drawing, drawings list),
3
+ * tool activation, style updates, and deletion.
4
+ * Provides setupDrawing() to initialize DrawingInteractionController
5
+ * with lifecycle callbacks that sync back to Vue refs.
6
+ */
7
+ import { ref, computed, shallowRef, type Ref } from 'vue'
8
+ import {
9
+ DrawingInteractionController,
10
+ type ChartController,
11
+ type DrawingToolId,
12
+ } from '@363045841yyt/klinechart-core/controllers'
13
+ import type { DrawingObject, DrawingStyle } from '@363045841yyt/klinechart-core/plugin'
14
+
15
+ export function useDrawingManager(ctrl: Ref<ChartController | null>) {
16
+ const drawingController = shallowRef<DrawingInteractionController | null>(null)
17
+ const selectedDrawingId = ref<string | null>(null)
18
+ const drawings = ref<DrawingObject[]>([])
19
+ const selectedDrawing = computed(() => {
20
+ const id = selectedDrawingId.value
21
+ if (!id) return null
22
+ return drawings.value.find((d) => d.id === id) ?? null
23
+ })
24
+
25
+ function handleSelectTool(toolId: string) {
26
+ drawingController.value?.setTool(toolId as DrawingToolId)
27
+ }
28
+
29
+ function onUpdateDrawingStyle(style: Partial<DrawingStyle>) {
30
+ const d = selectedDrawing.value
31
+ if (!d || !drawingController.value) return
32
+ drawingController.value.updateDrawingStyle(d.id, style)
33
+ drawings.value = drawingController.value.getDrawings()
34
+ }
35
+
36
+ function onDeleteDrawing() {
37
+ const d = selectedDrawing.value
38
+ if (!d || !drawingController.value) return
39
+ drawingController.value.removeDrawing(d.id)
40
+ drawings.value = drawingController.value.getDrawings()
41
+ }
42
+
43
+ function setupDrawing(chartCtrl: ChartController): void {
44
+ drawingController.value = new DrawingInteractionController(chartCtrl)
45
+ drawingController.value.setCallbacks({
46
+ onDrawingCreated: (drawing) => {
47
+ drawings.value = [...drawings.value, drawing]
48
+ selectedDrawingId.value = drawing.id
49
+ },
50
+ onToolChange: () => {},
51
+ onDrawingSelected: (drawing) => {
52
+ selectedDrawingId.value = drawing?.id ?? null
53
+ },
54
+ })
55
+ }
56
+
57
+ return {
58
+ drawingController,
59
+ selectedDrawingId,
60
+ selectedDrawing,
61
+ drawings,
62
+ handleSelectTool,
63
+ onUpdateDrawingStyle,
64
+ onDeleteDrawing,
65
+ setupDrawing,
66
+ }
67
+ }