@363045841yyt/klinechart 0.8.3 → 0.8.5

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 (51) hide show
  1. package/dist/components/BatchStockDialog.vue.d.ts +13 -0
  2. package/dist/components/BatchStockDialog.vue.d.ts.map +1 -0
  3. package/dist/components/CompareSymbolSelector.vue.d.ts.map +1 -1
  4. package/dist/components/Dropdown.vue.d.ts.map +1 -1
  5. package/dist/components/ExportProgressDialog.vue.d.ts +15 -0
  6. package/dist/components/ExportProgressDialog.vue.d.ts.map +1 -0
  7. package/dist/components/IndicatorSelector.vue.d.ts.map +1 -1
  8. package/dist/components/KLineChart.vue.d.ts +5 -9
  9. package/dist/components/KLineChart.vue.d.ts.map +1 -1
  10. package/dist/components/LeftToolbar.vue.d.ts.map +1 -1
  11. package/dist/components/SymbolSelector.vue.d.ts.map +1 -1
  12. package/dist/components/TopToolbar.vue.d.ts.map +1 -1
  13. package/dist/composables/chart/useChartTheme.d.ts +329 -0
  14. package/dist/composables/chart/useChartTheme.d.ts.map +1 -0
  15. package/dist/composables/chart/useDrawingManager.d.ts +86 -0
  16. package/dist/composables/chart/useDrawingManager.d.ts.map +1 -0
  17. package/dist/composables/chart/useIndicatorManager.d.ts +38 -0
  18. package/dist/composables/chart/useIndicatorManager.d.ts.map +1 -0
  19. package/dist/composables/chart/useRangeSelection.d.ts +65 -0
  20. package/dist/composables/chart/useRangeSelection.d.ts.map +1 -0
  21. package/dist/composables/useTeleportedPopup.d.ts +8 -0
  22. package/dist/composables/useTeleportedPopup.d.ts.map +1 -0
  23. package/dist/index.cjs +9 -2
  24. package/dist/index.css +1 -1
  25. package/dist/index.d.cts +1 -1
  26. package/dist/index.d.ts +1 -1
  27. package/dist/index.d.ts.map +1 -1
  28. package/dist/index.js +1769 -1090
  29. package/dist/tools/calcRangeOverlayPixel.d.ts +15 -0
  30. package/dist/tools/calcRangeOverlayPixel.d.ts.map +1 -0
  31. package/dist/tools/getKLineIndexByTimestamp.d.ts +4 -0
  32. package/dist/tools/getKLineIndexByTimestamp.d.ts.map +1 -0
  33. package/dist/web-component.d.ts.map +1 -1
  34. package/package.json +1 -1
  35. package/src/components/BatchStockDialog.vue +293 -0
  36. package/src/components/CompareSymbolSelector.vue +35 -8
  37. package/src/components/Dropdown.vue +42 -19
  38. package/src/components/ExportProgressDialog.vue +226 -0
  39. package/src/components/IndicatorSelector.vue +13 -5
  40. package/src/components/KLineChart.vue +329 -399
  41. package/src/components/LeftToolbar.vue +2 -1
  42. package/src/components/SymbolSelector.vue +35 -8
  43. package/src/components/TopToolbar.vue +55 -2
  44. package/src/composables/chart/useChartTheme.ts +86 -0
  45. package/src/composables/chart/useDrawingManager.ts +67 -0
  46. package/src/composables/chart/useIndicatorManager.ts +307 -0
  47. package/src/composables/chart/useRangeSelection.ts +417 -0
  48. package/src/composables/useTeleportedPopup.ts +33 -0
  49. package/src/index.ts +41 -14
  50. package/src/tools/calcRangeOverlayPixel.ts +28 -0
  51. package/src/tools/getKLineIndexByTimestamp.ts +40 -0
