@363045841yyt/klinechart 0.8.5 → 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 (43) 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.map +1 -1
  5. package/dist/components/ChartSettingsDialog.vue.d.ts.map +1 -1
  6. package/dist/components/ColorPresetPanel.vue.d.ts +4 -1
  7. package/dist/components/ColorPresetPanel.vue.d.ts.map +1 -1
  8. package/dist/components/DrawingStyleToolbar.vue.d.ts.map +1 -1
  9. package/dist/components/ExportProgressDialog.vue.d.ts.map +1 -1
  10. package/dist/components/IndicatorParams.vue.d.ts.map +1 -1
  11. package/dist/components/IndicatorSelector.vue.d.ts.map +1 -1
  12. package/dist/components/KLineChart.vue.d.ts.map +1 -1
  13. package/dist/components/RangeSelectionExport.vue.d.ts +23 -0
  14. package/dist/components/RangeSelectionExport.vue.d.ts.map +1 -0
  15. package/dist/components/common/CanvasToolbar.vue.d.ts +14 -0
  16. package/dist/components/common/CanvasToolbar.vue.d.ts.map +1 -0
  17. package/dist/components/common/CanvasToolbarStack.vue.d.ts +14 -0
  18. package/dist/components/common/CanvasToolbarStack.vue.d.ts.map +1 -0
  19. package/dist/composables/chart/useRangeSelection.d.ts +1 -0
  20. package/dist/composables/chart/useRangeSelection.d.ts.map +1 -1
  21. package/dist/composables/useTeleportedPopup.d.ts.map +1 -1
  22. package/dist/index.cjs +6 -6
  23. package/dist/index.css +1 -1
  24. package/dist/index.js +1293 -1215
  25. package/dist/web-component.d.ts.map +1 -1
  26. package/package.json +1 -1
  27. package/src/components/BaseModal.vue +292 -0
  28. package/src/components/BatchStockDialog.vue +15 -180
  29. package/src/components/ChartSettingsDialog.vue +248 -405
  30. package/src/components/ColorPresetPanel.vue +58 -106
  31. package/src/components/CompareSymbolSelector.vue +2 -2
  32. package/src/components/DrawingStyleToolbar.vue +33 -72
  33. package/src/components/ExportProgressDialog.vue +25 -133
  34. package/src/components/IndicatorParams.vue +194 -321
  35. package/src/components/IndicatorSelector.vue +188 -405
  36. package/src/components/KLineChart.vue +34 -138
  37. package/src/components/LeftToolbar.vue +1 -1
  38. package/src/components/RangeSelectionExport.vue +117 -0
  39. package/src/components/SymbolSelector.vue +2 -2
  40. package/src/components/common/CanvasToolbar.vue +70 -0
  41. package/src/components/common/CanvasToolbarStack.vue +32 -0
  42. package/src/composables/chart/useRangeSelection.ts +7 -0
  43. package/src/composables/useTeleportedPopup.ts +15 -2
@@ -60,59 +60,25 @@
60
60
  <div class="canvas-layer" ref="canvasLayerRef">
61
61
  <canvas class="x-axis-canvas" ref="xAxisCanvasRef"></canvas>
62
62
 
63
- <DrawingStyleToolbar
64
- v-if="selectedDrawing"
65
- :drawing="selectedDrawing"
66
- @update-style="onUpdateDrawingStyle"
67
- @delete="onDeleteDrawing"
68
- />
69
- <div
70
- v-if="rangeSelectionReady"
71
- class="range-selection-export"
72
- @pointerdown.stop
73
- @pointermove.stop
74
- @pointerup.stop
75
- >
76
- <input
77
- class="range-selection-export__label"
78
- v-model="customStartDate"
79
- :placeholder="rangeSelectionStartLabel"
63
+ <CanvasToolbarStack>
64
+ <RangeSelectionExport
65
+ v-if="rangeSelectionReady"
66
+ v-model:start-date="customStartDate"
67
+ v-model:end-date="customEndDate"
68
+ :start-label="rangeSelectionStartLabel"
69
+ :end-label="rangeSelectionEndLabel"
70
+ :count="rangeSelectionCount"
71
+ @export="exportRangeToCsv"
72
+ @clear="clearRangeSelection"
73
+ @batch-setting="showBatchStockDialog = true"
80
74
  />
