cynx-lib 1.3.51 → 1.3.53

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.
@@ -0,0 +1,706 @@
1
+ <script setup lang="ts">
2
+ import {
3
+ ref,
4
+ computed,
5
+ watch,
6
+ onMounted,
7
+ onBeforeUnmount,
8
+ nextTick,
9
+ useSlots,
10
+ } from "vue";
11
+ import { ChevronDown, Check, X, Plus } from "lucide-vue-next";
12
+
13
+ export interface SelectOption {
14
+ value: string | number;
15
+ label?: string | number;
16
+ name?: string | number;
17
+ search?: string | number;
18
+ employee_id?: string | number;
19
+ code?: string | number;
20
+ title?: string | number;
21
+ disabled?: boolean;
22
+ displayLabel?: string | number;
23
+ [key: string]: any;
24
+ }
25
+
26
+ const SIZES: Record<
27
+ string,
28
+ {
29
+ height: number;
30
+ fontSize: number;
31
+ paddingH: number;
32
+ searchHeight: number;
33
+ searchFont: number;
34
+ itemMinHeight: number;
35
+ itemPadding: string;
36
+ }
37
+ > = {
38
+ xs: { height: 24, fontSize: 11, paddingH: 6, searchHeight: 22, searchFont: 10.5, itemMinHeight: 18, itemPadding: "3px 5px" },
39
+ sm: { height: 28, fontSize: 11.5, paddingH: 8, searchHeight: 26, searchFont: 11.5, itemMinHeight: 22, itemPadding: "4.5px 7px" },
40
+ md: { height: 32, fontSize: 13, paddingH: 10, searchHeight: 32, searchFont: 12.5, itemMinHeight: 28, itemPadding: "8px 10px" },
41
+ lg: { height: 36, fontSize: 14, paddingH: 14, searchHeight: 34, searchFont: 13, itemMinHeight: 32, itemPadding: "9px 12px" },
42
+ };
43
+
44
+ const KEYFRAMES = `
45
+ @keyframes selFadeInDown{from{opacity:0;transform:translateY(-4px)}to{opacity:1;transform:translateY(0)}}
46
+ @keyframes selFadeInUp{from{opacity:0;transform:translateY(4px)}to{opacity:1;transform:translateY(0)}}
47
+ `;
48
+ const _id = "cynx-select-styles";
49
+
50
+ function ensureStyles() {
51
+ if (typeof document === "undefined") return;
52
+ if (document.getElementById(_id)) return;
53
+ const tag = document.createElement("style");
54
+ tag.id = _id;
55
+ tag.textContent = KEYFRAMES;
56
+ document.head.appendChild(tag);
57
+ }
58
+
59
+ const props = withDefaults(
60
+ defineProps<{
61
+ options?: SelectOption[];
62
+ modelValue?: any;
63
+ value?: any;
64
+ placeholder?: string;
65
+ disabled?: boolean;
66
+ multi?: boolean;
67
+ searchable?: boolean;
68
+ clearable?: boolean;
69
+ size?: "xs" | "sm" | "md" | "lg";
70
+ style?: Record<string, any>;
71
+ rounded?: boolean | string | number;
72
+ showCount?: boolean | ((count: number) => string);
73
+ searchMode?: "includes" | "starts_with" | "word_start";
74
+ filterOption?: (option: SelectOption, query: string) => boolean;
75
+ creatable?: boolean;
76
+ createLabel?: (query: string) => string;
77
+ }>(),
78
+ {
79
+ options: () => [],
80
+ modelValue: undefined,
81
+ value: undefined,
82
+ placeholder: "Select...",
83
+ disabled: false,
84
+ multi: false,
85
+ searchable: false,
86
+ clearable: false,
87
+ size: "md",
88
+ style: () => ({}),
89
+ rounded: false,
90
+ showCount: false,
91
+ searchMode: "includes",
92
+ filterOption: undefined,
93
+ creatable: false,
94
+ createLabel: undefined,
95
+ }
96
+ );
97
+
98
+ const emit = defineEmits<{
99
+ (e: "update:modelValue", value: any): void;
100
+ (e: "change", value: any): void;
101
+ (e: "clear"): void;
102
+ (e: "visible-change", visible: boolean): void;
103
+ }>();
104
+
105
+ const slots = useSlots();
106
+ const open = ref(false);
107
+ const query = ref("");
108
+ const pos = ref({ top: 0, left: 0, width: 0, openUp: false, maxHeight: 200 });
109
+ const wrapRef = ref<HTMLElement | null>(null);
110
+ const dropRef = ref<HTMLElement | null>(null);
111
+ const inputRef = ref<HTMLInputElement | null>(null);
112
+
113
+ ensureStyles();
114
+
115
+ const s = computed(() => SIZES[props.size] || SIZES.md);
116
+
117
+ const currentVal = computed(() => {
118
+ return props.modelValue !== undefined ? props.modelValue : props.value;
119
+ });
120
+
121
+ const selectedValues = computed<any[]>(() => {
122
+ const v = currentVal.value;
123
+ if (props.multi) {
124
+ return Array.isArray(v) ? v : [];
125
+ }
126
+ return v !== undefined && v !== null && v !== "" ? [v] : [];
127
+ });
128
+
129
+ function getLabel(optVal: any) {
130
+ const opt = props.options.find((o) => o.value === optVal);
131
+ return opt ? (opt.displayLabel || opt.label) : optVal;
132
+ }
133
+
134
+ function isSelected(optVal: any) {
135
+ const v = currentVal.value;
136
+ return props.multi ? (Array.isArray(v) && v.includes(optVal)) : v === optVal;
137
+ }
138
+
139
+ const radiusVal = computed(() => {
140
+ const curSize = s.value;
141
+ if (props.rounded) {
142
+ if (typeof props.rounded === "string") {
143
+ return props.rounded.endsWith("px") ||
144
+ props.rounded.endsWith("rem") ||
145
+ props.rounded.startsWith("var")
146
+ ? props.rounded
147
+ : `${props.rounded}px`;
148
+ }
149
+ if (typeof props.rounded === "number") {
150
+ return `${props.rounded}px`;
151
+ }
152
+ return "6px";
153
+ }
154
+ return curSize.height <= 26
155
+ ? "4px"
156
+ : curSize.height <= 28
157
+ ? "6px"
158
+ : "var(--rmd,var(--rads, 8px))";
159
+ });
160
+
161
+ const calcPosition = () => {
162
+ if (!wrapRef.value) return;
163
+ const r = wrapRef.value.getBoundingClientRect();
164
+ const dropEl = dropRef.value;
165
+ const dropHeight = dropEl ? dropEl.offsetHeight : 220;
166
+ const spaceBelow = window.innerHeight - r.bottom;
167
+ const spaceAbove = r.top;
168
+
169
+ // If not enough space below (< dropHeight + 10) and more space above, open up
170
+ const openUp = spaceBelow < Math.min(dropHeight, 220) + 12 && spaceAbove > spaceBelow;
171
+
172
+ // Dynamic max-height for inner list to prevent overflow
173
+ const availableSpace = openUp ? spaceAbove - 16 : spaceBelow - 16;
174
+ const isSearchActive = props.searchable || props.creatable;
175
+ const calculatedMaxHeight = Math.max(
176
+ 90,
177
+ Math.min(220, availableSpace - (isSearchActive ? 46 : 14))
178
+ );
179
+
180
+ let top: number;
181
+ if (openUp) {
182
+ top = Math.max(8, r.top - (dropEl ? dropEl.offsetHeight : dropHeight) - 4);
183
+ } else {
184
+ top = Math.min(window.innerHeight - 40, r.bottom + 4);
185
+ }
186
+
187
+ let left = r.left;
188
+ const width = r.width;
189
+ if (left + width > window.innerWidth - 8) {
190
+ left = Math.max(8, window.innerWidth - width - 8);
191
+ }
192
+
193
+ pos.value = {
194
+ top,
195
+ left,
196
+ width,
197
+ openUp,
198
+ maxHeight: calculatedMaxHeight,
199
+ };
200
+ };
201
+
202
+ const handleWindowClick = (e: MouseEvent) => {
203
+ if (!open.value) return;
204
+ const target = e.target as Node;
205
+ const inTrigger = wrapRef.value && wrapRef.value.contains(target);
206
+ const inDrop = dropRef.value && dropRef.value.contains(target);
207
+ if (!inTrigger && !inDrop) {
208
+ open.value = false;
209
+ emit("visible-change", false);
210
+ }
211
+ };
212
+
213
+ watch(open, (isOpen) => {
214
+ if (isOpen) {
215
+ calcPosition();
216
+ window.addEventListener("scroll", calcPosition, true);
217
+ window.addEventListener("resize", calcPosition);
218
+ document.addEventListener("mousedown", handleWindowClick, true);
219
+ nextTick(() => {
220
+ calcPosition();
221
+ if ((props.searchable || props.creatable) && inputRef.value) {
222
+ inputRef.value.focus();
223
+ }
224
+ });
225
+ } else {
226
+ query.value = "";
227
+ window.removeEventListener("scroll", calcPosition, true);
228
+ window.removeEventListener("resize", calcPosition);
229
+ document.removeEventListener("mousedown", handleWindowClick, true);
230
+ }
231
+ });
232
+
233
+ onBeforeUnmount(() => {
234
+ window.removeEventListener("scroll", calcPosition, true);
235
+ window.removeEventListener("resize", calcPosition);
236
+ document.removeEventListener("mousedown", handleWindowClick, true);
237
+ });
238
+
239
+ const filtered = computed(() => {
240
+ if (!query.value) return props.options;
241
+ const q = query.value.toLowerCase().trim();
242
+ if (typeof props.filterOption === "function") {
243
+ return props.options.filter((o) => props.filterOption!(o, q));
244
+ }
245
+ const isStartsWith = props.searchMode === "starts_with" || props.searchMode === "word_start";
246
+ return props.options.filter((o) => {
247
+ const targets: string[] = [];
248
+ if (o.label != null) targets.push(String(o.label));
249
+ if (o.name != null && o.name !== o.label) targets.push(String(o.name));
250
+ if (o.search != null) targets.push(String(o.search));
251
+ if (o.employee_id != null) targets.push(String(o.employee_id));
252
+ if (o.code != null) targets.push(String(o.code));
253
+ if (o.title != null) targets.push(String(o.title));
254
+ if (targets.length === 0 && o.value != null) {
255
+ targets.push(String(o.value));
256
+ }
257
+
258
+ for (const t of targets) {
259
+ const str = t.toLowerCase().trim();
260
+ if (isStartsWith) {
261
+ if (str.startsWith(q)) return true;
262
+ const words = str.split(/[\s\-.,()\/_:]+/);
263
+ if (words.some((w) => w.startsWith(q))) return true;
264
+ } else {
265
+ if (str.includes(q)) return true;
266
+ }
267
+ }
268
+ return false;
269
+ });
270
+ });
271
+
272
+ function handleOpen() {
273
+ if (props.disabled) return;
274
+ if (!open.value && wrapRef.value) {
275
+ calcPosition();
276
+ }
277
+ open.value = !open.value;
278
+ emit("visible-change", open.value);
279
+ }
280
+
281
+ function toggleOption(optVal: any) {
282
+ if (props.multi) {
283
+ const arr = Array.isArray(currentVal.value) ? currentVal.value : [];
284
+ const next = arr.includes(optVal)
285
+ ? arr.filter((v: any) => v !== optVal)
286
+ : [...arr, optVal];
287
+ emit("update:modelValue", next);
288
+ emit("change", next);
289
+ } else {
290
+ emit("update:modelValue", optVal);
291
+ emit("change", optVal);
292
+ open.value = false;
293
+ emit("visible-change", false);
294
+ }
295
+ }
296
+
297
+ function removeTag(e: MouseEvent, valToRemove: any) {
298
+ e.stopPropagation();
299
+ const arr = Array.isArray(currentVal.value) ? currentVal.value : [];
300
+ const next = arr.filter((v: any) => v !== valToRemove);
301
+ emit("update:modelValue", next);
302
+ emit("change", next);
303
+ }
304
+
305
+ function handleClear(e: MouseEvent) {
306
+ e.stopPropagation();
307
+ const next = props.multi ? [] : "";
308
+ emit("update:modelValue", next);
309
+ emit("change", next);
310
+ emit("clear");
311
+ }
312
+
313
+ function handleInputKeyDown(e: KeyboardEvent) {
314
+ if (e.key === "Enter") {
315
+ e.preventDefault();
316
+ e.stopPropagation();
317
+ const trimmed = query.value.trim();
318
+ if (!trimmed) return;
319
+ const exact = filtered.value.find(
320
+ (o) => String(o.label || o.value || "").toLowerCase() === trimmed.toLowerCase()
321
+ );
322
+ if (exact && !exact.disabled) {
323
+ toggleOption(exact.value);
324
+ query.value = "";
325
+ } else if (props.creatable) {
326
+ toggleOption(trimmed);
327
+ query.value = "";
328
+ } else if (filtered.value.length > 0 && !filtered.value[0].disabled) {
329
+ toggleOption(filtered.value[0].value);
330
+ query.value = "";
331
+ }
332
+ }
333
+ }
334
+ </script>
335
+
336
+ <template>
337
+ <div
338
+ ref="wrapRef"
339
+ :style="{
340
+ position: 'relative',
341
+ width: '100%',
342
+ boxSizing: 'border-box',
343
+ fontFamily: 'var(--f,var(--sans, inherit))',
344
+ opacity: disabled ? 0.5 : 1,
345
+ pointerEvents: disabled ? 'none' : undefined,
346
+ ...style,
347
+ }"
348
+ >
349
+ <div
350
+ :style="{
351
+ width: '100%',
352
+ boxSizing: 'border-box',
353
+ display: 'flex',
354
+ alignItems: 'center',
355
+ justifyContent: 'space-between',
356
+ gap: '4px',
357
+ padding: `0 ${s.paddingH}px`,
358
+ height: `${s.height}px`,
359
+ background: 'var(--sf,var(--surface, #ffffff))',
360
+ border: `1px solid ${open ? 'var(--accent, #f99e2c)' : 'var(--bd2,var(--border, #e2e8f0))'}`,
361
+ borderRadius: radiusVal,
362
+ cursor: 'pointer',
363
+ fontFamily: 'var(--f,var(--sans,inherit))',
364
+ fontSize: `${s.fontSize}px`,
365
+ lineHeight: 1.2,
366
+ color: 'var(--t,var(--primary-text, #0f172a))',
367
+ outline: 'none',
368
+ transition: 'border-color var(--tr,.15s), box-shadow var(--tr,.15s)',
369
+ boxShadow: open
370
+ ? 'var(--infd, rgba(37,99,235,.1))'
371
+ : 'var(--shadow,0 1px 3px rgba(20,22,41,.1))',
372
+ }"
373
+ @click="handleOpen"
374
+ >
375
+ <slot v-if="slots.prefix" name="prefix" />
376
+
377
+ <div
378
+ style="
379
+ flex: 1;
380
+ min-width: 0;
381
+ display: flex;
382
+ align-items: center;
383
+ "
384
+ >
385
+ <slot
386
+ v-if="slots.label && selectedValues.length > 0"
387
+ name="label"
388
+ :value="multi ? selectedValues : selectedValues[0]"
389
+ />
390
+
391
+ <template v-else-if="multi && selectedValues.length > 0">
392
+ <span
393
+ v-if="showCount"
394
+ style="
395
+ font-weight: 500;
396
+ white-space: nowrap;
397
+ overflow: hidden;
398
+ text-overflow: ellipsis;
399
+ color: var(--t,var(--primary-text, #0f172a));
400
+ "
401
+ >
402
+ {{
403
+ typeof showCount === "function"
404
+ ? showCount(selectedValues.length)
405
+ : selectedValues.length === 1
406
+ ? getLabel(selectedValues[0])
407
+ : `Выбрано: ${selectedValues.length}`
408
+ }}
409
+ </span>
410
+ <div
411
+ v-else
412
+ style="
413
+ display: flex;
414
+ flex-wrap: wrap;
415
+ gap: 4px;
416
+ flex: 1;
417
+ min-width: 0;
418
+ "
419
+ >
420
+ <span
421
+ v-for="v in selectedValues"
422
+ :key="v"
423
+ :style="{
424
+ display: 'inline-flex',
425
+ alignItems: 'center',
426
+ gap: '4px',
427
+ padding: '2px 6px',
428
+ background: 'var(--pd,var(--accent-bg, rgba(249,158,44,.1)))',
429
+ border: '1px solid var(--accent, #f99e2c)',
430
+ borderRadius: '4px',
431
+ fontSize: '11px',
432
+ fontWeight: 600,
433
+ color: 'var(--accent, #f99e2c)',
434
+ whiteSpace: 'nowrap',
435
+ maxWidth: '140px',
436
+ opacity: 0.85,
437
+ }"
438
+ >
439
+ <span style="overflow: hidden; text-overflow: ellipsis">
440
+ {{ getLabel(v) }}
441
+ </span>
442
+ <X
443
+ :size="11"
444
+ style="cursor: pointer; flex-shrink: 0; opacity: 0.5"
445
+ @mousedown="removeTag($event, v)"
446
+ />
447
+ </span>
448
+ </div>
449
+ </template>
450
+
451
+ <span
452
+ v-else-if="!multi && selectedValues.length > 0"
453
+ style="
454
+ font-weight: 400;
455
+ white-space: nowrap;
456
+ overflow: hidden;
457
+ text-overflow: ellipsis;
458
+ "
459
+ >
460
+ {{ getLabel(selectedValues[0]) || "&nbsp;" }}
461
+ </span>
462
+
463
+ <span
464
+ v-else
465
+ style="
466
+ color: var(--t3,var(--grey-3, #cbd5e1));
467
+ font-weight: 400;
468
+ "
469
+ >
470
+ {{ placeholder }}
471
+ </span>
472
+ </div>
473
+
474
+ <div style="display: flex; align-items: center; gap: 4px; flex-shrink: 0">
475
+ <slot v-if="slots.suffix" name="suffix" />
476
+
477
+ <span
478
+ v-if="
479
+ selectedValues.length > 0 &&
480
+ selectedValues[0] !== '' &&
481
+ selectedValues[0] !== null &&
482
+ clearable
483
+ "
484
+ title="Сбросить выбор"
485
+ style="
486
+ display: inline-flex;
487
+ align-items: center;
488
+ justify-content: center;
489
+ cursor: pointer;
490
+ color: var(--muted, #64748b);
491
+ padding: 0 2px;
492
+ flex-shrink: 0;
493
+ border-radius: var(--radxs, 4px);
494
+ "
495
+ @mouseenter="($event.currentTarget as HTMLElement).style.color = 'var(--danger, #ef4444)'"
496
+ @mouseleave="($event.currentTarget as HTMLElement).style.color = 'var(--muted, #64748b)'"
497
+ @click="handleClear"
498
+ >
499
+ <X :size="12" />
500
+ </span>
501
+
502
+ <ChevronDown
503
+ v-else
504
+ :size="12"
505
+ :style="{
506
+ flexShrink: 0,
507
+ color: 'var(--t3,var(--grey-3, #cbd5e1))',
508
+ transition: 'transform .2s ease',
509
+ transform: open ? 'rotate(180deg)' : undefined,
510
+ }"
511
+ />
512
+ </div>
513
+ </div>
514
+
515
+ <!-- Teleport dropdown to body -->
516
+ <Teleport to="body">
517
+ <div
518
+ v-if="open"
519
+ ref="dropRef"
520
+ :style="{
521
+ position: 'fixed',
522
+ zIndex: 9000,
523
+ top: `${pos.top}px`,
524
+ left: `${pos.left}px`,
525
+ width: `${pos.width}px`,
526
+ boxSizing: 'border-box',
527
+ background: 'var(--sf,var(--surface, #ffffff))',
528
+ border: '1px solid var(--bd,var(--border, #e2e8f0))',
529
+ borderRadius: radiusVal,
530
+ boxShadow: 'var(--shadow,0 1px 3px rgba(20,22,41,.1))',
531
+ padding: '3px',
532
+ animation: `${pos.openUp ? 'selFadeInUp' : 'selFadeInDown'} .15s ease`,
533
+ overflow: 'hidden',
534
+ }"
535
+ >
536
+ <slot v-if="slots.header" name="header" />
537
+
538
+ <!-- Search input if searchable or creatable -->
539
+ <div
540
+ v-if="searchable || creatable"
541
+ style="padding: 3px"
542
+ >
543
+ <input
544
+ ref="inputRef"
545
+ v-model="query"
546
+ type="text"
547
+ :placeholder="creatable ? 'Поиск или ввод текста...' : 'Поиск...'"
548
+ :style="{
549
+ width: '100%',
550
+ boxSizing: 'border-box',
551
+ padding: s.height <= 26 ? '2px 5px' : s.height <= 28 ? '3px 6px' : '7px 10px',
552
+ height: `${s.searchHeight}px`,
553
+ background: 'var(--sf2,var(--grey-1, #f5f5f8))',
554
+ border: '1.5px solid var(--bd2,var(--border, #e2e8f0))',
555
+ borderRadius: s.height <= 26 ? '3px' : s.height <= 28 ? '4px' : 'var(--rmd,var(--rads, 8px))',
556
+ fontFamily: 'var(--f,var(--sans, inherit))',
557
+ fontSize: `${s.searchFont}px`,
558
+ lineHeight: 1,
559
+ color: 'var(--t,var(--primary-text, #0f172a))',
560
+ outline: 'none',
561
+ transition: 'border-color var(--tr,.15s), box-shadow var(--tr,.15s)',
562
+ }"
563
+ @keydown="handleInputKeyDown"
564
+ @mousedown.stop
565
+ />
566
+ </div>
567
+
568
+ <div
569
+ :style="{
570
+ maxHeight: `${pos.maxHeight || 200}px`,
571
+ overflowY: 'auto',
572
+ }"
573
+ >
574
+ <!-- Creatable option row -->
575
+ <div
576
+ v-if="
577
+ creatable &&
578
+ query.trim() &&
579
+ !options.some(
580
+ (o) => String(o.label || o.value || '').toLowerCase() === query.trim().toLowerCase()
581
+ )
582
+ "
583
+ :style="{
584
+ display: 'flex',
585
+ alignItems: 'center',
586
+ gap: '6px',
587
+ minHeight: `${s.itemMinHeight}px`,
588
+ padding: s.itemPadding,
589
+ borderRadius: s.height <= 26 ? '3px' : s.height <= 28 ? '4px' : 'var(--rmd,var(--rads, 8px))',
590
+ cursor: 'pointer',
591
+ color: 'var(--accent, #f99e2c)',
592
+ background: 'var(--pd,var(--accent-bg, rgba(249,158,44,.08)))',
593
+ fontSize: `${s.searchFont}px`,
594
+ fontWeight: 600,
595
+ marginBottom: filtered.length > 0 ? '3px' : '0px',
596
+ userSelect: 'none',
597
+ }"
598
+ @mousedown.prevent.stop="
599
+ () => {
600
+ toggleOption(query.trim());
601
+ query = '';
602
+ }
603
+ "
604
+ >
605
+ <Plus :size="12" style="flex-shrink: 0" />
606
+ <span
607
+ style="
608
+ flex: 1;
609
+ white-space: nowrap;
610
+ overflow: hidden;
611
+ text-overflow: ellipsis;
612
+ "
613
+ >
614
+ {{
615
+ typeof createLabel === "function"
616
+ ? createLabel(query.trim())
617
+ : `Ввести: "${query.trim()}"`
618
+ }}
619
+ </span>
620
+ </div>
621
+
622
+ <!-- Empty state -->
623
+ <div
624
+ v-if="filtered.length === 0 && (!creatable || !query.trim())"
625
+ style="
626
+ padding: 10px 8px;
627
+ text-align: center;
628
+ color: var(--t3,var(--grey-3, #cbd5e1));
629
+ font-size: 11px;
630
+ "
631
+ >
632
+ Ничего не найдено
633
+ </div>
634
+
635
+ <!-- Option items -->
636
+ <div
637
+ v-for="opt in filtered"
638
+ :key="opt.value"
639
+ :style="{
640
+ display: 'flex',
641
+ alignItems: 'center',
642
+ gap: '6px',
643
+ minHeight: `${s.itemMinHeight}px`,
644
+ padding: s.itemPadding,
645
+ borderRadius: s.height <= 26 ? '3px' : s.height <= 28 ? '4px' : 'var(--rmd,var(--rads, 8px))',
646
+ cursor: opt.disabled ? 'not-allowed' : 'pointer',
647
+ color: isSelected(opt.value)
648
+ ? 'var(--accent, #f99e2c)'
649
+ : 'var(--t,var(--primary-text, #0f172a))',
650
+ fontSize: `${s.searchFont}px`,
651
+ fontWeight: 400,
652
+ transition: 'background .1s',
653
+ userSelect: 'none',
654
+ opacity: opt.disabled ? 0.4 : 1,
655
+ pointerEvents: opt.disabled ? 'none' : undefined,
656
+ }"
657
+ @mouseenter="
658
+ (e) => {
659
+ if (!opt.disabled)
660
+ (e.currentTarget as HTMLElement).style.background = 'var(--grey-1, #f5f5f8)';
661
+ }
662
+ "
663
+ @mouseleave="
664
+ (e) => {
665
+ if (!opt.disabled)
666
+ (e.currentTarget as HTMLElement).style.background = 'transparent';
667
+ }
668
+ "
669
+ @mousedown.prevent.stop="
670
+ () => {
671
+ if (!opt.disabled) toggleOption(opt.value);
672
+ }
673
+ "
674
+ >
675
+ <slot
676
+ v-if="slots.option"
677
+ name="option"
678
+ :option="opt"
679
+ :selected="isSelected(opt.value)"
680
+ />
681
+ <span
682
+ v-else
683
+ style="
684
+ flex: 1;
685
+ white-space: nowrap;
686
+ overflow: hidden;
687
+ text-overflow: ellipsis;
688
+ min-height: 1em;
689
+ "
690
+ >
691
+ {{ opt.displayLabel || opt.label || "&nbsp;" }}
692
+ </span>
693
+
694
+ <Check
695
+ v-if="isSelected(opt.value)"
696
+ :size="13"
697
+ style="flex-shrink: 0; color: var(--accent, #f99e2c)"
698
+ />
699
+ </div>
700
+ </div>
701
+
702
+ <slot v-if="slots.footer" name="footer" />
703
+ </div>
704
+ </Teleport>
705
+ </div>
706
+ </template>