@sia.soul/sia-react-ui 0.1.80 → 0.1.81

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/core.css CHANGED
@@ -10376,10 +10376,14 @@ button.sia-treemap__tile:focus-visible,
10376
10376
  }
10377
10377
  .sia-treemap__value {
10378
10378
  flex: 0 0 auto;
10379
+ max-width: 100%;
10380
+ overflow: hidden;
10379
10381
  color: rgb(21 34 56 / 72%);
10380
10382
  font-size: var(--sia-treemap-value-font-size, 11px);
10381
10383
  font-variant-numeric: tabular-nums;
10382
10384
  line-height: 1.2;
10385
+ text-overflow: ellipsis;
10386
+ white-space: nowrap;
10383
10387
  }
10384
10388
  [data-sia-theme=dark] .sia-treemap__tile {
10385
10389
  --sia-treemap-tile-color: color-mix(in srgb, var(--sia-treemap-color, #7aa8e8) 34%, #243248);
package/dist/index.cjs CHANGED
@@ -11912,12 +11912,86 @@ function collapseNarrowNodes(nodes, placements, bounds, minimumArea, minimumWidt
11912
11912
  };
11913
11913
  return squarify([...kept, overflowNode], bounds, minimumArea);
11914
11914
  }
11915
- function collectPositions(nodes, bounds, expanded, gap, headerHeight, minTileWidth, minTileHeight, maxVisibleNodes, overflowLabel, activeKey, depth = 0, parentKey = "root") {
11915
+ function placeCompactGrid(nodes, bounds, minimumWidth, minimumHeight) {
11916
+ if (nodes.length === 0 || bounds.width <= 0 || bounds.height <= 0) return [];
11917
+ const preferredAspectRatio = Math.max(0.1, minimumWidth / Math.max(1, minimumHeight));
11918
+ let columnCount = 1;
11919
+ let bestScore = Number.POSITIVE_INFINITY;
11920
+ for (let columns = 1; columns <= nodes.length; columns += 1) {
11921
+ const rows = Math.ceil(nodes.length / columns);
11922
+ const cellWidth = bounds.width / columns;
11923
+ const cellHeight = bounds.height / rows;
11924
+ const widthDeficit = Math.max(0, minimumWidth - cellWidth) / Math.max(1, minimumWidth);
11925
+ const heightDeficit = Math.max(0, minimumHeight - cellHeight) / Math.max(1, minimumHeight);
11926
+ const aspectPenalty = Math.abs(Math.log(Math.max(0.01, cellWidth / Math.max(1, cellHeight)) / preferredAspectRatio));
11927
+ const emptyPenalty = (rows * columns - nodes.length) / nodes.length;
11928
+ const score = (widthDeficit + heightDeficit) * 12 + aspectPenalty + emptyPenalty * 0.25;
11929
+ if (score < bestScore) {
11930
+ bestScore = score;
11931
+ columnCount = columns;
11932
+ }
11933
+ }
11934
+ const orderedNodes = [...nodes].sort((left, right) => layoutValue(right) - layoutValue(left));
11935
+ const rowCount = Math.ceil(orderedNodes.length / columnCount);
11936
+ const rowHeight = bounds.height / rowCount;
11937
+ return orderedNodes.map((node, index) => {
11938
+ const row = Math.floor(index / columnCount);
11939
+ const firstIndex = row * columnCount;
11940
+ const itemsInRow = Math.min(columnCount, orderedNodes.length - firstIndex);
11941
+ const column = index - firstIndex;
11942
+ const cellWidth = bounds.width / itemsInRow;
11943
+ return {
11944
+ node,
11945
+ rectangle: {
11946
+ x: bounds.x + column * cellWidth,
11947
+ y: bounds.y + row * rowHeight,
11948
+ width: column === itemsInRow - 1 ? bounds.x + bounds.width - (bounds.x + column * cellWidth) : cellWidth,
11949
+ height: row === rowCount - 1 ? bounds.y + bounds.height - (bounds.y + row * rowHeight) : rowHeight
11950
+ }
11951
+ };
11952
+ });
11953
+ }
11954
+ function preserveNarrowNodes(nodes, placements, bounds, minimumArea, minimumWidth, minimumHeight, groupKey) {
11955
+ const narrowKeys = new Set(
11956
+ placements.filter(({ rectangle }) => rectangle.width < minimumWidth || rectangle.height < minimumHeight).map(({ node }) => node.key)
11957
+ );
11958
+ if (narrowKeys.size === 0) return placements;
11959
+ let kept = nodes.filter((node) => !narrowKeys.has(node.key));
11960
+ let compact = nodes.filter((node) => narrowKeys.has(node.key));
11961
+ const totalArea = Math.max(1, bounds.width * bounds.height);
11962
+ while (kept.length > 0) {
11963
+ const keptWeight = kept.reduce((sum, node) => sum + layoutValue(node), 0);
11964
+ const compactWeight = compact.reduce((sum, node) => sum + layoutValue(node), 0);
11965
+ const proportionalArea = totalArea * compactWeight / Math.max(1, keptWeight + compactWeight);
11966
+ const readableArea = Math.min(
11967
+ totalArea * 0.82,
11968
+ Math.max(proportionalArea, compact.length * minimumArea)
11969
+ );
11970
+ const compactLayoutWeight = readableArea * keptWeight / Math.max(1, totalArea - readableArea);
11971
+ const compactGroup = {
11972
+ key: `${groupKey}::sia-compact-grid`,
11973
+ label: "",
11974
+ value: compact.reduce((sum, node) => sum + Math.max(0, node.value), 0),
11975
+ layoutValue: Math.max(compactWeight, compactLayoutWeight)
11976
+ };
11977
+ const outerPlacements = squarify([...kept, compactGroup], bounds, minimumArea);
11978
+ const newlyNarrowKeys = new Set(
11979
+ outerPlacements.filter(({ node, rectangle }) => node.key !== compactGroup.key && (rectangle.width < minimumWidth || rectangle.height < minimumHeight)).map(({ node }) => node.key)
11980
+ );
11981
+ if (newlyNarrowKeys.size === 0) {
11982
+ return outerPlacements.flatMap(({ node, rectangle }) => node.key === compactGroup.key ? placeCompactGrid(compact, rectangle, minimumWidth, minimumHeight) : [{ node, rectangle }]);
11983
+ }
11984
+ compact = [...compact, ...kept.filter((node) => newlyNarrowKeys.has(node.key))];
11985
+ kept = kept.filter((node) => !newlyNarrowKeys.has(node.key));
11986
+ }
11987
+ return placeCompactGrid(compact, bounds, minimumWidth, minimumHeight);
11988
+ }
11989
+ function collectPositions(nodes, bounds, expanded, gap, headerHeight, minTileWidth, minTileHeight, maxVisibleNodes, aggregateOverflow, overflowLabel, activeKey, depth = 0, parentKey = "root") {
11916
11990
  const result = [];
11917
11991
  const minimumWidthWithGap = minTileWidth + gap * 2;
11918
11992
  const minimumHeightWithGap = minTileHeight + gap * 2;
11919
11993
  const minimumArea = Math.max(1, minimumWidthWithGap * minimumHeightWithGap);
11920
- const visibleNodes = limitNodes(
11994
+ const visibleNodes = aggregateOverflow ? limitNodes(
11921
11995
  nodes,
11922
11996
  bounds,
11923
11997
  minimumArea,
@@ -11925,9 +11999,9 @@ function collectPositions(nodes, bounds, expanded, gap, headerHeight, minTileWid
11925
11999
  `${parentKey}:${depth}`,
11926
12000
  overflowLabel,
11927
12001
  activeKey
11928
- );
12002
+ ) : nodes;
11929
12003
  const initialPlacements = squarify(visibleNodes, bounds, minimumArea);
11930
- const placements = parentKey.endsWith("::sia-readable-overflow") ? initialPlacements : collapseNarrowNodes(
12004
+ const placements = aggregateOverflow ? parentKey.endsWith("::sia-readable-overflow") ? initialPlacements : collapseNarrowNodes(
11931
12005
  visibleNodes,
11932
12006
  initialPlacements,
11933
12007
  bounds,
@@ -11937,6 +12011,14 @@ function collectPositions(nodes, bounds, expanded, gap, headerHeight, minTileWid
11937
12011
  `${parentKey}:${depth}`,
11938
12012
  overflowLabel,
11939
12013
  activeKey
12014
+ ) : preserveNarrowNodes(
12015
+ visibleNodes,
12016
+ initialPlacements,
12017
+ bounds,
12018
+ minimumArea,
12019
+ minimumWidthWithGap,
12020
+ minimumHeightWithGap,
12021
+ `${parentKey}:${depth}`
11940
12022
  );
11941
12023
  placements.forEach(({ node, rectangle }) => {
11942
12024
  const inset = Math.min(gap, rectangle.width / 4, rectangle.height / 4);
@@ -11965,6 +12047,7 @@ function collectPositions(nodes, bounds, expanded, gap, headerHeight, minTileWid
11965
12047
  minTileWidth,
11966
12048
  minTileHeight,
11967
12049
  maxVisibleNodes,
12050
+ aggregateOverflow,
11968
12051
  overflowLabel,
11969
12052
  activeKey,
11970
12053
  depth + 1,
@@ -12009,9 +12092,11 @@ function Treemap({
12009
12092
  minTileWidth = 28,
12010
12093
  minTileHeight = 18,
12011
12094
  maxVisibleNodes = 180,
12095
+ aggregateOverflow = true,
12012
12096
  emptyContent = "\u6682\u65E0\u6570\u636E",
12013
12097
  valueFormatter = defaultValueFormatter,
12014
12098
  overflowLabel = (count) => `\u5176\u4ED6 ${count} \u9879`,
12099
+ showValueInCompactTiles = false,
12015
12100
  onExpandedChange,
12016
12101
  onFocusedChange,
12017
12102
  onNodeClick,
@@ -12041,10 +12126,11 @@ function Treemap({
12041
12126
  Math.max(1, minTileWidth),
12042
12127
  Math.max(1, minTileHeight),
12043
12128
  Math.max(1, maxVisibleNodes),
12129
+ aggregateOverflow,
12044
12130
  overflowLabel,
12045
12131
  activeKey
12046
12132
  ),
12047
- [activeKey, data, effectiveFocusedNode, expandedSet, gap, headerHeight, maxVisibleNodes, minTileHeight, minTileWidth, overflowLabel, size]
12133
+ [activeKey, aggregateOverflow, data, effectiveFocusedNode, expandedSet, gap, headerHeight, maxVisibleNodes, minTileHeight, minTileWidth, overflowLabel, size]
12048
12134
  );
12049
12135
  (0, import_react47.useEffect)(() => {
12050
12136
  const element = containerRef.current;
@@ -12102,7 +12188,7 @@ function Treemap({
12102
12188
  "--sia-treemap-value-font-size": `${Math.max(7, fontSize - 1)}px`
12103
12189
  };
12104
12190
  const showLabel = rectangle.width >= minLabelWidth && rectangle.height >= minLabelHeight;
12105
- const showValue = rectangle.width >= Math.max(52, minLabelWidth) && rectangle.height >= Math.max(34, minLabelHeight + 12);
12191
+ const showValue = showLabel && (showValueInCompactTiles || rectangle.width >= Math.max(52, minLabelWidth) && rectangle.height >= Math.max(34, minLabelHeight + 12));
12106
12192
  const hasChildren = Boolean(node.hasChildren || node.children?.length);
12107
12193
  const title = `${node.label} \xB7 ${String(valueFormatter(node.value, node))}`;
12108
12194
  if (expanded) {
@@ -12494,6 +12580,69 @@ var import_react53 = require("react");
12494
12580
 
12495
12581
  // src/components/Table/plugins.tsx
12496
12582
  var import_react52 = require("react");
12583
+
12584
+ // src/components/Table/columnSettings.ts
12585
+ function reconcileColumnSettings(columns, defaults, saved) {
12586
+ const stored = new Map(saved.map((item) => [item.key, item]));
12587
+ const base = new Map(defaults.map((item) => [item.key, item]));
12588
+ const result = [];
12589
+ const rank = (column) => {
12590
+ const own = stored.get(column.key)?.order;
12591
+ if (own !== void 0) return own;
12592
+ const children = column.children?.map(rank).filter((value) => value !== void 0);
12593
+ return children?.length ? Math.min(...children) : void 0;
12594
+ };
12595
+ const walk = (source, parent) => {
12596
+ const entries = source.map((column, index) => ({ column, index, rank: rank(column) }));
12597
+ let anchor = -1;
12598
+ for (const entry of entries) {
12599
+ if (entry.rank !== void 0) anchor = entry.rank;
12600
+ else entry.rank = anchor + (entry.index + 1) / (entries.length + 1);
12601
+ }
12602
+ entries.sort((a, b) => a.rank - b.rank || a.index - b.index);
12603
+ for (const { column } of entries) {
12604
+ const own = stored.get(column.key);
12605
+ if (column.children?.length) {
12606
+ walk(column.children, own ?? parent);
12607
+ } else {
12608
+ const fallback = base.get(column.key);
12609
+ if (!fallback) continue;
12610
+ const item = own ?? {
12611
+ ...fallback,
12612
+ fixed: parent?.fixed ?? fallback.fixed,
12613
+ visible: fallback.visible && (parent?.visible ?? true)
12614
+ };
12615
+ result.push({ ...item, order: result.length });
12616
+ }
12617
+ }
12618
+ };
12619
+ walk(columns);
12620
+ return result;
12621
+ }
12622
+ function mergeColumnSettings(columns, saved, active) {
12623
+ const merged = new Map(saved.filter((item) => !["__index__", "__selection__"].includes(item.key)).map((item) => [item.key, item]));
12624
+ active.forEach((item) => merged.set(item.key, item));
12625
+ const current = new Map(active.map((item) => [item.key, item]));
12626
+ const walk = (column) => {
12627
+ if (!column.children?.length) return current.has(column.key) ? [current.get(column.key)] : [];
12628
+ const leaves = column.children.flatMap(walk);
12629
+ if (leaves.length) {
12630
+ const old = merged.get(column.key);
12631
+ merged.set(column.key, {
12632
+ key: column.key,
12633
+ width: old?.width ?? column.width,
12634
+ fixed: old?.fixed,
12635
+ visible: leaves.some((item) => item.visible),
12636
+ order: Math.min(...leaves.map((item) => item.order))
12637
+ });
12638
+ }
12639
+ return leaves;
12640
+ };
12641
+ columns.forEach(walk);
12642
+ return [...merged.values()].sort((left, right) => left.order - right.order);
12643
+ }
12644
+
12645
+ // src/components/Table/plugins.tsx
12497
12646
  var import_jsx_runtime51 = require("react/jsx-runtime");
12498
12647
  function TableCheckbox({
12499
12648
  checked,
@@ -14584,13 +14733,14 @@ function createColumnSettingPlugin(options = {}) {
14584
14733
  const keys = new Set(baseItems(context).map((item) => item.key));
14585
14734
  return items.filter((item) => keys.has(item.key));
14586
14735
  };
14587
- const currentItems = (context) => context.state.items?.length ? editableItems(context.state.items, context) : baseItems(context);
14736
+ const currentItems = (context) => reconcileColumnSettings(context.baseColumns, baseItems(context), context.state.items ?? []);
14588
14737
  const save = (snapshot) => options.adapter && options.scope ? options.adapter.save(options.scope, snapshot).catch(() => void 0) : Promise.resolve();
14589
14738
  function apply(items, context, persist = true) {
14590
14739
  const normalized = orderColumnSettings(
14591
14740
  editableItems(items, context).sort((a, b) => a.order - b.order)
14592
14741
  );
14593
- context.setState((state) => ({ ...state, items: normalized, open: false }));
14742
+ const snapshot = mergeColumnSettings(context.baseColumns, context.state.items, normalized);
14743
+ context.setState((state) => ({ ...state, items: snapshot, open: false }));
14594
14744
  const columnMap = new Map(
14595
14745
  flattenTableColumns(context.baseColumns).map((column) => [
14596
14746
  column.key,
@@ -14605,7 +14755,7 @@ function createColumnSettingPlugin(options = {}) {
14605
14755
  );
14606
14756
  if (persist)
14607
14757
  void save({
14608
- columns: normalized,
14758
+ columns: snapshot,
14609
14759
  pageSize: context.state.pageSize,
14610
14760
  version: 1
14611
14761
  });
@@ -14670,9 +14820,10 @@ function createColumnSettingPlugin(options = {}) {
14670
14820
  const next = currentItems(context).map(
14671
14821
  (item) => item.key === columnKey ? { ...item, width } : item
14672
14822
  );
14673
- context.setState((state) => ({ ...state, items: next }));
14823
+ const snapshot = mergeColumnSettings(context.baseColumns, context.state.items, next);
14824
+ context.setState((state) => ({ ...state, items: snapshot }));
14674
14825
  void save({
14675
- columns: next,
14826
+ columns: snapshot,
14676
14827
  pageSize: context.state.pageSize,
14677
14828
  version: 1
14678
14829
  });
@@ -14680,7 +14831,7 @@ function createColumnSettingPlugin(options = {}) {
14680
14831
  if (event.type === "pageSizeChange") {
14681
14832
  const pageSize = event.payload;
14682
14833
  context.setState((state) => ({ ...state, pageSize }));
14683
- void save({ columns: currentItems(context), pageSize, version: 1 });
14834
+ void save({ columns: mergeColumnSettings(context.baseColumns, context.state.items, currentItems(context)), pageSize, version: 1 });
14684
14835
  }
14685
14836
  },
14686
14837
  renderToolbarEnd: options.showButton === false ? void 0 : (context) => {
package/dist/index.css CHANGED
@@ -10376,10 +10376,14 @@ button.sia-treemap__tile:focus-visible,
10376
10376
  }
10377
10377
  .sia-treemap__value {
10378
10378
  flex: 0 0 auto;
10379
+ max-width: 100%;
10380
+ overflow: hidden;
10379
10381
  color: rgb(21 34 56 / 72%);
10380
10382
  font-size: var(--sia-treemap-value-font-size, 11px);
10381
10383
  font-variant-numeric: tabular-nums;
10382
10384
  line-height: 1.2;
10385
+ text-overflow: ellipsis;
10386
+ white-space: nowrap;
10383
10387
  }
10384
10388
  [data-sia-theme=dark] .sia-treemap__tile {
10385
10389
  --sia-treemap-tile-color: color-mix(in srgb, var(--sia-treemap-color, #7aa8e8) 34%, #243248);
package/dist/index.d.cts CHANGED
@@ -1802,10 +1802,15 @@ interface TreemapProps extends Omit<HTMLAttributes<HTMLDivElement>, "onChange">
1802
1802
  */
1803
1803
  minTileHeight?: number;
1804
1804
  /**
1805
- * 最大可见节点数,超出后合并为其他项
1805
+ * 启用节点聚合时允许直接显示的最大节点数;超出后合并为一个汇总节点。
1806
1806
  * @defaultValue 180
1807
1807
  */
1808
1808
  maxVisibleNodes?: number;
1809
+ /**
1810
+ * 是否把数量超限或无法满足最小尺寸的节点合并为汇总节点。关闭后保留每个原始节点,并将尺寸不足的节点排列为紧凑网格;当容器面积不足以容纳所有最小尺寸时,网格仍会按现有空间等分。
1811
+ * @defaultValue true
1812
+ */
1813
+ aggregateOverflow?: boolean;
1809
1814
  /**
1810
1815
  * 没有数据时的显示内容。
1811
1816
  * @defaultValue "暂无数据"
@@ -1816,8 +1821,13 @@ interface TreemapProps extends Omit<HTMLAttributes<HTMLDivElement>, "onChange">
1816
1821
  * @defaultValue defaultValueFormatter
1817
1822
  */
1818
1823
  valueFormatter?: (value: number, node: TreemapNode) => ReactNode;
1819
- /** 自定义合并节点文案 */
1824
+ /** 启用节点聚合时用于生成汇总节点名称;参数为被合并的节点数量。 */
1820
1825
  overflowLabel?: (count: number) => string;
1826
+ /**
1827
+ * 是否在紧凑矩形块中也显示格式化后的数值;内容超出矩形块时按组件样式截断。
1828
+ * @defaultValue false
1829
+ */
1830
+ showValueInCompactTiles?: boolean;
1821
1831
  /** 展开状态改变时调用。 */
1822
1832
  onExpandedChange?: (keys: string[]) => void;
1823
1833
  /** 聚焦节点改变时调用。 */
@@ -1826,7 +1836,7 @@ interface TreemapProps extends Omit<HTMLAttributes<HTMLDivElement>, "onChange">
1826
1836
  onNodeClick?: (info: TreemapNodeClickInfo) => void;
1827
1837
  }
1828
1838
  /** 矩形树图组件。 */
1829
- declare function Treemap({ data, activeKey, expandedKeys, defaultExpandedKeys, focusedKey, defaultFocusedKey, gap, headerHeight, minLabelWidth, minLabelHeight, minTileWidth, minTileHeight, maxVisibleNodes, emptyContent, valueFormatter, overflowLabel, onExpandedChange, onFocusedChange, onNodeClick, className, style, ...props }: TreemapProps): react.JSX.Element;
1839
+ declare function Treemap({ data, activeKey, expandedKeys, defaultExpandedKeys, focusedKey, defaultFocusedKey, gap, headerHeight, minLabelWidth, minLabelHeight, minTileWidth, minTileHeight, maxVisibleNodes, aggregateOverflow, emptyContent, valueFormatter, overflowLabel, showValueInCompactTiles, onExpandedChange, onFocusedChange, onNodeClick, className, style, ...props }: TreemapProps): react.JSX.Element;
1830
1840
 
1831
1841
  /** 表格行或列的唯一标识类型。 */
1832
1842
  type TableKey = Key;
@@ -2744,7 +2754,7 @@ interface TableColumnSettingApi {
2744
2754
  /** 应用传入的配置;是否持久化由方法参数或插件选项控制。 */
2745
2755
  apply: (settings: readonly TableColumnSettingItem[]) => void;
2746
2756
  }
2747
- /** 创建表格列设置和持久化插件。将返回值加入 Table 的 plugins 后生效;序号和勾选附加列不显示在设置中,不参与业务列排序、显隐、固定位置与宽度配置,旧存储中的附加列配置会被忽略。 */
2757
+ /** 创建表格列设置和持久化插件。将返回值加入 Table 的 plugins 后生效;序号和勾选附加列不显示在设置中,不参与业务列排序、显隐、固定位置与宽度配置,旧存储中的附加列配置会被忽略。动态分组展开时自动补齐新叶子列,收起时保留子列设置供再次展开恢复。 */
2748
2758
  declare function createColumnSettingPlugin<T extends Record<string, unknown>>(options?: TableColumnSettingPluginOptions<T>): TablePlugin<T, ColumnSettingState, TableColumnSettingApi>;
2749
2759
  /** 创建使用 localStorage 持久化表格设置的适配器,按模块及业务标识隔离配置。 */
2750
2760
  declare function createLocalStorageTableSettingsAdapter(prefix?: string): TableSettingsAdapter;
package/dist/index.d.ts CHANGED
@@ -1802,10 +1802,15 @@ interface TreemapProps extends Omit<HTMLAttributes<HTMLDivElement>, "onChange">
1802
1802
  */
1803
1803
  minTileHeight?: number;
1804
1804
  /**
1805
- * 最大可见节点数,超出后合并为其他项
1805
+ * 启用节点聚合时允许直接显示的最大节点数;超出后合并为一个汇总节点。
1806
1806
  * @defaultValue 180
1807
1807
  */
1808
1808
  maxVisibleNodes?: number;
1809
+ /**
1810
+ * 是否把数量超限或无法满足最小尺寸的节点合并为汇总节点。关闭后保留每个原始节点,并将尺寸不足的节点排列为紧凑网格;当容器面积不足以容纳所有最小尺寸时,网格仍会按现有空间等分。
1811
+ * @defaultValue true
1812
+ */
1813
+ aggregateOverflow?: boolean;
1809
1814
  /**
1810
1815
  * 没有数据时的显示内容。
1811
1816
  * @defaultValue "暂无数据"
@@ -1816,8 +1821,13 @@ interface TreemapProps extends Omit<HTMLAttributes<HTMLDivElement>, "onChange">
1816
1821
  * @defaultValue defaultValueFormatter
1817
1822
  */
1818
1823
  valueFormatter?: (value: number, node: TreemapNode) => ReactNode;
1819
- /** 自定义合并节点文案 */
1824
+ /** 启用节点聚合时用于生成汇总节点名称;参数为被合并的节点数量。 */
1820
1825
  overflowLabel?: (count: number) => string;
1826
+ /**
1827
+ * 是否在紧凑矩形块中也显示格式化后的数值;内容超出矩形块时按组件样式截断。
1828
+ * @defaultValue false
1829
+ */
1830
+ showValueInCompactTiles?: boolean;
1821
1831
  /** 展开状态改变时调用。 */
1822
1832
  onExpandedChange?: (keys: string[]) => void;
1823
1833
  /** 聚焦节点改变时调用。 */
@@ -1826,7 +1836,7 @@ interface TreemapProps extends Omit<HTMLAttributes<HTMLDivElement>, "onChange">
1826
1836
  onNodeClick?: (info: TreemapNodeClickInfo) => void;
1827
1837
  }
1828
1838
  /** 矩形树图组件。 */
1829
- declare function Treemap({ data, activeKey, expandedKeys, defaultExpandedKeys, focusedKey, defaultFocusedKey, gap, headerHeight, minLabelWidth, minLabelHeight, minTileWidth, minTileHeight, maxVisibleNodes, emptyContent, valueFormatter, overflowLabel, onExpandedChange, onFocusedChange, onNodeClick, className, style, ...props }: TreemapProps): react.JSX.Element;
1839
+ declare function Treemap({ data, activeKey, expandedKeys, defaultExpandedKeys, focusedKey, defaultFocusedKey, gap, headerHeight, minLabelWidth, minLabelHeight, minTileWidth, minTileHeight, maxVisibleNodes, aggregateOverflow, emptyContent, valueFormatter, overflowLabel, showValueInCompactTiles, onExpandedChange, onFocusedChange, onNodeClick, className, style, ...props }: TreemapProps): react.JSX.Element;
1830
1840
 
1831
1841
  /** 表格行或列的唯一标识类型。 */
1832
1842
  type TableKey = Key;
@@ -2744,7 +2754,7 @@ interface TableColumnSettingApi {
2744
2754
  /** 应用传入的配置;是否持久化由方法参数或插件选项控制。 */
2745
2755
  apply: (settings: readonly TableColumnSettingItem[]) => void;
2746
2756
  }
2747
- /** 创建表格列设置和持久化插件。将返回值加入 Table 的 plugins 后生效;序号和勾选附加列不显示在设置中,不参与业务列排序、显隐、固定位置与宽度配置,旧存储中的附加列配置会被忽略。 */
2757
+ /** 创建表格列设置和持久化插件。将返回值加入 Table 的 plugins 后生效;序号和勾选附加列不显示在设置中,不参与业务列排序、显隐、固定位置与宽度配置,旧存储中的附加列配置会被忽略。动态分组展开时自动补齐新叶子列,收起时保留子列设置供再次展开恢复。 */
2748
2758
  declare function createColumnSettingPlugin<T extends Record<string, unknown>>(options?: TableColumnSettingPluginOptions<T>): TablePlugin<T, ColumnSettingState, TableColumnSettingApi>;
2749
2759
  /** 创建使用 localStorage 持久化表格设置的适配器,按模块及业务标识隔离配置。 */
2750
2760
  declare function createLocalStorageTableSettingsAdapter(prefix?: string): TableSettingsAdapter;
package/dist/index.js CHANGED
@@ -4434,12 +4434,86 @@ function collapseNarrowNodes(nodes, placements, bounds, minimumArea, minimumWidt
4434
4434
  };
4435
4435
  return squarify([...kept, overflowNode], bounds, minimumArea);
4436
4436
  }
4437
- function collectPositions(nodes, bounds, expanded, gap, headerHeight, minTileWidth, minTileHeight, maxVisibleNodes, overflowLabel, activeKey, depth = 0, parentKey = "root") {
4437
+ function placeCompactGrid(nodes, bounds, minimumWidth, minimumHeight) {
4438
+ if (nodes.length === 0 || bounds.width <= 0 || bounds.height <= 0) return [];
4439
+ const preferredAspectRatio = Math.max(0.1, minimumWidth / Math.max(1, minimumHeight));
4440
+ let columnCount = 1;
4441
+ let bestScore = Number.POSITIVE_INFINITY;
4442
+ for (let columns = 1; columns <= nodes.length; columns += 1) {
4443
+ const rows = Math.ceil(nodes.length / columns);
4444
+ const cellWidth = bounds.width / columns;
4445
+ const cellHeight = bounds.height / rows;
4446
+ const widthDeficit = Math.max(0, minimumWidth - cellWidth) / Math.max(1, minimumWidth);
4447
+ const heightDeficit = Math.max(0, minimumHeight - cellHeight) / Math.max(1, minimumHeight);
4448
+ const aspectPenalty = Math.abs(Math.log(Math.max(0.01, cellWidth / Math.max(1, cellHeight)) / preferredAspectRatio));
4449
+ const emptyPenalty = (rows * columns - nodes.length) / nodes.length;
4450
+ const score = (widthDeficit + heightDeficit) * 12 + aspectPenalty + emptyPenalty * 0.25;
4451
+ if (score < bestScore) {
4452
+ bestScore = score;
4453
+ columnCount = columns;
4454
+ }
4455
+ }
4456
+ const orderedNodes = [...nodes].sort((left, right) => layoutValue(right) - layoutValue(left));
4457
+ const rowCount = Math.ceil(orderedNodes.length / columnCount);
4458
+ const rowHeight = bounds.height / rowCount;
4459
+ return orderedNodes.map((node, index) => {
4460
+ const row = Math.floor(index / columnCount);
4461
+ const firstIndex = row * columnCount;
4462
+ const itemsInRow = Math.min(columnCount, orderedNodes.length - firstIndex);
4463
+ const column = index - firstIndex;
4464
+ const cellWidth = bounds.width / itemsInRow;
4465
+ return {
4466
+ node,
4467
+ rectangle: {
4468
+ x: bounds.x + column * cellWidth,
4469
+ y: bounds.y + row * rowHeight,
4470
+ width: column === itemsInRow - 1 ? bounds.x + bounds.width - (bounds.x + column * cellWidth) : cellWidth,
4471
+ height: row === rowCount - 1 ? bounds.y + bounds.height - (bounds.y + row * rowHeight) : rowHeight
4472
+ }
4473
+ };
4474
+ });
4475
+ }
4476
+ function preserveNarrowNodes(nodes, placements, bounds, minimumArea, minimumWidth, minimumHeight, groupKey) {
4477
+ const narrowKeys = new Set(
4478
+ placements.filter(({ rectangle }) => rectangle.width < minimumWidth || rectangle.height < minimumHeight).map(({ node }) => node.key)
4479
+ );
4480
+ if (narrowKeys.size === 0) return placements;
4481
+ let kept = nodes.filter((node) => !narrowKeys.has(node.key));
4482
+ let compact = nodes.filter((node) => narrowKeys.has(node.key));
4483
+ const totalArea = Math.max(1, bounds.width * bounds.height);
4484
+ while (kept.length > 0) {
4485
+ const keptWeight = kept.reduce((sum, node) => sum + layoutValue(node), 0);
4486
+ const compactWeight = compact.reduce((sum, node) => sum + layoutValue(node), 0);
4487
+ const proportionalArea = totalArea * compactWeight / Math.max(1, keptWeight + compactWeight);
4488
+ const readableArea = Math.min(
4489
+ totalArea * 0.82,
4490
+ Math.max(proportionalArea, compact.length * minimumArea)
4491
+ );
4492
+ const compactLayoutWeight = readableArea * keptWeight / Math.max(1, totalArea - readableArea);
4493
+ const compactGroup = {
4494
+ key: `${groupKey}::sia-compact-grid`,
4495
+ label: "",
4496
+ value: compact.reduce((sum, node) => sum + Math.max(0, node.value), 0),
4497
+ layoutValue: Math.max(compactWeight, compactLayoutWeight)
4498
+ };
4499
+ const outerPlacements = squarify([...kept, compactGroup], bounds, minimumArea);
4500
+ const newlyNarrowKeys = new Set(
4501
+ outerPlacements.filter(({ node, rectangle }) => node.key !== compactGroup.key && (rectangle.width < minimumWidth || rectangle.height < minimumHeight)).map(({ node }) => node.key)
4502
+ );
4503
+ if (newlyNarrowKeys.size === 0) {
4504
+ return outerPlacements.flatMap(({ node, rectangle }) => node.key === compactGroup.key ? placeCompactGrid(compact, rectangle, minimumWidth, minimumHeight) : [{ node, rectangle }]);
4505
+ }
4506
+ compact = [...compact, ...kept.filter((node) => newlyNarrowKeys.has(node.key))];
4507
+ kept = kept.filter((node) => !newlyNarrowKeys.has(node.key));
4508
+ }
4509
+ return placeCompactGrid(compact, bounds, minimumWidth, minimumHeight);
4510
+ }
4511
+ function collectPositions(nodes, bounds, expanded, gap, headerHeight, minTileWidth, minTileHeight, maxVisibleNodes, aggregateOverflow, overflowLabel, activeKey, depth = 0, parentKey = "root") {
4438
4512
  const result = [];
4439
4513
  const minimumWidthWithGap = minTileWidth + gap * 2;
4440
4514
  const minimumHeightWithGap = minTileHeight + gap * 2;
4441
4515
  const minimumArea = Math.max(1, minimumWidthWithGap * minimumHeightWithGap);
4442
- const visibleNodes = limitNodes(
4516
+ const visibleNodes = aggregateOverflow ? limitNodes(
4443
4517
  nodes,
4444
4518
  bounds,
4445
4519
  minimumArea,
@@ -4447,9 +4521,9 @@ function collectPositions(nodes, bounds, expanded, gap, headerHeight, minTileWid
4447
4521
  `${parentKey}:${depth}`,
4448
4522
  overflowLabel,
4449
4523
  activeKey
4450
- );
4524
+ ) : nodes;
4451
4525
  const initialPlacements = squarify(visibleNodes, bounds, minimumArea);
4452
- const placements = parentKey.endsWith("::sia-readable-overflow") ? initialPlacements : collapseNarrowNodes(
4526
+ const placements = aggregateOverflow ? parentKey.endsWith("::sia-readable-overflow") ? initialPlacements : collapseNarrowNodes(
4453
4527
  visibleNodes,
4454
4528
  initialPlacements,
4455
4529
  bounds,
@@ -4459,6 +4533,14 @@ function collectPositions(nodes, bounds, expanded, gap, headerHeight, minTileWid
4459
4533
  `${parentKey}:${depth}`,
4460
4534
  overflowLabel,
4461
4535
  activeKey
4536
+ ) : preserveNarrowNodes(
4537
+ visibleNodes,
4538
+ initialPlacements,
4539
+ bounds,
4540
+ minimumArea,
4541
+ minimumWidthWithGap,
4542
+ minimumHeightWithGap,
4543
+ `${parentKey}:${depth}`
4462
4544
  );
4463
4545
  placements.forEach(({ node, rectangle }) => {
4464
4546
  const inset = Math.min(gap, rectangle.width / 4, rectangle.height / 4);
@@ -4487,6 +4569,7 @@ function collectPositions(nodes, bounds, expanded, gap, headerHeight, minTileWid
4487
4569
  minTileWidth,
4488
4570
  minTileHeight,
4489
4571
  maxVisibleNodes,
4572
+ aggregateOverflow,
4490
4573
  overflowLabel,
4491
4574
  activeKey,
4492
4575
  depth + 1,
@@ -4531,9 +4614,11 @@ function Treemap({
4531
4614
  minTileWidth = 28,
4532
4615
  minTileHeight = 18,
4533
4616
  maxVisibleNodes = 180,
4617
+ aggregateOverflow = true,
4534
4618
  emptyContent = "\u6682\u65E0\u6570\u636E",
4535
4619
  valueFormatter = defaultValueFormatter,
4536
4620
  overflowLabel = (count) => `\u5176\u4ED6 ${count} \u9879`,
4621
+ showValueInCompactTiles = false,
4537
4622
  onExpandedChange,
4538
4623
  onFocusedChange,
4539
4624
  onNodeClick,
@@ -4563,10 +4648,11 @@ function Treemap({
4563
4648
  Math.max(1, minTileWidth),
4564
4649
  Math.max(1, minTileHeight),
4565
4650
  Math.max(1, maxVisibleNodes),
4651
+ aggregateOverflow,
4566
4652
  overflowLabel,
4567
4653
  activeKey
4568
4654
  ),
4569
- [activeKey, data, effectiveFocusedNode, expandedSet, gap, headerHeight, maxVisibleNodes, minTileHeight, minTileWidth, overflowLabel, size]
4655
+ [activeKey, aggregateOverflow, data, effectiveFocusedNode, expandedSet, gap, headerHeight, maxVisibleNodes, minTileHeight, minTileWidth, overflowLabel, size]
4570
4656
  );
4571
4657
  useEffect10(() => {
4572
4658
  const element = containerRef.current;
@@ -4624,7 +4710,7 @@ function Treemap({
4624
4710
  "--sia-treemap-value-font-size": `${Math.max(7, fontSize - 1)}px`
4625
4711
  };
4626
4712
  const showLabel = rectangle.width >= minLabelWidth && rectangle.height >= minLabelHeight;
4627
- const showValue = rectangle.width >= Math.max(52, minLabelWidth) && rectangle.height >= Math.max(34, minLabelHeight + 12);
4713
+ const showValue = showLabel && (showValueInCompactTiles || rectangle.width >= Math.max(52, minLabelWidth) && rectangle.height >= Math.max(34, minLabelHeight + 12));
4628
4714
  const hasChildren = Boolean(node.hasChildren || node.children?.length);
4629
4715
  const title = `${node.label} \xB7 ${String(valueFormatter(node.value, node))}`;
4630
4716
  if (expanded) {
@@ -5016,6 +5102,69 @@ import { Fragment as Fragment8, forwardRef as forwardRef5, isValidElement as isV
5016
5102
 
5017
5103
  // src/components/Table/plugins.tsx
5018
5104
  import { isValidElement as isValidElement3, useEffect as useEffect11, useRef as useRef15, useState as useState18 } from "react";
5105
+
5106
+ // src/components/Table/columnSettings.ts
5107
+ function reconcileColumnSettings(columns, defaults, saved) {
5108
+ const stored = new Map(saved.map((item) => [item.key, item]));
5109
+ const base = new Map(defaults.map((item) => [item.key, item]));
5110
+ const result = [];
5111
+ const rank = (column) => {
5112
+ const own = stored.get(column.key)?.order;
5113
+ if (own !== void 0) return own;
5114
+ const children = column.children?.map(rank).filter((value) => value !== void 0);
5115
+ return children?.length ? Math.min(...children) : void 0;
5116
+ };
5117
+ const walk = (source, parent) => {
5118
+ const entries = source.map((column, index) => ({ column, index, rank: rank(column) }));
5119
+ let anchor = -1;
5120
+ for (const entry of entries) {
5121
+ if (entry.rank !== void 0) anchor = entry.rank;
5122
+ else entry.rank = anchor + (entry.index + 1) / (entries.length + 1);
5123
+ }
5124
+ entries.sort((a, b) => a.rank - b.rank || a.index - b.index);
5125
+ for (const { column } of entries) {
5126
+ const own = stored.get(column.key);
5127
+ if (column.children?.length) {
5128
+ walk(column.children, own ?? parent);
5129
+ } else {
5130
+ const fallback = base.get(column.key);
5131
+ if (!fallback) continue;
5132
+ const item = own ?? {
5133
+ ...fallback,
5134
+ fixed: parent?.fixed ?? fallback.fixed,
5135
+ visible: fallback.visible && (parent?.visible ?? true)
5136
+ };
5137
+ result.push({ ...item, order: result.length });
5138
+ }
5139
+ }
5140
+ };
5141
+ walk(columns);
5142
+ return result;
5143
+ }
5144
+ function mergeColumnSettings(columns, saved, active) {
5145
+ const merged = new Map(saved.filter((item) => !["__index__", "__selection__"].includes(item.key)).map((item) => [item.key, item]));
5146
+ active.forEach((item) => merged.set(item.key, item));
5147
+ const current = new Map(active.map((item) => [item.key, item]));
5148
+ const walk = (column) => {
5149
+ if (!column.children?.length) return current.has(column.key) ? [current.get(column.key)] : [];
5150
+ const leaves = column.children.flatMap(walk);
5151
+ if (leaves.length) {
5152
+ const old = merged.get(column.key);
5153
+ merged.set(column.key, {
5154
+ key: column.key,
5155
+ width: old?.width ?? column.width,
5156
+ fixed: old?.fixed,
5157
+ visible: leaves.some((item) => item.visible),
5158
+ order: Math.min(...leaves.map((item) => item.order))
5159
+ });
5160
+ }
5161
+ return leaves;
5162
+ };
5163
+ columns.forEach(walk);
5164
+ return [...merged.values()].sort((left, right) => left.order - right.order);
5165
+ }
5166
+
5167
+ // src/components/Table/plugins.tsx
5019
5168
  import { Fragment as Fragment7, jsx as jsx22, jsxs as jsxs20 } from "react/jsx-runtime";
5020
5169
  function TableCheckbox({
5021
5170
  checked,
@@ -7106,13 +7255,14 @@ function createColumnSettingPlugin(options = {}) {
7106
7255
  const keys = new Set(baseItems(context).map((item) => item.key));
7107
7256
  return items.filter((item) => keys.has(item.key));
7108
7257
  };
7109
- const currentItems = (context) => context.state.items?.length ? editableItems(context.state.items, context) : baseItems(context);
7258
+ const currentItems = (context) => reconcileColumnSettings(context.baseColumns, baseItems(context), context.state.items ?? []);
7110
7259
  const save = (snapshot) => options.adapter && options.scope ? options.adapter.save(options.scope, snapshot).catch(() => void 0) : Promise.resolve();
7111
7260
  function apply(items, context, persist = true) {
7112
7261
  const normalized = orderColumnSettings(
7113
7262
  editableItems(items, context).sort((a, b) => a.order - b.order)
7114
7263
  );
7115
- context.setState((state) => ({ ...state, items: normalized, open: false }));
7264
+ const snapshot = mergeColumnSettings(context.baseColumns, context.state.items, normalized);
7265
+ context.setState((state) => ({ ...state, items: snapshot, open: false }));
7116
7266
  const columnMap = new Map(
7117
7267
  flattenTableColumns(context.baseColumns).map((column) => [
7118
7268
  column.key,
@@ -7127,7 +7277,7 @@ function createColumnSettingPlugin(options = {}) {
7127
7277
  );
7128
7278
  if (persist)
7129
7279
  void save({
7130
- columns: normalized,
7280
+ columns: snapshot,
7131
7281
  pageSize: context.state.pageSize,
7132
7282
  version: 1
7133
7283
  });
@@ -7192,9 +7342,10 @@ function createColumnSettingPlugin(options = {}) {
7192
7342
  const next = currentItems(context).map(
7193
7343
  (item) => item.key === columnKey ? { ...item, width } : item
7194
7344
  );
7195
- context.setState((state) => ({ ...state, items: next }));
7345
+ const snapshot = mergeColumnSettings(context.baseColumns, context.state.items, next);
7346
+ context.setState((state) => ({ ...state, items: snapshot }));
7196
7347
  void save({
7197
- columns: next,
7348
+ columns: snapshot,
7198
7349
  pageSize: context.state.pageSize,
7199
7350
  version: 1
7200
7351
  });
@@ -7202,7 +7353,7 @@ function createColumnSettingPlugin(options = {}) {
7202
7353
  if (event.type === "pageSizeChange") {
7203
7354
  const pageSize = event.payload;
7204
7355
  context.setState((state) => ({ ...state, pageSize }));
7205
- void save({ columns: currentItems(context), pageSize, version: 1 });
7356
+ void save({ columns: mergeColumnSettings(context.baseColumns, context.state.items, currentItems(context)), pageSize, version: 1 });
7206
7357
  }
7207
7358
  },
7208
7359
  renderToolbarEnd: options.showButton === false ? void 0 : (context) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sia.soul/sia-react-ui",
3
- "version": "0.1.80",
3
+ "version": "0.1.81",
4
4
  "description": "Sia Design components and tokens for React",
5
5
  "publishConfig": {
6
6
  "access": "public",