@veltra/sheet 2.2.0 → 2.3.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 (84) hide show
  1. package/dist/components/report/binding-badge.js +45 -21
  2. package/dist/components/report/designer/col-widths.js +18 -4
  3. package/dist/components/report/designer/conditional-rules/helpers.js +3 -3
  4. package/dist/components/report/designer/float-panel.js +73 -36
  5. package/dist/components/report/designer/rule-row.js +111 -127
  6. package/dist/components/report/designer/rules-dialog.js +5 -22
  7. package/dist/components/report/preview-grid-size.js +32 -0
  8. package/dist/components/report/report-designer.js +75 -25
  9. package/dist/components/report/report-viewer.js +38 -25
  10. package/dist/components/report/style2.css +44 -39
  11. package/dist/components/report/use-report-designer.js +34 -12
  12. package/dist/components/report/use-report-viewer.js +6 -1
  13. package/dist/components/sheet/popups/border-popup.js +30 -2
  14. package/dist/components/sheet/popups/fill-color-popup.js +2 -2
  15. package/dist/components/sheet/popups/font-color-popup.js +2 -2
  16. package/dist/components/sheet/popups/font-size-popup.js +2 -2
  17. package/dist/components/sheet/sheet-context-menu.js +6 -3
  18. package/dist/components/sheet/sheet.js +8 -0
  19. package/dist/components/sheet/use-sheet-grid.js +14 -4
  20. package/dist/components/sheet/use-sheet-state.js +3 -2
  21. package/dist/index.d.ts +2 -2
  22. package/dist/index.js +2 -2
  23. package/dist/report/binding.d.ts +49 -11
  24. package/dist/report/binding.js +186 -29
  25. package/dist/report/connector.js +1 -1
  26. package/dist/report/export-xlsx.d.ts +3 -5
  27. package/dist/report/export-xlsx.js +7 -10
  28. package/dist/report/index.d.ts +2 -2
  29. package/dist/report/render/aggregate.js +20 -8
  30. package/dist/report/template.d.ts +8 -4
  31. package/dist/report/template.js +5 -2
  32. package/dist/tools/apply-style.js +29 -0
  33. package/dist/tools/builtin.js +17 -17
  34. package/dist/tools/context.d.ts +38 -5
  35. package/dist/tools/context.js +38 -1
  36. package/dist/types/report-designer.d.ts +2 -0
  37. package/dist/types/report-viewer.d.ts +2 -2
  38. package/dist/types/sheet.d.ts +4 -0
  39. package/package.json +6 -6
  40. package/src/components/report/__test__/conditional-rules-helpers.test.ts +1 -0
  41. package/src/components/report/__test__/preview-grid-size.test.ts +63 -0
  42. package/src/components/report/__test__/report-designer.test.ts +76 -0
  43. package/src/components/report/__test__/report-viewer.test.ts +24 -1
  44. package/src/components/report/__test__/use-report-designer.test.ts +142 -10
  45. package/src/components/report/binding-badge.ts +39 -20
  46. package/src/components/report/designer/col-widths.ts +34 -11
  47. package/src/components/report/designer/conditional-rules/helpers.ts +6 -3
  48. package/src/components/report/designer/float-panel.vue +71 -27
  49. package/src/components/report/designer/rule-row.vue +67 -67
  50. package/src/components/report/designer/rules-dialog.vue +3 -19
  51. package/src/components/report/preview-grid-size.ts +33 -0
  52. package/src/components/report/report-designer.vue +79 -15
  53. package/src/components/report/report-viewer.vue +37 -29
  54. package/src/components/report/style-designer.scss +45 -41
  55. package/src/components/report/use-report-designer.ts +63 -14
  56. package/src/components/report/use-report-viewer.ts +11 -2
  57. package/src/components/sheet/__test__/sheet-component.test.ts +37 -0
  58. package/src/components/sheet/__test__/sheet-context-menu.test.ts +3 -2
  59. package/src/components/sheet/popups/border-popup.vue +31 -3
  60. package/src/components/sheet/popups/fill-color-popup.vue +2 -2
  61. package/src/components/sheet/popups/font-color-popup.vue +2 -2
  62. package/src/components/sheet/popups/font-size-popup.vue +2 -2
  63. package/src/components/sheet/sheet-context-menu.ts +8 -2
  64. package/src/components/sheet/sheet.vue +2 -0
  65. package/src/components/sheet/use-sheet-grid.ts +17 -3
  66. package/src/components/sheet/use-sheet-state.ts +8 -3
  67. package/src/report/__test__/binding.test.ts +103 -1
  68. package/src/report/__test__/connector.test.ts +21 -0
  69. package/src/report/__test__/export-xlsx.test.ts +4 -4
  70. package/src/report/__test__/render.test.ts +102 -0
  71. package/src/report/__test__/template.test.ts +11 -0
  72. package/src/report/binding.ts +278 -47
  73. package/src/report/connector.ts +3 -2
  74. package/src/report/export-xlsx.ts +9 -14
  75. package/src/report/render/aggregate.ts +29 -8
  76. package/src/report/template.ts +8 -4
  77. package/src/tools/__test__/apply-style.test.ts +129 -0
  78. package/src/tools/apply-style.ts +41 -0
  79. package/src/tools/builtin.ts +18 -18
  80. package/src/tools/context.ts +67 -4
  81. package/src/tools/index.ts +1 -0
  82. package/src/types/report-designer.ts +2 -0
  83. package/src/types/report-viewer.ts +2 -2
  84. package/src/types/sheet.ts +4 -0
@@ -1,4 +1,4 @@
1
- import { DatasetCatalogItem, ReportBinding, ReportPreset } from "./types.js";
1
+ import { DatasetCatalogItem, ReportAggregate, ReportBinding, ReportPreset } from "./types.js";
2
2
  import { CellAddress } from "@veltra/sheet-core";
3
3
  //#region src/report/binding.d.ts
4
4
  declare function setBindingCatalog(catalog: DatasetCatalogItem[]): void;
@@ -13,24 +13,55 @@ declare function applyReportPreset(binding: ReportBinding, preset: ReportPreset,
13
13
  }): ReportBinding;
14
14
  declare function isVerticalExpandBinding(binding: ReportBinding): boolean;
15
15
  declare function isHorizontalExpandBinding(binding: ReportBinding): boolean;
