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