@363045841yyt/klinechart 0.7.5-alpha.2 → 0.7.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.
package/dist/version.d.ts CHANGED
@@ -1,3 +1,3 @@
1
1
  export declare const VERSION: string;
2
- export { VERSION as CORE_VERSION } from '../../core/src';
2
+ export { VERSION as CORE_VERSION } from '@363045841yyt/klinechart-core';
3
3
  //# sourceMappingURL=version.d.ts.map
@@ -0,0 +1,18 @@
1
+ import { SemanticChartConfig, DataFetcher } from '@363045841yyt/klinechart-core/semantic';
2
+ declare const KLineChartElement: import('vue').VueElementConstructor<{
3
+ semanticConfig: SemanticChartConfig;
4
+ dataFetcher: DataFetcher;
5
+ yPaddingPx?: number;
6
+ minKWidth?: number;
7
+ maxKWidth?: number;
8
+ rightAxisWidth?: number;
9
+ bottomAxisHeight?: number;
10
+ priceLabelWidth?: number;
11
+ zoomLevels?: number;
12
+ initialZoomLevel?: number;
13
+ isFullscreen?: boolean;
14
+ }>;
15
+ export { KLineChartElement };
16
+ export default KLineChartElement;
17
+ export type { SemanticChartConfig, DataFetcher };
18
+ //# sourceMappingURL=web-component.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"web-component.d.ts","sourceRoot":"","sources":["../src/web-component.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,mBAAmB,EAAE,WAAW,EAAE,MAAM,wCAAwC,CAAA;AAE9F,QAAA,MAAM,iBAAiB;;;;;;;;;;;;EAErB,CAAA;AAIF,OAAO,EAAE,iBAAiB,EAAE,CAAA;AAC5B,eAAe,iBAAiB,CAAA;AAEhC,YAAY,EAAE,mBAAmB,EAAE,WAAW,EAAE,CAAA"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@363045841yyt/klinechart",
3
- "version": "0.7.5-alpha.2",
3
+ "version": "0.7.6",
4
4
  "description": "Vue 3 bindings for @363045841yyt/klinechart-core. Idiomatic composables, SFC components.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -28,6 +28,11 @@
28
28
  "default": "./dist/index.cjs"
29
29
  }
30
30
  },
31
+ "./web-component": {
32
+ "import": {
33
+ "default": "./dist/kline-chart.js"
34
+ }
35
+ },
31
36
  "./style.css": "./dist/klinechart.css"
32
37
  },
33
38
  "files": [
@@ -39,7 +44,8 @@
39
44
  },
40
45
  "scripts": {
41
46
  "dev": "vite --config preview/vite.config.ts",
42
- "build": "vite build && node -e \"require('fs').copyFileSync('dist/index.d.ts','dist/index.d.cts')\"",
47
+ "build": "vite build",
48
+ "build:wc": "cross-env BUILD_TARGET=web-component vite build",
43
49
  "postbuild": "node -e \"require('fs').copyFileSync('dist/index.d.ts','dist/index.d.cts')\"",
44
50
  "test": "vitest run",
45
51
  "test:watch": "vitest",
@@ -69,6 +75,7 @@
69
75
  "@size-limit/preset-small-lib": "^12.1.0",
70
76
  "@vitejs/plugin-vue": "^6.0.0",
71
77
  "@vue/test-utils": "^2.4.10",
78
+ "cross-env": "^7.0.3",
72
79
  "jsdom": "^29.1.1",
73
80
  "publint": "^0.3.0",
74
81
  "size-limit": "^12.1.0",
@@ -76,6 +83,7 @@
76
83
  "unplugin-icons": "^23.0.1",
77
84
  "vite": "^8.0.14",
78
85
  "vite-plugin-babel": "^1.3.2",
86
+ "vite-plugin-css-injected-by-js": "^5.0.1",
79
87
  "vite-plugin-dts": "^5.0.1",
80
88
  "vitest": "^4.1.5",
81
89
  "vue": "^3.5.0"
@@ -20,13 +20,14 @@ import type {
20
20
  IndicatorDefinition,
21
21
  IndicatorSelectorController,
22
22
  KLineData,
23
+ PaneSpec,
23
24
  ToolbarController,
24
25
  ToolDefinition,
25
26
  ToolId,
26
27
  } from '@363045841yyt/klinechart-core'
27
28
 
28
29
  // ---------------------------------------------------------------------------
29
- // Inline mini-signal �?Object.is-equality, sync notify. Drop-in compatible
30
+ // Inline mini-signal �?Object.is-equality, sync notify. Drop-in compatible
30
31
  // with `@363045841yyt/klinechart-core/reactivity` for shape-only test purposes.
31
32
  // ---------------------------------------------------------------------------
32
33
 
@@ -132,6 +133,7 @@ export function createMockChartController(
132
133
  })