@@ -0,0 +1,15 @@
1
+ import { ChartController } from '@363045841yyt/klinechart-core/controllers';
2
+ export interface Bounds {
3
+ start: number;
4
+ end: number;
5
+ }
6
+ export declare function calcRangeOverlayPixel(bounds: Bounds, controller: ChartController, container: HTMLElement, viewport: {
7
+ scrollLeft: number;
8
+ plotWidth: number;
9
+ plotHeight: number;
10
+ }): {
11
+ left: number;
12
+ width: number;
13
+ height: number;
14
+ };
15
+ //# sourceMappingURL=calcRangeOverlayPixel.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"calcRangeOverlayPixel.d.ts","sourceRoot":"","sources":["../../src/tools/calcRangeOverlayPixel.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,2CAA2C,CAAA;AAEhF,MAAM,WAAW,MAAM;IACrB,KAAK,EAAE,MAAM,CAAA;IACb,GAAG,EAAE,MAAM,CAAA;CACZ;AAED,wBAAgB,qBAAqB,CACnC,MAAM,EAAE,MAAM,EACd,UAAU,EAAE,eAAe,EAC3B,SAAS,EAAE,WAAW,EACtB,QAAQ,EAAE;IAAE,UAAU,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAA;CAAE,GACtE;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAejD"}
@@ -0,0 +1,4 @@
1
+ import { KLineData } from '@363045841yyt/klinechart-core/controllers';
2
+ export declare function getKLineIndexByTimestamp(data: ReadonlyArray<KLineData>, timestamp: number): number | null;
3
+ export declare function findNearestKLineIndex(data: ReadonlyArray<KLineData>, timestamp: number, direction: 'left' | 'right'): number | null;
4
+ //# sourceMappingURL=getKLineIndexByTimestamp.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"getKLineIndexByTimestamp.d.ts","sourceRoot":"","sources":["../../src/tools/getKLineIndexByTimestamp.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,2CAA2C,CAAA;AAkB1E,wBAAgB,wBAAwB,CACtC,IAAI,EAAE,aAAa,CAAC,SAAS,CAAC,EAC9B,SAAS,EAAE,MAAM,GAChB,MAAM,GAAG,IAAI,CAIf;AAED,wBAAgB,qBAAqB,CACnC,IAAI,EAAE,aAAa,CAAC,SAAS,CAAC,EAC9B,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,MAAM,GAAG,OAAO,GAC1B,MAAM,GAAG,IAAI,CAQf"}
@@ -1 +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;;;;;;;;;;;;;;aAU6uzC,CAAC;kBAAyB,CAAC;;;;;iBAAsH,CAAC;gBAAc,CAAC;;;iBAAuD,CAAC;gBAAc,CAAC;;qBAAgC,CAAC;;EAR3g0C,CAAA;AAIF,OAAO,EAAE,iBAAiB,EAAE,CAAA;AAC5B,eAAe,iBAAiB,CAAA;AAEhC,YAAY,EAAE,mBAAmB,EAAE,WAAW,EAAE,CAAA"}
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;;;;;;;;;;;;;;aAU2qsC,CAAC;kBAAyB,CAAC;;;;;iBAAsH,CAAC;gBAAc,CAAC;;;iBAAuD,CAAC;gBAAc,CAAC;;qBAAgC,CAAC;;EARz8sC,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.8.3",
3
+ "version": "0.8.5",
4
4
  "description": "Vue 3 bindings for @363045841yyt/klinechart-core. Idiomatic composables, SFC components.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -0,0 +1,293 @@