16
+ declare function isRowGroupBinding(binding: ReportBinding): boolean;
17
+ declare function isColGroupBinding(binding: ReportBinding): boolean;
18
+ declare function isReductionAggregate(aggregate: ReportAggregate): boolean;
19
+ /** 小计 / 汇总 / 交叉格的父格候选只列分组头 */
20
+ declare function prefersGroupParent(binding: ReportBinding): boolean;
21
+ /** 当前 expand / aggregate 是否仍属于已写入的预设族(同族内改聚合不掉到「自定义」) */
22
+ declare function presetStillApplies(binding: ReportBinding): boolean;
23
+ type GetBindingAt = (addr: CellAddress) => ReportBinding | undefined;
24
+ interface InferParentOptions {
25
+ /** 小计落格:必须优先分组头,禁止把同列/同行明细当成父格 */
26
+ preferGroup?: boolean;
27
+ }
16
28
  /** 指定模板行是否含纵向扩展绑定(展开带行) */
17
- declare function isExpansionBandRow(row: number, getBindingAt: (addr: CellAddress) => ReportBinding | undefined, scanCols?: number): boolean;
18
- /** 落格时推断预设:默认明细;数值字段且位于展开带正下方相邻行 → 小计 */
19
- declare function inferDropPreset(addr: CellAddress, fieldType: DatasetCatalogItem['fields'][number]['type'], getBindingAt: (addr: CellAddress) => ReportBinding | undefined): ReportPreset;
29
+ declare function isExpansionBandRow(row: number, getBindingAt: GetBindingAt, scanCols?: number): boolean;
30
+ /** 指定模板列是否含横向扩展绑定(展开带列) */
31
+ declare function isExpansionBandCol(col: number, getBindingAt: GetBindingAt, scanRows?: number): boolean;
32
+ /** 落格时推断预设:默认明细;数值字段且位于展开带正下方或正右相邻 → 小计 */
33
+ declare function inferDropPreset(addr: CellAddress, fieldType: DatasetCatalogItem['fields'][number]['type'], getBindingAt: GetBindingAt): ReportPreset;
34
+ interface ListParentCandidateOptions {
35
+ preferGroup?: boolean;
36
+ }
20
37
  /** 同数据集纵向扩展绑定格(行方向父格下拉候选) */
21
38
  declare function listRowParentCandidates(binding: ReportBinding, entries: ReadonlyArray<{
22
39
  addr: CellAddress;
23
40
  binding: ReportBinding;
24
- }>): CellAddress[];
41
+ }>, options?: ListParentCandidateOptions): CellAddress[];
25
42
  /** 同数据集横向扩展绑定格(列方向父格下拉候选) */
26
43
  declare function listColParentCandidates(binding: ReportBinding, entries: ReadonlyArray<{
27
44
  addr: CellAddress;
28
45
  binding: ReportBinding;
29
- }>): CellAddress[];
30
- /** 同列向上找最近的纵向扩展绑定,作为 rowParent 候选 */
31
- declare function inferRowParentCandidate(addr: CellAddress, getBindingAt: (addr: CellAddress) => ReportBinding | undefined): CellAddress | null;
32
- /** 同行向左找最近的横向扩展绑定,作为 colParent 候选 */
33
- declare function inferColParentCandidate(addr: CellAddress, getBindingAt: (addr: CellAddress) => ReportBinding | undefined): CellAddress | null;
46
+ }>, options?: ListParentCandidateOptions): CellAddress[];
47
+ /**
48
+ * 行方向父格推断:
49
+ * 1. 同行向左:先找分组头,否则最近纵向扩展格
50
+ * 2. 上方各行整行扫描:每行先找分组头,再退回任意纵向扩展格
51
+ * 小计(preferGroup)禁止把明细当成父格
52
+ */
53
+ declare function inferRowParentCandidate(addr: CellAddress, getBindingAt: GetBindingAt, options?: InferParentOptions): CellAddress | null;
54
+ /**
55
+ * 列方向父格推断(与行方向对称):
56
+ * 1. 同列向上:先找列分组头,否则最近横向扩展格
57
+ * 2. 左侧各列整列扫描:每列先找列分组头
58
+ * 列小计(preferGroup)禁止把交叉格/明细当成列父格
59
+ */
60
+ declare function inferColParentCandidate(addr: CellAddress, getBindingAt: GetBindingAt, options?: InferParentOptions): CellAddress | null;
61
+ /**
62
+ * 按预设写入落格父格:明细/分组/交叉双方向推断;小计按「下方行小计 / 右侧列小计」只挂对应轴。
63
+ */
64
+ declare function inferParentsForPreset(addr: CellAddress, preset: ReportPreset, getBindingAt: GetBindingAt): Pick<ReportBinding, 'rowParent' | 'colParent'>;
34
65
  /** 从绑定字段推断预设;不匹配任何预设时返回 null(自定义) */
35
66
  declare function inferReportPreset(binding: ReportBinding): ReportPreset | null;
36
67
  /** 默认明细 + 纵向扩展 */
