@363045841yyt/klinechart 0.7.12 → 0.8.1-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.
@@ -1,87 +1,5 @@
1
1
  <template>
2
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.stop="controller.toggleMenu()" 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
3
 
86
4
  <!-- 添加指标弹窗 -->
87
5
  <Teleport :to="teleportTarget">
@@ -127,9 +45,8 @@
127
45
  </div>
128
46
  </div>
129
47
 
130
- <!-- 弹窗主体 -->
131
- <div class="modal-body">
132
- <!-- 搜索框 -->
48
+ <!-- 搜索区域 -->
49
+ <div class="modal-search-area">
133
50
  <div class="search-box">
134
51
  <svg
135
52
  class="search-icon"
@@ -149,6 +66,10 @@
149
66
  placeholder="搜索指标名称..."
150
67
  />
151
68
  </div>
69
+ </div>
70
+
71
+ <!-- 弹窗主体 -->
72
+ <div class="modal-body">
152
73
  <!-- 主图指标区域 -->
153
74
  <div v-if="filteredMain.length > 0" class="indicator-section">
154
75
  <div class="section-header">
@@ -297,7 +218,6 @@ import {
297
218
  type IndicatorDefinition,
298
219
  allIndicators,
299
220
  findIndicator,
300
- isSubIndicatorId,
301
221
  type Indicator,
302
222
  } from '@363045841yyt/klinechart-core/controllers'
303
223
 
