@zerodep-dev/vue-tsx-pro-ui 0.3.2 → 0.4.0

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.
Files changed (133) hide show
  1. package/dist/charts/AreaChart.d.ts +1 -1
  2. package/dist/charts/AreaChart.js +6 -48
  3. package/dist/charts/AreaChart.js.map +1 -1
  4. package/dist/charts/BarChart.js +6 -49
  5. package/dist/charts/BarChart.js.map +1 -1
  6. package/dist/charts/ChordChart.d.ts +7 -2
  7. package/dist/charts/ChordChart.js +151 -60
  8. package/dist/charts/ChordChart.js.map +1 -1
  9. package/dist/charts/ComboChart.d.ts +2 -1
  10. package/dist/charts/ComboChart.js +17 -93
  11. package/dist/charts/ComboChart.js.map +1 -1
  12. package/dist/charts/DonutChart.d.ts +6 -1
  13. package/dist/charts/DonutChart.js +134 -51
  14. package/dist/charts/DonutChart.js.map +1 -1
  15. package/dist/charts/FunnelChart.js.map +1 -1
  16. package/dist/charts/GraphChart.d.ts +22 -10
  17. package/dist/charts/GraphChart.js +415 -175
  18. package/dist/charts/GraphChart.js.map +1 -1
  19. package/dist/charts/LineChart.d.ts +1 -29
  20. package/dist/charts/LineChart.js +6 -54
  21. package/dist/charts/LineChart.js.map +1 -1
  22. package/dist/charts/MapChart.d.ts +21 -48
  23. package/dist/charts/MapChart.js +426 -353
  24. package/dist/charts/MapChart.js.map +1 -1
  25. package/dist/charts/RadarChart.js.map +1 -1
  26. package/dist/charts/SankeyChart.d.ts +7 -2
  27. package/dist/charts/SankeyChart.js +164 -76
  28. package/dist/charts/SankeyChart.js.map +1 -1
  29. package/dist/charts/ScatterChart.d.ts +7 -1
  30. package/dist/charts/ScatterChart.js +45 -30
  31. package/dist/charts/ScatterChart.js.map +1 -1
  32. package/dist/charts/TimelineChart.d.ts +8 -1
  33. package/dist/charts/TimelineChart.js +141 -89
  34. package/dist/charts/TimelineChart.js.map +1 -1
  35. package/dist/charts/TreemapChart.d.ts +6 -0
  36. package/dist/charts/TreemapChart.js +174 -58
  37. package/dist/charts/TreemapChart.js.map +1 -1
  38. package/dist/charts/WaterfallChart.d.ts +6 -7
  39. package/dist/charts/WaterfallChart.js +46 -51
  40. package/dist/charts/WaterfallChart.js.map +1 -1
  41. package/dist/charts/XYChart.d.ts +44 -35
  42. package/dist/charts/XYChart.js +493 -281
  43. package/dist/charts/XYChart.js.map +1 -1
  44. package/dist/charts/axis-ticks.d.ts +1 -1
  45. package/dist/charts/axis-ticks.js +4 -5
  46. package/dist/charts/axis-ticks.js.map +1 -1
  47. package/dist/charts/flow.d.ts +4 -11
  48. package/dist/charts/flow.js +4 -4
  49. package/dist/charts/flow.js.map +1 -1
  50. package/dist/charts/frame.d.ts +6 -30
  51. package/dist/charts/frame.js +6 -99
  52. package/dist/charts/frame.js.map +1 -1
  53. package/dist/charts/geo/projection.d.ts +45 -0
  54. package/dist/charts/geo/projection.js +201 -0
  55. package/dist/charts/geo/projection.js.map +1 -0
  56. package/dist/charts/geo/topojson.d.ts +50 -0
  57. package/dist/charts/geo/topojson.js +119 -0
  58. package/dist/charts/geo/topojson.js.map +1 -0
  59. package/dist/charts/index.d.ts +21 -9
  60. package/dist/charts/index.js +19 -8
  61. package/dist/charts/label-layout.js.map +1 -1
  62. package/dist/charts/layout/chord.d.ts +44 -0
  63. package/dist/charts/layout/chord.js +45 -0
  64. package/dist/charts/layout/chord.js.map +1 -0
  65. package/dist/charts/layout/graph.d.ts +49 -0
  66. package/dist/charts/layout/graph.js +268 -0
  67. package/dist/charts/layout/graph.js.map +1 -0
  68. package/dist/charts/layout/sankey.d.ts +79 -0
  69. package/dist/charts/layout/sankey.js +200 -0
  70. package/dist/charts/layout/sankey.js.map +1 -0
  71. package/dist/charts/layout/treemap.d.ts +46 -0
  72. package/dist/charts/layout/treemap.js +152 -0
  73. package/dist/charts/layout/treemap.js.map +1 -0
  74. package/dist/charts/marks.d.ts +79 -0
  75. package/dist/charts/marks.js +231 -0
  76. package/dist/charts/marks.js.map +1 -0
  77. package/dist/charts/plot.d.ts +107 -0
  78. package/dist/charts/plot.js +231 -0
  79. package/dist/charts/plot.js.map +1 -0
  80. package/dist/charts/scale.d.ts +29 -0
  81. package/dist/charts/scale.js +244 -0
  82. package/dist/charts/scale.js.map +1 -0
  83. package/dist/charts/shape.d.ts +46 -0
  84. package/dist/charts/shape.js +131 -0
  85. package/dist/charts/shape.js.map +1 -0
  86. package/dist/charts/stack.d.ts +17 -0
  87. package/dist/charts/stack.js +35 -0
  88. package/dist/charts/stack.js.map +1 -0
  89. package/dist/charts/text-size.d.ts +12 -0
  90. package/dist/charts/text-size.js +32 -0
  91. package/dist/charts/text-size.js.map +1 -0
  92. package/dist/display/VirtualList.d.ts +1 -2
  93. package/dist/display/VirtualList.js +11 -21
  94. package/dist/display/VirtualList.js.map +1 -1
  95. package/dist/input/Combobox.d.ts +1 -1
  96. package/dist/input/Combobox.js.map +1 -1
  97. package/dist/input/ListPopover.d.ts +1 -1
  98. package/dist/input/ListPopover.js.map +1 -1
  99. package/dist/input/Listbox.d.ts +1 -2
  100. package/dist/input/Listbox.js +7 -19
  101. package/dist/input/Listbox.js.map +1 -1
  102. package/dist/input/Select.d.ts +1 -1
  103. package/dist/input/Select.js.map +1 -1
  104. package/dist/maps/china-provinces.js +35680 -0
  105. package/dist/maps/china-provinces.js.map +1 -0
  106. package/dist/maps/china.d.ts +13 -0
  107. package/dist/maps/china.js +8 -0
  108. package/dist/maps/china.js.map +1 -0
  109. package/dist/maps/index.d.ts +9 -0
  110. package/dist/maps/index.js +3 -0
  111. package/dist/maps/world-countries.js +22273 -0
  112. package/dist/maps/world-countries.js.map +1 -0
  113. package/dist/maps/world.d.ts +13 -0
  114. package/dist/maps/world.js +8 -0
  115. package/dist/maps/world.js.map +1 -0
  116. package/dist/overlay/CommandPalette.d.ts +1 -1
  117. package/dist/overlay/CommandPalette.js.map +1 -1
  118. package/dist/overlay/detached.js +14 -1
  119. package/dist/overlay/detached.js.map +1 -1
  120. package/dist/patterns/QueryTable.js +0 -2
  121. package/dist/patterns/QueryTable.js.map +1 -1
  122. package/dist/table/DataTable.js +1 -1
  123. package/dist/table/DataTable.js.map +1 -1
  124. package/dist/table/DataTableView.d.ts +0 -3
  125. package/dist/table/DataTableView.js +32 -26
  126. package/dist/table/DataTableView.js.map +1 -1
  127. package/dist/table/table-styles.d.ts +1 -1
  128. package/dist/table/table-styles.js +0 -13
  129. package/dist/table/table-styles.js.map +1 -1
  130. package/package.json +7 -8
  131. package/dist/charts/single.d.ts +0 -41
  132. package/dist/charts/single.js +0 -66
  133. package/dist/charts/single.js.map +0 -1
