perf: 优化节点下拉框UI
This commit is contained in:
@@ -3,13 +3,14 @@
|
|||||||
import {createFloating} from '../utils/createFloating';
|
import {createFloating} from '../utils/createFloating';
|
||||||
import type {Placement} from '@floating-ui/dom';
|
import type {Placement} from '@floating-ui/dom';
|
||||||
|
|
||||||
const { children, floating, placement = 'bottom', onShow, onHide }:
|
const { children, floating, placement = 'bottom', onShow, onHide, syncWidth = false }:
|
||||||
{
|
{
|
||||||
children: Snippet,
|
children: Snippet,
|
||||||
floating: Snippet,
|
floating: Snippet,
|
||||||
placement?: Placement,
|
placement?: Placement,
|
||||||
onShow?: () => void,
|
onShow?: () => void,
|
||||||
onHide?: () => void
|
onHide?: () => void,
|
||||||
|
syncWidth?: boolean
|
||||||
} = $props();
|
} = $props();
|
||||||
|
|
||||||
let triggerEl!: HTMLDivElement, contentEl!: HTMLDivElement;
|
let triggerEl!: HTMLDivElement, contentEl!: HTMLDivElement;
|
||||||
@@ -22,7 +23,8 @@
|
|||||||
interactive: true,
|
interactive: true,
|
||||||
placement,
|
placement,
|
||||||
onShow,
|
onShow,
|
||||||
onHide
|
onHide,
|
||||||
|
syncWidth
|
||||||
});
|
});
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
|
|||||||
@@ -179,7 +179,7 @@
|
|||||||
{/snippet}
|
{/snippet}
|
||||||
|
|
||||||
<div {...rest} class="tf-select {rest['class']}">
|
<div {...rest} class="tf-select {rest['class']}">
|
||||||
<FloatingTrigger bind:this={triggerObject} onShow={() => isOpen = true} onHide={() => { isOpen = false; hoveredItem = null; }}>
|
<FloatingTrigger bind:this={triggerObject} onShow={() => isOpen = true} onHide={() => { isOpen = false; hoveredItem = null; }} syncWidth={true}>
|
||||||
<button class="tf-select-input nopan nodrag {isOpen ? 'active' : ''}" {...rest}>
|
<button class="tf-select-input nopan nodrag {isOpen ? 'active' : ''}" {...rest}>
|
||||||
<div class="tf-select-input-value">
|
<div class="tf-select-input-value">
|
||||||
{#each activeItemsState as item, index (`${index}_${item.value}`)}
|
{#each activeItemsState as item, index (`${index}_${item.value}`)}
|
||||||
@@ -303,11 +303,10 @@
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
background: #fff;
|
background: #fff;
|
||||||
margin-top: 5px;
|
margin-top: 5px;
|
||||||
border: 1px solid #ccc;
|
border: 1px solid #e5e7eb;
|
||||||
border-radius: 5px;
|
border-radius: 6px;
|
||||||
padding: 5px;
|
padding: 4px;
|
||||||
width: max-content;
|
width: 100%;
|
||||||
min-width: 100%;
|
|
||||||
z-index: 99999;
|
z-index: 99999;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
max-height: 220px;
|
max-height: 220px;
|
||||||
@@ -318,20 +317,20 @@
|
|||||||
.tf-select-default-item {
|
.tf-select-default-item {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
padding: 5px 10px;
|
padding: 6px 12px;
|
||||||
border: none;
|
border: none;
|
||||||
background: #fff;
|
background: #fff;
|
||||||
border-radius: 5px;
|
border-radius: 4px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
line-height: 100%;
|
line-height: 1.5;
|
||||||
gap: 2px;
|
gap: 2px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
color: #333;
|
color: #111827;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background: #f0f0f0;
|
background: #f5f5f7;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -347,11 +346,11 @@
|
|||||||
background: #fff;
|
background: #fff;
|
||||||
margin-top: 5px;
|
margin-top: 5px;
|
||||||
border: 1px solid #e5e7eb;
|
border: 1px solid #e5e7eb;
|
||||||
border-radius: 10px; /* slightly smaller radius */
|
border-radius: 10px;
|
||||||
width: 280px; /* Reduced width to be more compact */
|
/* Removed fixed width to allow syncWidth to control it */
|
||||||
z-index: 99999;
|
z-index: 99999;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
max-height: 400px; /* slightly less tall */
|
max-height: 400px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
box-shadow: 0 8px 20px rgba(0, 0, 0, 0.12);
|
box-shadow: 0 8px 20px rgba(0, 0, 0, 0.12);
|
||||||
}
|
}
|
||||||
@@ -360,13 +359,13 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
padding: 10px 12px; /* tighter padding */
|
padding: 10px 12px;
|
||||||
border-bottom: 1px solid #f3f4f6;
|
border-bottom: 1px solid #f3f4f6;
|
||||||
background: #fff;
|
background: #fff;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tf-select-model-header-title {
|
.tf-select-model-header-title {
|
||||||
font-size: 13px; /* smaller title */
|
font-size: 13px;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
color: #111827;
|
color: #111827;
|
||||||
}
|
}
|
||||||
@@ -381,13 +380,13 @@
|
|||||||
.tf-select-model-list {
|
.tf-select-model-list {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
padding: 8px; /* tighter padding */
|
padding: 8px;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
gap: 4px; /* tighter gap between items */
|
gap: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tf-select-model-group-title {
|
.tf-select-model-group-title {
|
||||||
font-size: 12px; /* smaller group title */
|
font-size: 12px;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
color: #6b7280;
|
color: #6b7280;
|
||||||
margin-top: 6px;
|
margin-top: 6px;
|
||||||
@@ -399,7 +398,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.tf-select-model-group-icon {
|
.tf-select-model-group-icon {
|
||||||
width: 14px; /* smaller icon */
|
width: 14px;
|
||||||
height: 14px;
|
height: 14px;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@@ -420,7 +419,7 @@
|
|||||||
.tf-select-model-item {
|
.tf-select-model-item {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
padding: 6px 8px; /* tighter item padding */
|
padding: 6px 8px;
|
||||||
border: none;
|
border: none;
|
||||||
background: transparent;
|
background: transparent;
|
||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
@@ -440,7 +439,7 @@
|
|||||||
|
|
||||||
.tf-select-model-icon {
|
.tf-select-model-icon {
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
width: 32px; /* smaller item icon */
|
width: 32px;
|
||||||
height: 32px;
|
height: 32px;
|
||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
@@ -452,7 +451,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.tf-select-model-icon :global(svg), .tf-select-model-icon img {
|
.tf-select-model-icon :global(svg), .tf-select-model-icon img {
|
||||||
width: 70%; /* icon inside box */
|
width: 70%;
|
||||||
height: 70%;
|
height: 70%;
|
||||||
object-fit: contain;
|
object-fit: contain;
|
||||||
}
|
}
|
||||||
@@ -466,13 +465,13 @@
|
|||||||
.tf-select-model-info {
|
.tf-select-model-info {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 2px; /* tighter info gap */
|
gap: 2px;
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tf-select-model-title {
|
.tf-select-model-title {
|
||||||
font-size: 13px; /* smaller font */
|
font-size: 13px;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
color: #111827;
|
color: #111827;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
@@ -489,18 +488,18 @@
|
|||||||
.tf-select-model-tag {
|
.tf-select-model-tag {
|
||||||
background: #f3f4f6;
|
background: #f3f4f6;
|
||||||
color: #6b7280;
|
color: #6b7280;
|
||||||
font-size: 10px; /* smaller tag */
|
font-size: 10px;
|
||||||
padding: 1px 6px;
|
padding: 1px 6px;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tf-select-model-desc {
|
.tf-select-model-desc {
|
||||||
font-size: 11px; /* smaller desc */
|
font-size: 11px;
|
||||||
color: #6b7280;
|
color: #6b7280;
|
||||||
line-height: 1.3;
|
line-height: 1.3;
|
||||||
margin-top: 2px;
|
margin-top: 2px;
|
||||||
display: -webkit-box;
|
display: -webkit-box;
|
||||||
-webkit-line-clamp: 2; /* limit lines */
|
-webkit-line-clamp: 2;
|
||||||
-webkit-box-orient: vertical;
|
-webkit-box-orient: vertical;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
@@ -513,11 +512,10 @@
|
|||||||
box-shadow: 0 10px 25px rgba(0, 0, 0, 0.15);
|
box-shadow: 0 10px 25px rgba(0, 0, 0, 0.15);
|
||||||
border: 1px solid #e5e7eb;
|
border: 1px solid #e5e7eb;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
min-width: 220px;
|
/* width expands naturally since createFloating sets minWidth, not width */
|
||||||
width: max-content;
|
width: max-content;
|
||||||
max-width: 600px;
|
box-sizing: border-box;
|
||||||
max-height: 480px;
|
max-height: 480px;
|
||||||
transition: width 0.2s ease;
|
|
||||||
z-index: 99999;
|
z-index: 99999;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -528,22 +526,27 @@
|
|||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
|
|
||||||
&.tf-select-primary-list {
|
&.tf-select-primary-list {
|
||||||
width: 220px;
|
width: 100%; /* Default fills the wrapper, which is minWidth-constrained by the input */
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
background: #f9fafb;
|
background: #f9fafb;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* When a secondary list is open, we fix the primary list to 100% of the input's width (done via absolute positioning or just keeping it wide enough) */
|
||||||
|
.tf-select-wrapper:has(.tf-select-secondary-list) &.tf-select-primary-list {
|
||||||
|
/* Let it take the width of the input minus borders/paddings if needed, but minWidth handles it mostly */
|
||||||
|
width: auto;
|
||||||
|
min-width: 180px;
|
||||||
|
}
|
||||||
|
|
||||||
&.tf-select-secondary-list {
|
&.tf-select-secondary-list {
|
||||||
flex-grow: 1;
|
min-width: 220px;
|
||||||
min-width: 200px;
|
|
||||||
background: #fff;
|
background: #fff;
|
||||||
padding: 12px;
|
padding: 12px;
|
||||||
border-left: 1px solid #f3f4f6;
|
border-left: 1px solid #f3f4f6;
|
||||||
animation: slideIn 0.2s ease-out;
|
animation: slideIn 0.2s ease-out;
|
||||||
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
}
|
} @keyframes slideIn {
|
||||||
|
|
||||||
@keyframes slideIn {
|
|
||||||
from { opacity: 0; transform: translateX(-10px); }
|
from { opacity: 0; transform: translateX(-10px); }
|
||||||
to { opacity: 1; transform: translateX(0); }
|
to { opacity: 1; transform: translateX(0); }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,8 @@ import {
|
|||||||
type OffsetOptions,
|
type OffsetOptions,
|
||||||
type Placement,
|
type Placement,
|
||||||
shift,
|
shift,
|
||||||
type ShiftOptions
|
type ShiftOptions,
|
||||||
|
size
|
||||||
} from '@floating-ui/dom';
|
} from '@floating-ui/dom';
|
||||||
|
|
||||||
export type FloatingOptions = {
|
export type FloatingOptions = {
|
||||||
@@ -21,6 +22,7 @@ export type FloatingOptions = {
|
|||||||
showArrow?: boolean;
|
showArrow?: boolean;
|
||||||
onShow?: () => void;
|
onShow?: () => void;
|
||||||
onHide?: () => void;
|
onHide?: () => void;
|
||||||
|
syncWidth?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type FloatingInstance = {
|
export type FloatingInstance = {
|
||||||
@@ -40,7 +42,8 @@ export const createFloating = ({
|
|||||||
interactive,
|
interactive,
|
||||||
showArrow,
|
showArrow,
|
||||||
onShow,
|
onShow,
|
||||||
onHide
|
onHide,
|
||||||
|
syncWidth = false
|
||||||
}: FloatingOptions): FloatingInstance => {
|
}: FloatingOptions): FloatingInstance => {
|
||||||
if (typeof trigger === 'string') {
|
if (typeof trigger === 'string') {
|
||||||
const triggerEl = document.querySelector(trigger);
|
const triggerEl = document.querySelector(trigger);
|
||||||
@@ -83,7 +86,14 @@ export const createFloating = ({
|
|||||||
offset(offsetOptions), // 手动偏移配置
|
offset(offsetOptions), // 手动偏移配置
|
||||||
// flip(flipOptions), // 注释掉自动翻转,强制向下弹出,避免遮挡顶部工具栏
|
// flip(flipOptions), // 注释掉自动翻转,强制向下弹出,避免遮挡顶部工具栏
|
||||||
shift(shiftOptions), //自动偏移(使得浮动元素能够进入视野)
|
shift(shiftOptions), //自动偏移(使得浮动元素能够进入视野)
|
||||||
...(showArrow ? [arrow({ element: arrowElement })] : [])
|
...(showArrow ? [arrow({ element: arrowElement })] : []),
|
||||||
|
...(syncWidth ? [size({
|
||||||
|
apply({ rects, elements }) {
|
||||||
|
Object.assign(elements.floating.style, {
|
||||||
|
minWidth: `${rects.reference.width}px`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})] : [])
|
||||||
]
|
]
|
||||||
}).then(({ x, y, placement, middlewareData }) => {
|
}).then(({ x, y, placement, middlewareData }) => {
|
||||||
Object.assign(floating.style, {
|
Object.assign(floating.style, {
|
||||||
|
|||||||
Reference in New Issue
Block a user