@sia.soul/sia-react-ui 0.1.39 → 0.1.40

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.
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  FloatingScrollbarProvider,
3
3
  Select
4
- } from "./chunk-LRGD2QDA.js";
4
+ } from "./chunk-ZTJUFVJO.js";
5
5
  import {
6
6
  Icon
7
7
  } from "./chunk-XCVAVCIO.js";
@@ -2,11 +2,10 @@ import {
2
2
  TextArea
3
3
  } from "./chunk-SMCYEPBL.js";
4
4
  import {
5
- FloatingScrollbarProvider,
6
5
  Popover,
7
6
  Tooltip,
8
7
  useOverlayLifecycle
9
- } from "./chunk-LRGD2QDA.js";
8
+ } from "./chunk-ZTJUFVJO.js";
10
9
  import {
11
10
  Dropdown,
12
11
  Icon
@@ -191,6 +190,7 @@ function Tag({
191
190
  // src/components/Tabs.tsx
192
191
  import { useEffect, useId, useLayoutEffect as useLayoutEffect2, useMemo, useRef as useRef2, useState as useState3 } from "react";
193
192
  import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
193
+ import { createElement } from "react";
194
194
  function Tabs({
195
195
  items,
196
196
  activeKey,
@@ -225,6 +225,42 @@ function Tabs({
225
225
  const sliding = ["capsule", "line", "underline", "tech", "tech-line"].includes(type);
226
226
  const listRef = useRef2(null);
227
227
  const indicatorRef = useRef2(null);
228
+ const moreRef = useRef2(null);
229
+ const [overflow, setOverflow] = useState3({ count: items.length, width: 0 });
230
+ const [moreOpen, setMoreOpen] = useState3(false);
231
+ const visibleCount = resolvedOrientation === "horizontal" ? overflow.count : items.length;
232
+ const hiddenItems = items.slice(visibleCount);
233
+ useLayoutEffect2(() => {
234
+ const list = listRef.current;
235
+ if (!list || resolvedOrientation !== "horizontal") return;
236
+ const entries = Array.from(list.children).filter((node) => node instanceof HTMLElement && node.hasAttribute("data-tab-entry"));
237
+ const measure = () => {
238
+ const css = getComputedStyle(list);
239
+ const gap = parseFloat(css.columnGap) || 0;
240
+ const padding = (parseFloat(css.paddingLeft) || 0) + (parseFloat(css.paddingRight) || 0);
241
+ const widths = entries.map((node) => node.getBoundingClientRect().width);
242
+ const total = widths.reduce((sum, width2) => sum + width2, 0) + gap * Math.max(0, widths.length - 1);
243
+ const available = Math.max(0, list.clientWidth - padding);
244
+ let count = widths.length;
245
+ if (total > available + 0.5) {
246
+ const moreWidth = moreRef.current?.getBoundingClientRect().width ?? 36;
247
+ let used = moreWidth;
248
+ count = 0;
249
+ for (const width2 of widths) {
250
+ if (used + gap + width2 > available + 0.5) break;
251
+ used += gap + width2;
252
+ count++;
253
+ }
254
+ }
255
+ const width = Math.ceil(total + padding + (parseFloat(css.borderLeftWidth) || 0) + (parseFloat(css.borderRightWidth) || 0));
256
+ setOverflow((previous) => previous.count === count && previous.width === width ? previous : { count, width });
257
+ };
258
+ measure();
259
+ const observer = new ResizeObserver(measure);
260
+ observer.observe(list);
261
+ entries.forEach((entry) => observer.observe(entry));
262
+ return () => observer.disconnect();
263
+ }, [items, type, size, closable, resolvedOrientation, tabBarTitle, tabBarExtraContent]);
228
264
  const [contentState, setContentState] = useState3(() => ({ key: selectedKey, visited: /* @__PURE__ */ new Set([selectedKey]) }));
229
265
  const settledKeyRef = useRef2(selectedKey);
230
266
  const afterChangeRef = useRef2(onAfterChange);
@@ -239,16 +275,12 @@ function Tabs({
239
275
  function updateIndicator() {
240
276
  if (!list || !indicator) return;
241
277
  const active = list.querySelector('[role="tab"][aria-selected="true"]');
242
- if (!active) {
278
+ if (!active || active.closest('[data-tab-hidden="true"]')) {
243
279
  indicator.style.visibility = "hidden";
244
280
  return;
245
281
  }
246
282
  const listRect = list.getBoundingClientRect();
247
283
  const rect = active.getBoundingClientRect();
248
- if (type === "tech-line" && resolvedOrientation === "horizontal") {
249
- if (rect.left < listRect.left) list.scrollLeft += rect.left - listRect.left;
250
- else if (rect.right > listRect.right) list.scrollLeft += rect.right - listRect.right;
251
- }
252
284
  const currentRect = active.getBoundingClientRect();
253
285
  indicator.style.transform = `translate(${currentRect.left - listRect.left - list.clientLeft + list.scrollLeft}px, ${currentRect.top - listRect.top - list.clientTop + list.scrollTop}px)`;
254
286
  indicator.style.width = `${rect.width}px`;
@@ -264,7 +296,7 @@ function Tabs({
264
296
  observer.observe(list);
265
297
  list.querySelectorAll('[role="tab"]').forEach((tab) => observer.observe(tab));
266
298
  return () => observer.disconnect();
267
- }, [sliding, type, selectedKey, items, size, resolvedPosition, centered, closable]);
299
+ }, [sliding, type, selectedKey, items, size, resolvedPosition, centered, closable, visibleCount]);
268
300
  useEffect(() => {
269
301
  let cancelled = false;
270
302
  let frame = 0;
@@ -298,7 +330,8 @@ function Tabs({
298
330
  }
299
331
  function focusAndSelect(key) {
300
332
  selectTab(key);
301
- document.getElementById(`${baseId}-tab-${key}`)?.focus();
333
+ if (items.findIndex((item) => item.key === key) >= visibleCount) moreRef.current?.focus();
334
+ else document.getElementById(`${baseId}-tab-${key}`)?.focus();
302
335
  }
303
336
  function handleKeyDown(event, key) {
304
337
  const previousKey = resolvedOrientation === "horizontal" ? "ArrowLeft" : "ArrowUp";
@@ -320,54 +353,53 @@ function Tabs({
320
353
  ...withTitleTooltip(props),
321
354
  children: [
322
355
  /* @__PURE__ */ jsxs4("div", { className: "sia-tabs__header", children: [
323
- /* @__PURE__ */ jsx4(FloatingScrollbarProvider, { targetRef: listRef, disabled: type !== "tech-line" || resolvedOrientation !== "horizontal" }),
324
356
  tabBarTitle ? /* @__PURE__ */ jsxs4("div", { className: "sia-tabs__title", children: [
325
357
  tabBarTitle,
326
358
  type === "tech-line" ? /* @__PURE__ */ jsx4("svg", { className: "sia-tabs__title-connector", viewBox: "0 0 32 40", "aria-hidden": "true", children: /* @__PURE__ */ jsx4("path", { d: "M0 39.25 H5 Q8 39.25 10 35.25 L28 3.75 Q29 1.75 32 1.75", fill: "none", stroke: "currentColor", strokeWidth: "1.5", strokeLinejoin: "round" }) }) : null
327
359
  ] }) : null,
328
- /* @__PURE__ */ jsxs4("div", { ref: listRef, className: `sia-tabs__list${sliding ? " sia-tabs__list--sliding" : ""}`, role: "tablist", "aria-orientation": resolvedOrientation, children: [
360
+ /* @__PURE__ */ jsxs4("div", { ref: listRef, style: resolvedOrientation === "horizontal" && overflow.width ? { width: overflow.width } : void 0, className: `sia-tabs__list${sliding ? " sia-tabs__list--sliding" : ""}`, role: "tablist", "aria-orientation": resolvedOrientation, children: [
329
361
  sliding ? /* @__PURE__ */ jsx4("span", { ref: indicatorRef, className: `sia-tabs__indicator${type === "capsule" ? " sia-tabs__capsule-indicator" : ""}`, "aria-hidden": "true", children: type === "tech-line" ? /* @__PURE__ */ jsx4("span", { className: "sia-tabs__indicator-shape", "aria-hidden": "true" }) : null }, type) : null,
330
- items.map((item) => {
362
+ items.map((item, index) => {
331
363
  const selected = item.key === selectedKey;
332
364
  const canClose = item.closable ?? closable;
333
- const tabNode = /* @__PURE__ */ jsxs4("span", { className: `sia-tabs__tab-wrap${canClose ? " is-closable" : ""}`, children: [
334
- /* @__PURE__ */ jsxs4(
335
- "button",
336
- {
337
- id: `${baseId}-tab-${item.key}`,
338
- type: "button",
339
- className: "sia-tabs__tab",
340
- role: "tab",
341
- "aria-selected": selected,
342
- "aria-controls": hasPanels ? `${baseId}-panel-${item.key}` : void 0,
343
- tabIndex: selected ? 0 : -1,
344
- disabled: item.disabled,
345
- onClick: () => selectTab(item.key),
346
- onKeyDown: (event) => handleKeyDown(event, item.key),
347
- children: [
348
- type === "tech-line" ? /* @__PURE__ */ jsx4("span", { className: "sia-tabs__tab-shape", "aria-hidden": "true" }) : null,
349
- item.icon ? /* @__PURE__ */ jsx4("span", { className: "sia-tabs__icon", children: item.icon }) : null,
350
- /* @__PURE__ */ jsx4("span", { children: item.label })
351
- ]
352
- }
353
- ),
354
- canClose ? /* @__PURE__ */ jsx4(
355
- "button",
356
- {
357
- type: "button",
358
- className: "sia-tabs__close",
359
- "aria-label": `\u5173\u95ED${typeof item.label === "string" ? item.label : "\u6807\u7B7E\u9875"}`,
360
- disabled: item.disabled,
361
- onClick: () => onEdit?.(item.key, "remove"),
362
- children: /* @__PURE__ */ jsx4(Icon, { name: "close", size: 12 })
363
- }
364
- ) : null
365
- ] }, item.key);
366
365
  const contextMenuItems = typeof tabContextMenu === "function" ? tabContextMenu(item) : tabContextMenu;
366
+ const entryProps = { "data-tab-entry": "", "data-tab-hidden": index >= visibleCount, "aria-hidden": index >= visibleCount || void 0 };
367
+ const tabNode = /* @__PURE__ */ createElement("span", { ...!contextMenuItems?.length ? entryProps : {}, className: `sia-tabs__tab-wrap${canClose ? " is-closable" : ""}`, key: item.key }, /* @__PURE__ */ jsxs4(
368
+ "button",
369
+ {
370
+ id: `${baseId}-tab-${item.key}`,
371
+ type: "button",
372
+ className: "sia-tabs__tab",
373
+ role: "tab",
374
+ "aria-selected": selected,
375
+ "aria-controls": hasPanels ? `${baseId}-panel-${item.key}` : void 0,
376
+ tabIndex: selected ? 0 : -1,
377
+ disabled: item.disabled,
378
+ onClick: () => selectTab(item.key),
379
+ onKeyDown: (event) => handleKeyDown(event, item.key),
380
+ children: [
381
+ type === "tech-line" ? /* @__PURE__ */ jsx4("span", { className: "sia-tabs__tab-shape", "aria-hidden": "true" }) : null,
382
+ item.icon ? /* @__PURE__ */ jsx4("span", { className: "sia-tabs__icon", children: item.icon }) : null,
383
+ /* @__PURE__ */ jsx4("span", { children: item.label })
384
+ ]
385
+ }
386
+ ), canClose ? /* @__PURE__ */ jsx4(
387
+ "button",
388
+ {
389
+ type: "button",
390
+ className: "sia-tabs__close",
391
+ "aria-label": `\u5173\u95ED${typeof item.label === "string" ? item.label : "\u6807\u7B7E\u9875"}`,
392
+ disabled: item.disabled,
393
+ onClick: () => onEdit?.(item.key, "remove"),
394
+ children: /* @__PURE__ */ jsx4(Icon, { name: "close", size: 12 })
395
+ }
396
+ ) : null);
367
397
  if (!contextMenuItems?.length) return tabNode;
368
- return /* @__PURE__ */ jsx4(
398
+ return /* @__PURE__ */ createElement(
369
399
  Dropdown,
370
400
  {
401
+ ...entryProps,
402
+ key: item.key,
371
403
  className: "sia-tabs__tab-context-trigger",
372
404
  popupClassName: "sia-tabs__tab-context-menu",
373
405
  trigger: ["contextMenu"],
@@ -376,12 +408,26 @@ function Tabs({
376
408
  items: contextMenuItems,
377
409
  selectable: false,
378
410
  onClick: (info) => onTabContextMenuClick?.({ ...info, tabKey: item.key, tab: item })
379
- },
380
- children: tabNode
411
+ }
381
412
  },
382
- item.key
413
+ tabNode
383
414
  );
384
- })
415
+ }),
416
+ resolvedOrientation === "horizontal" ? /* @__PURE__ */ jsx4(
417
+ Dropdown,
418
+ {
419
+ className: "sia-tabs__more",
420
+ "data-tab-hidden": !hiddenItems.length,
421
+ trigger: ["hover", "click"],
422
+ placement: "bottomRight",
423
+ showTriggerIcon: false,
424
+ autoFocus: true,
425
+ open: moreOpen && hiddenItems.length > 0,
426
+ onOpenChange: setMoreOpen,
427
+ menu: { items: hiddenItems.map((item) => ({ key: item.key, label: item.label, icon: item.icon, disabled: item.disabled })), selectedKeys: [selectedKey], onClick: (info) => selectTab(info.key) },
428
+ children: /* @__PURE__ */ jsx4(Button, { ref: moreRef, variant: "text", icon: /* @__PURE__ */ jsx4(Icon, { name: "more-horizontal" }), "aria-label": "\u66F4\u591A\u9009\u9879\u5361", onKeyDown: (event) => handleKeyDown(event, selectedKey), className: hiddenItems.some((item) => item.key === selectedKey) ? "is-active" : void 0 })
429
+ }
430
+ ) : null
385
431
  ] }),
386
432
  tabBarExtraContent ? /* @__PURE__ */ jsx4("div", { className: "sia-tabs__extra", children: tabBarExtraContent }) : null
387
433
  ] }),