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