@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 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, { label: option.label, gap: rowGap2, children: /* @__PURE__ */ jsxRuntime.jsx(
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 draggingIdRef = React6.useRef(null);
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
- const dropHandledRef = React6.useRef(false);
8114
- draggingIdRef.current = draggingId;
8115
- dropTargetIdRef.current = dropTargetId;
8116
- dropSideRef.current = dropSide;
8117
- tabsRef.current = tabs;
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 resolveDropPosition = (clientX) => {
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 === draggingIdRef.current) continue;
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 === draggingIdRef.current) continue;
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 (!onTabsReorder) return;
8194
- const onDocDragOver = (e) => {
8195
- if (!draggingIdRef.current) return;
8196
- e.preventDefault();
8197
- resolveDropPosition(e.clientX);
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 onDocDrop = (e) => {
8200
- if (dropHandledRef.current) {
8201
- dropHandledRef.current = false;
8202
- return;
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
- e.preventDefault();
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
- document.addEventListener("dragover", onDocDragOver);
8212
- document.addEventListener("drop", onDocDrop);
8461
+ window.addEventListener("pointermove", onPointerMove);
8462
+ window.addEventListener("pointerup", onPointerUp);
8213
8463
  return () => {
8214
- document.removeEventListener("dragover", onDocDragOver);
8215
- document.removeEventListener("drop", onDocDrop);
8464
+ window.removeEventListener("pointermove", onPointerMove);
8465
+ window.removeEventListener("pointerup", onPointerUp);
8216
8466
  };
8217
- }, [onTabsReorder]);
8218
- const handleDragStart = (e, tabId) => {
8219
- setDraggingId(tabId);
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 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);
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.jsx("div", { className: `flex-shrink-0 w-full overflow-hidden ${s.container}`, children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative flex items-stretch", children: [
8255
- showLeftFade && /* @__PURE__ */ jsxRuntime.jsx(
8256
- "div",
8257
- {
8258
- className: "absolute left-0 top-0 bottom-0 w-10 pointer-events-none z-10",
8259
- style: { background: easedFade("to right", s.fadeOpacity) }
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
- /* @__PURE__ */ jsxRuntime.jsx(
8273
- "div",
8274
- {
8275
- ref: scrollRef,
8276
- className: `flex-1 min-w-0 flex items-end gap-0.5 ${s.scrollPadding} overflow-x-auto [&::-webkit-scrollbar]:hidden`,
8277
- style: { scrollbarWidth: "none" },
8278
- onScroll: updateFades,
8279
- onDragOver: (e) => {
8280
- if (draggingId) e.preventDefault();
8281
- },
8282
- onDrop: handleContainerDrop,
8283
- children: tabs.map((tab) => {
8284
- const isActive = tab.id === activeTabId;
8285
- const isDraggingThis = tab.id === draggingId;
8286
- const isDropTarget = dropTargetId === tab.id;
8287
- return /* @__PURE__ */ jsxRuntime.jsxs(
8288
- "div",
8289
- {
8290
- ref: (el) => {
8291
- if (el) tabRefs.current.set(tab.id, el);
8292
- else tabRefs.current.delete(tab.id);
8293
- },
8294
- draggable: !!onTabsReorder,
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
- onClick: (e) => {
8330
- e.stopPropagation();
8331
- onTabClose(tab.id);
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
- className: `
8334
- flex-shrink-0 ${s.closeBtnWH} flex items-center justify-center
8335
- transition-all duration-100 cursor-pointer
8336
- ${s.closeBtn}
8337
- ${isActive ? "opacity-100" : "opacity-40 group-hover:opacity-60"}
8338
- `,
8339
- children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.X, { size: s.closeBtnSize, strokeWidth: 2.5 })
8340
- }
8341
- )
8342
- ]
8343
- },
8344
- tab.id
8345
- );
8346
- })
8347
- }
8348
- ),
8349
- showPlus && /* @__PURE__ */ jsxRuntime.jsx(
8350
- "div",
8351
- {
8352
- className: `flex-shrink-0 flex items-center justify-center ${s.plusBtnArea}`,
8353
- style: { width: PLUS_BTN_WIDTH },
8354
- children: /* @__PURE__ */ jsxRuntime.jsx(
8355
- "button",
8356
- {
8357
- onClick: onTabAdd,
8358
- title: "\uD604\uC7AC \uD398\uC774\uC9C0\uB97C \uC0C8 \uD0ED\uC73C\uB85C \uC5F4\uAE30",
8359
- className: `w-6 h-6 flex items-center justify-center rounded cursor-pointer transition-colors ${s.plusBtn}`,
8360
- children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Plus, { size: 13, strokeWidth: 2 })
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) => {