1
+ <template>
2
+ <Teleport :to="teleportTarget">
3
+ <Transition name="overlay">
4
+ <div v-if="show" class="batch-overlay" @click="emit('close')">
5
+ <Transition name="modal">
6
+ <div class="batch-modal" @click.stop>
7
+ <div class="batch-header">
8
+ <span class="batch-title">批量设置股票代码</span>
9
+ <button class="batch-close-btn" @click="emit('close')">
10
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
11
+ <path d="M18 6L6 18M6 6l12 12" />
12
+ </svg>
13
+ </button>
14
+ </div>
15
+
16
+ <div class="batch-body">
17
+ <textarea
18
+ v-model="codesText"
19
+ class="batch-textarea"
20
+ placeholder="每行一个股票代码&#10;例如:&#10;000001&#10;600036&#10;002415"
21
+ rows="8"
22
+ spellcheck="false"
23
+ />
24
+ </div>
25
+
26
+ <div class="batch-footer">
27
+ <button class="batch-btn batch-btn--cancel" @click="emit('close')">取消</button>
28
+ <button class="batch-btn batch-btn--confirm" @click="onApply">应用</button>
29
+ </div>
30
+ </div>
31
+ </Transition>
32
+ </div>
33
+ </Transition>
34
+ </Teleport>
35
+ </template>
36
+
37
+ <script setup lang="ts">
38
+ import { ref, computed } from 'vue'
39
+ import { useFullscreenTeleportTarget } from '../composables/useFullscreenTeleportTarget'
40
+
41
+ const props = defineProps<{
42
+ show: boolean
43
+ }>()
44
+
45
+ const emit = defineEmits<{
46
+ close: []
47
+ apply: [codes: string[]]
48
+ }>()
49
+
50
+ const teleportTarget = useFullscreenTeleportTarget()
51
+
52
+ const codes = ref<string[]>([])
53
+
54
+ const codesText = computed({
55
+ get: () => codes.value.join('\n'),
56
+ set: (val: string) => {
57
+ codes.value = val
58
+ .split('\n')
59
+ .map((s) => s.trim())
60
+ .filter(Boolean)
61
+ },
62
+ })
63
+
64
+ function onApply() {
65
+ if (codes.value.length === 0) return
66
+ emit('apply', codes.value)
67
+ emit('close')
68
+ }
69
+ </script>
70
+
71
+ <style scoped>
72
+ .batch-overlay {
73
+ position: fixed;
74
+ inset: 0;
75
+ background: rgba(0, 0, 0, 0.3);
76
+ backdrop-filter: blur(4px);
77
+ padding: 24px;
78
+ display: flex;
79
+ align-items: center;
80
+ justify-content: center;
81
+ z-index: 1000;
82
+ }
83
+
84
+ .batch-modal {
85
+ background: var(--klc-color-tag-bg-white);
86
+ border: 1px solid var(--klc-color-border-button);
87
+ border-radius: 10px;
88
+ box-shadow: 0 18px 48px rgba(0, 0, 0, 0.15);
89
+ min-width: 360px;
90
+ max-width: 400px;
91
+ width: min(92vw, 400px);
92
+ max-height: min(600px, calc(100vh - 48px));
93
+ overflow: hidden;
94
+ display: flex;
95
+ flex-direction: column;
96
+ }
97
+
98
+ .batch-header {
99
+ display: flex;
100
+ justify-content: space-between;
101
+ align-items: center;
102
+ padding: 14px 18px 14px 20px;
103
+ background: var(--klc-color-background);
104
+ border-bottom: 1px solid var(--klc-color-grid-major);
105
+ flex-shrink: 0;
106
+ }
107
+
108
+ .batch-title {
109
+ font-size: 15px;
110
+ font-weight: 600;
111
+ color: var(--klc-color-foreground);
112
+ line-height: 1.35;
113
+ }
114
+
115
+ .batch-close-btn {
116
+ background: var(--klc-color-tag-bg-white);
117
+ border: 1px solid var(--klc-color-border-button);
118
+ border-radius: 7px;
119
+ width: 30px;
120
+ height: 30px;
121
+ display: flex;
122
+ align-items: center;
123
+ justify-content: center;
124
+ cursor: pointer;
125
+ color: var(--klc-color-axis-text);
126
+ transition:
127
+ background 0.15s,
128
+ color 0.15s,
129
+ border-color 0.15s;
130
+ padding: 0;
131
+ }
132
+
133
+ .batch-close-btn:hover {
134
+ background: var(--klc-color-tag-bg-hover);
135
+ color: var(--klc-color-foreground);
136
+ border-color: var(--klc-color-axis-line);
137
+ }
138
+
139
+ .batch-close-btn svg {
140
+ width: 14px;
141
+ height: 14px;
142
+ }
143
+
144
+ .batch-body {
145
+ padding: 16px 20px;
146
+ display: flex;
147
+ flex-direction: column;
148
+ gap: 12px;
149
+ }
150
+
151
+ .batch-textarea {
152
+ width: 100%;
153
+ min-height: 160px;
154
+ padding: 10px 12px;
155
+ border: 1px solid var(--klc-color-border-button);
156
+ border-radius: 6px;
157
+ background: var(--klc-color-background);
158
+ color: var(--klc-color-foreground);
159
+ font-size: 13px;
160
+ font-family: 'Cascadia Code', 'Fira Code', 'Consolas', monospace;
161
+ line-height: 1.5;
162
+ resize: vertical;
163
+ outline: none;
164
+ transition: border-color 0.15s;
165
+ box-sizing: border-box;
166
+ }
167
+
168
+ .batch-textarea:focus {
169
+ border-color: var(--klc-color-axis-text);
170
+ }
171
+
172
+ .batch-textarea::placeholder {
173
+ color: var(--klc-color-axis-text);
174
+ opacity: 0.5;
175
+ }
176
+
177
+ .batch-footer {
178
+ display: flex;
179
+ align-items: center;
180
+ justify-content: flex-end;
181
+ gap: 8px;
182
+ padding: 12px 20px;
183
+ background: var(--klc-color-background);
184
+ border-top: 1px solid var(--klc-color-grid-major);
185
+ flex-shrink: 0;
186
+ }
187
+
188
+ .batch-btn {
189
+ display: flex;
190
+ align-items: center;
191
+ justify-content: center;
192
+ min-width: 68px;
193
+ height: 32px;
194
+ padding: 0 14px;
195
+ border-radius: 7px;
196
+ font-size: 13px;
197
+ font-weight: 500;
198
+ cursor: pointer;
199
+ border: 1px solid transparent;
200
+ transition:
201
+ background 0.15s,
202
+ border-color 0.15s,
203
+ color 0.15s,
204
+ box-shadow 0.15s,
205
+ transform 0.15s;
206
+ line-height: 1;
207
+ white-space: nowrap;
208
+ }
209
+
210
+ .batch-btn--cancel {
211
+ background: transparent;
212
+ border-color: var(--klc-color-axis-line);
213
+ color: var(--klc-color-axis-text);
214
+ }
215
+
216
+ .batch-btn--cancel:hover {
217
+ background: var(--klc-color-tag-bg-hover);
218
+ color: var(--klc-color-foreground);
219
+ border-color: var(--klc-color-axis-text);
220
+ }
221
+
222
+ .batch-btn--confirm {
223
+ background: var(--klc-color-foreground);
224
+ border-color: var(--klc-color-foreground);
225
+ color: var(--klc-color-background);
226
+ }
227
+
228
+ .batch-btn--confirm:hover {
229
+ background: var(--klc-color-foreground);
230
+ border-color: var(--klc-color-foreground);
231
+ box-shadow: 0 2px 10px rgba(0, 0, 0, 0.15);
232
+ transform: translateY(-1px);
233
+ }
234
+
235
+ .batch-btn--confirm:active {
236
+ transform: translateY(0);
237
+ box-shadow: none;
238
+ }
239
+
240
+ .overlay-enter-active,
241
+ .overlay-leave-active {
242
+ transition: opacity 0.2s ease;
243
+ }
244
+
245
+ .overlay-enter-from,
246
+ .overlay-leave-to {
247
+ opacity: 0;
248
+ }
249
+
250
+ .modal-enter-active {
251
+ transition: all 0.22s cubic-bezier(0.34, 1.56, 0.64, 1);
252
+ }
253
+
254
+ .modal-leave-active {
255
+ transition: all 0.16s ease-in;
256
+ }
257
+
258
+ .modal-enter-from {
259
+ opacity: 0;
260
+ transform: scale(0.96) translateY(-10px);
261
+ }
262
+
263
+ .modal-leave-to {
264
+ opacity: 0;
265
+ transform: scale(0.98) translateY(8px);
266
+ }
267
+
268
+ @media (max-width: 480px) {
269
+ .batch-overlay {
270
+ padding: 12px;
271
+ align-items: flex-end;
272
+ }
273
+
274
+ .batch-modal {
275
+ min-width: 0;
276
+ width: 100%;
277
+ max-height: calc(100vh - 24px);
278
+ border-radius: 10px;
279
+ }
280
+
281
+ .batch-header,
282
+ .batch-body,
283
+ .batch-footer {
284
+ padding-left: 16px;
285
+ padding-right: 16px;
286
+ }
287
+
288
+ .batch-footer {
289
+ flex-direction: column-reverse;
290
+ align-items: stretch;
291
+ }
292
+ }
293
+ </style>
@@ -14,8 +14,16 @@
14
14
  <span v-if="comparisonLoading" class="compare-chip__spinner" />