@@ -39,7 +70,14 @@ declare function createReportBinding(dataset: DatasetCatalogItem, fieldName: str
39
70
  declare function formatCellAddress(addr: CellAddress): string;
40
71
  /** A1 标签 → 设计地址;非法输入返回 null */
41
72
  declare function parseCellAddress(label: string): CellAddress | null;
73
+ /** Binding Placeholder 分段文案(聚合标签 + 字段标签),供徽章分色渲染等场景使用 */
74
+ declare function formatBindingPlaceholderParts(binding: ReportBinding, resolveLabel?: (datasetId: string, fieldName: string) => string): {
75
+ tag: string;
76
+ label: string;
77
+ };
42
78
  /** Binding Placeholder 中文可读文案(如「分组 · 客户」) */
43
79
  declare function formatBindingPlaceholder(binding: ReportBinding, resolveLabel?: (datasetId: string, fieldName: string) => string): string;
80
+ /** 父格下拉候选文案(如「分组 · 客户 (A2)」) */
81
+ declare function formatParentCandidateLabel(addr: CellAddress, binding: ReportBinding, resolveLabel?: (datasetId: string, fieldName: string) => string): string;
44
82
  //#endregion
45
- export { REPORT_META_NAMESPACE, applyReportPreset, createReportBinding, formatBindingPlaceholder, formatCellAddress, inferColParentCandidate, inferDropPreset, inferReportPreset, inferRowParentCandidate, isExpansionBandRow, isHorizontalExpandBinding, isVerticalExpandBinding, listColParentCandidates, listRowParentCandidates, parseCellAddress, presetBindingPatch, setBindingCatalog };
83
+ export { InferParentOptions, ListParentCandidateOptions, REPORT_META_NAMESPACE, applyReportPreset, createReportBinding, formatBindingPlaceholder, formatBindingPlaceholderParts, formatCellAddress, formatParentCandidateLabel, inferColParentCandidate, inferDropPreset, inferParentsForPreset, inferReportPreset, inferRowParentCandidate, isColGroupBinding, isExpansionBandCol, isExpansionBandRow, isHorizontalExpandBinding, isReductionAggregate, isRowGroupBinding, isVerticalExpandBinding, listColParentCandidates, listRowParentCandidates, parseCellAddress, prefersGroupParent, presetBindingPatch, presetStillApplies, setBindingCatalog };
@@ -65,10 +65,34 @@ function isVerticalExpandBinding(binding) {
65
65
  return binding.expand === "down" && (binding.aggregate === "group" || binding.aggregate === "list");
66
66
  }
67
67
  function isHorizontalExpandBinding(binding) {
68
+ return binding.expand === "right" && (binding.aggregate === "group" || binding.aggregate === "list");
69
+ }
70
+ function isRowGroupBinding(binding) {
71
+ return binding.expand === "down" && binding.aggregate === "group";
72
+ }
73
+ function isColGroupBinding(binding) {
68
74
  return binding.expand === "right" && binding.aggregate === "group";
69
75
  }
76
+ function isReductionAggregate(aggregate) {
77
+ return aggregate === "sum" || aggregate === "avg" || aggregate === "count" || aggregate === "max" || aggregate === "min";
78
+ }
79
+ /** 小计 / 汇总 / 交叉格的父格候选只列分组头 */
80
+ function prefersGroupParent(binding) {
81
+ if (binding.preset === "subtotal" || binding.preset === "grandTotal" || binding.preset === "cross") return true;
82
+ return binding.expand === "none" && isReductionAggregate(binding.aggregate);
83
+ }
84
+ /** 当前 expand / aggregate 是否仍属于已写入的预设族(同族内改聚合不掉到「自定义」) */
85
+ function presetStillApplies(binding) {
86
+ const preset = binding.preset;
87
+ if (!preset) return false;
88
+ if (preset === "groupHeader") return binding.aggregate === "group" && (binding.expand === "down" || binding.expand === "right");
89
+ if (preset === "detail") return binding.aggregate === "list" && (binding.expand === "down" || binding.expand === "right");
90
+ return binding.expand === "none" && isReductionAggregate(binding.aggregate);
91
+ }
92
+ const DEFAULT_SCAN_COLS = 64;
93
+ const DEFAULT_SCAN_ROWS = 64;
70
94
  /** 指定模板行是否含纵向扩展绑定(展开带行) */
71
- function isExpansionBandRow(row, getBindingAt, scanCols = 64) {
95
+ function isExpansionBandRow(row, getBindingAt, scanCols = DEFAULT_SCAN_COLS) {
72
96
  for (let col = 0; col < scanCols; col++) {
73
97
  const binding = getBindingAt({
74
98
  row,
@@ -78,65 +102,186 @@ function isExpansionBandRow(row, getBindingAt, scanCols = 64) {
78
102
  }
79
103
  return false;
80
104
  }
81
- /** 落格时推断预设:默认明细;数值字段且位于展开带正下方相邻行 → 小计 */
105
+ /** 指定模板列是否含横向扩展绑定(展开带列) */
106
+ function isExpansionBandCol(col, getBindingAt, scanRows = DEFAULT_SCAN_ROWS) {
107
+ for (let row = 0; row < scanRows; row++) {
108
+ const binding = getBindingAt({
109
+ row,
110
+ col
111
+ });
112
+ if (binding && isHorizontalExpandBinding(binding)) return true;
113
+ }
114
+ return false;
115
+ }
116
+ /** 落格时推断预设:默认明细;数值字段且位于展开带正下方或正右相邻 → 小计 */
82
117
  function inferDropPreset(addr, fieldType, getBindingAt) {
83
- if (fieldType === "number" && addr.row > 0 && isExpansionBandRow(addr.row - 1, getBindingAt)) return "subtotal";
118
+ if (fieldType !== "number") return "detail";
119
+ if (addr.row > 0 && isExpansionBandRow(addr.row - 1, getBindingAt)) return "subtotal";
120
+ if (addr.col > 0 && isExpansionBandCol(addr.col - 1, getBindingAt)) return "subtotal";
84
121
  return "detail";
85
122
  }
86
123
  /** 同数据集纵向扩展绑定格(行方向父格下拉候选) */
87
- function listRowParentCandidates(binding, entries) {
124
+ function listRowParentCandidates(binding, entries, options) {
125
+ const preferGroup = options?.preferGroup === true;
88
126
  const candidates = [];
89
127
  for (const entry of entries) {
90
128
  if (entry.binding.dataset !== binding.dataset) continue;
91
- if (!isVerticalExpandBinding(entry.binding)) continue;
129
+ if (preferGroup) {
130
+ if (!isRowGroupBinding(entry.binding)) continue;
131
+ } else if (!isVerticalExpandBinding(entry.binding)) continue;
92
132
  candidates.push(entry.addr);
93
133
  }
94
134
  return candidates;
95
135
  }
96
136
  /** 同数据集横向扩展绑定格(列方向父格下拉候选) */
97
- function listColParentCandidates(binding, entries) {
137
+ function listColParentCandidates(binding, entries, options) {
138
+ const preferGroup = options?.preferGroup === true;
98
139
  const candidates = [];
99
140
  for (const entry of entries) {
100
141
  if (entry.binding.dataset !== binding.dataset) continue;
101
- if (!isHorizontalExpandBinding(entry.binding)) continue;
142
+ if (preferGroup) {
143
+ if (!isColGroupBinding(entry.binding)) continue;
144
+ } else if (!isHorizontalExpandBinding(entry.binding)) continue;
102
145
  candidates.push(entry.addr);
103
146
  }
104
147
  return candidates;
105
148
  }
106
- /** 同列向上找最近的纵向扩展绑定,作为 rowParent 候选 */
107
- function inferRowParentCandidate(addr, getBindingAt) {
108
- for (let row = addr.row - 1; row >= 0; row--) {
149
+ function betterHit(current, next) {
150
+ if (!current) return true;
151
+ if (next.distance !== current.distance) return next.distance < current.distance;
152
+ return next.addr.col + next.addr.row < current.addr.col + current.addr.row;
153
+ }
154
+ function pickExpandHit(hits, preferGroup) {
155
+ let group = null;
156
+ let expand = null;
157
+ for (const hit of hits) {
158
+ if (hit.group && betterHit(group, hit)) group = hit;
159
+ if (betterHit(expand, hit)) expand = hit;
160
+ }
161
+ if (preferGroup) return group?.addr ?? null;
162
+ return group?.addr ?? expand?.addr ?? null;
163
+ }
164
+ function scanVerticalOnRow(row, originCol, colStart, colEnd, getBindingAt) {
165
+ const hits = [];
166
+ for (let col = colStart; col < colEnd; col++) {
109
167
  const binding = getBindingAt({
110
168
  row,
111
- col: addr.col
169
+ col
170
+ });
171
+ if (!binding || !isVerticalExpandBinding(binding)) continue;
172
+ hits.push({
173
+ addr: {
174
+ row,
175
+ col
176
+ },
177
+ group: isRowGroupBinding(binding),
178
+ distance: Math.abs(col - originCol)
112
179
  });
113
- if (binding && isVerticalExpandBinding(binding)) return {
114
- row,
115
- col: addr.col
116
- };
117
180
  }
118
- return null;
181
+ return hits;
119
182
  }
120
- /** 同行向左找最近的横向扩展绑定,作为 colParent 候选 */
121
- function inferColParentCandidate(addr, getBindingAt) {
122
- for (let col = addr.col - 1; col >= 0; col--) {
183
+ function scanHorizontalOnCol(col, originRow, rowStart, rowEnd, getBindingAt) {
184
+ const hits = [];
185
+ for (let row = rowStart; row < rowEnd; row++) {
123
186
  const binding = getBindingAt({
124
- row: addr.row,
187
+ row,
125
188
  col
126
189
  });
127
- if (binding && isHorizontalExpandBinding(binding)) return {
128
- row: addr.row,
129
- col
130
- };
190
+ if (!binding || !isHorizontalExpandBinding(binding)) continue;
191
+ hits.push({
192
+ addr: {
193
+ row,
194
+ col
195
+ },
196
+ group: isColGroupBinding(binding),
197
+ distance: Math.abs(row - originRow)
198
+ });
199
+ }
200
+ return hits;
201
+ }
202
+ /**
203
+ * 行方向父格推断:
204
+ * 1. 同行向左:先找分组头,否则最近纵向扩展格
205
+ * 2. 上方各行整行扫描:每行先找分组头,再退回任意纵向扩展格
206
+ * 小计(preferGroup)禁止把明细当成父格
207
+ */
208
+ function inferRowParentCandidate(addr, getBindingAt, options) {
209
+ const preferGroup = options?.preferGroup === true;
210
+ const sameRow = pickExpandHit(scanVerticalOnRow(addr.row, addr.col, 0, addr.col, getBindingAt), preferGroup);
211
+ if (sameRow) return sameRow;
212
+ for (let row = addr.row - 1; row >= 0; row--) {
213
+ const found = pickExpandHit(scanVerticalOnRow(row, addr.col, 0, DEFAULT_SCAN_COLS, getBindingAt), preferGroup);
214
+ if (found) return found;
215
+ }
216
+ return null;
217
+ }
218
+ /**
219
+ * 列方向父格推断(与行方向对称):
220
+ * 1. 同列向上:先找列分组头,否则最近横向扩展格
221
+ * 2. 左侧各列整列扫描:每列先找列分组头
222
+ * 列小计(preferGroup)禁止把交叉格/明细当成列父格
223
+ */
224
+ function inferColParentCandidate(addr, getBindingAt, options) {
225
+ const preferGroup = options?.preferGroup === true;
226
+ const sameCol = pickExpandHit(scanHorizontalOnCol(addr.col, addr.row, 0, addr.row, getBindingAt), preferGroup);
227
+ if (sameCol) return sameCol;
228
+ for (let col = addr.col - 1; col >= 0; col--) {
229
+ const found = pickExpandHit(scanHorizontalOnCol(col, addr.row, 0, DEFAULT_SCAN_ROWS, getBindingAt), preferGroup);
230
+ if (found) return found;
131
231
  }
132
232
  return null;
133
233
  }
234
+ function suggestsColumnSubtotal(above) {
235
+ if (!above) return false;
236
+ return isHorizontalExpandBinding(above) || !!above.colParent || above.preset === "cross";
237
+ }
238
+ function suggestsRowSubtotal(left) {
239
+ if (!left) return false;
240
+ return isVerticalExpandBinding(left) || !!left.rowParent || left.preset === "cross";
241
+ }
242
+ /**
243
+ * 按预设写入落格父格:明细/分组/交叉双方向推断;小计按「下方行小计 / 右侧列小计」只挂对应轴。
244
+ */
245
+ function inferParentsForPreset(addr, preset, getBindingAt) {
246
+ if (preset === "grandTotal") return {};
247
+ const preferGroup = preset === "subtotal" || preset === "cross";
248
+ const rowParent = inferRowParentCandidate(addr, getBindingAt, { preferGroup }) ?? void 0;
249
+ const colParent = inferColParentCandidate(addr, getBindingAt, { preferGroup }) ?? void 0;
250
+ if (preset !== "subtotal") return {
251
+ ...rowParent ? { rowParent } : {},
252
+ ...colParent ? { colParent } : {}
253
+ };
254
+ const above = addr.row > 0 ? getBindingAt({
255
+ row: addr.row - 1,
256
+ col: addr.col
257
+ }) : void 0;
258
+ const left = addr.col > 0 ? getBindingAt({
259
+ row: addr.row,
260
+ col: addr.col - 1
261
+ }) : void 0;
262
+ const belowBand = addr.row > 0 && isExpansionBandRow(addr.row - 1, getBindingAt);
263
+ const rightOfBand = addr.col > 0 && isExpansionBandCol(addr.col - 1, getBindingAt);
264
+ const columnSubtotal = suggestsColumnSubtotal(above) || rightOfBand && !suggestsRowSubtotal(left);
265
+ const rowSubtotal = suggestsRowSubtotal(left) || belowBand && !columnSubtotal;
266
+ const result = {};
267
+ if (rowSubtotal && rowParent) result.rowParent = rowParent;
268
+ if (columnSubtotal && colParent) result.colParent = colParent;
269
+ if (!rowSubtotal && !columnSubtotal) if (belowBand && rowParent) result.rowParent = rowParent;
270
+ else if (rightOfBand && colParent) result.colParent = colParent;
271
+ else {
272
+ if (rowParent) result.rowParent = rowParent;
273
+ if (colParent) result.colParent = colParent;
274
+ }
275
+ return result;
276
+ }
134
277
  /** 从绑定字段推断预设;不匹配任何预设时返回 null(自定义) */
135
278
  function inferReportPreset(binding) {
136
279
  if (binding.preset) return binding.preset;
137
- if (binding.expand === "none" && binding.aggregate === "sum" && binding.rowParent && binding.colParent) return "cross";
138
- if (binding.expand === "none" && binding.aggregate === "sum" && !binding.rowParent && !binding.colParent) return "grandTotal";
139
- if (binding.expand === "none" && binding.aggregate === "sum" && binding.rowParent && !binding.colParent) return "subtotal";
280
+ if (binding.expand === "none" && isReductionAggregate(binding.aggregate)) {
281
+ if (binding.rowParent && binding.colParent) return "cross";
282
+ if (binding.rowParent || binding.colParent) return "subtotal";
283
+ return "grandTotal";
284
+ }
140
285
  if (binding.aggregate === "group" && (binding.expand === "down" || binding.expand === "right")) return "groupHeader";
141
286
  if (binding.aggregate === "list" && (binding.expand === "down" || binding.expand === "right")) return "detail";
142
287
  return null;
@@ -167,9 +312,21 @@ function resolveFieldLabel(datasetId, fieldName, resolveLabel) {
167
312
  if (override) return override;
168
313
  return (bindingCatalog.find((d) => d.id === datasetId)?.fields.find((f) => f.name === fieldName))?.label ?? fieldName;
169
314
  }
315
+ /** Binding Placeholder 分段文案(聚合标签 + 字段标签),供徽章分色渲染等场景使用 */
316
+ function formatBindingPlaceholderParts(binding, resolveLabel) {
317
+ return {
318
+ tag: AGGREGATE_PLACEHOLDER_TAG[binding.aggregate],
319
+ label: resolveFieldLabel(binding.dataset, binding.field, resolveLabel)
320
+ };
321
+ }
170
322
  /** Binding Placeholder 中文可读文案(如「分组 · 客户」) */
171
323
  function formatBindingPlaceholder(binding, resolveLabel) {
172
- return `${AGGREGATE_PLACEHOLDER_TAG[binding.aggregate]} · ${resolveFieldLabel(binding.dataset, binding.field, resolveLabel)}`;
324
+ const { tag, label } = formatBindingPlaceholderParts(binding, resolveLabel);
325
+ return `${tag} · ${label}`;
326
+ }
327
+ /** 父格下拉候选文案(如「分组 · 客户 (A2)」) */
328
+ function formatParentCandidateLabel(addr, binding, resolveLabel) {
329
+ return `${formatBindingPlaceholder(binding, resolveLabel)} (${formatCellAddress(addr)})`;
173
330
  }
174
331
  //#endregion
175
- export { REPORT_META_NAMESPACE, applyReportPreset, createReportBinding, formatBindingPlaceholder, formatCellAddress, inferColParentCandidate, inferDropPreset, inferReportPreset, inferRowParentCandidate, isExpansionBandRow, isHorizontalExpandBinding, isVerticalExpandBinding, listColParentCandidates, listRowParentCandidates, parseCellAddress, presetBindingPatch, setBindingCatalog };
332
+ export { REPORT_META_NAMESPACE, applyReportPreset, createReportBinding, formatBindingPlaceholder, formatBindingPlaceholderParts, formatCellAddress, formatParentCandidateLabel, inferColParentCandidate, inferDropPreset, inferParentsForPreset, inferReportPreset, inferRowParentCandidate, isColGroupBinding, isExpansionBandCol, isExpansionBandRow, isHorizontalExpandBinding, isReductionAggregate, isRowGroupBinding, isVerticalExpandBinding, listColParentCandidates, listRowParentCandidates, parseCellAddress, prefersGroupParent, presetBindingPatch, presetStillApplies, setBindingCatalog };
@@ -43,7 +43,7 @@ function toConnectorError(value) {
43
43
  function normalizeField(field) {
44
44
  return {
45
45
  name: field.name,
46
- label: field.name,
46
+ label: field.label ?? field.name,
47
47
  type: field.type ?? "string"
48
48
  };
49
49
  }
@@ -1,7 +1,7 @@
1
1
  import { Sheet } from "@veltra/sheet-core";
2
2
  import { ColumnDef } from "hucre";
3
3
  //#region src/report/export-xlsx.d.ts
4
- /** 列宽条目:模型列索引 → 像素宽(sheet-core 列宽未进 SheetSnapshot,导出时由调用方显式传入) */
4
+ /** 列宽条目:模型列索引 → 像素宽(与 SheetSnapshot.colWidths 同构) */
5
5
  type ReportColWidthEntry = readonly [number, number];
6
6
  /** VTable 像素列宽 → Excel 字符宽度(近似换算) */
7
7
  declare function pxToExcelColWidth(px: number): number;
@@ -10,10 +10,8 @@ declare function buildColumnDefs(colWidths: ReadonlyArray<ReportColWidthEntry>):
10
10
  /**
11
11
  * 将已填充的 Sheet(Filled Report)导出为保真 XLSX(合并 / 样式 / 行高 / 列宽)。
12
12
  * 条件样式颜色已在 renderReport 展开阶段打平进 StylePool(ADR-0001 决策 2)。
13
- *
14
- * 与 `exportWorkbookXlsx`(sheet-core)的差异:列宽未进 SheetSnapshot,
15
- * 只能经 VTable 运行时读取,故这里接收显式列宽并写入 columns 定义。
13
+ * 列宽取自 sheet 模型(`getColWidths`)。
16
14
  */
17
- declare function exportFilledReportXlsx(sheet: Sheet, colWidths: ReadonlyArray<ReportColWidthEntry>): Promise<Uint8Array>;
15
+ declare function exportFilledReportXlsx(sheet: Sheet): Promise<Uint8Array>;
18
16
  //#endregion
19
17
  export { ReportColWidthEntry, buildColumnDefs, exportFilledReportXlsx, pxToExcelColWidth };
@@ -20,11 +20,10 @@ function buildColumnDefs(colWidths) {
20
20
  /**
21
21
  * 将已填充的 Sheet(Filled Report)导出为保真 XLSX(合并 / 样式 / 行高 / 列宽)。
22
22
  * 条件样式颜色已在 renderReport 展开阶段打平进 StylePool(ADR-0001 决策 2)。
23
- *
24
- * 与 `exportWorkbookXlsx`(sheet-core)的差异:列宽未进 SheetSnapshot,
25
- * 只能经 VTable 运行时读取,故这里接收显式列宽并写入 columns 定义。
23
+ * 列宽取自 sheet 模型(`getColWidths`)。
26
24
  */
27
- async function exportFilledReportXlsx(sheet, colWidths) {
25
+ async function exportFilledReportXlsx(sheet) {
26
+ const widths = [...sheet.getColWidths()];
28
27
  let maxRow = -1;
29
28
  let maxCol = -1;
30
29
  for (const [addr] of sheet.store.entries()) {
@@ -32,17 +31,15 @@ async function exportFilledReportXlsx(sheet, colWidths) {
32
31
  if (addr.col > maxCol) maxCol = addr.col;
33
32
  }
34
33
  for (const [row] of sheet.getRowHeights()) if (row > maxRow) maxRow = row;
35
- for (const [col] of colWidths) if (col > maxCol) maxCol = col;
34
+ for (const [col] of widths) if (col > maxCol) maxCol = col;
36
35
  const rows = maxRow < 0 ? [] : Array.from({ length: maxRow + 1 }, () => Array.from({ length: maxCol + 1 }, () => null));
37
36
  const cells = /* @__PURE__ */ new Map();
38
37
  for (const [addr, data] of sheet.store.entries()) {
39
38
  const row = rows[addr.row];
40
39
  const key = `${addr.row},${addr.col}`;
41
40
  const cell = {};
42
- if (data.s != null) {
43
- const style = sheet.stylePool.get(data.s);
44
- if (style) cell.style = styleToHucre(style);
45
- }
41
+ const style = sheet.getEffectiveStyle(addr);
42
+ if (style) cell.style = styleToHucre(style);
46
43
  if (data.f != null && data.f !== "") {
47
44
  cell.formula = data.f;
48
45
  if (data.v != null) cell.formulaResult = data.v;
@@ -76,7 +73,7 @@ async function exportFilledReportXlsx(sheet, colWidths) {
76
73
  rows,
77
74
  ...cells.size > 0 ? { cells } : {},
78
75
  ...sheet.merges.size > 0 ? { merges: sheet.merges.getMerges().map((range) => rangeToHucre(range)) } : {},
79
- ...colWidths.length > 0 ? { columns: buildColumnDefs(colWidths) } : {}
76
+ ...widths.length > 0 ? { columns: buildColumnDefs(widths) } : {}
80
77
  };
81
78
  const frozen = sheet.frozen;
82
79
  if (frozen.rows > 0 || frozen.cols > 0) writeSheet.freezePane = {
@@ -1,5 +1,5 @@
1
1
  import { ConditionalOperator, ConditionalRule, DatasetCatalogItem, DatasetField, DatasetRecords, ParamValues, QueryParamDef, QueryParamOption, QueryParamType, ReportAggregate, ReportBinding, ReportExpand, ReportPreset, ReportSort } from "./types.js";
2
- import { REPORT_META_NAMESPACE, applyReportPreset, createReportBinding, formatBindingPlaceholder, formatCellAddress, inferColParentCandidate, inferDropPreset, inferReportPreset, inferRowParentCandidate, isExpansionBandRow, isHorizontalExpandBinding, isVerticalExpandBinding, listColParentCandidates, listRowParentCandidates, parseCellAddress, presetBindingPatch, setBindingCatalog } from "./binding.js";
2
+ import { InferParentOptions, ListParentCandidateOptions, REPORT_META_NAMESPACE, applyReportPreset, createReportBinding, formatBindingPlaceholder, formatBindingPlaceholderParts, formatCellAddress, formatParentCandidateLabel, inferColParentCandidate, inferDropPreset, inferParentsForPreset, inferReportPreset, inferRowParentCandidate, isColGroupBinding, isExpansionBandCol, isExpansionBandRow, isHorizontalExpandBinding, isReductionAggregate, isRowGroupBinding, isVerticalExpandBinding, listColParentCandidates, listRowParentCandidates, parseCellAddress, prefersGroupParent, presetBindingPatch, presetStillApplies, setBindingCatalog } from "./binding.js";
3
3
  import { ConditionalEvalContext, evaluateCondition, evaluateConditionalStyle, resolveRuleCompareValue, toComparableNumber } from "./rules/evaluate.js";
4
4
  import "./rules/index.js";
5
5
  import { buildParamDefs, extractParamIds, resolveBoundDatasetParams } from "./params.js";
@@ -9,4 +9,4 @@ import { renderReport, resolveFilledColWidths } from "./render/index.js";
9
9
  import { ConnectionType, ConnectorError, CreateHttpConnectorOptions, DEFAULT_CONNECTION_PORTS, DataConnection, DataConnector, QueryResult, Result, createHttpConnector, resolvePortOnTypeChange } from "./connector.js";
10
10
  import { IncompatibleTemplateVersionError, REPORT_TEMPLATE_VERSION, ReportDatasetDef, ReportTemplate, assertCompatibleTemplateVersion, createReportTemplate, fetchTemplateRecords, getBoundDatasetIds, getTemplateDatasets, resolveParamDefaults, resolveTemplateParams } from "./template.js";
11
11
  import { parseDateRangeValue, patchParamValues, resolveNumberParamValue } from "./filter-bar.js";
12
- export { type ConditionalEvalContext, ConditionalOperator, ConditionalRule, ConnectionType, ConnectorError, CreateHttpConnectorOptions, DEFAULT_CONNECTION_PORTS, DataConnection, DataConnector, DatasetCatalogItem, DatasetField, DatasetRecords, IncompatibleTemplateVersionError, ParamValues, QueryParamDef, QueryParamOption, QueryParamType, QueryResult, REPORT_META_NAMESPACE, REPORT_TEMPLATE_VERSION, ReportAggregate, ReportBinding, ReportColWidthEntry, ReportDatasetDef, ReportExpand, ReportPreset, ReportSort, ReportTemplate, Result, applyReportPreset, assertCompatibleTemplateVersion, buildColumnDefs, buildParamDefs, createHttpConnector, createReportBinding, createReportTemplate, evaluateCondition, evaluateConditionalStyle, exportFilledReportXlsx, extractParamIds, fetchTemplateRecords, formatBindingPlaceholder, formatCellAddress, getBoundDatasetIds, getTemplateDatasets, inferColParentCandidate, inferDropPreset, inferReportPreset, inferRowParentCandidate, isExpansionBandRow, isHorizontalExpandBinding, isVerticalExpandBinding, listColParentCandidates, listRowParentCandidates, mapFilledColWidths, parseCellAddress, parseDateRangeValue, patchParamValues, presetBindingPatch, pxToExcelColWidth, renderReport, resolveBoundDatasetParams, resolveFilledColWidths, resolveNumberParamValue, resolveParamDefaults, resolvePortOnTypeChange, resolveRuleCompareValue, resolveTemplateParams, setBindingCatalog, toComparableNumber };
12
+ export { type ConditionalEvalContext, ConditionalOperator, ConditionalRule, ConnectionType, ConnectorError, CreateHttpConnectorOptions, DEFAULT_CONNECTION_PORTS, DataConnection, DataConnector, DatasetCatalogItem, DatasetField, DatasetRecords, IncompatibleTemplateVersionError, InferParentOptions, ListParentCandidateOptions, ParamValues, QueryParamDef, QueryParamOption, QueryParamType, QueryResult, REPORT_META_NAMESPACE, REPORT_TEMPLATE_VERSION, ReportAggregate, ReportBinding, ReportColWidthEntry, ReportDatasetDef, ReportExpand, ReportPreset, ReportSort, ReportTemplate, Result, applyReportPreset, assertCompatibleTemplateVersion, buildColumnDefs, buildParamDefs, createHttpConnector, createReportBinding, createReportTemplate, evaluateCondition, evaluateConditionalStyle, exportFilledReportXlsx, extractParamIds, fetchTemplateRecords, formatBindingPlaceholder, formatBindingPlaceholderParts, formatCellAddress, formatParentCandidateLabel, getBoundDatasetIds, getTemplateDatasets, inferColParentCandidate, inferDropPreset, inferParentsForPreset, inferReportPreset, inferRowParentCandidate, isColGroupBinding, isExpansionBandCol, isExpansionBandRow, isHorizontalExpandBinding, isReductionAggregate, isRowGroupBinding, isVerticalExpandBinding, listColParentCandidates, listRowParentCandidates, mapFilledColWidths, parseCellAddress, parseDateRangeValue, patchParamValues, prefersGroupParent, presetBindingPatch, presetStillApplies, pxToExcelColWidth, renderReport, resolveBoundDatasetParams, resolveFilledColWidths, resolveNumberParamValue, resolveParamDefaults, resolvePortOnTypeChange, resolveRuleCompareValue, resolveTemplateParams, setBindingCatalog, toComparableNumber };
@@ -25,7 +25,10 @@ function isRowExpanding(binding) {
25
25
  function isColExpanding(binding) {
26
26
  return binding.expand === "right" && (binding.aggregate === "group" || binding.aggregate === "list");
27
27
  }
28
- function rowExpandingAncestors(binding, index) {
28
+ function isReductionAggregate(aggregate) {
29
+ return aggregate === "sum" || aggregate === "avg" || aggregate === "count" || aggregate === "max" || aggregate === "min";
30
+ }
31
+ function rowExpandingAncestors(binding, index, groupsOnly) {
29
32
  const chain = [];
30
33
  let current = binding.rowParent;
31
34
  while (current) {
@@ -36,9 +39,13 @@ function rowExpandingAncestors(binding, index) {
36
39
  });
37
40
  current = parentBinding?.rowParent;
38
41
  }
39
- return chain.filter((item) => isRowExpanding(item.binding));
42
+ return chain.filter((item) => {
43
+ if (!isRowExpanding(item.binding)) return false;
44
+ if (groupsOnly && item.binding.aggregate !== "group") return false;
45
+ return true;
46
+ });
40
47
  }
41
- function colExpandingAncestors(binding, index) {
48
+ function colExpandingAncestors(binding, index, groupsOnly) {
42
49
  const chain = [];
43
50
  let current = binding.colParent;
44
51
  while (current) {
@@ -49,7 +56,11 @@ function colExpandingAncestors(binding, index) {
49
56
  });
50
57
  current = parentBinding?.colParent;
51
58
  }
52
- return chain.filter((item) => isColExpanding(item.binding));
59
+ return chain.filter((item) => {
60
+ if (!isColExpanding(item.binding)) return false;
61
+ if (groupsOnly && item.binding.aggregate !== "group") return false;
62
+ return true;
63
+ });
53
64
  }
54
65
  function mergeFilters(rowFilter, colFilter) {
55
66
  return {
@@ -61,7 +72,8 @@ function resolveAncestorFilters(binding, placement, index, data) {
61
72
  const rowFilter = {};
62
73
  const colFilter = {};
63
74
  let parentFilter = {};
64
- const rowAncestors = rowExpandingAncestors(binding, index);
75
+ const groupsOnly = isReductionAggregate(binding.aggregate);
76
+ const rowAncestors = rowExpandingAncestors(binding, index, groupsOnly);
65
77
  for (let i = 0; i < rowAncestors.length; i++) {
66
78
  const ancestor = rowAncestors[i];
67
79
  if (ancestor.binding.dataset !== binding.dataset) continue;
@@ -74,7 +86,7 @@ function resolveAncestorFilters(binding, placement, index, data) {
74
86
  parentFilter = instance.filter;
75
87
  }
76
88
  parentFilter = {};
77
- const colAncestors = colExpandingAncestors(binding, index);
89
+ const colAncestors = colExpandingAncestors(binding, index, groupsOnly);
78
90
  for (let i = 0; i < colAncestors.length; i++) {
79
91
  const ancestor = colAncestors[i];
80
92
  if (ancestor.binding.dataset !== binding.dataset) continue;
@@ -89,8 +101,8 @@ function resolveAncestorFilters(binding, placement, index, data) {
89
101
  return mergeFilters(rowFilter, colFilter);
90
102
  }
91
103
  function resolveOwnGroupValue(binding, placement, index, data) {
92
- const rowAncestors = rowExpandingAncestors(binding, index);
93
- const colAncestors = colExpandingAncestors(binding, index);
104
+ const rowAncestors = rowExpandingAncestors(binding, index, false);
105
+ const colAncestors = colExpandingAncestors(binding, index, false);
94
106
  let parentFilter = {};
95
107
  for (let i = 0; i < rowAncestors.length; i++) {
96
108
  const ancestor = rowAncestors[i];
@@ -3,7 +3,7 @@ import { DataConnection, DataConnector, Result } from "./connector.js";
3
3
  import { SheetSnapshot } from "@veltra/sheet-core";
4
4
  //#region src/report/template.d.ts
5
5
  /**
6
- * 模板内嵌的数据集定义(Dataset = Data Connection + SQL,ADR-0002 决策 2)。
6
+ * 模板内嵌的数据集定义
7
7
  * 与 playground DatasetDef 同构,connectionId 替换为内嵌的完整连接对象。
8
8
  */
9
9
  interface ReportDatasetDef {
@@ -16,7 +16,7 @@ interface ReportDatasetDef {
16
16
  /** 字段中文名覆盖(name → label);查看器不消费,随模板序列化往返供设计器还原 */
17
17
  fieldOverrides?: Record<string, Partial<Pick<DatasetField, 'label'>>>;
18
18
  }
19
- /** 当前支持的报表模板版本(ADR-0005 决策 4) */
19
+ /** 当前支持的报表模板版本 */
20
20
  declare const REPORT_TEMPLATE_VERSION = 1;
21
21
  /** 模板版本不兼容时抛出 */
22
22
  declare class IncompatibleTemplateVersionError extends Error {
@@ -25,8 +25,9 @@ declare class IncompatibleTemplateVersionError extends Error {
25
25
  /**
26
26
  * 自包含 Report Template:SheetSnapshot + 内嵌数据集定义 + 版本段。
27
27
  * 查看器仅凭 `template` + `connector` 即可完成「参数提取 → 取数 → 展开渲染」闭环;
28
- * 模板可 JSON 序列化流转(凭据随之流转,持久化与安全存储由下游负责,ADR-0003 决策 4)。
28
+ * 模板可 JSON 序列化流转。
29
29
  *
30
+ * `colWidths` 继承自 `SheetSnapshot.colWidths`(模型列索引 → 像素)。
30
31
  * 注意:`Sheet.snapshot()` 不产生 `datasets` / `version` 字段,经 `restore()`/`snapshot()` 往返会丢失,
31
32
  * 由设计器 `getTemplate()` 在吐出快照时重新附加。
32
33
  */
@@ -37,7 +38,10 @@ interface ReportTemplate extends SheetSnapshot {
37
38
  }
38
39
  /** 载入模板前校验版本:缺失或高于当前支持版本时抛可读错误 */
39
40
  declare function assertCompatibleTemplateVersion(template: ReportTemplate): void;
40
- /** 由工作簿快照构造带版本段的 Report Template */
41
+ /**
42
+ * 由工作簿快照构造带版本段的 Report Template。
43
+ * `colWidths` 随 `snapshot` 展开(已在 `SheetSnapshot.colWidths`)。
44
+ */
41
45
  declare function createReportTemplate(snapshot: SheetSnapshot, datasets?: ReportDatasetDef[]): ReportTemplate;
42
46
  /** 读取模板内嵌的数据集定义(旧模板未内嵌时回退空数组) */
43
47
  declare function getTemplateDatasets(template: ReportTemplate): ReportDatasetDef[];
@@ -1,7 +1,7 @@
1
1
  import "./binding.js";
2
2
  import { buildParamDefs } from "./params.js";
3
3
  //#region src/report/template.ts
4
- /** 当前支持的报表模板版本(ADR-0005 决策 4) */
4
+ /** 当前支持的报表模板版本 */
5
5
  const REPORT_TEMPLATE_VERSION = 1;
6
6
  /** 模板版本不兼容时抛出 */
7
7
  var IncompatibleTemplateVersionError = class extends Error {
@@ -15,7 +15,10 @@ function assertCompatibleTemplateVersion(template) {
15
15
  if (template.version === void 0) throw new IncompatibleTemplateVersionError("报表模板缺少 version 字段,请在设计器中重建模板后重试");
16
16
  if (template.version > 1) throw new IncompatibleTemplateVersionError(`报表模板版本 ${template.version} 高于当前支持的版本 1,请升级组件库后重试`);
17
17
  }
18
- /** 由工作簿快照构造带版本段的 Report Template */
18
+ /**
19
+ * 由工作簿快照构造带版本段的 Report Template。
20
+ * `colWidths` 随 `snapshot` 展开(已在 `SheetSnapshot.colWidths`)。
21
+ */
19
22
  function createReportTemplate(snapshot, datasets) {
20
23
  return {
21
24
  ...snapshot,
@@ -0,0 +1,29 @@
1
+ //#region src/tools/apply-style.ts
2
+ /**
3
+ * 按选区相对渲染网格的跨度判定样式写入目标(对齐 Excel):
4
+ * - 跨满全部列且未跨满全部行 → 行默认样式(rowStyles)
5
+ * - 跨满全部行且未跨满全部列 → 列默认样式(colStyles)
6
+ * - 其余(含全表 / 局部)→ 单元格样式
7
+ */
8
+ function classifySelectionStyleTarget(range, renderRows, renderCols) {
9
+ const cols = Math.max(renderCols, 1);
10
+ const rows = Math.max(renderRows, 1);
11
+ const spansAllCols = range.start.col === 0 && range.end.col >= cols - 1;
12
+ const spansAllRows = range.start.row === 0 && range.end.row >= rows - 1;
13
+ if (spansAllCols && !spansAllRows) return "row";
14
+ if (spansAllRows && !spansAllCols) return "col";
15
+ return "cell";
16
+ }
17
+ /** 整行/整列选区 → set-axis-style 写入项(partial)或清除项(clear) */
18
+ function axisStyleItemsForRange(range, axis, patch) {
19
+ const start = axis === "row" ? range.start.row : range.start.col;
20
+ const end = axis === "row" ? range.end.row : range.end.col;
21
+ const items = [];
22
+ for (let index = start; index <= end; index++) items.push({
23
+ index,
24
+ ...patch
25
+ });
26
+ return items;
27
+ }
28
+ //#endregion
29
+ export { axisStyleItemsForRange, classifySelectionStyleTarget };