133
134
  const data = createSignal<ReadonlyArray<KLineData>>(opts.data ?? [])
134
135
  const theme = createSignal<'light' | 'dark'>(opts.theme ?? 'light')
136
+ const paneLayout = createSignal<ReadonlyArray<PaneSpec>>([])
135
137
  const indicatorSelector = createMockIndicatorSelector()
136
138
  const toolbar = createMockToolbar()
137
139
  const drawing = createMockDrawing()
@@ -140,12 +142,15 @@ export function createMockChartController(
140
142
  viewport,
141
143
  data,
142
144
  theme,
145
+ paneLayout,
143
146
  indicatorSelector,
144
147
  toolbar,
145
148
  drawing,
146
149
  setData: (next) => data.set(next),
147
150
  appendData: (next) => data.set([...data.peek(), ...next]),
148
151
  updateData: (next) => data.set(next),
152
+ getData: () => data.peek(),
153
+ getZoomLevelCount: () => 10,
149
154
  setTheme: (next) => theme.set(next),
150
155
  zoomToLevel: (level) =>
151
156
  viewport.set({ ...viewport.peek(), zoomLevel: level }),
@@ -167,12 +172,17 @@ export function createMockChartController(
167
172
  removeIndicator: () => false,
168
173
  updateIndicatorParams: () => false,
169
174
  updateRendererConfig: () => {},
175
+ setTooltipSize: () => {},
176
+ setTooltipAnchorPositioning: () => {},
177
+ getIndicatorTitle: () => undefined,
170
178
  setDrawingTool: (tool) => drawing.setActiveTool(tool),
171
179
  clearDrawings: () => drawing.clearAll(),
172
180
  removeDrawing: () => {},
173
181
  resizeSubPane: () => false,
174
182
  createSubPane: () => false,
175
183
  clearSubPanes: () => {},
184
+ replaceSubPaneIndicator: () => false,
185
+ updatePaneLayout: (_panes: PaneSpec[]) => {},
176
186
  updateCustomMarkers: () => {},
177
187
  clearCustomMarkers: () => {},
178
188
  updateSettingsFacade: () => {},
@@ -50,20 +50,7 @@
50
50
 
51
51
  <script setup lang="ts">
52
52
  import { onMounted, onUnmounted } from 'vue'
53
-
54
- export interface DrawingStyle {
55
- stroke?: string
56
- strokeWidth?: number
57
- strokeStyle?: 'solid' | 'dashed' | 'dotted'
58
- fill?: string
59
- }
60
-
61
- export interface DrawingObject {
62
- id: string
63
- type: string
64
- points: { x: number; y: number }[]
65
- style: DrawingStyle
66
- }
53
+ import type { DrawingObject, DrawingStyle } from '@363045841yyt/klinechart-core/plugin'
67
54
 
68
55
  const props = defineProps<{
69
56
  drawing: DrawingObject
@@ -117,6 +117,7 @@
117
117
 
118
118
  <script setup lang="ts">
119
119
  import { ref, watch, computed } from 'vue'
120
+ import { useFullscreenTeleportTarget } from '../composables/useFullscreenTeleportTarget'
120
121
 
121
122
  export interface ParamConfig {
122
123
  key: string
@@ -146,7 +147,7 @@ const emit = defineEmits<{
146
147
  const localValues = ref<Record<string, number>>({ ...props.values })
147
148
  const showDescription = ref(true)
148
149
 
149
- const teleportTarget = computed(() => 'body')
150
+ const teleportTarget = useFullscreenTeleportTarget()
150
151
 
151
152
  watch(
152
153
  () => props.values,
@@ -74,7 +74,7 @@
74
74
 
75
75
  <!-- 添加按钮 -->
76
76
  <div class="indicator-item">
77
- <button ref="addBtnRef" class="add-btn" @click="toggleAddMenu" title="添加指标">
77
+ <button ref="addBtnRef" class="add-btn" @click.stop="controller.toggleMenu()" title="添加指标">
78
78
  <svg viewBox="0 0 24 24" width="16" height="16" fill="currentColor">
79
79
  <path d="M19 13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z" />
80
80
  </svg>
@@ -86,14 +86,14 @@
86
86
  <!-- 添加指标弹窗 -->
87
87
  <Teleport :to="teleportTarget">
88
88
  <Transition name="overlay">
89
- <div v-if="showAddMenu" class="selector-overlay" @click="closeAddMenu">
89
+ <div v-if="menuOpen" class="selector-overlay" @click="controller.closeMenu()">
90
90
  <Transition name="modal">
91
- <div v-if="showAddMenu" class="selector-modal" @click.stop>
91
+ <div v-if="menuOpen" class="selector-modal" @click.stop>
92
92
  <!-- 弹窗头部 -->
93
93
  <div class="modal-header">
94
94
  <div class="header-title">
95
95
  <span class="title-text">添加指标</span>
96
- <span class="title-sub">{{ totalIndicatorsCount }} 个可用指标</span>
96
+ <span class="title-sub">{{ catalogLen }} 个可用指标</span>
97
97
  </div>
98
98
  <div class="header-actions">
99
99
  <button
@@ -117,7 +117,7 @@
117
117
  />
118
118
  </svg>
119
119
  </button>
120
- <button class="modal-close" @click="closeAddMenu" title="关闭">
120
+ <button class="modal-close" @click="controller.closeMenu()" title="关闭">
121
121
  <svg viewBox="0 0 24 24" width="14" height="14" fill="currentColor">
122
122
  <path
123
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"
@@ -143,21 +143,21 @@
143
143
  />
144
144
  </svg>
145
145
  <input
146
- v-model="searchQuery"
146
+ :value="searchQuery" @input="controller.setSearchQuery(($event.target as HTMLInputElement).value)"
147
147
  type="text"
148
148
  class="search-input"
149
149
  placeholder="搜索指标名称..."
150
150
  />
151
151
  </div>
152
152
  <!-- 主图指标区域 -->
153
- <div v-if="filteredMainIndicators.length > 0" class="indicator-section">
153
+ <div v-if="filteredMain.length > 0" class="indicator-section">
154
154
  <div class="section-header">
155
155
  <span class="section-title">主图指标</span>
156
- <span class="section-count">{{ filteredMainIndicators.length }}</span>
156
+ <span class="section-count">{{ filteredMain.length }}</span>
157
157
  </div>
158
158
  <div class="indicator-grid" :class="{ compact: isCompactView }">
159
159
  <button
160
- v-for="indicator in filteredMainIndicators"
160
+ v-for="indicator in filteredMain"
161
161
  :key="indicator.id"
162
162
  class="indicator-card"
163
163
  :class="{ active: isActive(indicator.id), compact: isCompactView }"
@@ -197,7 +197,7 @@
197
197
 
198
198
  <!-- 分隔线 -->
199
199
  <div
200
- v-if="filteredMainIndicators.length > 0 && filteredSubIndicators.length > 0"
200
+ v-if="filteredMain.length > 0 && filteredSub.length > 0"
201
201
  class="section-divider"
202
202
  ></div>
203
203
 
@@ -213,14 +213,14 @@
213
213
  </div>
214
214
 
215
215
  <!-- 副图指标区域 -->
216
- <div v-if="filteredSubIndicators.length > 0" class="indicator-section">
216
+ <div v-if="filteredSub.length > 0" class="indicator-section">
217
217
  <div class="section-header">
218
218
  <span class="section-title">副图指标</span>
219
- <span class="section-count">{{ filteredSubIndicators.length }}</span>
219
+ <span class="section-count">{{ filteredSub.length }}</span>
220
220
  </div>
221
221
  <div class="indicator-grid" :class="{ compact: isCompactView }">
222
222
  <button
223
- v-for="indicator in filteredSubIndicators"
223
+ v-for="indicator in filteredSub"
224
224
  :key="indicator.id"
225
225
  class="indicator-card"
226
226
  :class="{ active: isActive(indicator.id), compact: isCompactView }"
@@ -264,7 +264,7 @@
264
264
  <div class="footer-info">
265
265
  <span class="info-text">已激活 {{ activeCount }} 个指标</span>
266
266
  </div>
267
- <button class="btn btn-confirm" @click="closeAddMenu">确认</button>
267
+ <button class="btn btn-confirm" @click="controller.closeMenu()">确认</button>
268
268
  </div>
269
269
  </div>
270
270
  </Transition>
@@ -291,13 +291,15 @@
291
291
  import { ref, computed, onMounted, onUnmounted } from 'vue'
292
292
  import IndicatorParams from './IndicatorParams.vue'
293
293
  import { useFullscreenTeleportTarget } from '../composables/useFullscreenTeleportTarget'
294
+ import { coreSignalToVueRef } from '../index'
294
295
  import {
295
- mainIndicators,
296
- subIndicators,
296
+ createIndicatorSelectorController,
297
+ type IndicatorDefinition,
298
+ allIndicators,
297
299
  findIndicator,
298
300
  isSubIndicatorId,
299
- } from '@363045841yyt/klinechart-core/engine/renderers/Indicator/indicatorData'
300
- import type { Indicator } from '@363045841yyt/klinechart-core/engine/renderers/Indicator/indicatorData'
301
+ type Indicator,
302
+ } from '@363045841yyt/klinechart-core/controllers'
301
303
 
302
304
  const props = defineProps<{
303
305
  activeIndicators?: string[]
@@ -310,15 +312,51 @@ const emit = defineEmits<{
310
312
  reorderSubIndicators: [orderedIndicatorIds: string[]]
311
313
  }>()
312
314
 
315
+ // ── 将 Indicator[] 转换为 IndicatorDefinition[] ──
316
+ function toIndicatorDefinitions(source: typeof allIndicators): IndicatorDefinition[] {
317
+ return source.map((i) => ({
318
+ id: i.id,
319
+ label: i.label,
320
+ name: i.name,
321
+ description: i.description,
322
+ role: i.pane,
323
+ params: (i.params ?? []).map((p) => ({
324
+ key: p.key,
325
+ label: p.label,
326
+ type: p.type,
327
+ default: p.default ?? (p.type === 'number' ? 0 : ''),
328
+ min: p.min,
329
+ max: p.max,
330
+ step: p.step,
331
+ })),
332
+ }))
333
+ }
334
+
335
+ // ── Controller ──
336
+ const controller = createIndicatorSelectorController({
337
+ catalog: toIndicatorDefinitions(allIndicators),
338
+ })
339
+
340
+ // ── 从 Controller Signal 桥接的 Vue 响应式状态 ──
341
+ const menuOpen = coreSignalToVueRef(controller.menuOpen)
342
+ const searchQuery = coreSignalToVueRef(controller.searchQuery)
343
+ const filteredMain = coreSignalToVueRef(controller.filteredMain)
344
+ const filteredSub = coreSignalToVueRef(controller.filteredSub)
345
+
346
+ const hasSearchResults = computed(
347
+ () => filteredMain.value.length > 0 || filteredSub.value.length > 0,
348
+ )
349
+
350
+ const catalogLen = controller.catalog.peek().length
351
+
352
+ // ── 本地 UI 状态(非 Controller 管理的纯 UI 状态) ──
313
353
  const addBtnRef = ref<HTMLButtonElement | null>(null)
314
354
  const paramsVisible = ref(false)
315
355
  const currentIndicatorId = ref<string | null>(null)
316
356
  const hoveredIndicator = ref<string | null>(null)
317
- const showAddMenu = ref(false)
318
357
  const dragOverIndicatorId = ref<string | null>(null)
319
358
  const draggingIndicatorId = ref<string | null>(null)
320
359
  const isCompactView = ref(false)
321
- const searchQuery = ref('')
322
360
 
323
361
  // Teleport target for fullscreen modal visibility
324
362
  const teleportTarget = useFullscreenTeleportTarget()
@@ -346,39 +384,8 @@ const currentIndicator = computed(() => {
346
384
  return findIndicator(currentIndicatorId.value)
347
385
  })
348
386
 
349
- const totalIndicatorsCount = computed(() => mainIndicators.length + subIndicators.length)
350
-
351
387
  const activeCount = computed(() => props.activeIndicators?.length ?? 0)
352
388
 
353
- // 过滤后的主图指标
354
- const filteredMainIndicators = computed(() => {
355
- if (!searchQuery.value.trim()) return mainIndicators
356
- const query = searchQuery.value.toLowerCase().trim()
357
- return mainIndicators.filter(
358
- (i) =>
359
- i.label.toLowerCase().includes(query) ||
360
- i.name.toLowerCase().includes(query) ||
361
- i.id.toLowerCase().includes(query),
362
- )
363
- })
364
-
365
- // 过滤后的副图指标
366
- const filteredSubIndicators = computed(() => {
367
- if (!searchQuery.value.trim()) return subIndicators
368
- const query = searchQuery.value.toLowerCase().trim()
369
- return subIndicators.filter(
370
- (i) =>
371
- i.label.toLowerCase().includes(query) ||
372
- i.name.toLowerCase().includes(query) ||
373
- i.id.toLowerCase().includes(query),
374
- )
375
- })
376
-
377
- // 是否有搜索结果
378
- const hasSearchResults = computed(
379
- () => filteredMainIndicators.value.length > 0 || filteredSubIndicators.value.length > 0,
380
- )
381
-
382
389
  function isActive(indicatorId: string): boolean {
383
390
  return props.activeIndicators?.includes(indicatorId) ?? false
384
391
  }
@@ -390,8 +397,9 @@ function addIndicator(indicatorId: string) {
390
397
  if (!indicator) return
391
398
 
392
399
  if (indicator.pane === 'main') {
393
- mainIndicators
394
- .filter((i) => i.id !== indicatorId && isActive(i.id))
400
+ const allItems = allIndicators
401
+ allItems
402
+ .filter((i) => i.id !== indicatorId && isActive(i.id) && i.pane === 'main')
395
403
  .forEach((i) => emit('toggle', i.id, false))
396
404
  }
397
405
 
@@ -407,10 +415,6 @@ function showParams(indicatorId: string) {
407
415
  paramsVisible.value = true
408
416
  }
409
417
 
410
- function closeAddMenu() {
411
- showAddMenu.value = false
412
- }
413
-
414
418
  function getParamValues(indicatorId: string): Record<string, number> {
415
419
  const indicator = findIndicator(indicatorId)
416
420
  if (!indicator?.params) return {}
@@ -510,13 +514,9 @@ function onDragEnd() {
510
514
  draggingIndicatorId.value = null
511
515
  }
512
516
 
513
- function toggleAddMenu() {
514
- showAddMenu.value = !showAddMenu.value
515
- }
516
-
517
517
  function handleKeydown(event: KeyboardEvent) {
518
- if (event.key === 'Escape' && showAddMenu.value) {
519
- showAddMenu.value = false
518
+ if (event.key === 'Escape' && controller.menuOpen.peek()) {
519
+ controller.closeMenu()
520
520
  }
521
521
  }
522
522