15
15
  <span v-if="selected.length > 0" class="compare-chip__badge">{{ selected.length }}</span>
16
16
  </button>
17
- <Transition name="symbol-popover">
18
- <div v-if="showPopup" class="compare-popover" role="dialog" aria-label="比较商品">
17
+ <Teleport :to="teleportTarget">
18
+ <Transition name="symbol-popover">
19
+ <div
20
+ v-if="showPopup"
21
+ ref="popupRef"
22
+ class="compare-popover"
23
+ :style="popupStyle"
24
+ role="dialog"
25
+ aria-label="比较商品"
26
+ >
19
27
  <div class="compare-search">
20
28
  <span class="compare-search__icon" aria-hidden="true">
21
29
  <svg width="14" height="14" viewBox="0 0 16 16" fill="none">
@@ -134,13 +142,16 @@
134
142
  </button>
135
143
  </div>
136
144
  </div>
137
- </Transition>
145
+ </Transition>
146
+ </Teleport>
138
147
  </div>
139
148
  </template>
140
149
 
141
150
  <script setup lang="ts">
142
- import { ref, computed, nextTick, onMounted, onBeforeUnmount } from 'vue'
151
+ import { ref, computed, watch, nextTick, onMounted, onBeforeUnmount } from 'vue'
143
152
  import type { SymbolItem } from './SymbolSelector.vue'