81
- <span class="range-selection-export__sep">~</span>
82
- <input
83
- class="range-selection-export__label"
84
- v-model="customEndDate"
85
- :placeholder="rangeSelectionEndLabel"
75
+ <DrawingStyleToolbar
76
+ v-if="selectedDrawing"
77
+ :drawing="selectedDrawing"
78
+ @update-style="onUpdateDrawingStyle"
79
+ @delete="onDeleteDrawing"
86
80
  />
87
- <button
88
- type="button"
89
- class="toolbar-btn"
90
- title="批量设置"
91
- @click.stop="showBatchStockDialog = true"
92
- >
93
- 批量设置
94
- </button>
95
- <button
96
- type="button"
97
- class="toolbar-btn"
98
- title="导出"
99
- @click.stop="exportRangeToCsv"
100
- >
101
- 导出
102
- </button>
103
- <button
104
- type="button"
105
- class="toolbar-btn delete-btn"
106
- title="删除选区"
107
- @click.stop="clearRangeSelection"
108
- >
109
- <svg class="delete-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
110
- <path d="M3 6h18" />
111
- <path d="M19 6v14c0 1-1 2-2 2H7c-1 0-2-1-2-2V6" />
112
- <path d="M8 6V4c0-1 1-2 2-2h4c1 0 2 1 2 2v2" />
113
- </svg>
114
- </button>
115
- </div>
81
+ </CanvasToolbarStack>
116
82
  </div>
117
83
  <div
118
84
  v-if="rangeSelectionOverlayStyle"
@@ -140,7 +106,7 @@
140
106
  </div>
141
107
  <Teleport v-if="tooltipLayerRef" :to="tooltipLayerRef">
142
108
  <div
143
- v-if="hovered"
109
+ v-if="hovered && !isMobile"
144
110
  class="tooltip-anchor kline-tooltip-anchor"
145
111
  :class="{ 'use-anchor': useAnchorPositioning }"
146
112
  :style="klineTooltipAnchorStyle"
@@ -152,7 +118,7 @@
152
118
  :style="markerTooltipAnchorStyle"
153
119
  ></div>
154
120
  <KLineTooltip
155
- v-if="hovered"
121
+ v-if="hovered && !isMobile"
156
122
  :k="hovered"
157
123
  :index="hoveredIndex"
158
124
  :data="chartData"
@@ -213,6 +179,8 @@ import KLineTooltip from './KLineTooltip.vue'
213
179
  import MarkerTooltip from './MarkerTooltip.vue'
214
180
  import IndicatorSelector from './IndicatorSelector.vue'
215
181
  import DrawingStyleToolbar from './DrawingStyleToolbar.vue'
182
+ import RangeSelectionExport from './RangeSelectionExport.vue'
183
+ import CanvasToolbarStack from './common/CanvasToolbarStack.vue'
216
184
  import { provideFullscreenTeleportTarget } from '../composables/useFullscreenTeleportTarget'
