@farmzone/fz-react-ui 1.1.1 → 1.1.3
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/README.md +85 -58
- package/dist/index.cjs +484 -170
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +28 -1
- package/dist/index.d.ts +28 -1
- package/dist/index.js +484 -170
- package/dist/index.js.map +1 -1
- package/dist/styles.css +1 -1
- package/package.json +4 -3
package/dist/index.js
CHANGED
|
@@ -7887,7 +7887,8 @@ function PageFilter(props) {
|
|
|
7887
7887
|
onChange: handleInputChange(optionKey),
|
|
7888
7888
|
onKeyDown: (e) => {
|
|
7889
7889
|
if (e.key === "Enter") handleSubmit();
|
|
7890
|
-
}
|
|
7890
|
+
},
|
|
7891
|
+
leftIcon: option.leftIcon !== false ? /* @__PURE__ */ jsx(Search, { size: 16 }) : void 0
|
|
7891
7892
|
}
|
|
7892
7893
|
) }, String(optionKey));
|
|
7893
7894
|
case "select":
|
|
@@ -8050,6 +8051,122 @@ function easedFade(dir, opacity) {
|
|
|
8050
8051
|
const c = (t) => `rgba(0,0,0,${(opacity * t).toFixed(3)})`;
|
|
8051
8052
|
return `linear-gradient(${dir}, ${c(1)} 0%, ${c(0.72)} 20%, ${c(0.38)} 45%, ${c(0.1)} 70%, transparent 100%)`;
|
|
8052
8053
|
}
|
|
8054
|
+
function clusterTabs(tabs, getTabGroup) {
|
|
8055
|
+
const clusters = [];
|
|
8056
|
+
const clusterByGroupKey = /* @__PURE__ */ new Map();
|
|
8057
|
+
for (const tab of tabs) {
|
|
8058
|
+
const group = getTabGroup(tab) ?? null;
|
|
8059
|
+
if (!group) {
|
|
8060
|
+
clusters.push({ group: null, tabs: [tab] });
|
|
8061
|
+
continue;
|
|
8062
|
+
}
|
|
8063
|
+
const existing = clusterByGroupKey.get(group.key);
|
|
8064
|
+
if (existing) {
|
|
8065
|
+
existing.tabs.push(tab);
|
|
8066
|
+
} else {
|
|
8067
|
+
const cluster = { group, tabs: [tab] };
|
|
8068
|
+
clusterByGroupKey.set(group.key, cluster);
|
|
8069
|
+
clusters.push(cluster);
|
|
8070
|
+
}
|
|
8071
|
+
}
|
|
8072
|
+
return clusters;
|
|
8073
|
+
}
|
|
8074
|
+
var TAB_PALETTE = [
|
|
8075
|
+
{ base: "rgb(38, 117, 226)", bg: "rgba(38, 117, 226, 0.12)", border: "rgba(38, 117, 226, 0.4)" },
|
|
8076
|
+
// blue
|
|
8077
|
+
{ base: "rgb(249, 115, 22)", bg: "rgba(249, 115, 22, 0.12)", border: "rgba(249, 115, 22, 0.4)" },
|
|
8078
|
+
// orange
|
|
8079
|
+
{ base: "rgb(239, 68, 68)", bg: "rgba(239, 68, 68, 0.12)", border: "rgba(239, 68, 68, 0.4)" },
|
|
8080
|
+
// red
|
|
8081
|
+
{ base: "rgb(34, 197, 94)", bg: "rgba(34, 197, 94, 0.12)", border: "rgba(34, 197, 94, 0.4)" },
|
|
8082
|
+
// green
|
|
8083
|
+
{ base: "rgb(168, 85, 247)", bg: "rgba(168, 85, 247, 0.12)", border: "rgba(168, 85, 247, 0.4)" },
|
|
8084
|
+
// purple
|
|
8085
|
+
{ base: "rgb(245, 158, 11)", bg: "rgba(245, 158, 11, 0.12)", border: "rgba(245, 158, 11, 0.4)" },
|
|
8086
|
+
// amber
|
|
8087
|
+
{ base: "rgb(236, 72, 153)", bg: "rgba(236, 72, 153, 0.12)", border: "rgba(236, 72, 153, 0.4)" },
|
|
8088
|
+
// pink
|
|
8089
|
+
{ base: "rgb(20, 184, 166)", bg: "rgba(20, 184, 166, 0.12)", border: "rgba(20, 184, 166, 0.4)" },
|
|
8090
|
+
// teal
|
|
8091
|
+
{ base: "rgb(99, 102, 241)", bg: "rgba(99, 102, 241, 0.12)", border: "rgba(99, 102, 241, 0.4)" },
|
|
8092
|
+
// indigo
|
|
8093
|
+
{ base: "rgb(6, 182, 212)", bg: "rgba(6, 182, 212, 0.12)", border: "rgba(6, 182, 212, 0.4)" }
|
|
8094
|
+
// cyan
|
|
8095
|
+
];
|
|
8096
|
+
function assignGroupColors(groupKeys, existing) {
|
|
8097
|
+
const missing = groupKeys.filter((key) => !existing.has(key));
|
|
8098
|
+
if (missing.length === 0) return existing;
|
|
8099
|
+
const next = new Map(existing);
|
|
8100
|
+
for (const key of missing) {
|
|
8101
|
+
if (next.has(key)) continue;
|
|
8102
|
+
const used = new Set([...next.values()].map((c) => c.bg));
|
|
8103
|
+
const free = TAB_PALETTE.find((c) => !used.has(c.bg)) ?? TAB_PALETTE[next.size % TAB_PALETTE.length];
|
|
8104
|
+
next.set(key, free ?? TAB_PALETTE[0]);
|
|
8105
|
+
}
|
|
8106
|
+
return next;
|
|
8107
|
+
}
|
|
8108
|
+
function MultiTabPill(props) {
|
|
8109
|
+
const {
|
|
8110
|
+
tab,
|
|
8111
|
+
isActive,
|
|
8112
|
+
showClose,
|
|
8113
|
+
accentColor,
|
|
8114
|
+
style: s,
|
|
8115
|
+
onClick,
|
|
8116
|
+
onClose,
|
|
8117
|
+
registerRef,
|
|
8118
|
+
draggable = false,
|
|
8119
|
+
onPointerDown,
|
|
8120
|
+
dropIndicator = null,
|
|
8121
|
+
isDraggingThis = false
|
|
8122
|
+
} = props;
|
|
8123
|
+
return /* @__PURE__ */ jsxs(
|
|
8124
|
+
"div",
|
|
8125
|
+
{
|
|
8126
|
+
ref: (el) => registerRef(tab.id, el),
|
|
8127
|
+
onPointerDown: draggable ? onPointerDown : void 0,
|
|
8128
|
+
onClick: () => {
|
|
8129
|
+
if (!isActive) onClick();
|
|
8130
|
+
},
|
|
8131
|
+
className: `
|
|
8132
|
+
group relative flex items-center gap-1.5 ${s.tabPx} ${s.tabPy} flex-shrink-0
|
|
8133
|
+
${s.rounded} border border-b-0 select-none
|
|
8134
|
+
transition-colors duration-100
|
|
8135
|
+
${isActive ? s.activeTab : s.inactiveTab}
|
|
8136
|
+
${draggable ? isDraggingThis ? "cursor-grabbing" : "cursor-grab" : "cursor-pointer"}
|
|
8137
|
+
${isDraggingThis ? "opacity-40" : ""}
|
|
8138
|
+
${dropIndicator === "before" ? "[box-shadow:inset_2px_0_0_0_#3b82f6]" : ""}
|
|
8139
|
+
${dropIndicator === "after" ? "[box-shadow:inset_-2px_0_0_0_#3b82f6]" : ""}
|
|
8140
|
+
`,
|
|
8141
|
+
children: [
|
|
8142
|
+
isActive && /* @__PURE__ */ jsx(
|
|
8143
|
+
"div",
|
|
8144
|
+
{
|
|
8145
|
+
className: `absolute top-0 left-0 right-0 h-0.5 ${s.rounded} ${s.indicator}`,
|
|
8146
|
+
style: accentColor ? { backgroundColor: accentColor } : void 0
|
|
8147
|
+
}
|
|
8148
|
+
),
|
|
8149
|
+
/* @__PURE__ */ jsx("span", { className: `${s.tabTextSize} max-w-35 truncate leading-none ${isActive ? s.activeText : s.inactiveText}`, children: tab.label }),
|
|
8150
|
+
showClose && /* @__PURE__ */ jsx(
|
|
8151
|
+
"button",
|
|
8152
|
+
{
|
|
8153
|
+
onClick: (e) => {
|
|
8154
|
+
e.stopPropagation();
|
|
8155
|
+
onClose();
|
|
8156
|
+
},
|
|
8157
|
+
className: `
|
|
8158
|
+
flex-shrink-0 ${s.closeBtnWH} flex items-center justify-center
|
|
8159
|
+
transition-all duration-100 cursor-pointer
|
|
8160
|
+
${s.closeBtn}
|
|
8161
|
+
${isActive ? "opacity-100" : "opacity-40 group-hover:opacity-60"}
|
|
8162
|
+
`,
|
|
8163
|
+
children: /* @__PURE__ */ jsx(X, { size: s.closeBtnSize, strokeWidth: 2.5 })
|
|
8164
|
+
}
|
|
8165
|
+
)
|
|
8166
|
+
]
|
|
8167
|
+
}
|
|
8168
|
+
);
|
|
8169
|
+
}
|
|
8053
8170
|
function MultiTabBar(props) {
|
|
8054
8171
|
const {
|
|
8055
8172
|
tabs,
|
|
@@ -8059,25 +8176,77 @@ function MultiTabBar(props) {
|
|
|
8059
8176
|
onTabAdd,
|
|
8060
8177
|
onTabsReorder,
|
|
8061
8178
|
variant = "light",
|
|
8062
|
-
primaryColor
|
|
8179
|
+
primaryColor,
|
|
8180
|
+
getTabGroup
|
|
8063
8181
|
} = props;
|
|
8064
8182
|
const s = VARIANT_STYLES[variant];
|
|
8065
8183
|
const scrollRef = useRef(null);
|
|
8066
8184
|
const tabRefs = useRef(/* @__PURE__ */ new Map());
|
|
8185
|
+
const registerTabRef = (id, el) => {
|
|
8186
|
+
if (el) tabRefs.current.set(id, el);
|
|
8187
|
+
else tabRefs.current.delete(id);
|
|
8188
|
+
};
|
|
8067
8189
|
const [showLeftFade, setShowLeftFade] = useState(false);
|
|
8068
8190
|
const [showRightFade, setShowRightFade] = useState(false);
|
|
8069
8191
|
const [draggingId, setDraggingId] = useState(null);
|
|
8070
8192
|
const [dropTargetId, setDropTargetId] = useState(null);
|
|
8071
8193
|
const [dropSide, setDropSide] = useState("before");
|
|
8072
|
-
const
|
|
8194
|
+
const [draggingGroupKey, setDraggingGroupKey] = useState(null);
|
|
8195
|
+
const [groupDropTargetKey, setGroupDropTargetKey] = useState(null);
|
|
8196
|
+
const [groupDropSide, setGroupDropSide] = useState("before");
|
|
8197
|
+
const [dragPos, setDragPos] = useState(null);
|
|
8073
8198
|
const dropTargetIdRef = useRef(null);
|
|
8074
8199
|
const dropSideRef = useRef("before");
|
|
8200
|
+
const groupDropTargetKeyRef = useRef(null);
|
|
8201
|
+
const groupDropSideRef = useRef("before");
|
|
8075
8202
|
const tabsRef = useRef(tabs);
|
|
8076
|
-
|
|
8077
|
-
|
|
8078
|
-
|
|
8079
|
-
|
|
8080
|
-
|
|
8203
|
+
useEffect(() => {
|
|
8204
|
+
dropTargetIdRef.current = dropTargetId;
|
|
8205
|
+
dropSideRef.current = dropSide;
|
|
8206
|
+
groupDropTargetKeyRef.current = groupDropTargetKey;
|
|
8207
|
+
groupDropSideRef.current = groupDropSide;
|
|
8208
|
+
tabsRef.current = tabs;
|
|
8209
|
+
});
|
|
8210
|
+
const groupRefs = useRef(/* @__PURE__ */ new Map());
|
|
8211
|
+
const registerGroupRef = (key, el) => {
|
|
8212
|
+
if (el) groupRefs.current.set(key, el);
|
|
8213
|
+
else groupRefs.current.delete(key);
|
|
8214
|
+
};
|
|
8215
|
+
const clusters = useMemo(
|
|
8216
|
+
() => getTabGroup ? clusterTabs(tabs, getTabGroup) : null,
|
|
8217
|
+
[tabs, getTabGroup]
|
|
8218
|
+
);
|
|
8219
|
+
const groupKeys = useMemo(
|
|
8220
|
+
() => clusters ? Array.from(new Set(clusters.map((c) => c.group?.key).filter((k) => !!k))) : [],
|
|
8221
|
+
[clusters]
|
|
8222
|
+
);
|
|
8223
|
+
const [groupColorAssignments, setGroupColorAssignments] = useState(() => /* @__PURE__ */ new Map());
|
|
8224
|
+
const groupColors = assignGroupColors(groupKeys, groupColorAssignments);
|
|
8225
|
+
if (groupColors !== groupColorAssignments) {
|
|
8226
|
+
setGroupColorAssignments(groupColors);
|
|
8227
|
+
}
|
|
8228
|
+
const [collapsedGroups, setCollapsedGroups] = useState(/* @__PURE__ */ new Set());
|
|
8229
|
+
const [prevActiveTabId, setPrevActiveTabId] = useState(activeTabId);
|
|
8230
|
+
if (activeTabId !== prevActiveTabId) {
|
|
8231
|
+
setPrevActiveTabId(activeTabId);
|
|
8232
|
+
if (clusters) {
|
|
8233
|
+
const activeCluster = clusters.find((c) => c.tabs.some((t) => t.id === activeTabId));
|
|
8234
|
+
const key = activeCluster?.group?.key;
|
|
8235
|
+
if (key && collapsedGroups.has(key)) {
|
|
8236
|
+
const next = new Set(collapsedGroups);
|
|
8237
|
+
next.delete(key);
|
|
8238
|
+
setCollapsedGroups(next);
|
|
8239
|
+
}
|
|
8240
|
+
}
|
|
8241
|
+
}
|
|
8242
|
+
const toggleGroupCollapsed = (key) => {
|
|
8243
|
+
setCollapsedGroups((prev) => {
|
|
8244
|
+
const next = new Set(prev);
|
|
8245
|
+
if (next.has(key)) next.delete(key);
|
|
8246
|
+
else next.add(key);
|
|
8247
|
+
return next;
|
|
8248
|
+
});
|
|
8249
|
+
};
|
|
8081
8250
|
useEffect(() => {
|
|
8082
8251
|
if (!activeTabId || !scrollRef.current) return;
|
|
8083
8252
|
const el = tabRefs.current.get(activeTabId);
|
|
@@ -8115,9 +8284,12 @@ function MultiTabBar(props) {
|
|
|
8115
8284
|
el.addEventListener("wheel", onWheel, { passive: false });
|
|
8116
8285
|
return () => el.removeEventListener("wheel", onWheel);
|
|
8117
8286
|
}, []);
|
|
8118
|
-
const
|
|
8287
|
+
const reorderEnabled = !!onTabsReorder;
|
|
8288
|
+
const pointerStateRef = useRef(null);
|
|
8289
|
+
const suppressClickRef = useRef(false);
|
|
8290
|
+
const resolveDropPosition = (clientX, draggingTabId) => {
|
|
8119
8291
|
for (const [tabId, el] of tabRefs.current) {
|
|
8120
|
-
if (tabId ===
|
|
8292
|
+
if (tabId === draggingTabId) continue;
|
|
8121
8293
|
const rect = el.getBoundingClientRect();
|
|
8122
8294
|
if (clientX >= rect.left && clientX < rect.right) {
|
|
8123
8295
|
setDropSide(clientX < rect.left + rect.width / 2 ? "before" : "after");
|
|
@@ -8128,7 +8300,7 @@ function MultiTabBar(props) {
|
|
|
8128
8300
|
let rightmostTabId = null;
|
|
8129
8301
|
let maxRight = -Infinity;
|
|
8130
8302
|
for (const [tabId, el] of tabRefs.current) {
|
|
8131
|
-
if (tabId ===
|
|
8303
|
+
if (tabId === draggingTabId) continue;
|
|
8132
8304
|
const rect = el.getBoundingClientRect();
|
|
8133
8305
|
if (rect.right > maxRight) {
|
|
8134
8306
|
maxRight = rect.right;
|
|
@@ -8152,180 +8324,322 @@ function MultiTabBar(props) {
|
|
|
8152
8324
|
newTabs.splice(insertIdx, 0, moved);
|
|
8153
8325
|
onTabsReorder(newTabs);
|
|
8154
8326
|
};
|
|
8327
|
+
const resolveGroupDropPosition = (clientX, draggingKey) => {
|
|
8328
|
+
const candidates = [];
|
|
8329
|
+
for (const [tabId, el] of tabRefs.current) {
|
|
8330
|
+
const tab = tabsRef.current.find((t) => t.id === tabId);
|
|
8331
|
+
if (tab && getTabGroup?.(tab)?.key === draggingKey) continue;
|
|
8332
|
+
candidates.push([`tab:${tabId}`, el]);
|
|
8333
|
+
}
|
|
8334
|
+
for (const [key, el] of groupRefs.current) {
|
|
8335
|
+
if (key === draggingKey) continue;
|
|
8336
|
+
candidates.push([`group:${key}`, el]);
|
|
8337
|
+
}
|
|
8338
|
+
for (const [unitKey, el] of candidates) {
|
|
8339
|
+
const rect = el.getBoundingClientRect();
|
|
8340
|
+
if (clientX >= rect.left && clientX < rect.right) {
|
|
8341
|
+
setGroupDropSide(clientX < rect.left + rect.width / 2 ? "before" : "after");
|
|
8342
|
+
setGroupDropTargetKey(unitKey);
|
|
8343
|
+
return;
|
|
8344
|
+
}
|
|
8345
|
+
}
|
|
8346
|
+
let rightmostKey = null;
|
|
8347
|
+
let maxRight = -Infinity;
|
|
8348
|
+
for (const [unitKey, el] of candidates) {
|
|
8349
|
+
const rect = el.getBoundingClientRect();
|
|
8350
|
+
if (rect.right > maxRight) {
|
|
8351
|
+
maxRight = rect.right;
|
|
8352
|
+
rightmostKey = unitKey;
|
|
8353
|
+
}
|
|
8354
|
+
}
|
|
8355
|
+
if (rightmostKey && clientX >= maxRight) {
|
|
8356
|
+
setGroupDropSide("after");
|
|
8357
|
+
setGroupDropTargetKey(rightmostKey);
|
|
8358
|
+
}
|
|
8359
|
+
};
|
|
8360
|
+
const reorderGroupTabs = (draggedKey, targetUnitKey, side, sourceTabs) => {
|
|
8361
|
+
if (!onTabsReorder || !getTabGroup) return;
|
|
8362
|
+
const draggedTabs = sourceTabs.filter((t) => getTabGroup(t)?.key === draggedKey);
|
|
8363
|
+
const rest = sourceTabs.filter((t) => getTabGroup(t)?.key !== draggedKey);
|
|
8364
|
+
if (draggedTabs.length === 0) return;
|
|
8365
|
+
let insertIdx;
|
|
8366
|
+
if (targetUnitKey.startsWith("group:")) {
|
|
8367
|
+
const targetKey = targetUnitKey.slice("group:".length);
|
|
8368
|
+
if (targetKey === draggedKey) return;
|
|
8369
|
+
const targetIndices = rest.map((t, i) => getTabGroup(t)?.key === targetKey ? i : -1).filter((i) => i !== -1);
|
|
8370
|
+
if (targetIndices.length === 0) return;
|
|
8371
|
+
insertIdx = side === "before" ? targetIndices[0] : targetIndices[targetIndices.length - 1] + 1;
|
|
8372
|
+
} else {
|
|
8373
|
+
const targetTabId = targetUnitKey.slice("tab:".length);
|
|
8374
|
+
const idx = rest.findIndex((t) => t.id === targetTabId);
|
|
8375
|
+
if (idx === -1) return;
|
|
8376
|
+
insertIdx = side === "before" ? idx : idx + 1;
|
|
8377
|
+
}
|
|
8378
|
+
const newTabs = [...rest.slice(0, insertIdx), ...draggedTabs, ...rest.slice(insertIdx)];
|
|
8379
|
+
onTabsReorder(newTabs);
|
|
8380
|
+
};
|
|
8155
8381
|
useEffect(() => {
|
|
8156
|
-
if (!
|
|
8157
|
-
const
|
|
8158
|
-
|
|
8159
|
-
|
|
8160
|
-
|
|
8382
|
+
if (!reorderEnabled) return;
|
|
8383
|
+
const DRAG_THRESHOLD = 4;
|
|
8384
|
+
const onPointerMove = (e) => {
|
|
8385
|
+
const state = pointerStateRef.current;
|
|
8386
|
+
if (!state) return;
|
|
8387
|
+
if (!state.dragging) {
|
|
8388
|
+
const dx = Math.abs(e.clientX - state.startX);
|
|
8389
|
+
const dy = Math.abs(e.clientY - state.startY);
|
|
8390
|
+
if (dx < DRAG_THRESHOLD && dy < DRAG_THRESHOLD) return;
|
|
8391
|
+
state.dragging = true;
|
|
8392
|
+
suppressClickRef.current = true;
|
|
8393
|
+
if (state.kind === "tab") setDraggingId(state.id);
|
|
8394
|
+
else setDraggingGroupKey(state.id);
|
|
8395
|
+
}
|
|
8396
|
+
setDragPos({ x: e.clientX, y: e.clientY });
|
|
8397
|
+
if (state.kind === "tab") resolveDropPosition(e.clientX, state.id);
|
|
8398
|
+
else resolveGroupDropPosition(e.clientX, state.id);
|
|
8161
8399
|
};
|
|
8162
|
-
const
|
|
8163
|
-
|
|
8164
|
-
|
|
8165
|
-
|
|
8400
|
+
const onPointerUp = () => {
|
|
8401
|
+
const state = pointerStateRef.current;
|
|
8402
|
+
if (state?.dragging) {
|
|
8403
|
+
if (state.kind === "tab") {
|
|
8404
|
+
const target = dropTargetIdRef.current;
|
|
8405
|
+
if (target) reorderTabs(target, dropSideRef.current, tabsRef.current, state.id);
|
|
8406
|
+
} else {
|
|
8407
|
+
const target = groupDropTargetKeyRef.current;
|
|
8408
|
+
if (target) reorderGroupTabs(state.id, target, groupDropSideRef.current, tabsRef.current);
|
|
8409
|
+
}
|
|
8166
8410
|
}
|
|
8167
|
-
|
|
8168
|
-
const id = draggingIdRef.current;
|
|
8169
|
-
const target = dropTargetIdRef.current;
|
|
8170
|
-
if (id && target) reorderTabs(target, dropSideRef.current, tabsRef.current, id);
|
|
8411
|
+
pointerStateRef.current = null;
|
|
8171
8412
|
setDraggingId(null);
|
|
8172
8413
|
setDropTargetId(null);
|
|
8414
|
+
setDraggingGroupKey(null);
|
|
8415
|
+
setGroupDropTargetKey(null);
|
|
8416
|
+
setDragPos(null);
|
|
8417
|
+
requestAnimationFrame(() => {
|
|
8418
|
+
suppressClickRef.current = false;
|
|
8419
|
+
});
|
|
8173
8420
|
};
|
|
8174
|
-
|
|
8175
|
-
|
|
8421
|
+
window.addEventListener("pointermove", onPointerMove);
|
|
8422
|
+
window.addEventListener("pointerup", onPointerUp);
|
|
8176
8423
|
return () => {
|
|
8177
|
-
|
|
8178
|
-
|
|
8424
|
+
window.removeEventListener("pointermove", onPointerMove);
|
|
8425
|
+
window.removeEventListener("pointerup", onPointerUp);
|
|
8179
8426
|
};
|
|
8180
|
-
}, [
|
|
8181
|
-
const
|
|
8182
|
-
|
|
8183
|
-
e.dataTransfer.effectAllowed = "move";
|
|
8184
|
-
};
|
|
8185
|
-
const handleDrop = (e, targetTabId) => {
|
|
8186
|
-
e.preventDefault();
|
|
8187
|
-
e.stopPropagation();
|
|
8188
|
-
dropHandledRef.current = true;
|
|
8189
|
-
const id = draggingIdRef.current;
|
|
8190
|
-
if (id && id !== targetTabId) reorderTabs(targetTabId, dropSideRef.current, tabsRef.current, id);
|
|
8191
|
-
setDraggingId(null);
|
|
8192
|
-
setDropTargetId(null);
|
|
8193
|
-
};
|
|
8194
|
-
const handleContainerDrop = (e) => {
|
|
8195
|
-
e.preventDefault();
|
|
8196
|
-
dropHandledRef.current = true;
|
|
8197
|
-
const id = draggingIdRef.current;
|
|
8198
|
-
if (id && onTabsReorder) {
|
|
8199
|
-
const fromIdx = tabsRef.current.findIndex((t) => t.id === id);
|
|
8200
|
-
if (fromIdx !== -1 && fromIdx < tabsRef.current.length - 1) {
|
|
8201
|
-
const newTabs = [...tabsRef.current];
|
|
8202
|
-
const moved = newTabs.splice(fromIdx, 1)[0];
|
|
8203
|
-
newTabs.push(moved);
|
|
8204
|
-
onTabsReorder(newTabs);
|
|
8205
|
-
}
|
|
8206
|
-
}
|
|
8207
|
-
setDraggingId(null);
|
|
8208
|
-
setDropTargetId(null);
|
|
8427
|
+
}, [reorderEnabled]);
|
|
8428
|
+
const handlePointerDown = (e, tabId) => {
|
|
8429
|
+
pointerStateRef.current = { kind: "tab", id: tabId, startX: e.clientX, startY: e.clientY, dragging: false };
|
|
8209
8430
|
};
|
|
8210
|
-
const
|
|
8211
|
-
|
|
8212
|
-
setDraggingId(null);
|
|
8213
|
-
setDropTargetId(null);
|
|
8431
|
+
const handleGroupPointerDown = (e, groupKey) => {
|
|
8432
|
+
pointerStateRef.current = { kind: "group", id: groupKey, startX: e.clientX, startY: e.clientY, dragging: false };
|
|
8214
8433
|
};
|
|
8215
8434
|
const showPlus = !!onTabAdd;
|
|
8435
|
+
const canClose = tabs.length > 1;
|
|
8436
|
+
const draggingTab = draggingId ? tabs.find((tab) => tab.id === draggingId) : void 0;
|
|
8437
|
+
const draggingCluster = clusters?.find((c) => c.tabs.some((t) => t.id === draggingId));
|
|
8438
|
+
const draggingColor = draggingCluster?.group ? groupColors.get(draggingCluster.group.key)?.border : primaryColor;
|
|
8439
|
+
const draggingGroupCluster = draggingGroupKey ? clusters?.find((c) => c.group?.key === draggingGroupKey) : void 0;
|
|
8440
|
+
const draggingGroupColor = draggingGroupKey ? groupColors.get(draggingGroupKey) : void 0;
|
|
8441
|
+
const ghost = draggingGroupKey && draggingGroupCluster?.group ? {
|
|
8442
|
+
label: draggingGroupCluster.group.label,
|
|
8443
|
+
bg: (draggingGroupColor ?? TAB_PALETTE[0]).bg,
|
|
8444
|
+
border: (draggingGroupColor ?? TAB_PALETTE[0]).border,
|
|
8445
|
+
color: (draggingGroupColor ?? TAB_PALETTE[0]).base
|
|
8446
|
+
} : draggingTab ? { label: draggingTab.label, bg: "white", border: draggingColor ?? "#e5e7eb", color: "#111827" } : null;
|
|
8216
8447
|
if (tabs.length === 0) return null;
|
|
8217
|
-
return /* @__PURE__ */
|
|
8218
|
-
|
|
8219
|
-
|
|
8220
|
-
|
|
8221
|
-
|
|
8222
|
-
|
|
8223
|
-
|
|
8224
|
-
),
|
|
8225
|
-
showRightFade && /* @__PURE__ */ jsx(
|
|
8226
|
-
"div",
|
|
8227
|
-
{
|
|
8228
|
-
className: "absolute top-0 bottom-0 w-10 pointer-events-none z-10",
|
|
8229
|
-
style: {
|
|
8230
|
-
right: showPlus ? PLUS_BTN_WIDTH : 0,
|
|
8231
|
-
background: easedFade("to left", s.fadeOpacity)
|
|
8448
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
8449
|
+
/* @__PURE__ */ jsx("div", { className: `flex-shrink-0 w-full overflow-hidden ${s.container}`, children: /* @__PURE__ */ jsxs("div", { className: "relative flex items-stretch", children: [
|
|
8450
|
+
showLeftFade && /* @__PURE__ */ jsx(
|
|
8451
|
+
"div",
|
|
8452
|
+
{
|
|
8453
|
+
className: "absolute left-0 top-0 bottom-0 w-10 pointer-events-none z-10",
|
|
8454
|
+
style: { background: easedFade("to right", s.fadeOpacity) }
|
|
8232
8455
|
}
|
|
8233
|
-
|
|
8234
|
-
|
|
8235
|
-
|
|
8236
|
-
|
|
8237
|
-
|
|
8238
|
-
|
|
8239
|
-
|
|
8240
|
-
|
|
8241
|
-
|
|
8242
|
-
|
|
8243
|
-
|
|
8244
|
-
|
|
8245
|
-
|
|
8246
|
-
|
|
8247
|
-
|
|
8248
|
-
|
|
8249
|
-
|
|
8250
|
-
|
|
8251
|
-
|
|
8252
|
-
{
|
|
8253
|
-
|
|
8254
|
-
|
|
8255
|
-
|
|
8256
|
-
|
|
8257
|
-
|
|
8258
|
-
onClick: () => {
|
|
8259
|
-
if (!isActive) onTabClick(tab.id);
|
|
8260
|
-
},
|
|
8261
|
-
onDragStart: (e) => handleDragStart(e, tab.id),
|
|
8262
|
-
onDrop: (e) => handleDrop(e, tab.id),
|
|
8263
|
-
onDragEnd: handleDragEnd,
|
|
8264
|
-
className: `
|
|
8265
|
-
group relative flex items-center gap-1.5 ${s.tabPx} ${s.tabPy} flex-shrink-0
|
|
8266
|
-
${s.rounded} border border-b-0 select-none
|
|
8267
|
-
transition-colors duration-100
|
|
8268
|
-
${isActive ? s.activeTab : s.inactiveTab}
|
|
8269
|
-
${onTabsReorder ? "cursor-grab" : "cursor-pointer"}
|
|
8270
|
-
${isDraggingThis ? "opacity-40" : ""}
|
|
8271
|
-
${isDropTarget && dropSide === "before" ? "[box-shadow:inset_2px_0_0_0_#3b82f6]" : ""}
|
|
8272
|
-
${isDropTarget && dropSide === "after" ? "[box-shadow:inset_-2px_0_0_0_#3b82f6]" : ""}
|
|
8273
|
-
`,
|
|
8274
|
-
children: [
|
|
8275
|
-
isActive && /* @__PURE__ */ jsx(
|
|
8276
|
-
"div",
|
|
8277
|
-
{
|
|
8278
|
-
className: `absolute top-0 left-0 right-0 h-0.5 ${s.rounded} ${s.indicator}`,
|
|
8279
|
-
style: primaryColor ? { backgroundColor: primaryColor } : void 0
|
|
8280
|
-
}
|
|
8281
|
-
),
|
|
8282
|
-
/* @__PURE__ */ jsx(
|
|
8283
|
-
"span",
|
|
8284
|
-
{
|
|
8285
|
-
className: `${s.tabTextSize} max-w-35 truncate leading-none ${isActive ? s.activeText : s.inactiveText}`,
|
|
8286
|
-
children: tab.label
|
|
8287
|
-
}
|
|
8288
|
-
),
|
|
8289
|
-
tabs.length > 1 && /* @__PURE__ */ jsx(
|
|
8290
|
-
"button",
|
|
8456
|
+
),
|
|
8457
|
+
showRightFade && /* @__PURE__ */ jsx(
|
|
8458
|
+
"div",
|
|
8459
|
+
{
|
|
8460
|
+
className: "absolute top-0 bottom-0 w-10 pointer-events-none z-10",
|
|
8461
|
+
style: {
|
|
8462
|
+
right: showPlus ? PLUS_BTN_WIDTH : 0,
|
|
8463
|
+
background: easedFade("to left", s.fadeOpacity)
|
|
8464
|
+
}
|
|
8465
|
+
}
|
|
8466
|
+
),
|
|
8467
|
+
/* @__PURE__ */ jsx(
|
|
8468
|
+
"div",
|
|
8469
|
+
{
|
|
8470
|
+
ref: scrollRef,
|
|
8471
|
+
className: `flex-1 min-w-0 flex items-end gap-0.5 ${s.scrollPadding} overflow-x-auto [&::-webkit-scrollbar]:hidden`,
|
|
8472
|
+
style: { scrollbarWidth: "none" },
|
|
8473
|
+
onScroll: updateFades,
|
|
8474
|
+
children: clusters ? clusters.map((cluster) => {
|
|
8475
|
+
if (!cluster.group) {
|
|
8476
|
+
return cluster.tabs.map((tab) => {
|
|
8477
|
+
const isDropTarget = dropTargetId === tab.id;
|
|
8478
|
+
const isGroupDropTarget2 = groupDropTargetKey === `tab:${tab.id}`;
|
|
8479
|
+
return /* @__PURE__ */ jsx(
|
|
8480
|
+
MultiTabPill,
|
|
8291
8481
|
{
|
|
8292
|
-
|
|
8293
|
-
|
|
8294
|
-
|
|
8482
|
+
tab,
|
|
8483
|
+
isActive: tab.id === activeTabId,
|
|
8484
|
+
showClose: canClose,
|
|
8485
|
+
accentColor: primaryColor,
|
|
8486
|
+
style: s,
|
|
8487
|
+
onClick: () => {
|
|
8488
|
+
if (suppressClickRef.current) return;
|
|
8489
|
+
onTabClick(tab.id);
|
|
8295
8490
|
},
|
|
8296
|
-
|
|
8297
|
-
|
|
8298
|
-
|
|
8299
|
-
|
|
8300
|
-
|
|
8301
|
-
|
|
8302
|
-
|
|
8303
|
-
|
|
8304
|
-
)
|
|
8305
|
-
|
|
8306
|
-
}
|
|
8307
|
-
|
|
8308
|
-
|
|
8309
|
-
|
|
8310
|
-
|
|
8311
|
-
|
|
8312
|
-
|
|
8313
|
-
|
|
8314
|
-
|
|
8315
|
-
|
|
8316
|
-
|
|
8317
|
-
|
|
8318
|
-
|
|
8319
|
-
|
|
8320
|
-
|
|
8321
|
-
|
|
8322
|
-
|
|
8323
|
-
|
|
8324
|
-
|
|
8325
|
-
|
|
8326
|
-
|
|
8327
|
-
|
|
8328
|
-
|
|
8491
|
+
onClose: () => onTabClose(tab.id),
|
|
8492
|
+
registerRef: registerTabRef,
|
|
8493
|
+
draggable: reorderEnabled,
|
|
8494
|
+
isDraggingThis: tab.id === draggingId,
|
|
8495
|
+
dropIndicator: isDropTarget ? dropSide : isGroupDropTarget2 ? groupDropSide : null,
|
|
8496
|
+
onPointerDown: (e) => handlePointerDown(e, tab.id)
|
|
8497
|
+
},
|
|
8498
|
+
tab.id
|
|
8499
|
+
);
|
|
8500
|
+
});
|
|
8501
|
+
}
|
|
8502
|
+
const { key, label } = cluster.group;
|
|
8503
|
+
const color = groupColors.get(key) ?? TAB_PALETTE[0];
|
|
8504
|
+
const collapsed = collapsedGroups.has(key);
|
|
8505
|
+
const groupHasActiveTab = cluster.tabs.some((t) => t.id === activeTabId);
|
|
8506
|
+
const isGroupDropTarget = groupDropTargetKey === `group:${key}`;
|
|
8507
|
+
const isDraggingThisGroup = draggingGroupKey === key;
|
|
8508
|
+
return /* @__PURE__ */ jsxs(
|
|
8509
|
+
"div",
|
|
8510
|
+
{
|
|
8511
|
+
ref: (el) => registerGroupRef(key, el),
|
|
8512
|
+
className: "flex shrink-0 flex-col items-stretch",
|
|
8513
|
+
children: [
|
|
8514
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1", children: [
|
|
8515
|
+
/* @__PURE__ */ jsxs(
|
|
8516
|
+
"button",
|
|
8517
|
+
{
|
|
8518
|
+
type: "button",
|
|
8519
|
+
onClick: () => {
|
|
8520
|
+
if (suppressClickRef.current) return;
|
|
8521
|
+
toggleGroupCollapsed(key);
|
|
8522
|
+
},
|
|
8523
|
+
onPointerDown: reorderEnabled ? (e) => handleGroupPointerDown(e, key) : void 0,
|
|
8524
|
+
"aria-expanded": !collapsed,
|
|
8525
|
+
title: collapsed ? "\uD558\uC704 \uD0ED \uD3BC\uCE58\uAE30" : "\uD558\uC704 \uD0ED \uC811\uAE30",
|
|
8526
|
+
className: `flex items-center gap-1 rounded-md border px-2.5 py-1 text-xs font-bold whitespace-nowrap select-none ${reorderEnabled ? isDraggingThisGroup ? "cursor-grabbing" : "cursor-grab" : "cursor-pointer"} ${isDraggingThisGroup ? "opacity-40" : ""} ${isGroupDropTarget && groupDropSide === "before" ? "[box-shadow:inset_2px_0_0_0_#3b82f6]" : ""} ${isGroupDropTarget && groupDropSide === "after" ? "[box-shadow:inset_-2px_0_0_0_#3b82f6]" : ""}`,
|
|
8527
|
+
style: { backgroundColor: color.bg, borderColor: color.border, color: color.base },
|
|
8528
|
+
children: [
|
|
8529
|
+
collapsed ? /* @__PURE__ */ jsx(ChevronRight, { size: 12 }) : /* @__PURE__ */ jsx(ChevronDown, { size: 12 }),
|
|
8530
|
+
label,
|
|
8531
|
+
canClose && /* @__PURE__ */ jsx(
|
|
8532
|
+
X,
|
|
8533
|
+
{
|
|
8534
|
+
size: 12,
|
|
8535
|
+
onClick: (e) => {
|
|
8536
|
+
e.stopPropagation();
|
|
8537
|
+
cluster.tabs.forEach((t) => onTabClose(t.id));
|
|
8538
|
+
}
|
|
8539
|
+
}
|
|
8540
|
+
)
|
|
8541
|
+
]
|
|
8542
|
+
}
|
|
8543
|
+
),
|
|
8544
|
+
!collapsed && cluster.tabs.map((tab) => {
|
|
8545
|
+
const isDropTarget = dropTargetId === tab.id;
|
|
8546
|
+
const isTabGroupDropTarget = groupDropTargetKey === `tab:${tab.id}`;
|
|
8547
|
+
return /* @__PURE__ */ jsx(
|
|
8548
|
+
MultiTabPill,
|
|
8549
|
+
{
|
|
8550
|
+
tab,
|
|
8551
|
+
isActive: tab.id === activeTabId,
|
|
8552
|
+
showClose: canClose,
|
|
8553
|
+
accentColor: color.base,
|
|
8554
|
+
style: s,
|
|
8555
|
+
onClick: () => {
|
|
8556
|
+
if (suppressClickRef.current) return;
|
|
8557
|
+
onTabClick(tab.id);
|
|
8558
|
+
},
|
|
8559
|
+
onClose: () => onTabClose(tab.id),
|
|
8560
|
+
registerRef: registerTabRef,
|
|
8561
|
+
draggable: reorderEnabled,
|
|
8562
|
+
isDraggingThis: tab.id === draggingId,
|
|
8563
|
+
dropIndicator: isDropTarget ? dropSide : isTabGroupDropTarget ? groupDropSide : null,
|
|
8564
|
+
onPointerDown: (e) => handlePointerDown(e, tab.id)
|
|
8565
|
+
},
|
|
8566
|
+
tab.id
|
|
8567
|
+
);
|
|
8568
|
+
})
|
|
8569
|
+
] }),
|
|
8570
|
+
/* @__PURE__ */ jsx(
|
|
8571
|
+
"div",
|
|
8572
|
+
{
|
|
8573
|
+
className: "mt-1 h-0.75 rounded-full",
|
|
8574
|
+
style: { backgroundColor: color.base, opacity: groupHasActiveTab ? 1 : 0.35 }
|
|
8575
|
+
}
|
|
8576
|
+
)
|
|
8577
|
+
]
|
|
8578
|
+
},
|
|
8579
|
+
key
|
|
8580
|
+
);
|
|
8581
|
+
}) : tabs.map((tab) => {
|
|
8582
|
+
const isDropTarget = dropTargetId === tab.id;
|
|
8583
|
+
return /* @__PURE__ */ jsx(
|
|
8584
|
+
MultiTabPill,
|
|
8585
|
+
{
|
|
8586
|
+
tab,
|
|
8587
|
+
isActive: tab.id === activeTabId,
|
|
8588
|
+
showClose: canClose,
|
|
8589
|
+
accentColor: primaryColor,
|
|
8590
|
+
style: s,
|
|
8591
|
+
onClick: () => {
|
|
8592
|
+
if (suppressClickRef.current) return;
|
|
8593
|
+
onTabClick(tab.id);
|
|
8594
|
+
},
|
|
8595
|
+
onClose: () => onTabClose(tab.id),
|
|
8596
|
+
registerRef: registerTabRef,
|
|
8597
|
+
draggable: reorderEnabled,
|
|
8598
|
+
isDraggingThis: tab.id === draggingId,
|
|
8599
|
+
dropIndicator: isDropTarget ? dropSide : null,
|
|
8600
|
+
onPointerDown: (e) => handlePointerDown(e, tab.id)
|
|
8601
|
+
},
|
|
8602
|
+
tab.id
|
|
8603
|
+
);
|
|
8604
|
+
})
|
|
8605
|
+
}
|
|
8606
|
+
),
|
|
8607
|
+
showPlus && /* @__PURE__ */ jsx(
|
|
8608
|
+
"div",
|
|
8609
|
+
{
|
|
8610
|
+
className: `flex-shrink-0 flex items-center justify-center ${s.plusBtnArea}`,
|
|
8611
|
+
style: { width: PLUS_BTN_WIDTH },
|
|
8612
|
+
children: /* @__PURE__ */ jsx(
|
|
8613
|
+
"button",
|
|
8614
|
+
{
|
|
8615
|
+
onClick: onTabAdd,
|
|
8616
|
+
title: "\uD604\uC7AC \uD398\uC774\uC9C0\uB97C \uC0C8 \uD0ED\uC73C\uB85C \uC5F4\uAE30",
|
|
8617
|
+
className: `w-6 h-6 flex items-center justify-center rounded cursor-pointer transition-colors ${s.plusBtn}`,
|
|
8618
|
+
children: /* @__PURE__ */ jsx(Plus, { size: 13, strokeWidth: 2 })
|
|
8619
|
+
}
|
|
8620
|
+
)
|
|
8621
|
+
}
|
|
8622
|
+
)
|
|
8623
|
+
] }) }),
|
|
8624
|
+
dragPos && ghost ? createPortal(
|
|
8625
|
+
/* @__PURE__ */ jsx(
|
|
8626
|
+
"div",
|
|
8627
|
+
{
|
|
8628
|
+
className: "pointer-events-none fixed z-50 flex items-center gap-1.5 rounded-md border px-2.5 py-1 text-xs font-bold whitespace-nowrap shadow-lg",
|
|
8629
|
+
style: {
|
|
8630
|
+
left: dragPos.x + 12,
|
|
8631
|
+
top: dragPos.y + 12,
|
|
8632
|
+
backgroundColor: ghost.bg,
|
|
8633
|
+
borderColor: ghost.border,
|
|
8634
|
+
color: ghost.color,
|
|
8635
|
+
transform: "rotate(-2deg)"
|
|
8636
|
+
},
|
|
8637
|
+
children: ghost.label
|
|
8638
|
+
}
|
|
8639
|
+
),
|
|
8640
|
+
document.body
|
|
8641
|
+
) : null
|
|
8642
|
+
] });
|
|
8329
8643
|
}
|
|
8330
8644
|
var userScopedStorage = createJSONStorage(() => ({
|
|
8331
8645
|
getItem: (name) => {
|