@@ -0,0 +1,46 @@
1
+ /** 一个点 [x, y] */
2
+ export type ChartPoint = readonly [number, number];
3
+ /** 线的形状 */
4
+ export interface LineCurves {
5
+ /** 折线:相邻的点直线相连(默认) */
6
+ linear: unknown;
7
+ /** 平滑曲线:不会越过数据点(单调插值),趋势更柔和 */
8
+ monotone: unknown;
9
+ /** 阶梯:值保持到下一个点才跳变,适合离散的状态、价格档位 */
10
+ step: unknown;
11
+ }
12
+ /**
13
+ * 一串点连成的路径;start 是开头的命令(M 新起一段,L 接着上一段画:面积的下沿)。
14
+ * 少于三个点时平滑曲线退化成直线
15
+ */
16
+ export declare function linePath(points: readonly ChartPoint[], curve?: keyof LineCurves, start?: 'M' | 'L'): string;
17
+ /** 面积:上沿 top 从左到右,下沿 bottom(与 top 一一对应)从右到左,同一种曲线,闭合 */
18
+ export declare function areaPath(top: readonly ChartPoint[], bottom: readonly ChartPoint[], curve?: keyof LineCurves): string;
19
+ /** 柱子的哪一端是圆角:柱顶朝哪个方向 */
20
+ export type BarEnd = 'top' | 'bottom' | 'left' | 'right' | 'none';
21
+ /** 柱子:矩形(左上角 x、y,宽高),end 那一端的两个角是半径 radius 的圆角(不超过宽或高的一半) */
22
+ export declare function barPath(x: number, y: number, width: number, height: number, radius: number, end: BarEnd): string;
23
+ /** 环形的一段:角度从 12 点钟方向顺时针(弧度),圆心在原点 */
24
+ export interface ArcShape {
25
+ /** 内半径;0 是饼图的扇形 */
26
+ inner: number;
27
+ /** 外半径 */
28
+ outer: number;
29
+ /** 起始角 */
30
+ start: number;
31
+ /** 结束角 */
32
+ end: number;
33
+ /** 四个角的圆角半径,默认 0 */
34
+ corner?: number;
35
+ }
36
+ /** 半径 r、角度 angle(12 点钟方向顺时针)处的点 */
37
+ export declare function polarPoint(r: number, angle: number): [number, number];
38
+ /**
39
+ * 环形一段的路径:两条径向的边、内外两段弧;corner 时四个角各是一段与边、弧都相切的小圆弧
40
+ * (边是径向的,与圆正交,切点可以直接算出)。整圈时画成两段弧(没有边)
41
+ */
42
+ export declare function arcPath({ inner, outer, start, end, corner }: ArcShape): string;
43
+ /** 弦图的一根弦:两端是圆上的两段弧(半径 radius,角度同 arcPath),中间经圆心的二次曲线相连(同 d3 的 ribbon) */
44
+ export declare function ribbonPath(radius: number, source: readonly [number, number], target: readonly [number, number]): string;
45
+ /** 横向的流向带(桑基图的连线):从 (x0, y0) 到 (x1, y1) 的 S 形三次曲线,控制点在两端的中间 */
46
+ export declare function horizontalLinkPath(x0: number, y0: number, x1: number, y1: number): string;
@@ -0,0 +1,131 @@
1
+ import { svgNumber } from "../utils/data/number.js";
2
+ //#region src/charts/shape.ts
3
+ /**
4
+ * 图形的 SVG 路径:折线与面积(直线、单调平滑、阶梯)、一端圆角的柱子、环形的扇区、弦图的弦、桑基图的带子。
5
+ * 曲线的算法同 d3-shape(curveMonotoneX 是 Steffen 的单调三次插值,不会越过数据点),写成只产出路径字符串的精简版。
6
+ * 坐标保留三位小数(svgNumber):服务端与浏览器算出的路径一致。
7
+ *
8
+ * linePath([[0, 10], [40, 30], [80, 20]], 'monotone') // 'M0,10C13.333,…'
9
+ * areaPath(top, bottom, 'linear') // 上沿从左到右、下沿从右到左,闭合
10
+ * arcPath({ inner: 60, outer: 88, start: 0, end: Math.PI / 2, corner: 3 })
11
+ */
12
+ /** 两个数字写成“x,y” */
13
+ var pair = (x, y) => `${svgNumber(x)},${svgNumber(y)}`;
14
+ var sign = (value) => value < 0 ? -1 : 1;
15
+ /** 中间一点的切线斜率:两侧的斜率异号时为 0(极值点不越过),否则取不超过两侧的调和值(同 d3 的 slope3) */
16
+ function interiorSlope(a, b, c) {
17
+ const h0 = b[0] - a[0];
18
+ const h1 = c[0] - b[0];
19
+ const s0 = (b[1] - a[1]) / (h0 || (h1 < 0 ? -0 : 0));
20
+ const s1 = (c[1] - b[1]) / (h1 || (h0 < 0 ? -0 : 0));
21
+ const p = (s0 * h1 + s1 * h0) / (h0 + h1);
22
+ return (sign(s0) + sign(s1)) * Math.min(Math.abs(s0), Math.abs(s1), .5 * Math.abs(p)) || 0;
23
+ }
24
+ /** 端点的切线斜率:由相邻一段的斜率与相邻点的切线推出(同 d3 的 slope2) */
25
+ function endSlope(a, b, tangent) {
26
+ const h = b[0] - a[0];
27
+ return h ? (3 * (b[1] - a[1]) / h - tangent) / 2 : tangent;
28
+ }
29
+ /** 单调三次插值:各点的切线,再按 Hermite 写成三次贝塞尔 */
30
+ function monotoneCommands(points) {
31
+ const n = points.length;
32
+ const tangents = points.map((point, i) => i > 0 && i < n - 1 ? interiorSlope(points[i - 1], point, points[i + 1]) : 0);
33
+ tangents[0] = endSlope(points[0], points[1], tangents[1]);
34
+ tangents[n - 1] = endSlope(points[n - 2], points[n - 1], tangents[n - 2]);
35
+ let path = "";
36
+ for (let i = 1; i < n; i += 1) {
37
+ const [x0, y0] = points[i - 1];
38
+ const [x1, y1] = points[i];
39
+ const dx = (x1 - x0) / 3;
40
+ path += `C${pair(x0 + dx, y0 + dx * tangents[i - 1])},${pair(x1 - dx, y1 - dx * tangents[i])},${pair(x1, y1)}`;
41
+ }
42
+ return path;
43
+ }
44
+ /**
45
+ * 一串点连成的路径;start 是开头的命令(M 新起一段,L 接着上一段画:面积的下沿)。
46
+ * 少于三个点时平滑曲线退化成直线
47
+ */
48
+ function linePath(points, curve = "linear", start = "M") {
49
+ if (points.length === 0) return "";
50
+ const [first, ...rest] = points;
51
+ let path = `${start}${pair(first[0], first[1])}`;
52
+ if (curve === "monotone" && points.length > 2) return path + monotoneCommands(points);
53
+ let previous = first;
54
+ for (const point of rest) {
55
+ if (curve === "step") path += `L${pair(point[0], previous[1])}`;
56
+ path += `L${pair(point[0], point[1])}`;
57
+ previous = point;
58
+ }
59
+ return path;
60
+ }
61
+ /** 面积:上沿 top 从左到右,下沿 bottom(与 top 一一对应)从右到左,同一种曲线,闭合 */
62
+ function areaPath(top, bottom, curve = "linear") {
63
+ if (top.length === 0) return "";
64
+ const back = curve === "step" ? bottom.flatMap((point, i) => i === 0 ? [point] : [[point[0], bottom[i - 1][1]], point]) : bottom;
65
+ return `${linePath(top, curve)}${linePath([...back].reverse(), curve === "step" ? "linear" : curve, "L")}Z`;
66
+ }
67
+ /** 柱子:矩形(左上角 x、y,宽高),end 那一端的两个角是半径 radius 的圆角(不超过宽或高的一半) */
68
+ function barPath(x, y, width, height, radius, end) {
69
+ const w = Math.max(width, 0);
70
+ const h = Math.max(height, 0);
71
+ const vertical = end === "top" || end === "bottom";
72
+ const r = end === "none" ? 0 : Math.max(0, Math.min(radius, vertical ? w / 2 : h / 2, vertical ? h : w));
73
+ const [x1, y1] = [x + w, y + h];
74
+ const corner = (cx, cy) => `A${pair(r, r)},0,0,1,${pair(cx, cy)}`;
75
+ if (!r) return `M${pair(x, y)}H${svgNumber(x1)}V${svgNumber(y1)}H${svgNumber(x)}Z`;
76
+ if (end === "top") return `M${pair(x, y1)}V${svgNumber(y + r)}${corner(x + r, y)}H${svgNumber(x1 - r)}${corner(x1, y + r)}V${svgNumber(y1)}Z`;
77
+ if (end === "bottom") return `M${pair(x1, y)}V${svgNumber(y1 - r)}${corner(x1 - r, y1)}H${svgNumber(x + r)}${corner(x, y1 - r)}V${svgNumber(y)}Z`;
78
+ if (end === "right") return `M${pair(x, y)}H${svgNumber(x1 - r)}${corner(x1, y + r)}V${svgNumber(y1 - r)}${corner(x1 - r, y1)}H${svgNumber(x)}Z`;
79
+ return `M${pair(x1, y1)}H${svgNumber(x + r)}${corner(x, y1 - r)}V${svgNumber(y + r)}${corner(x + r, y)}H${svgNumber(x1)}Z`;
80
+ }
81
+ /** 半径 r、角度 angle(12 点钟方向顺时针)处的点 */
82
+ function polarPoint(r, angle) {
83
+ return [r * Math.sin(angle), -r * Math.cos(angle)];
84
+ }
85
+ /**
86
+ * 环形一段的路径:两条径向的边、内外两段弧;corner 时四个角各是一段与边、弧都相切的小圆弧
87
+ * (边是径向的,与圆正交,切点可以直接算出)。整圈时画成两段弧(没有边)
88
+ */
89
+ function arcPath({ inner, outer, start, end, corner = 0 }) {
90
+ const sweep = end - start;
91
+ if (!(sweep > 0) || !(outer > 0)) return "";
92
+ const at = (r, angle) => pair(...polarPoint(r, angle));
93
+ const arc = (r, to, clockwise, span) => `A${pair(r, r)},0,${span > Math.PI ? 1 : 0},${clockwise ? 1 : 0},${at(r, to)}`;
94
+ if (sweep >= Math.PI * 2 - 1e-9) {
95
+ const outerRing = `M${at(outer, 0)}${arc(outer, Math.PI, true, Math.PI)}${arc(outer, 0, true, Math.PI)}Z`;
96
+ if (inner <= 0) return outerRing;
97
+ return `${outerRing}M${at(inner, 0)}${arc(inner, Math.PI, false, Math.PI)}${arc(inner, 0, false, Math.PI)}Z`;
98
+ }
99
+ const r = Math.max(0, Math.min(corner, (outer - inner) / 2, (inner > 0 ? inner : outer) * sweep / 2.2));
100
+ if (r < .5) {
101
+ const outerPart = `M${at(outer, start)}${arc(outer, end, true, sweep)}`;
102
+ if (inner <= 0) return `${outerPart}L0,0Z`;
103
+ return `${outerPart}L${at(inner, end)}${arc(inner, start, false, sweep)}Z`;
104
+ }
105
+ const outerShift = Math.asin(r / (outer - r));
106
+ const outerEdge = Math.sqrt((outer - r) ** 2 - r * r);
107
+ const fillet = (to) => `A${pair(r, r)},0,0,1,${to}`;
108
+ let path = `M${at(outerEdge, start)}${fillet(at(outer, start + outerShift))}${arc(outer, end - outerShift, true, sweep - outerShift * 2)}${fillet(at(outerEdge, end))}`;
109
+ if (inner <= 0) return `${path}L0,0Z`;
110
+ const innerShift = Math.asin(r / (inner + r));
111
+ const innerEdge = Math.sqrt((inner + r) ** 2 - r * r);
112
+ path += `L${at(innerEdge, end)}${fillet(at(inner, end - innerShift))}${arc(inner, start + innerShift, false, sweep - innerShift * 2)}${fillet(at(innerEdge, start))}Z`;
113
+ return path;
114
+ }
115
+ /** 弦图的一根弦:两端是圆上的两段弧(半径 radius,角度同 arcPath),中间经圆心的二次曲线相连(同 d3 的 ribbon) */
116
+ function ribbonPath(radius, source, target) {
117
+ const at = (angle) => pair(...polarPoint(radius, angle));
118
+ const arc = ([from, to]) => `A${pair(radius, radius)},0,${to - from > Math.PI ? 1 : 0},1,${at(to)}`;
119
+ let path = `M${at(source[0])}${arc(source)}`;
120
+ if (source[0] !== target[0] || source[1] !== target[1]) path += `Q0,0,${at(target[0])}${arc(target)}`;
121
+ return `${path}Q0,0,${at(source[0])}Z`;
122
+ }
123
+ /** 横向的流向带(桑基图的连线):从 (x0, y0) 到 (x1, y1) 的 S 形三次曲线,控制点在两端的中间 */
124
+ function horizontalLinkPath(x0, y0, x1, y1) {
125
+ const middle = (x0 + x1) / 2;
126
+ return `M${pair(x0, y0)}C${pair(middle, y0)},${pair(middle, y1)},${pair(x1, y1)}`;
127
+ }
128
+ //#endregion
129
+ export { arcPath, areaPath, barPath, horizontalLinkPath, linePath, polarPoint, ribbonPath };
130
+
131
+ //# sourceMappingURL=shape.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"shape.js","names":[],"sources":["../../src/charts/shape.ts"],"sourcesContent":["/**\n * 图形的 SVG 路径:折线与面积(直线、单调平滑、阶梯)、一端圆角的柱子、环形的扇区、弦图的弦、桑基图的带子。\n * 曲线的算法同 d3-shape(curveMonotoneX 是 Steffen 的单调三次插值,不会越过数据点),写成只产出路径字符串的精简版。\n * 坐标保留三位小数(svgNumber):服务端与浏览器算出的路径一致。\n *\n * linePath([[0, 10], [40, 30], [80, 20]], 'monotone') // 'M0,10C13.333,…'\n * areaPath(top, bottom, 'linear') // 上沿从左到右、下沿从右到左,闭合\n * arcPath({ inner: 60, outer: 88, start: 0, end: Math.PI / 2, corner: 3 })\n */\nimport { svgNumber } from '../utils/data/number';\n\n/** 一个点 [x, y] */\nexport type ChartPoint = readonly [number, number];\n\n/** 线的形状 */\nexport interface LineCurves {\n /** 折线:相邻的点直线相连(默认) */\n linear: unknown;\n /** 平滑曲线:不会越过数据点(单调插值),趋势更柔和 */\n monotone: unknown;\n /** 阶梯:值保持到下一个点才跳变,适合离散的状态、价格档位 */\n step: unknown;\n}\n\n/** 两个数字写成“x,y” */\nconst pair = (x: number, y: number) => `${svgNumber(x)},${svgNumber(y)}`;\n\nconst sign = (value: number) => (value < 0 ? -1 : 1);\n\n/** 中间一点的切线斜率:两侧的斜率异号时为 0(极值点不越过),否则取不超过两侧的调和值(同 d3 的 slope3) */\nfunction interiorSlope(a: ChartPoint, b: ChartPoint, c: ChartPoint): number {\n const h0 = b[0] - a[0];\n const h1 = c[0] - b[0];\n // 横坐标相同时除以带符号的 0:得到同向的无穷大,下面取 min 时自然被别的值替代\n const s0 = (b[1] - a[1]) / (h0 || (h1 < 0 ? -0 : 0));\n const s1 = (c[1] - b[1]) / (h1 || (h0 < 0 ? -0 : 0));\n const p = (s0 * h1 + s1 * h0) / (h0 + h1);\n return (sign(s0) + sign(s1)) * Math.min(Math.abs(s0), Math.abs(s1), 0.5 * Math.abs(p)) || 0;\n}\n\n/** 端点的切线斜率:由相邻一段的斜率与相邻点的切线推出(同 d3 的 slope2) */\nfunction endSlope(a: ChartPoint, b: ChartPoint, tangent: number): number {\n const h = b[0] - a[0];\n return h ? ((3 * (b[1] - a[1])) / h - tangent) / 2 : tangent;\n}\n\n/** 单调三次插值:各点的切线,再按 Hermite 写成三次贝塞尔 */\nfunction monotoneCommands(points: readonly ChartPoint[]): string {\n const n = points.length;\n const tangents = points.map((point, i) =>\n i > 0 && i < n - 1 ? interiorSlope(points[i - 1]!, point, points[i + 1]!) : 0,\n );\n tangents[0] = endSlope(points[0]!, points[1]!, tangents[1]!);\n tangents[n - 1] = endSlope(points[n - 2]!, points[n - 1]!, tangents[n - 2]!);\n let path = '';\n for (let i = 1; i < n; i += 1) {\n const [x0, y0] = points[i - 1]!;\n const [x1, y1] = points[i]!;\n const dx = (x1 - x0) / 3;\n path += `C${pair(x0 + dx, y0 + dx * tangents[i - 1]!)},${pair(x1 - dx, y1 - dx * tangents[i]!)},${pair(x1, y1)}`;\n }\n return path;\n}\n\n/**\n * 一串点连成的路径;start 是开头的命令(M 新起一段,L 接着上一段画:面积的下沿)。\n * 少于三个点时平滑曲线退化成直线\n */\nexport function linePath(\n points: readonly ChartPoint[],\n curve: keyof LineCurves = 'linear',\n start: 'M' | 'L' = 'M',\n): string {\n if (points.length === 0) return '';\n const [first, ...rest] = points;\n let path = `${start}${pair(first![0], first![1])}`;\n if (curve === 'monotone' && points.length > 2) return path + monotoneCommands(points);\n let previous = first!;\n for (const point of rest) {\n // 阶梯:横着走到下一个点的横坐标,再竖着跳到它的值\n if (curve === 'step') path += `L${pair(point[0], previous[1])}`;\n path += `L${pair(point[0], point[1])}`;\n previous = point;\n }\n return path;\n}\n\n/** 面积:上沿 top 从左到右,下沿 bottom(与 top 一一对应)从右到左,同一种曲线,闭合 */\nexport function areaPath(\n top: readonly ChartPoint[],\n bottom: readonly ChartPoint[],\n curve: keyof LineCurves = 'linear',\n): string {\n if (top.length === 0) return '';\n // 阶梯的下沿倒着画时要先竖后横,才与上沿的拐角对齐:倒着画的阶梯把每一步的值挪到前一个点\n const back =\n curve === 'step'\n ? bottom.flatMap((point, i) =>\n i === 0 ? [point] : [[point[0], bottom[i - 1]![1]] as const, point],\n )\n : bottom;\n return `${linePath(top, curve)}${linePath([...back].reverse(), curve === 'step' ? 'linear' : curve, 'L')}Z`;\n}\n\n/** 柱子的哪一端是圆角:柱顶朝哪个方向 */\nexport type BarEnd = 'top' | 'bottom' | 'left' | 'right' | 'none';\n\n/** 柱子:矩形(左上角 x、y,宽高),end 那一端的两个角是半径 radius 的圆角(不超过宽或高的一半) */\nexport function barPath(\n x: number,\n y: number,\n width: number,\n height: number,\n radius: number,\n end: BarEnd,\n): string {\n const w = Math.max(width, 0);\n const h = Math.max(height, 0);\n const vertical = end === 'top' || end === 'bottom';\n const r =\n end === 'none' ? 0 : Math.max(0, Math.min(radius, vertical ? w / 2 : h / 2, vertical ? h : w));\n const [x1, y1] = [x + w, y + h];\n const corner = (cx: number, cy: number) => `A${pair(r, r)},0,0,1,${pair(cx, cy)}`;\n if (!r) return `M${pair(x, y)}H${svgNumber(x1)}V${svgNumber(y1)}H${svgNumber(x)}Z`;\n if (end === 'top')\n return `M${pair(x, y1)}V${svgNumber(y + r)}${corner(x + r, y)}H${svgNumber(x1 - r)}${corner(x1, y + r)}V${svgNumber(y1)}Z`;\n if (end === 'bottom')\n return `M${pair(x1, y)}V${svgNumber(y1 - r)}${corner(x1 - r, y1)}H${svgNumber(x + r)}${corner(x, y1 - r)}V${svgNumber(y)}Z`;\n if (end === 'right')\n return `M${pair(x, y)}H${svgNumber(x1 - r)}${corner(x1, y + r)}V${svgNumber(y1 - r)}${corner(x1 - r, y1)}H${svgNumber(x)}Z`;\n return `M${pair(x1, y1)}H${svgNumber(x + r)}${corner(x, y1 - r)}V${svgNumber(y + r)}${corner(x + r, y)}H${svgNumber(x1)}Z`;\n}\n\n/** 环形的一段:角度从 12 点钟方向顺时针(弧度),圆心在原点 */\nexport interface ArcShape {\n /** 内半径;0 是饼图的扇形 */\n inner: number;\n /** 外半径 */\n outer: number;\n /** 起始角 */\n start: number;\n /** 结束角 */\n end: number;\n /** 四个角的圆角半径,默认 0 */\n corner?: number;\n}\n\n/** 半径 r、角度 angle(12 点钟方向顺时针)处的点 */\nexport function polarPoint(r: number, angle: number): [number, number] {\n return [r * Math.sin(angle), -r * Math.cos(angle)];\n}\n\n/**\n * 环形一段的路径:两条径向的边、内外两段弧;corner 时四个角各是一段与边、弧都相切的小圆弧\n * (边是径向的,与圆正交,切点可以直接算出)。整圈时画成两段弧(没有边)\n */\nexport function arcPath({ inner, outer, start, end, corner = 0 }: ArcShape): string {\n const sweep = end - start;\n if (!(sweep > 0) || !(outer > 0)) return '';\n const at = (r: number, angle: number) => pair(...polarPoint(r, angle));\n const arc = (r: number, to: number, clockwise: boolean, span: number) =>\n `A${pair(r, r)},0,${span > Math.PI ? 1 : 0},${clockwise ? 1 : 0},${at(r, to)}`;\n if (sweep >= Math.PI * 2 - 1e-9) {\n const outerRing = `M${at(outer, 0)}${arc(outer, Math.PI, true, Math.PI)}${arc(outer, 0, true, Math.PI)}Z`;\n if (inner <= 0) return outerRing;\n return `${outerRing}M${at(inner, 0)}${arc(inner, Math.PI, false, Math.PI)}${arc(inner, 0, false, Math.PI)}Z`;\n }\n // 圆角不超过环宽的一半,也不超过这一段在内(饼图是外)弧上长度的一半\n const r = Math.max(\n 0,\n Math.min(corner, (outer - inner) / 2, ((inner > 0 ? inner : outer) * sweep) / 2.2),\n );\n if (r < 0.5) {\n const outerPart = `M${at(outer, start)}${arc(outer, end, true, sweep)}`;\n if (inner <= 0) return `${outerPart}L0,0Z`;\n return `${outerPart}L${at(inner, end)}${arc(inner, start, false, sweep)}Z`;\n }\n // 外角:小圆心在半径 outer - r 上,切边的点离圆心 √((outer-r)² - r²),切弧的点偏开 asin(r / (outer - r))\n const outerShift = Math.asin(r / (outer - r));\n const outerEdge = Math.sqrt((outer - r) ** 2 - r * r);\n const fillet = (to: string) => `A${pair(r, r)},0,0,1,${to}`;\n let path =\n `M${at(outerEdge, start)}${fillet(at(outer, start + outerShift))}` +\n `${arc(outer, end - outerShift, true, sweep - outerShift * 2)}${fillet(at(outerEdge, end))}`;\n if (inner <= 0) return `${path}L0,0Z`;\n // 内角:小圆心在半径 inner + r 上\n const innerShift = Math.asin(r / (inner + r));\n const innerEdge = Math.sqrt((inner + r) ** 2 - r * r);\n path +=\n `L${at(innerEdge, end)}${fillet(at(inner, end - innerShift))}` +\n `${arc(inner, start + innerShift, false, sweep - innerShift * 2)}${fillet(at(innerEdge, start))}Z`;\n return path;\n}\n\n/** 弦图的一根弦:两端是圆上的两段弧(半径 radius,角度同 arcPath),中间经圆心的二次曲线相连(同 d3 的 ribbon) */\nexport function ribbonPath(\n radius: number,\n source: readonly [number, number],\n target: readonly [number, number],\n): string {\n const at = (angle: number) => pair(...polarPoint(radius, angle));\n const arc = ([from, to]: readonly [number, number]) =>\n `A${pair(radius, radius)},0,${to - from > Math.PI ? 1 : 0},1,${at(to)}`;\n let path = `M${at(source[0])}${arc(source)}`;\n if (source[0] !== target[0] || source[1] !== target[1])\n path += `Q0,0,${at(target[0])}${arc(target)}`;\n return `${path}Q0,0,${at(source[0])}Z`;\n}\n\n/** 横向的流向带(桑基图的连线):从 (x0, y0) 到 (x1, y1) 的 S 形三次曲线,控制点在两端的中间 */\nexport function horizontalLinkPath(x0: number, y0: number, x1: number, y1: number): string {\n const middle = (x0 + x1) / 2;\n return `M${pair(x0, y0)}C${pair(middle, y0)},${pair(middle, y1)},${pair(x1, y1)}`;\n}\n"],"mappings":"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"}
@@ -0,0 +1,17 @@
1
+ /**
2
+ * 堆叠:各系列在同一类目上依次叠起,正值从 0 往上叠、负值从 0 往下叠(同 d3 的 stackOffsetDiverging)。
3
+ * 柱状图、面积图、组合图的堆叠与十字线圆点、标注圈的位置共用。
4
+ *
5
+ * stackValues([[3, 5], [2, -1]]) // 系列 0:[[0,3], [0,5]];系列 1:[[3,5], [-1,0]]
6
+ */
7
+ /** 一个系列在一行上叠出的一段 */
8
+ export interface StackSegment {
9
+ /** 下沿(负值是靠近 0 的一端以外的那端) */
10
+ low: number;
11
+ /** 上沿 */
12
+ high: number;
13
+ /** 原值;空值是 undefined,这时 low、high 都在当前的正向累计处(高度为 0,面积连续) */
14
+ value: number | undefined;
15
+ }
16
+ /** values[系列][行] → 每个系列在每一行的一段 */
17
+ export declare function stackValues(values: readonly (readonly (number | undefined)[])[]): StackSegment[][];
@@ -0,0 +1,35 @@
1
+ //#region src/charts/stack.ts
2
+ /** values[系列][行] → 每个系列在每一行的一段 */
3
+ function stackValues(values) {
4
+ const rows = Math.max(0, ...values.map((list) => list.length));
5
+ const positive = Array.from({ length: rows }, () => 0);
6
+ const negative = Array.from({ length: rows }, () => 0);
7
+ return values.map((list) => Array.from({ length: rows }, (_, row) => {
8
+ const value = list[row];
9
+ if (value === void 0 || !Number.isFinite(value)) return {
10
+ low: positive[row],
11
+ high: positive[row],
12
+ value: void 0
13
+ };
14
+ if (value >= 0) {
15
+ const low = positive[row];
16
+ positive[row] = low + value;
17
+ return {
18
+ low,
19
+ high: low + value,
20
+ value
21
+ };
22
+ }
23
+ const high = negative[row];
24
+ negative[row] = high + value;
25
+ return {
26
+ low: high + value,
27
+ high,
28
+ value
29
+ };
30
+ }));
31
+ }
32
+ //#endregion
33
+ export { stackValues };
34
+
35
+ //# sourceMappingURL=stack.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stack.js","names":[],"sources":["../../src/charts/stack.ts"],"sourcesContent":["/**\n * 堆叠:各系列在同一类目上依次叠起,正值从 0 往上叠、负值从 0 往下叠(同 d3 的 stackOffsetDiverging)。\n * 柱状图、面积图、组合图的堆叠与十字线圆点、标注圈的位置共用。\n *\n * stackValues([[3, 5], [2, -1]]) // 系列 0:[[0,3], [0,5]];系列 1:[[3,5], [-1,0]]\n */\n\n/** 一个系列在一行上叠出的一段 */\nexport interface StackSegment {\n /** 下沿(负值是靠近 0 的一端以外的那端) */\n low: number;\n /** 上沿 */\n high: number;\n /** 原值;空值是 undefined,这时 low、high 都在当前的正向累计处(高度为 0,面积连续) */\n value: number | undefined;\n}\n\n/** values[系列][行] → 每个系列在每一行的一段 */\nexport function stackValues(\n values: readonly (readonly (number | undefined)[])[],\n): StackSegment[][] {\n const rows = Math.max(0, ...values.map((list) => list.length));\n const positive = Array.from({ length: rows }, () => 0);\n const negative = Array.from({ length: rows }, () => 0);\n return values.map((list) =>\n Array.from({ length: rows }, (_, row) => {\n const value = list[row];\n if (value === undefined || !Number.isFinite(value))\n return { low: positive[row]!, high: positive[row]!, value: undefined };\n if (value >= 0) {\n const low = positive[row]!;\n positive[row] = low + value;\n return { low, high: low + value, value };\n }\n const high = negative[row]!;\n negative[row] = high + value;\n return { low: high + value, high, value };\n }),\n );\n}\n"],"mappings":";;AAkBA,SAAgB,YACd,QACkB;CAClB,MAAM,OAAO,KAAK,IAAI,GAAG,GAAG,OAAO,KAAK,SAAS,KAAK,MAAM,CAAC;CAC7D,MAAM,WAAW,MAAM,KAAK,EAAE,QAAQ,KAAK,SAAS,CAAC;CACrD,MAAM,WAAW,MAAM,KAAK,EAAE,QAAQ,KAAK,SAAS,CAAC;CACrD,OAAO,OAAO,KAAK,SACjB,MAAM,KAAK,EAAE,QAAQ,KAAK,IAAI,GAAG,QAAQ;EACvC,MAAM,QAAQ,KAAK;EACnB,IAAI,UAAU,KAAA,KAAa,CAAC,OAAO,SAAS,KAAK,GAC/C,OAAO;GAAE,KAAK,SAAS;GAAO,MAAM,SAAS;GAAO,OAAO,KAAA;EAAU;EACvE,IAAI,SAAS,GAAG;GACd,MAAM,MAAM,SAAS;GACrB,SAAS,OAAO,MAAM;GACtB,OAAO;IAAE;IAAK,MAAM,MAAM;IAAO;GAAM;EACzC;EACA,MAAM,OAAO,SAAS;EACtB,SAAS,OAAO,OAAO;EACvB,OAAO;GAAE,KAAK,OAAO;GAAO;GAAM;EAAM;CAC1C,CAAC,CACH;AACF"}
@@ -0,0 +1,12 @@
1
+ /**
2
+ * 图表里文字的宽度估算:留坐标轴的边距、决定类目刻度隔几个写一个、地点名字的避让都要在画之前知道文字多宽。
3
+ * 不去量 DOM(服务端渲染、还没挂上时也要算):中日韩文字、全角符号按一个字号宽,其余按 0.6 个字号宽,
4
+ * 宁宽勿窄(宽了只是多留一点空,窄了会叠在一起)。
5
+ *
6
+ * estimateTextWidth('华东区 2026', 12) // 3 × 12 + 5 × 7.2 = 72
7
+ * truncateText('很长很长的类目名', 60, 12) // '很长很长…'
8
+ */
9
+ /** 估算文字的宽度(px);fontSize 是字号(px),默认 12(主题的 xs) */
10
+ export declare function estimateTextWidth(text: string, fontSize?: number): number;
11
+ /** 文字超过 maxWidth 时截短并加省略号;放得下时原样返回 */
12
+ export declare function truncateText(text: string, maxWidth: number, fontSize?: number): string;
@@ -0,0 +1,32 @@
1
+ //#region src/charts/text-size.ts
2
+ /**
3
+ * 图表里文字的宽度估算:留坐标轴的边距、决定类目刻度隔几个写一个、地点名字的避让都要在画之前知道文字多宽。
4
+ * 不去量 DOM(服务端渲染、还没挂上时也要算):中日韩文字、全角符号按一个字号宽,其余按 0.6 个字号宽,
5
+ * 宁宽勿窄(宽了只是多留一点空,窄了会叠在一起)。
6
+ *
7
+ * estimateTextWidth('华东区 2026', 12) // 3 × 12 + 5 × 7.2 = 72
8
+ * truncateText('很长很长的类目名', 60, 12) // '很长很长…'
9
+ */
10
+ /** 按一个字号宽算的字:中日韩文字、全角标点、假名、谚文 */
11
+ var WIDE = /[ᄀ-ᅟ⺀-꓏가-힣豈-﫿︰-﹏＀-⦆¢-₩]/u;
12
+ /** 估算文字的宽度(px);fontSize 是字号(px),默认 12(主题的 xs) */
13
+ function estimateTextWidth(text, fontSize = 12) {
14
+ let width = 0;
15
+ for (const char of text) width += WIDE.test(char) ? fontSize : fontSize * .6;
16
+ return width;
17
+ }
18
+ /** 文字超过 maxWidth 时截短并加省略号;放得下时原样返回 */
19
+ function truncateText(text, maxWidth, fontSize = 12) {
20
+ if (estimateTextWidth(text, fontSize) <= maxWidth) return text;
21
+ const room = maxWidth - fontSize;
22
+ let result = "";
23
+ for (const char of text) {
24
+ if (estimateTextWidth(result + char, fontSize) > room) break;
25
+ result += char;
26
+ }
27
+ return `${result}…`;
28
+ }
29
+ //#endregion
30
+ export { estimateTextWidth, truncateText };
31
+
32
+ //# sourceMappingURL=text-size.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"text-size.js","names":[],"sources":["../../src/charts/text-size.ts"],"sourcesContent":["/**\n * 图表里文字的宽度估算:留坐标轴的边距、决定类目刻度隔几个写一个、地点名字的避让都要在画之前知道文字多宽。\n * 不去量 DOM(服务端渲染、还没挂上时也要算):中日韩文字、全角符号按一个字号宽,其余按 0.6 个字号宽,\n * 宁宽勿窄(宽了只是多留一点空,窄了会叠在一起)。\n *\n * estimateTextWidth('华东区 2026', 12) // 3 × 12 + 5 × 7.2 = 72\n * truncateText('很长很长的类目名', 60, 12) // '很长很长…'\n */\n\n/** 按一个字号宽算的字:中日韩文字、全角标点、假名、谚文 */\nconst WIDE = /[ᄀ-ᅟ⺀-꓏가-힣豈-﫿︰-﹏＀-⦆¢-₩]/u;\n\n/** 估算文字的宽度(px);fontSize 是字号(px),默认 12(主题的 xs) */\nexport function estimateTextWidth(text: string, fontSize = 12): number {\n let width = 0;\n for (const char of text) width += WIDE.test(char) ? fontSize : fontSize * 0.6;\n return width;\n}\n\n/** 文字超过 maxWidth 时截短并加省略号;放得下时原样返回 */\nexport function truncateText(text: string, maxWidth: number, fontSize = 12): string {\n if (estimateTextWidth(text, fontSize) <= maxWidth) return text;\n const room = maxWidth - fontSize;\n let result = '';\n for (const char of text) {\n if (estimateTextWidth(result + char, fontSize) > room) break;\n result += char;\n }\n return `${result}…`;\n}\n"],"mappings":";;;;;;;;;;AAUA,IAAM,OAAO;;AAGb,SAAgB,kBAAkB,MAAc,WAAW,IAAY;CACrE,IAAI,QAAQ;CACZ,KAAK,MAAM,QAAQ,MAAM,SAAS,KAAK,KAAK,IAAI,IAAI,WAAW,WAAW;CAC1E,OAAO;AACT;;AAGA,SAAgB,aAAa,MAAc,UAAkB,WAAW,IAAY;CAClF,IAAI,kBAAkB,MAAM,QAAQ,KAAK,UAAU,OAAO;CAC1D,MAAM,OAAO,WAAW;CACxB,IAAI,SAAS;CACb,KAAK,MAAM,QAAQ,MAAM;EACvB,IAAI,kBAAkB,SAAS,MAAM,QAAQ,IAAI,MAAM;EACvD,UAAU;CACZ;CACA,OAAO,GAAG,OAAO;AACnB"}
@@ -37,7 +37,7 @@ export interface VirtualListProps<T> extends PartProps<'div', VirtualListManaged
37
37
  horizontal?: boolean;