153
+ import { useTeleportedPopup } from '../composables/useTeleportedPopup'
154
+ import { useFullscreenTeleportTarget } from '../composables/useFullscreenTeleportTarget'
144
155
 
145
156
  const props = withDefaults(defineProps<{
146
157
  symbols: SymbolItem[]
@@ -160,6 +171,15 @@ const showPopup = ref(false)
160
171
  const searchQuery = ref('')
161
172
  const searchInputRef = ref<HTMLInputElement | null>(null)
162
173
  const rootRef = ref<HTMLElement | null>(null)
174
+ const popupRef = ref<HTMLElement | null>(null)
175
+
176
+ const teleportTarget = useFullscreenTeleportTarget()
177
+
178
+ const { popupStyle, startPositionSync, stopPositionSync } = useTeleportedPopup(
179
+ rootRef,
180
+ popupRef,
181
+ 8,
182
+ )
163
183
 
164
184
  const selectedSet = computed(() => new Set(props.selected ?? []))
165
185
 
@@ -202,13 +222,23 @@ function togglePopup() {
202
222
  }
203
223
  }
204
224
 
225
+ watch(showPopup, (val) => {
226
+ if (val) {
227
+ startPositionSync()
228
+ } else {
229
+ stopPositionSync()
230
+ }
231
+ })
232
+
205
233
  function clearSearch() {
206
234
  searchQuery.value = ''
207
235
  searchInputRef.value?.focus()
208
236
  }
209
237
 
210
238
  function onDocumentClick(e: MouseEvent) {
211
- if (rootRef.value && !rootRef.value.contains(e.target as Node)) {
239
+ const root = rootRef.value
240
+ const popup = popupRef.value
241
+ if (root && !root.contains(e.target as Node) && !popup?.contains(e.target as Node)) {
212
242
  showPopup.value = false
213
243
  searchQuery.value = ''
214
244
  }
@@ -302,9 +332,6 @@ onBeforeUnmount(() => document.removeEventListener('mousedown', onDocumentClick)
302
332
  }
303
333
 
304
334
  .compare-popover {
305
- position: absolute;
306
- top: calc(100% + 8px);
307
- left: 0;
308
335
  z-index: 20;
309
336
  width: min(360px, calc(100vw - 24px));
310
337
  padding: 14px;
@@ -19,20 +19,29 @@
19
19
  <span class="dropdown__chevron" aria-hidden="true"></span>
20
20
  </button>
21
21
 
22
- <div v-if="isOpen" class="dropdown__menu" :style="menuStyle" role="listbox" tabindex="-1">
23
- <button
24
- v-for="option in options"
25
- :key="option.value"
26
- type="button"
27
- class="dropdown__option"
28
- :class="{ 'is-selected': option.value === selectedValue }"
29
- role="option"
30
- :aria-selected="option.value === selectedValue"
31
- @click="selectOption(option.value)"
22
+ <Teleport :to="teleportTarget">
23
+ <div
24
+ v-if="isOpen"
25
+ ref="menuRef"
26
+ class="dropdown__menu"
27
+ :style="menuStyle"
28
+ role="listbox"
29
+ tabindex="-1"
32
30
  >
33
- {{ option.label }}
34
- </button>
35
- </div>
31
+ <button
32
+ v-for="option in options"
33
+ :key="option.value"
34
+ type="button"
35
+ class="dropdown__option"
36
+ :class="{ 'is-selected': option.value === selectedValue }"
37
+ role="option"
38
+ :aria-selected="option.value === selectedValue"
39
+ @click="selectOption(option.value)"
40
+ >
41
+ {{ option.label }}
42
+ </button>
43
+ </div>
44
+ </Teleport>
36
45
  </div>
37
46
  </template>
38
47
 
@@ -44,6 +53,8 @@ let dropdownIdSeed = 0
44
53
 
45
54
  <script setup lang="ts">
46
55
  import { computed, onBeforeUnmount, ref } from 'vue'
56
+ import { useTeleportedPopup } from '../composables/useTeleportedPopup'
57
+ import { useFullscreenTeleportTarget } from '../composables/useFullscreenTeleportTarget'
47
58
 
48
59
  export interface DropdownOption<T extends string = string> {
49
60
  label: string
@@ -71,10 +82,19 @@ const emit = defineEmits<{
71
82
 
72
83
  const rootRef = ref<HTMLElement | null>(null)
73
84
  const triggerRef = ref<HTMLElement | null>(null)
85
+ const menuRef = ref<HTMLElement | null>(null)
74
86
  const isOpen = ref(false)
75
87
  const menuWidth = ref(0)
76
88
  const dropdownId = ++dropdownIdSeed
77
89
 
90
+ const teleportTarget = useFullscreenTeleportTarget()
91
+
92
+ const { popupStyle, startPositionSync, stopPositionSync } = useTeleportedPopup(
93
+ triggerRef,
94
+ menuRef,
95
+ 4,
96
+ )
97
+
78
98
  const triggerStyle = computed(() => {
79
99
  if (props.minWidth) return { minWidth: props.minWidth }
80
100
  return {}
@@ -83,7 +103,11 @@ const triggerStyle = computed(() => {
83
103
  const menuStyle = computed(() => {
84
104
  if (!isOpen.value) return undefined
85
105
  const w = menuWidth.value || (props.minWidth ? parseInt(props.minWidth) : 0)
86
- return { width: w ? `${w}px` : undefined }
106
+ return {
107
+ width: w ? `${w}px` : undefined,
108
+ zIndex: 1010,
109
+ ...popupStyle.value,
110
+ }
87
111
  })
88
112
 
89
113
  const selectedValue = computed(() => {
@@ -107,6 +131,7 @@ function open() {
107
131
  activeDropdownClose = close
108
132
  menuWidth.value = triggerRef.value?.offsetWidth ?? 0
109
133
  isOpen.value = true
134
+ startPositionSync()
110
135
  document.addEventListener('pointerdown', handleDocumentPointerDown)
111
136
  }
112
137
 
@@ -117,6 +142,7 @@ function close() {
117
142
  activeDropdownId = 0
118
143
  activeDropdownClose = null
119
144
  }
145
+ stopPositionSync()
120
146
  document.removeEventListener('pointerdown', handleDocumentPointerDown)
121
147
  }
122
148
 
@@ -135,7 +161,8 @@ function selectOption(value: string) {
135
161
 
136
162
  function handleDocumentPointerDown(event: PointerEvent) {
137
163
  const root = rootRef.value
138
- if (root && !root.contains(event.target as Node | null)) {
164
+ const menu = menuRef.value
165
+ if (root && !root.contains(event.target as Node | null) && !menu?.contains(event.target as Node | null)) {
139
166
  close()
140
167
  }
141
168
  }
@@ -219,10 +246,6 @@ onBeforeUnmount(close)
219
246
  }
220
247
 
221
248
  .dropdown__menu {
222
- position: absolute;
223
- z-index: 30;
224
- top: calc(100% + 4px);
225
- left: 0;
226
249
  padding: 4px;
227
250
  border: 1px solid var(--klc-color-border-button);
228
251
  border-radius: 4px;