217
185
  import {
218
186
  createChartController,
@@ -449,6 +417,7 @@ const {
449
417
  isRangeSelectActive,
450
418
  rangeSelectionReady,
451
419
  rangeSelectionBounds,
420
+ rangeSelectionCount,
452
421
  rangeSelectionStartLabel,
453
422
  rangeSelectionEndLabel,
454
423
  rangeSelectionOverlayStyle,
@@ -567,6 +536,7 @@ const isResizingPane = computed(() => interactionState.value.isResizingPaneBound
567
536
  const isHoveringPaneSeparator = computed(() => interactionState.value.isHoveringPaneBoundary)
568
537
  const hoveredPaneBoundaryId = computed(() => interactionState.value.hoveredPaneBoundaryId)
569
538
  const isHoveringRightAxis = computed(() => interactionState.value.isHoveringRightAxis)
539
+ const isMobile = window.matchMedia('(pointer: coarse)').matches
570
540
  const hoveredIdx = computed(() => interactionState.value.hoveredIndex)
571
541
  const crosshairIdx = computed(() => interactionState.value.crosshairIndex)
572
542
 
@@ -1146,93 +1116,12 @@ watch(
1146
1116
  z-index: 101;
1147
1117
  }
1148
1118
 
1149
- .range-selection-handle--left { left: -4px; }
1150
-
1151
- .range-selection-handle--right { right: -4px; }
1152
-
1153
- .range-selection-export {
1154
- position: absolute;
1155
- left: 50%;
1156
- top: 8px;
1157
- transform: translateX(-50%);
1158
- display: flex;
1159
- align-items: center;
1160
- gap: 6px;
1161
- padding: 4px 8px;
1162
- height: 32px;
1163
- background: color-mix(in srgb, var(--klc-color-tag-bg-white) 88%, transparent);
1164
- backdrop-filter: blur(8px);
1165
- -webkit-backdrop-filter: blur(8px);
1166
- border: 1px solid var(--klc-color-border-button);
1167
- border-radius: 6px;
1168
- box-shadow: 0 1px 3px rgba(0, 0, 0, 0.06);
1169
- z-index: 100;
1170
- user-select: none;
1171
- pointer-events: auto;
1172
- }
1173
-
1174
- .range-selection-export .toolbar-btn {
1175
- display: inline-flex;
1176
- align-items: center;
1177
- justify-content: center;
1178
- height: 24px;
1179
- padding: 0 8px;
1180
- border: 1px solid var(--klc-color-border-button);
1181
- border-radius: 4px;
1182
- background: transparent;
1183
- color: var(--klc-color-axis-text);
1184
- font-size: 12px;
1185
- cursor: pointer;
1186
- transition:
1187
- border-color 0.15s ease,
1188
- background 0.15s ease,
1189
- color 0.15s ease;
1190
- white-space: nowrap;
1119
+ .range-selection-handle--left {
1120
+ left: -4px;
1191
1121
  }
1192
1122
 
1193
- .range-selection-export .toolbar-btn:hover {
1194
- border-color: var(--klc-color-axis-line);
1195
- background: var(--klc-color-grid-minor);
1196
- color: var(--klc-color-foreground);
1197
- }
1198
-
1199
- .range-selection-export .toolbar-btn.delete-btn {
1200
- padding: 0;
1201
- width: 24px;
1202
- border-color: transparent;
1203
- }
1204
-
1205
- .range-selection-export .toolbar-btn.delete-btn:hover {
1206
- color: #dc2626;
1207
- border-color: #fca5a5;
1208
- background: #fef2f2;
1209
- }
1210
-
1211
- .range-selection-export .delete-icon {
1212
- width: 14px;
1213
- height: 14px;
1214
- }
1215
-
1216
- .range-selection-export__label {
1217
- color: var(--klc-color-axis-text);
1218
- font-size: 11px;
1219
- white-space: nowrap;
1220
- border: 1px solid var(--klc-color-border-button);
1221
- background: none;
1222
- outline: none;
1223
- padding: 1px 4px;
1224
- width: 80px;
1225
- height: 24px;
1226
- box-sizing: border-box;
1227
- font-family: inherit;
1228
- border-radius: 3px;
1229
- text-align: center;
1230
- }
1231
-
1232
- .range-selection-export__sep {
1233
- color: var(--klc-color-axis-text);
1234
- font-size: 11px;
1235
- user-select: none;
1123
+ .range-selection-handle--right {
1124
+ right: -4px;
1236
1125
  }
1237
1126
 
1238
1127
  .canvas-layer {
@@ -1274,6 +1163,7 @@ watch(
1274
1163
  gap: 4px;
1275
1164
  }
1276
1165
  }
1166
+
1277
1167
  </style>
1278
1168
 
1279
1169
  <style>
@@ -1302,3 +1192,9 @@ watch(
1302
1192
  z-index: 15;
1303
1193
  }
1304
1194
  </style>
1195
+
1196
+ <style>
1197
+ * {
1198
+ -webkit-tap-highlight-color: transparent;
1199
+ }
1200
+ </style>
@@ -418,7 +418,7 @@ onUnmounted(() => {
418
418
  gap: 4px;
419
419
  padding: 0 5px;
420
420
  height: 40px;
421
- background: var(--klc-color-tag-bg-white);
421
+ background: var(--klc-color-background);
422
422
  backdrop-filter: blur(8px);
423
423
  -webkit-backdrop-filter: blur(8px);
424
424
  border: 1px solid var(--klc-color-border-chart);
@@ -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>
@@ -268,12 +268,12 @@ watch(() => props.symbol, () => {
268
268
  }
269
269
 
270
270
  .symbol-popover {
271
- z-index: 20;
271
+ z-index: 110;
272
272
  width: min(320px, calc(100vw - 24px));
273
273
  padding: 14px;
274
274
  border: 1px solid var(--klc-color-border-button);
275
275
  border-radius: 3px;
276
- background: var(--klc-color-tag-bg-white);
276
+ background: var(--klc-color-background);
277
277
  color: var(--klc-color-foreground);
278
278
 
279
279
  box-sizing: border-box;
@@ -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>
@@ -108,6 +108,12 @@ export function useRangeSelection(options: {
108
108
  return fmtDate(data[bounds.end])
109
109
  })
110
110
 
111
+ const rangeSelectionCount = computed(() => {
112
+ const bounds = rangeSelectionBounds.value
113
+ if (!bounds) return 0
114
+ return bounds.end - bounds.start + 1
115
+ })
116
+
111
117
  const rangeSelectionOverlayStyle = computed(() => {
112
118
  const bounds = rangeSelectionBounds.value
113
119
  if (!bounds) return null
@@ -399,6 +405,7 @@ export function useRangeSelection(options: {
399
405
  isRangeSelectActive,
400
406
  rangeSelectionReady,
401
407
  rangeSelectionBounds,
408
+ rangeSelectionCount,
402
409
  rangeSelectionStartLabel,
403
410
  rangeSelectionEndLabel,
404
411
  rangeSelectionOverlayStyle,
@@ -1,4 +1,4 @@
1
- import { ref, type Ref } from 'vue'
1
+ import { ref, nextTick, type Ref } from 'vue'
2
2
 
3
3
  export function useTeleportedPopup(
4
4
  triggerRef: Ref<HTMLElement | null>,
@@ -11,15 +11,28 @@ export function useTeleportedPopup(
11
11
  const trigger = triggerRef.value
12
12
  if (!trigger) return
13
13
  const rect = trigger.getBoundingClientRect()
14
+ const popup = popupRef.value
15
+
16
+ let left = rect.left
17
+ if (popup) {
18
+ const popupWidth = popup.offsetWidth
19
+ const viewportWidth = window.innerWidth
20
+ const margin = 8
21
+ if (left + popupWidth > viewportWidth - margin) {
22
+ left = Math.max(margin, viewportWidth - popupWidth - margin)
23
+ }
24
+ }
25
+
14
26
  popupStyle.value = {
15
27
  position: 'fixed',
16
28
  top: `${rect.bottom + gap}px`,
17
- left: `${rect.left}px`,
29
+ left: `${left}px`,
18
30
  }
19
31
  }
20
32
 
21
33
  function startPositionSync() {
22
34
  updatePosition()
35
+ nextTick(() => updatePosition())
23
36
  document.addEventListener('scroll', updatePosition, { capture: true, passive: true })
24
37
  window.addEventListener('resize', updatePosition, { passive: true })
25
38
  }