38
38
  /** 没有数据时显示的内容 */
39
39
  empty?: VNodeChild;
40
- /** 加载中:没有数据时显示骨架行,已有数据时顶部显示进度条 */
40
+ /** 加载中:没有数据时铺几行骨架,已有数据(加载更多)时骨架接在末尾 */
41
41
  loading?: boolean;
42
42
  /** 加载中占位行的部件属性 */
43
43
  propsSkeleton?: SkeletonShapeProps;
@@ -62,7 +62,6 @@ export declare const virtualListStyles: Readonly<{
62
62
  itemHorizontal: css.CompiledStyles;
63
63
  itemVertical: css.CompiledStyles;
64
64
  list: css.CompiledStyles;
65
- progress: css.CompiledStyles;
66
65
  root: css.CompiledStyles;
67
66
  skeleton: css.CompiledStyles;
68
67
  skeletonRow: css.CompiledStyles;
@@ -1,11 +1,9 @@
1
- import { useLang } from "../config/context.js";
2
1
  import { elementProps, mergeAttrs, mergePartProps, mergeProps } from "../utils/props.js";
3
2
  import { focusRingStyles } from "../utils/style/styles.js";
4
3
  import { useDelayedLoading } from "../utils/motion/delayed-loading.js";
5
4
  import { ScrollArea } from "../layout/ScrollArea.js";
6
5
  import "../utils/style/shimmer.js";
7
6
  import { Skeleton } from "../feedback/Skeleton.js";
8
- import { Progress } from "../feedback/Progress.js";
9
7
  import { useVirtualizer } from "../utils/virtual/use-virtualizer.js";
10
8
  import { Fragment, createVNode, isVNode, shallowRef, watch } from "vue";
11
9
  import { _component, childrenProp, mergeJsxProps, readonlyProp, restProps } from "@zerodep-dev/vue-tsx-pro-tsx";
@@ -19,8 +17,9 @@ import { css } from "@zerodep-dev/vue-tsx-pro-tsx/styles";
19
17
  function _isSlot(s) {
20
18
  return typeof s === "function" || Object.prototype.toString.call(s) === "[object Object]" && !isVNode(s);
21
19
  }
22
- /** 加载中显示几行骨架 */
20
+ /** 加载中显示几行骨架:没有数据时铺几行,加载更多时接在末尾几行 */
23
21
  var SKELETON_ROWS = 5;
22
+ var MORE_SKELETON_ROWS = 2;
24
23
  /** VirtualList 的样式:root、container、list、item、itemVertical、itemHorizontal 等 */
25
24
  var virtualListStyles = css.create({
26
25
  root: { overflowAnchor: "none" },
@@ -37,12 +36,6 @@ var virtualListStyles = css.create({
37
36
  },
38
37
  itemVertical: { width: "_full" },
39
38
  itemHorizontal: { height: "_full" },
40
- progress: {
41
- position: "sticky",
42
- top: 0,
43
- zIndex: 1,
44
- height: 0
45
- },
46
39
  skeletonRow: { paddingBlock: "_xs" },
47
40
  skeleton: {
48
41
  display: "block",
@@ -77,10 +70,16 @@ var VirtualList = /*#__PURE__*/ _component((_props, _ctx) => {
77
70
  const propsList = readonlyProp(_props, "propsList");
78
71
  const propsItem = readonlyProp(_props, "propsItem");
79
72
  const rest = restProps(_props, _ctx, false);
80
- const lang = useLang();
81
73
  const indicator = useDelayedLoading(() => loading.value);
82
74
  let root = shallowRef(null);
83
75
  let list = shallowRef(null);
76
+ const skeletonRows = (count) => Array.from({ length: count }, (_, index) => createVNode("div", mergeJsxProps({
77
+ "key": `skeleton-${index}`,
78
+ "aria-hidden": "true"
79
+ }, css.attrs(virtualListStyles.skeletonRow)), [createVNode(Skeleton, mergePartProps({
80
+ sx: [virtualListStyles.skeleton, virtualListSizeStyles.skeleton(typeof estimateSize.value === "number" ? estimateSize.value : 40)],
81
+ delay: index * 120
82
+ }, propsSkeleton.value), null)]));
84
83
  const virtual = useVirtualizer(() => {
85
84
  const data = items.value;
86
85
  const estimate = estimateSize.value;
@@ -112,10 +111,7 @@ var VirtualList = /*#__PURE__*/ _component((_props, _ctx) => {
112
111
  "aria-busy": loading.value || void 0,
113
112
  tabindex: isContainer ? 0 : void 0
114
113
  });
115
- const content = createVNode(Fragment, null, [indicator.value && hasItems && createVNode("div", mergeJsxProps({ "aria-hidden": "true" }, css.attrs(virtualListStyles.progress)), [createVNode(Progress, {
116
- "size": "xs",
117
- "aria-label": lang.loading
118
- }, null)]), hasItems ? createVNode("div", mergeAttrs({ sx: virtualListStyles.list }, propsList.value, {
114
+ const content = createVNode(Fragment, null, [hasItems ? createVNode("div", mergeAttrs({ sx: virtualListStyles.list }, propsList.value, {
119
115
  sx: horizontal.value ? css.bind(virtualListSizeStyles.width, () => [virtual.total], true) : css.bind(virtualListSizeStyles.height, () => [virtual.total], true),
120
116
  ref: list,
121
117
  role: "presentation"
@@ -129,13 +125,7 @@ var VirtualList = /*#__PURE__*/ _component((_props, _ctx) => {
129
125
  "aria-setsize": items.value.length,
130
126
  "aria-posinset": item.index + 1
131
127
  }), { "data-index": item.index }), [children.value(data, item.index)]);
132
- })]) : loading.value ? indicator.value && Array.from({ length: SKELETON_ROWS }, (_, index) => createVNode("div", mergeJsxProps({
133
- "key": index,
134
- "aria-hidden": "true"
135
- }, css.attrs(virtualListStyles.skeletonRow)), [createVNode(Skeleton, mergePartProps({
136
- sx: [virtualListStyles.skeleton, css.bind(virtualListSizeStyles.skeleton, () => [typeof estimateSize.value === "number" ? estimateSize.value : 40], true)],
137
- delay: index * 120
138
- }, propsSkeleton.value), null)])) : empty.value]);
128
+ })]) : loading.value ? indicator.value && skeletonRows(SKELETON_ROWS) : empty.value, hasItems && indicator.value && skeletonRows(MORE_SKELETON_ROWS)]);
139
129
  return isContainer ? createVNode(ScrollArea, {
140
130
  "axis": horizontal.value ? "x" : "y",
141
131
  "focusable": false,
@@ -1 +1 @@
1
- {"version":3,"file":"VirtualList.js","names":["_isVNode","_vtp_readonlyProp","_vtp_childrenProp","_vtp_restProps","_vue_shallowRef","_createVNode","_Fragment","_mergeProps"],"sources":["../../src/display/VirtualList.tsx"],"sourcesContent":["/**\n * 虚拟列表:只渲染可见范围内的项,上万条数据也流畅。行高可以不固定,渲染后自动测量(COMPONENTS.md §10.7)。\n *\n * <VirtualList items={users} getKey={(user) => user.id} estimateSize={48} sx={styles.list}>\n * {(user) => <UserRow user={user} />}\n * </VirtualList>\n *\n * 默认组件自己是滚动容器,高度由使用方给(sx、class 或 style);scroll=\"window\" 时随页面滚动。\n * 读屏:role=\"list\",每项带 aria-setsize / aria-posinset,未渲染的项也算在总数里;没有数据时是 role=\"group\"(空状态、骨架不是列表项)。\n * loading:没有数据时显示骨架行(按 estimateSize 的高度),已有数据(刷新、加载更多)时顶部显示进度条;带 aria-busy。\n */\nimport { _component } from '@zerodep-dev/vue-tsx-pro-tsx';\nimport { css } from '@zerodep-dev/vue-tsx-pro-tsx/styles';\nimport { watch, type VNodeChild } from 'vue';\nimport { useDelayedLoading } from '../utils/motion/delayed-loading';\nimport { useLang } from '../config/context';\nimport { Progress } from '../feedback/Progress';\nimport { Skeleton, type SkeletonProps, type SkeletonShapeProps } from '../feedback/Skeleton';\nimport { SHIMMER_STAGGER } from '../utils/style/shimmer';\nimport {\n elementProps,\n mergeAttrs,\n mergePartProps,\n mergeProps,\n type PartProps,\n} from '../utils/props';\nimport { ScrollArea } from '../layout/ScrollArea';\nimport { focusRingStyles } from '../utils/style/styles';\nimport { useVirtualizer } from '../utils/virtual/use-virtualizer';\nimport type { VirtualScrollOptions, VisibleRange } from '../utils/virtual/virtualizer';\n\n/** 跟着谁滚动 */\nexport interface VirtualListScrolls {\n /** 组件自己是滚动容器(默认):要给它高度(横向时给宽度) */\n container: unknown;\n /** 随页面(window)滚动:列表按总高度撑开,放在页面的普通流里 */\n window: unknown;\n}\n\n/** handle 拿到的方法 */\nexport interface VirtualListHandle {\n /** 滚动到第 index 项;align 默认 auto(已经完整可见就不动) */\n scrollToIndex: (index: number, options?: VirtualScrollOptions) => void;\n /** 滚动到相对列表开头的位置(px) */\n scrollToOffset: (offset: number, options?: VirtualScrollOptions) => void;\n}\n\n/** 受管属性:角色与可聚焦由组件决定 */\nexport type VirtualListManaged = 'role' | 'tabindex';\n\n/** 未声明的属性落到外层元素(滚动容器)上;aria-label 写在这里就是列表的名称 */\nexport interface VirtualListProps<T> extends PartProps<'div', VirtualListManaged> {\n /** 数据 */\n items: readonly T[];\n /** 渲染一项:`{(item, index) => …}` */\n children: (item: T, index: number) => VNodeChild;\n /** 每项的 key,默认用序号;数据会重排、增删时一定要给,测量结果与组件状态才对得上 */\n getKey?: (item: T, index: number) => PropertyKey;\n /** 每项的估计大小(px),数字或按项计算;渲染后以实际大小为准,默认 40 */\n estimateSize?: number | ((item: T, index: number) => number);\n /** 可见范围前后各多渲染几项,默认 4 */\n overscan?: number;\n /** 跟着谁滚动,默认 container */\n scroll?: keyof VirtualListScrolls;\n /** 横向排列与滚动 */\n horizontal?: boolean;\n /** 没有数据时显示的内容 */\n empty?: VNodeChild;\n /** 加载中:没有数据时显示骨架行,已有数据时顶部显示进度条 */\n loading?: boolean;\n /** 加载中占位行的部件属性 */\n propsSkeleton?: SkeletonShapeProps;\n /** 拿到滚动方法:`handle={(list) => (listHandle = list)}` */\n handle?: (handle: VirtualListHandle) => void;\n /** 可见范围变化(不含 overscan):接近末尾时加载更多 */\n onRangeChange?: (range: VisibleRange) => void;\n /** 列表层(撑开总高度、role=\"list\" 的项都在它里面)的部件属性 */\n propsList?: PartProps<'div', 'role'>;\n /**\n * 每一项外层(role=\"listitem\")的部件属性;可以按项计算:`(item, index) => ({ class: … })`。\n * 位置、序号与无障碍属性由组件管理\n */\n propsItem?: ItemProps | ((item: T, index: number) => ItemProps);\n}\n\n/** 项外层可以定制的属性 */\ntype ItemProps = PartProps<'div', 'role' | 'aria-setsize' | 'aria-posinset'>;\n\n/** 加载中显示几行骨架 */\nconst SKELETON_ROWS = 5;\n\n/** VirtualList 的样式:root、container、list、item、itemVertical、itemHorizontal 等 */\nexport const virtualListStyles = css.create({\n // 自己调整滚动位置(见 virtualizer.ts),不要浏览器再按锚点调整一次\n root: { overflowAnchor: 'none' },\n // 容器模式:列表元素是 ScrollArea 的视口(浮层滚动条)\n container: { overscrollBehavior: 'contain' },\n list: { position: 'relative', overflowAnchor: 'none' },\n item: { position: 'absolute', top: 0, left: 0, overflowAnchor: 'none' },\n itemVertical: { width: '_full' },\n itemHorizontal: { height: '_full' },\n // 刷新中的进度条贴在可见区域顶部\n progress: { position: 'sticky', top: 0, zIndex: 1, height: 0 },\n skeletonRow: { paddingBlock: '_xs' },\n skeleton: { display: 'block', width: '_full' },\n});\n\n// 总大小与位置是任意值,用动态样式(编译为 CSS 变量);单独一个 create,见 COMPONENTS.md §4\n/** VirtualList 的样式:height、width、y、x、skeleton */\nexport const virtualListSizeStyles = css.create({\n height: (px: number) => ({ height: px }),\n width: (px: number) => ({ width: px, height: '_full' }),\n y: (px: number) => ({ transform: `translateY(${px}px)` }),\n x: (px: number) => ({ transform: `translateX(${px}px)` }),\n // 骨架行比一项略矮:上下留出间隔\n skeleton: (px: number) => ({ height: Math.max(8, px - 8) }),\n});\n\n/** 虚拟列表:只渲染可见范围内的项,行高渲染后自动测量 */\nexport const VirtualList = _component(\n <T,>({\n items,\n children,\n getKey,\n estimateSize = 40,\n overscan = 4,\n scroll = 'container',\n horizontal = false,\n empty,\n loading = false,\n propsSkeleton,\n handle,\n onRangeChange,\n propsList,\n propsItem,\n ...rest\n }: VirtualListProps<T>) => {\n const lang = useLang();\n // 骨架、进度条防闪烁;aria-busy 按真实的 loading\n const indicator = useDelayedLoading(() => loading);\n let root = _state.raw<HTMLElement | null>(null);\n let list = _state.raw<HTMLElement | null>(null);\n const virtual = useVirtualizer(() => {\n const data = items;\n const estimate = estimateSize;\n const keyOf = getKey;\n return {\n count: data.length,\n overscan,\n horizontal,\n scrollElement: scroll === 'window' ? window : root,\n list,\n estimateSize: (index: number) =>\n typeof estimate === 'number' ? estimate : estimate(data[index]!, index),\n getItemKey: keyOf ? (index: number) => keyOf(data[index]!, index) : undefined,\n onRangeChange,\n };\n });\n\n // 挂载之后才交出 handle:使用方在回调里立即滚动(恢复上次的位置)也能生效。\n // handle 不放进监听源:父级写的箭头函数每次渲染都是新的,不该因此重新交出\n const api: VirtualListHandle = {\n scrollToIndex: virtual.scrollToIndex,\n scrollToOffset: virtual.scrollToOffset,\n };\n watch(\n () => root,\n (element) => {\n if (element) handle?.(api);\n },\n { flush: 'post' },\n );\n\n return () => {\n const isContainer = scroll === 'container';\n const hasItems = items.length > 0;\n const props = mergeProps<'div'>(\n {\n sx: [\n virtualListStyles.root,\n isContainer && [virtualListStyles.container, focusRingStyles.visible],\n ],\n },\n rest,\n {\n ref: _toRef(root),\n // 没有数据(空状态、首次加载的骨架)时不是列表;仍给 group 角色,写在外层的 aria-label 才有效\n role: hasItems ? 'list' : 'group',\n 'aria-busy': loading || undefined,\n // 容器模式下可以用键盘滚动\n tabindex: isContainer ? 0 : undefined,\n },\n );\n const content = (\n <>\n {indicator.value && hasItems && (\n <div aria-hidden=\"true\" {...css.attrs(virtualListStyles.progress)}>\n <Progress size=\"xs\" aria-label={lang.loading} />\n </div>\n )}\n {hasItems ? (\n <div\n {...mergeAttrs<'div'>({ sx: virtualListStyles.list }, propsList, {\n sx: horizontal\n ? virtualListSizeStyles.width(virtual.total)\n : virtualListSizeStyles.height(virtual.total),\n ref: _toRef(list),\n role: 'presentation',\n })}\n >\n {virtual.items.map((item) => {\n const data = items[item.index]!;\n const own =\n typeof propsItem === 'function' ? propsItem(data, item.index) : propsItem;\n return (\n <div\n key={item.key}\n {...mergeAttrs<'div'>(\n {\n sx: [\n virtualListStyles.item,\n horizontal\n ? virtualListStyles.itemHorizontal\n : virtualListStyles.itemVertical,\n ],\n },\n own,\n {\n sx: horizontal\n ? virtualListSizeStyles.x(item.start)\n : virtualListSizeStyles.y(item.start),\n ref: virtual.measure,\n role: 'listitem',\n 'aria-setsize': items.length,\n 'aria-posinset': item.index + 1,\n },\n )}\n data-index={item.index}\n >\n {children(data, item.index)}\n </div>\n );\n })}\n </div>\n ) : loading ? (\n indicator.value &&\n Array.from({ length: SKELETON_ROWS }, (_, index) => (\n <div key={index} aria-hidden=\"true\" {...css.attrs(virtualListStyles.skeletonRow)}>\n <Skeleton\n {...mergePartProps<SkeletonProps>(\n {\n sx: [\n virtualListStyles.skeleton,\n virtualListSizeStyles.skeleton(\n typeof estimateSize === 'number' ? estimateSize : 40,\n ),\n ],\n delay: index * SHIMMER_STAGGER,\n },\n propsSkeleton,\n )}\n />\n </div>\n ))\n ) : (\n empty\n )}\n </>\n );\n // 容器模式:列表元素就是 ScrollArea 的视口,滚动条是浮层的;跟着页面滚动时就是普通的 div\n return isContainer ? (\n <ScrollArea\n axis={horizontal ? 'x' : 'y'}\n focusable={false}\n propsViewport={props as PartProps<'div'>}\n >\n {content}\n </ScrollArea>\n ) : (\n <div {...elementProps(props)}>{content}</div>\n );\n };\n },\n);\n"],"mappings":"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"}
1
+ {"version":3,"file":"VirtualList.js","names":["_isVNode","_vtp_readonlyProp","_vtp_childrenProp","_vtp_restProps","_vue_shallowRef","_createVNode","_mergeProps","_Fragment"],"sources":["../../src/display/VirtualList.tsx"],"sourcesContent":["/**\n * 虚拟列表:只渲染可见范围内的项,上万条数据也流畅。行高可以不固定,渲染后自动测量(COMPONENTS.md §10.7)。\n *\n * <VirtualList items={users} getKey={(user) => user.id} estimateSize={48} sx={styles.list}>\n * {(user) => <UserRow user={user} />}\n * </VirtualList>\n *\n * 默认组件自己是滚动容器,高度由使用方给(sx、class 或 style);scroll=\"window\" 时随页面滚动。\n * 读屏:role=\"list\",每项带 aria-setsize / aria-posinset,未渲染的项也算在总数里;没有数据时是 role=\"group\"(空状态、骨架不是列表项)。\n * loading:只用骨架行(按 estimateSize 的高度):没有数据时铺满,已有数据(加载更多)时接在列表末尾,不另加进度条;带 aria-busy。\n */\nimport { _component } from '@zerodep-dev/vue-tsx-pro-tsx';\nimport { css } from '@zerodep-dev/vue-tsx-pro-tsx/styles';\nimport { watch, type VNodeChild } from 'vue';\nimport { useDelayedLoading } from '../utils/motion/delayed-loading';\nimport { Skeleton, type SkeletonProps, type SkeletonShapeProps } from '../feedback/Skeleton';\nimport { SHIMMER_STAGGER } from '../utils/style/shimmer';\nimport {\n elementProps,\n mergeAttrs,\n mergePartProps,\n mergeProps,\n type PartProps,\n} from '../utils/props';\nimport { ScrollArea } from '../layout/ScrollArea';\nimport { focusRingStyles } from '../utils/style/styles';\nimport { useVirtualizer } from '../utils/virtual/use-virtualizer';\nimport type { VirtualScrollOptions, VisibleRange } from '../utils/virtual/virtualizer';\n\n/** 跟着谁滚动 */\nexport interface VirtualListScrolls {\n /** 组件自己是滚动容器(默认):要给它高度(横向时给宽度) */\n container: unknown;\n /** 随页面(window)滚动:列表按总高度撑开,放在页面的普通流里 */\n window: unknown;\n}\n\n/** handle 拿到的方法 */\nexport interface VirtualListHandle {\n /** 滚动到第 index 项;align 默认 auto(已经完整可见就不动) */\n scrollToIndex: (index: number, options?: VirtualScrollOptions) => void;\n /** 滚动到相对列表开头的位置(px) */\n scrollToOffset: (offset: number, options?: VirtualScrollOptions) => void;\n}\n\n/** 受管属性:角色与可聚焦由组件决定 */\nexport type VirtualListManaged = 'role' | 'tabindex';\n\n/** 未声明的属性落到外层元素(滚动容器)上;aria-label 写在这里就是列表的名称 */\nexport interface VirtualListProps<T> extends PartProps<'div', VirtualListManaged> {\n /** 数据 */\n items: readonly T[];\n /** 渲染一项:`{(item, index) => …}` */\n children: (item: T, index: number) => VNodeChild;\n /** 每项的 key,默认用序号;数据会重排、增删时一定要给,测量结果与组件状态才对得上 */\n getKey?: (item: T, index: number) => PropertyKey;\n /** 每项的估计大小(px),数字或按项计算;渲染后以实际大小为准,默认 40 */\n estimateSize?: number | ((item: T, index: number) => number);\n /** 可见范围前后各多渲染几项,默认 4 */\n overscan?: number;\n /** 跟着谁滚动,默认 container */\n scroll?: keyof VirtualListScrolls;\n /** 横向排列与滚动 */\n horizontal?: boolean;\n /** 没有数据时显示的内容 */\n empty?: VNodeChild;\n /** 加载中:没有数据时铺几行骨架,已有数据(加载更多)时骨架接在末尾 */\n loading?: boolean;\n /** 加载中占位行的部件属性 */\n propsSkeleton?: SkeletonShapeProps;\n /** 拿到滚动方法:`handle={(list) => (listHandle = list)}` */\n handle?: (handle: VirtualListHandle) => void;\n /** 可见范围变化(不含 overscan):接近末尾时加载更多 */\n onRangeChange?: (range: VisibleRange) => void;\n /** 列表层(撑开总高度、role=\"list\" 的项都在它里面)的部件属性 */\n propsList?: PartProps<'div', 'role'>;\n /**\n * 每一项外层(role=\"listitem\")的部件属性;可以按项计算:`(item, index) => ({ class: … })`。\n * 位置、序号与无障碍属性由组件管理\n */\n propsItem?: ItemProps | ((item: T, index: number) => ItemProps);\n}\n\n/** 项外层可以定制的属性 */\ntype ItemProps = PartProps<'div', 'role' | 'aria-setsize' | 'aria-posinset'>;\n\n/** 加载中显示几行骨架:没有数据时铺几行,加载更多时接在末尾几行 */\nconst SKELETON_ROWS = 5;\nconst MORE_SKELETON_ROWS = 2;\n\n/** VirtualList 的样式:root、container、list、item、itemVertical、itemHorizontal 等 */\nexport const virtualListStyles = css.create({\n // 自己调整滚动位置(见 virtualizer.ts),不要浏览器再按锚点调整一次\n root: { overflowAnchor: 'none' },\n // 容器模式:列表元素是 ScrollArea 的视口(浮层滚动条)\n container: { overscrollBehavior: 'contain' },\n list: { position: 'relative', overflowAnchor: 'none' },\n item: { position: 'absolute', top: 0, left: 0, overflowAnchor: 'none' },\n itemVertical: { width: '_full' },\n itemHorizontal: { height: '_full' },\n skeletonRow: { paddingBlock: '_xs' },\n skeleton: { display: 'block', width: '_full' },\n});\n\n// 总大小与位置是任意值,用动态样式(编译为 CSS 变量);单独一个 create,见 COMPONENTS.md §4\n/** VirtualList 的样式:height、width、y、x、skeleton */\nexport const virtualListSizeStyles = css.create({\n height: (px: number) => ({ height: px }),\n width: (px: number) => ({ width: px, height: '_full' }),\n y: (px: number) => ({ transform: `translateY(${px}px)` }),\n x: (px: number) => ({ transform: `translateX(${px}px)` }),\n // 骨架行比一项略矮:上下留出间隔\n skeleton: (px: number) => ({ height: Math.max(8, px - 8) }),\n});\n\n/** 虚拟列表:只渲染可见范围内的项,行高渲染后自动测量 */\nexport const VirtualList = _component(\n <T,>({\n items,\n children,\n getKey,\n estimateSize = 40,\n overscan = 4,\n scroll = 'container',\n horizontal = false,\n empty,\n loading = false,\n propsSkeleton,\n handle,\n onRangeChange,\n propsList,\n propsItem,\n ...rest\n }: VirtualListProps<T>) => {\n // 骨架防闪烁;aria-busy 按真实的 loading\n const indicator = useDelayedLoading(() => loading);\n let root = _state.raw<HTMLElement | null>(null);\n let list = _state.raw<HTMLElement | null>(null);\n const skeletonRows = (count: number) =>\n Array.from({ length: count }, (_, index) => (\n <div\n key={`skeleton-${index}`}\n aria-hidden=\"true\"\n {...css.attrs(virtualListStyles.skeletonRow)}\n >\n <Skeleton\n {...mergePartProps<SkeletonProps>(\n {\n sx: [\n virtualListStyles.skeleton,\n virtualListSizeStyles.skeleton(\n typeof estimateSize === 'number' ? estimateSize : 40,\n ),\n ],\n delay: index * SHIMMER_STAGGER,\n },\n propsSkeleton,\n )}\n />\n </div>\n ));\n const virtual = useVirtualizer(() => {\n const data = items;\n const estimate = estimateSize;\n const keyOf = getKey;\n return {\n count: data.length,\n overscan,\n horizontal,\n scrollElement: scroll === 'window' ? window : root,\n list,\n estimateSize: (index: number) =>\n typeof estimate === 'number' ? estimate : estimate(data[index]!, index),\n getItemKey: keyOf ? (index: number) => keyOf(data[index]!, index) : undefined,\n onRangeChange,\n };\n });\n\n // 挂载之后才交出 handle:使用方在回调里立即滚动(恢复上次的位置)也能生效。\n // handle 不放进监听源:父级写的箭头函数每次渲染都是新的,不该因此重新交出\n const api: VirtualListHandle = {\n scrollToIndex: virtual.scrollToIndex,\n scrollToOffset: virtual.scrollToOffset,\n };\n watch(\n () => root,\n (element) => {\n if (element) handle?.(api);\n },\n { flush: 'post' },\n );\n\n return () => {\n const isContainer = scroll === 'container';\n const hasItems = items.length > 0;\n const props = mergeProps<'div'>(\n {\n sx: [\n virtualListStyles.root,\n isContainer && [virtualListStyles.container, focusRingStyles.visible],\n ],\n },\n rest,\n {\n ref: _toRef(root),\n // 没有数据(空状态、首次加载的骨架)时不是列表;仍给 group 角色,写在外层的 aria-label 才有效\n role: hasItems ? 'list' : 'group',\n 'aria-busy': loading || undefined,\n // 容器模式下可以用键盘滚动\n tabindex: isContainer ? 0 : undefined,\n },\n );\n const content = (\n <>\n {hasItems ? (\n <div\n {...mergeAttrs<'div'>({ sx: virtualListStyles.list }, propsList, {\n sx: horizontal\n ? virtualListSizeStyles.width(virtual.total)\n : virtualListSizeStyles.height(virtual.total),\n ref: _toRef(list),\n role: 'presentation',\n })}\n >\n {virtual.items.map((item) => {\n const data = items[item.index]!;\n const own =\n typeof propsItem === 'function' ? propsItem(data, item.index) : propsItem;\n return (\n <div\n key={item.key}\n {...mergeAttrs<'div'>(\n {\n sx: [\n virtualListStyles.item,\n horizontal\n ? virtualListStyles.itemHorizontal\n : virtualListStyles.itemVertical,\n ],\n },\n own,\n {\n sx: horizontal\n ? virtualListSizeStyles.x(item.start)\n : virtualListSizeStyles.y(item.start),\n ref: virtual.measure,\n role: 'listitem',\n 'aria-setsize': items.length,\n 'aria-posinset': item.index + 1,\n },\n )}\n data-index={item.index}\n >\n {children(data, item.index)}\n </div>\n );\n })}\n </div>\n ) : loading ? (\n indicator.value && skeletonRows(SKELETON_ROWS)\n ) : (\n empty\n )}\n {/* 加载更多:骨架接在已有的项后面(列表元素的高度就是全部项的总高度) */}\n {hasItems && indicator.value && skeletonRows(MORE_SKELETON_ROWS)}\n </>\n );\n // 容器模式:列表元素就是 ScrollArea 的视口,滚动条是浮层的;跟着页面滚动时就是普通的 div\n return isContainer ? (\n <ScrollArea\n axis={horizontal ? 'x' : 'y'}\n focusable={false}\n propsViewport={props as PartProps<'div'>}\n >\n {content}\n </ScrollArea>\n ) : (\n <div {...elementProps(props)}>{content}</div>\n );\n };\n },\n);\n"],"mappings":";;;;;;;;;;;;;;;;AAmFA,SAAA,QAAA,GAAA;CAAA,OAAA,OAAA,MAAA,cAAA,OAAA,UAAA,SAAA,KAAA,CAAA,MAAA,qBAAA,CAAAA,QAAA,CAAA;AAAA;;AAIA,IAAM,gBAAgB;AACtB,IAAM,qBAAqB;;AAG3B,IAAa,oBAAoB,IAAI,OAAO;CAE1C,MAAM,EAAE,gBAAgB,OAAO;CAE/B,WAAW,EAAE,oBAAoB,UAAU;CAC3C,MAAM;EAAE,UAAU;EAAY,gBAAgB;CAAO;CACrD,MAAM;EAAE,UAAU;EAAY,KAAK;EAAG,MAAM;EAAG,gBAAgB;CAAO;CACtE,cAAc,EAAE,OAAO,QAAQ;CAC/B,gBAAgB,EAAE,QAAQ,QAAQ;CAClC,aAAa,EAAE,cAAc,MAAM;CACnC,UAAU;EAAE,SAAS;EAAS,OAAO;CAAQ;AAC/C,CAAC;;AAID,IAAa,wBAAwB,IAAI,OAAO;CAC9C,SAAS,QAAgB,EAAE,QAAQ,GAAG;CACtC,QAAQ,QAAgB;EAAE,OAAO;EAAI,QAAQ;CAAQ;CACrD,IAAI,QAAgB,EAAE,WAAW,cAAc,GAAE,KAAM;CACvD,IAAI,QAAgB,EAAE,WAAW,cAAc,GAAE,KAAM;CAEvD,WAAW,QAAgB,EAAE,QAAQ,KAAK,IAAI,GAAG,KAAK,CAAC,EAAE;AAC3D,CAAC;;AAGD,IAAa,cAAc,0BACzB,QAAA,SAgB2B;CAAA,MAAA,QAAAC,aAAA,QAAA,OAAA;CAAA,MAAA,WAAAC,aAAA,MAAA;CAAA,MAAA,SAAAD,aAAA,QAAA,QAAA;CAAA,MAAA,eAAAA,aAAA,QAAA,gBAZV,EAAE;CAAA,MAAA,WAAAA,aAAA,QAAA,YACN,CAAC;CAAA,MAAA,SAAAA,aAAA,QAAA,UACH,WAAW;CAAA,MAAA,aAAAA,aAAA,QAAA,cACP,KAAK;CAAA,MAAA,QAAAA,aAAA,QAAA,OAAA;CAAA,MAAA,UAAAA,aAAA,QAAA,WAER,KAAK;CAAA,MAAA,gBAAAA,aAAA,QAAA,eAAA;CAAA,MAAA,SAAAA,aAAA,QAAA,QAAA;CAAA,MAAA,gBAAAA,aAAA,QAAA,eAAA;CAAA,MAAA,YAAAA,aAAA,QAAA,WAAA;CAAA,MAAA,YAAAA,aAAA,QAAA,WAAA;CAAA,MAMZ,OAAIE,UAAA,QAAA,MAAA,KAAA;CAGP,MAAM,YAAY,wBAAkB,QAAA,KAAa;CACjD,IAAI,OAAIC,WAAkC,IAAI;CAC9C,IAAI,OAAIA,WAAkC,IAAI;CAC9C,MAAM,gBAAgB,UACpB,MAAM,KAAK,EAAE,QAAQ,MAAM,IAAI,GAAG,UAAKC,YAAA,OAAAC,cAAA;EAAA,OAE9B,YAAY;EAAO,eAAA;CAAA,GAEpB,IAAI,MAAM,kBAAkB,WAAW,CAAC,GAAA,CAAAD,YAAA,UAGtC,eACF;EACE,IAAI,CACF,kBAAkB,UAClB,sBAAsB,SACpB,OAAA,aAAA,UAAwB,WAAQ,aAAA,QAAkB,EACpD,CAAC;EAEH,OAAO,QAAA;CACT,GAAC,cAAA,KAEH,GAAC,IAAA,CAAA,CAAA,CAGN;CACH,MAAM,UAAU,qBAAqB;EACnC,MAAM,OAAI,MAAA;EACV,MAAM,WAAQ,aAAA;EACd,MAAM,QAAK,OAAA;EACX,OAAO;GACL,OAAO,KAAK;GACZ,UAAQ,SAAA;GACR,YAAU,WAAA;GACV,eAAe,OAAA,UAAW,WAAW,SAAM,KAAA;GAC3C,MAAI,KAAA;GACJ,eAAe,UACb,OAAO,aAAa,WAAW,WAAW,SAAS,KAAK,QAAS,KAAK;GACxE,YAAY,SAAS,UAAkB,MAAM,KAAK,QAAS,KAAK,IAAI,KAAA;GACpE,eAAa,cAAA;EACf;CACF,CAAC;CAID,MAAM,MAAyB;EAC7B,eAAe,QAAQ;EACvB,gBAAgB,QAAQ;CAC1B;CACA,YACE,KAAA,QACC,YAAY;EACX,IAAI,SAAS,OAAA,QAAS,GAAG;CAC3B,GACA,EAAE,OAAO,OAAO,CAClB;CAEA,aAAa;EACX,MAAM,cAAc,OAAA,UAAW;EAC/B,MAAM,WAAW,MAAA,MAAM,SAAS;EAChC,MAAM,QAAQ,WACZ,EACE,IAAI,CACF,kBAAkB,MAClB,eAAe,CAAC,kBAAkB,WAAW,gBAAgB,OAAO,CAAC,EAEzE,GACA,MACA;GACE,KAAG;GAEH,MAAM,WAAW,SAAS;GAC1B,aAAa,QAAA,SAAW,KAAA;GAExB,UAAU,cAAc,IAAI,KAAA;EAC9B,CACF;EACA,MAAM,UAAOA,YAAAE,UAAA,MAAA,CAER,WAAQF,YAAA,OAED,WAAkB,EAAE,IAAI,kBAAkB,KAAK,GAAC,UAAA,OAAa;GAC/D,IAAI,WAAA,QAAA,IAAA,KACA,sBAAsB,aAAK,CAAC,QAAQ,KAAK,GAAA,IAAA,IAAA,IAAA,KACzC,sBAAsB,cAAM,CAAC,QAAQ,KAAK,GAAA,IAAA;GAC9C,KAAG;GACH,MAAM;EACR,CAAC,GAAC,CAED,QAAQ,MAAM,KAAK,SAAS;GAC3B,MAAM,OAAO,MAAA,MAAM,KAAK;GACxB,MAAM,MACJ,OAAA,UAAA,UAAqB,aAAa,UAAA,MAAU,MAAM,KAAK,KAAK,IAAC,UAAA;GAC/D,OAAAA,YAAA,OAAAC,cAAA,EAAA,OAES,KAAK,IAAG,GACT,WACF,EACE,IAAI,CACF,kBAAkB,MAClB,WAAA,QACI,kBAAkB,iBAClB,kBAAkB,YAAY,EAEtC,GACA,KACA;IACE,IAAI,WAAA,QAAA,IAAA,KACA,sBAAsB,SAAC,CAAC,KAAK,KAAK,CAAA,IAAA,IAAA,KAClC,sBAAsB,SAAC,CAAC,KAAK,KAAK,CAAA;IACtC,KAAK,QAAQ;IACb,MAAM;IACN,gBAAgB,MAAA,MAAM;IACtB,iBAAiB,KAAK,QAAQ;GAChC,CACF,GAAC,EAAA,cACW,KAAK,MAAK,CAAA,GAAA,CAErB,SAAA,MAAS,MAAM,KAAK,KAAK,CAAC,CAAA;EAGjC,CAAC,CAAC,CAAA,IAEF,QAAA,QACF,UAAU,SAAS,aAAa,aAAa,IAAC,MAAA,OAK/C,YAAY,UAAU,SAAS,aAAa,kBAAkB,CAAC,CAAA;EAIpE,OAAO,cAAWD,YAAA,YAAA;GAAA,QAER,WAAA,QAAa,MAAM;GAAG,aACjB;GAAK,iBACD;EAAK,GAAA,QAEnB,OAAO,IAAP,UAAO,EAAA,eAAA,CAAP,OAAO,EAAA,CAAA,IAAAA,YAAA,OAGD,aAAa,KAAK,GAAC,CAAG,OAAO,CAAA;CAE1C;AACF,GAAC;CAAA,MAAA;CAAA,OAAA;EAAA;EAAA;EAAA;EAAA;EAAA;EAAA;EAAA;EAAA;EAAA;EAAA;EAAA;EAAA;EAAA;CAAA;AAAA,CACH"}
@@ -34,7 +34,7 @@ export interface ComboboxBaseProps<T> extends PartProps<'input', ComboboxManaged
34
34
  showToggle?: boolean;
35
35
  /** 没有可选的项时不弹出列表(不显示“没有匹配的选项”):只当带建议的输入框用时(Autocomplete) */
36
36
  hideEmpty?: boolean;
37
- /** 正在取选项(异步搜索):没有选项时列表里是骨架行,已有选项时顶部显示进度条 */
37
+ /** 正在取选项(异步搜索):列表里换成骨架行 */
38
38
  loading?: boolean;
39
39
  /** 弹出位置,默认 bottom-start */
40
40
  placement?: FloatingPlacement;