@@ -350,35 +270,13 @@ const hasSearchResults = computed(
350
270
  const catalogLen = controller.catalog.peek().length
351
271
 
352
272
  // ── 本地 UI 状态(非 Controller 管理的纯 UI 状态) ──
353
- const addBtnRef = ref<HTMLButtonElement | null>(null)
354
273
  const paramsVisible = ref(false)
355
274
  const currentIndicatorId = ref<string | null>(null)
356
- const hoveredIndicator = ref<string | null>(null)
357
- const dragOverIndicatorId = ref<string | null>(null)
358
- const draggingIndicatorId = ref<string | null>(null)
359
275
  const isCompactView = ref(false)
360
276
 
361
277
  // Teleport target for fullscreen modal visibility
362
278
  const teleportTarget = useFullscreenTeleportTarget()
363
279
 
364
- const activeIndicatorsList = computed(() => {
365
- if (!props.activeIndicators?.length) return []
366
- return props.activeIndicators
367
- .map((id) => findIndicator(id))
368
- .filter((i): i is Indicator => i !== undefined)
369
- .sort((a, b) => {
370
- if (a.pane === b.pane) return 0
371
- return a.pane === 'main' ? -1 : 1
372
- })
373
- })
374
-
375
- const firstActiveSubIndicatorId = computed(() => {
376
- const hasMain = activeIndicatorsList.value.some((indicator) => indicator.pane === 'main')
377
- if (!hasMain) return null
378
- const firstSub = activeIndicatorsList.value.find((indicator) => indicator.pane === 'sub')
379
- return firstSub?.id ?? null
380
- })
381
-
382
280
  const currentIndicator = computed(() => {
383
281
  if (!currentIndicatorId.value) return null
384
282
  return findIndicator(currentIndicatorId.value)
@@ -396,13 +294,6 @@ function addIndicator(indicatorId: string) {
396
294
  const indicator = findIndicator(indicatorId)
397
295
  if (!indicator) return
398
296
 
399
- if (indicator.pane === 'main') {
400
- const allItems = allIndicators
401
- allItems
402
- .filter((i) => i.id !== indicatorId && isActive(i.id) && i.pane === 'main')
403
- .forEach((i) => emit('toggle', i.id, false))
404
- }
405
-
406
297
  emit('toggle', indicatorId, true)
407
298
  }
408
299
 
@@ -436,12 +327,6 @@ function getParamValues(indicatorId: string): Record<string, number> {
436
327
  return result
437
328
  }
438
329
 
439
- function getParamDisplay(indicator: Indicator): string {
440
- const values = getParamValues(indicator.id)
441
- if (!indicator.params) return ''
442
- return indicator.params.map((p) => values[p.key] ?? '').join(',')
443
- }
444
-
445
330
  function onParamsConfirm(values: Record<string, number>) {
446
331
  if (currentIndicatorId.value) {
447
332
  emit('updateParams', currentIndicatorId.value, values)
@@ -449,71 +334,6 @@ function onParamsConfirm(values: Record<string, number>) {
449
334
  paramsVisible.value = false
450
335
  }
451
336
 
452
- function onDragStart(event: DragEvent, indicatorId: string) {
453
- if (!isSubIndicatorId(indicatorId)) {
454
- event.preventDefault()
455
- return
456
- }
457
- draggingIndicatorId.value = indicatorId
458
- dragOverIndicatorId.value = null
459
- event.dataTransfer?.setData('text/plain', indicatorId)
460
- if (event.dataTransfer) {
461
- event.dataTransfer.effectAllowed = 'move'
462
- }
463
- }
464
-
465
- function onDragOver(event: DragEvent, indicatorId: string) {
466
- if (
467
- !draggingIndicatorId.value ||
468
- !isSubIndicatorId(indicatorId) ||
469
- draggingIndicatorId.value === indicatorId
470
- )
471
- return
472
- dragOverIndicatorId.value = indicatorId
473
- if (event.dataTransfer) {
474
- event.dataTransfer.dropEffect = 'move'
475
- }
476
- }
477
-
478
- function onDrop(event: DragEvent, targetIndicatorId: string) {
479
- const sourceIndicatorId =
480
- draggingIndicatorId.value || event.dataTransfer?.getData('text/plain') || ''
481
- if (!sourceIndicatorId || sourceIndicatorId === targetIndicatorId) {
482
- onDragEnd()
483
- return
484
- }
485
- if (!isSubIndicatorId(sourceIndicatorId) || !isSubIndicatorId(targetIndicatorId)) {
486
- onDragEnd()
487
- return
488
- }
489
-
490
- const sourceIndex = activeIndicatorsList.value.findIndex((i) => i.id === sourceIndicatorId)
491
- const targetIndex = activeIndicatorsList.value.findIndex((i) => i.id === targetIndicatorId)
492
- if (sourceIndex < 0 || targetIndex < 0) {
493
- onDragEnd()
494
- return
495
- }
496
-
497
- const next = [...activeIndicatorsList.value.map((i) => i.id)]
498
- const [moved] = next.splice(sourceIndex, 1)
499
- if (!moved) {
500
- onDragEnd()
501
- return
502
- }
503
- next.splice(targetIndex, 0, moved)
504
-
505
- emit(
506
- 'reorderSubIndicators',
507
- next.filter((id) => isSubIndicatorId(id)),
508
- )
509
- onDragEnd()
510
- }
511
-
512
- function onDragEnd() {
513
- dragOverIndicatorId.value = null
514
- draggingIndicatorId.value = null
515
- }
516
-
517
337
  function handleKeydown(event: KeyboardEvent) {
518
338
  if (event.key === 'Escape' && controller.menuOpen.peek()) {
519
339
  controller.closeMenu()
@@ -527,184 +347,19 @@ onMounted(() => {
527
347
  onUnmounted(() => {
528
348
  document.removeEventListener('keydown', handleKeydown)
529
349
  })
350
+
351
+ defineExpose({
352
+ openMenu: () => controller.openMenu(),
353
+ closeMenu: () => controller.closeMenu(),
354
+ toggleMenu: () => controller.toggleMenu(),
355
+ })
530
356
  </script>
531
357
 
532
358
  <style scoped>
533
359
  .indicator-selector {
534
- margin: 20px;
535
- width: 80%;
536
- position: relative;
537
- }
538
-
539
- .indicator-scroll-container {
540
- width: 100%;
541
- overflow-x: auto;
542
- overflow-y: hidden;
543
- scrollbar-width: none;
544
- -webkit-overflow-scrolling: touch;
545
- text-align: center;
546
- }
547
-
548
- .indicator-scroll-container::-webkit-scrollbar {
549
360
  display: none;
550
361
  }
551
362
 
552
- .indicator-list {
553
- display: inline-flex;
554
- gap: 8px;
555
- padding: 2px;
556
- margin: 0 auto;
557
- }
558
-
559
- .indicator-divider {
560
- width: 1px;
561
- height: 20px;
562
- align-self: center;
563
- background: var(--klc-color-axis-line);
564
- }
565
-
566
- .indicator-item {
567
- display: flex;
568
- align-items: center;
569
- gap: 4px;
570
- }
571
-
572
- .indicator-item.draggable,
573
- .indicator-item.draggable .indicator-btn,
574
- .indicator-item.draggable:hover,
575
- .indicator-item.draggable:hover .indicator-btn {
576
- cursor: move;
577
- }
578
-
579
- .indicator-item.is-dragging {
580
- opacity: 0.6;
581
- }
582
-
583
- .indicator-item.drag-over .indicator-btn {
584
- box-shadow: 0 0 0 2px color-mix(in srgb, var(--klc-color-foreground) 12%, transparent);
585
- }
586
-
587
- .indicator-btn-wrapper {
588
- position: relative;
589
- }
590
-
591
- .indicator-btn {
592
- position: relative;
593
- flex-shrink: 0;
594
- padding: 6px 16px;
595
- border: none;
596
- border-radius: 16px;
597
- background: var(--klc-color-tag-bg-white);
598
- color: var(--klc-color-axis-text);
599
- font-size: 13px;
600
- font-weight: 500;
601
- cursor: pointer;
602
- transition: all 0.3s ease;
603
- white-space: nowrap;
604
- display: flex;
605
- align-items: center;
606
- justify-content: center;
607
- gap: 4px;
608
- overflow: hidden;
609
- }
610
-
611
- .indicator-btn:hover:not(.hovering) {
612
- background: var(--klc-color-tag-bg-hover);
613
- color: var(--klc-color-foreground);
614
- }
615
-
616
- .indicator-btn.active {
617
- background: var(--klc-color-tag-bg-hover);
618
- color: var(--klc-color-foreground);
619
- }
620
-
621
- .indicator-btn.active:hover:not(.hovering) {
622
- background: var(--klc-color-tag-bg-hover);
623
- }
624
-
625
- .btn-content {
626
- position: relative;
627
- z-index: 1;
628
- }
629
-
630
- .param-hint {
631
- font-size: 11px;
632
- opacity: 0.85;
633
- }
634
-
635
- /* 悬浮操作层 */
636
- .hover-overlay {
637
- position: absolute;
638
- top: 0;
639
- left: 0;
640
- right: 0;
641
- bottom: 0;
642
- display: flex;
643
- align-items: center;
644
- justify-content: center;
645
- gap: 4px;
646
- background: color-mix(in srgb, var(--klc-color-background) 85%, transparent);
647
- backdrop-filter: blur(4px);
648
- border-radius: 16px;
649
- z-index: 2;
650
- }
651
-
652
- .action-btn {
653
- width: 24px;
654
- height: 24px;
655
- padding: 0;
656
- border: none;
657
- border-radius: 50%;
658
- background: transparent;
659
- color: var(--klc-color-axis-text);
660
- cursor: pointer;
661
- display: flex;
662
- align-items: center;
663
- justify-content: center;
664
- transition: all 0.2s;
665
- }
666
-
667
- .action-btn:hover {
668
- background: var(--klc-color-tag-bg-hover);
669
- color: var(--klc-color-foreground);
670
- }
671
-
672
- .settings-btn:hover {
673
- color: var(--klc-color-foreground);
674
- }
675
-
676
- .remove-btn:hover {
677
- color: #ff4d4f;
678
- }
679
-
680
- .divider {
681
- width: 1px;
682
- height: 14px;
683
- background: var(--klc-color-border-button);
684
- }
685
-
686
- /* 添加按钮 */
687
- .add-btn {
688
- flex-shrink: 0;
689
- width: 32px;
690
- height: 32px;
691
- padding: 0;
692
- border: none;
693
- border-radius: 50%;
694
- background: transparent;
695
- color: var(--klc-color-axis-text);
696
- cursor: pointer;
697
- display: flex;
698
- align-items: center;
699
- justify-content: center;
700
- transition: all 0.3s ease;
701
- }
702
-
703
- .add-btn:hover {
704
- color: var(--klc-color-foreground);
705
- background: var(--klc-color-tag-bg-hover);
706
- }
707
-
708
363
  /* ─────────────────────────────────────────────────────────────────
709
364
  弹窗样式 - 与其他弹窗保持一致
710
365
  ───────────────────────────────────────────────────────────────── */
@@ -823,6 +478,14 @@ onUnmounted(() => {
823
478
  color: var(--klc-color-foreground);
824
479
  }
825
480
 
481
+ /* 搜索区域 */
482
+ .modal-search-area {
483
+ padding: 16px 20px;
484
+ background: var(--klc-color-tag-bg-white);
485
+ border-bottom: 1px solid var(--klc-color-border-chart);
486
+ flex-shrink: 0;
487
+ }
488
+
826
489
  /* 弹窗主体 */
827
490
  .modal-body {
828
491
  padding: 20px;
@@ -851,6 +514,7 @@ onUnmounted(() => {
851
514
  padding: 10px 14px;
852
515
  border: 1px solid var(--klc-color-border-button);
853
516
  border-radius: 8px;
517
+ background: var(--klc-color-background);
854
518
  transition: all 0.2s ease;
855
519
  }
856
520