niuma-ui 1.0.0 → 1.0.2

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 (159) hide show
  1. package/CHANGELOG.md +50 -1
  2. package/NOTICE +2 -2
  3. package/package.json +13 -5
  4. package/src/__tests__/RsButton.spec.ts +47 -0
  5. package/src/__tests__/RsCard.spec.ts +160 -1
  6. package/src/__tests__/RsConfirmDialog.spec.ts +127 -0
  7. package/src/__tests__/RsContextMenu.spec.ts +12 -0
  8. package/src/__tests__/RsDateTimePicker.spec.ts +11 -0
  9. package/src/__tests__/RsDescriptions.spec.ts +50 -0
  10. package/src/__tests__/RsDialog.spec.ts +117 -0
  11. package/src/__tests__/RsDrawer.spec.ts +96 -3
  12. package/src/__tests__/RsDynamicTags.spec.ts +52 -0
  13. package/src/__tests__/RsForm.spec.ts +245 -1
  14. package/src/__tests__/RsInput.spec.ts +15 -1
  15. package/src/__tests__/RsMenu.spec.ts +406 -14
  16. package/src/__tests__/RsPagination.spec.ts +67 -2
  17. package/src/__tests__/RsSelect.spec.ts +42 -1
  18. package/src/__tests__/RsSwitch.spec.ts +44 -0
  19. package/src/__tests__/RsTable.a11y.spec.ts +83 -0
  20. package/src/__tests__/RsTable.dom-baseline.spec.ts +87 -0
  21. package/src/__tests__/RsTable.perf.spec.ts +23 -4
  22. package/src/__tests__/RsTable.spec.ts +63 -8
  23. package/src/__tests__/RsTable.ssr.spec.ts +71 -0
  24. package/src/__tests__/RsTable.summary.spec.ts +45 -0
  25. package/src/__tests__/RsTabs.spec.ts +102 -4
  26. package/src/__tests__/RsTerminal.spec.ts +1 -1
  27. package/src/__tests__/RsTimePicker.spec.ts +11 -0
  28. package/src/__tests__/RsUpload.spec.ts +62 -6
  29. package/src/__tests__/createRsDialog.spec.ts +145 -0
  30. package/src/__tests__/form-rules.spec.ts +76 -0
  31. package/src/__tests__/perf-budgets.ts +32 -0
  32. package/src/__tests__/rs-table-api.spec.ts +206 -0
  33. package/src/__tests__/rs-table-compat-matrix.spec.ts +108 -0
  34. package/src/__tests__/rs-table-echarts-adapter.spec.ts +43 -0
  35. package/src/__tests__/rs-table-grid-nav.spec.ts +63 -0
  36. package/src/__tests__/rs-table-isolation.spec.ts +158 -0
  37. package/src/__tests__/split-pane-utils.spec.ts +21 -0
  38. package/src/__tests__/table-chart-utils.spec.ts +88 -0
  39. package/src/__tests__/table-summary-utils.spec.ts +62 -0
  40. package/src/__tests__/table-utils.spec.ts +124 -0
  41. package/src/__tests__/theme.spec.ts +25 -0
  42. package/src/__tests__/useRsTableColumnResize.spec.ts +25 -0
  43. package/src/__tests__/useRsTableCore.perf.spec.ts +170 -0
  44. package/src/components/RsAlert.vue +212 -0
  45. package/src/components/RsButton.vue +145 -21
  46. package/src/components/RsCard.vue +200 -16
  47. package/src/components/RsConfirmDialog.vue +215 -28
  48. package/src/components/RsContextMenu.vue +51 -13
  49. package/src/components/RsDatePicker.vue +267 -34
  50. package/src/components/RsDateTimePicker.vue +19 -2
  51. package/src/components/RsDescriptions.vue +186 -0
  52. package/src/components/RsDescriptionsItem.vue +32 -0
  53. package/src/components/RsDialog.vue +363 -51
  54. package/src/components/RsDivider.vue +89 -0
  55. package/src/components/RsDrawer.vue +558 -33
  56. package/src/components/RsDropdown.vue +33 -2
  57. package/src/components/RsDynamicTags.vue +517 -0
  58. package/src/components/RsForm.vue +97 -8
  59. package/src/components/RsInput.vue +80 -82
  60. package/src/components/RsInputNumber.vue +64 -23
  61. package/src/components/RsLabel.vue +20 -5
  62. package/src/components/RsLoadingBar.vue +151 -0
  63. package/src/components/RsMenu.vue +73 -15
  64. package/src/components/RsMenuItems.vue +147 -9
  65. package/src/components/RsPagination.vue +93 -14
  66. package/src/components/RsRadio.vue +81 -0
  67. package/src/components/RsRadioItem.vue +156 -0
  68. package/src/components/RsSelect.vue +67 -58
  69. package/src/components/RsSplitPane.vue +69 -1
  70. package/src/components/RsSwitch.vue +179 -0
  71. package/src/components/RsTable.vue +653 -2555
  72. package/src/components/RsTabs.vue +415 -83
  73. package/src/components/RsTag.vue +191 -0
  74. package/src/components/RsTerminal.vue +3 -3
  75. package/src/components/RsTimePicker.vue +44 -5
  76. package/src/components/RsTimePickerColumns.vue +136 -77
  77. package/src/components/RsToaster.vue +58 -18
  78. package/src/components/RsTree.vue +39 -21
  79. package/src/components/RsUpload.vue +145 -12
  80. package/src/components/RsVNodeHost.vue +20 -0
  81. package/src/components/button-utils.ts +39 -0
  82. package/src/components/context-menu-utils.ts +6 -0
  83. package/src/components/date-picker-utils.ts +84 -4
  84. package/src/components/descriptions-utils.ts +24 -0
  85. package/src/components/dialog-utils.ts +147 -0
  86. package/src/components/dialog-window.ts +74 -9
  87. package/src/components/drawer-utils.ts +73 -0
  88. package/src/components/form-rules.ts +257 -0
  89. package/src/components/form-utils.ts +52 -2
  90. package/src/components/menu-utils.ts +11 -0
  91. package/src/components/overlay-utils.ts +3 -0
  92. package/src/components/radio-utils.ts +12 -0
  93. package/src/components/reka.ts +5 -0
  94. package/src/components/select-utils.ts +21 -2
  95. package/src/components/split-pane-utils.ts +65 -12
  96. package/src/components/table/RsTableBody.vue +217 -0
  97. package/src/components/table/RsTableBodyRow.vue +90 -1
  98. package/src/components/table/RsTableColGroup.vue +41 -0
  99. package/src/components/table/RsTableHeader.vue +158 -0
  100. package/src/components/table/RsTableSummaryRow.vue +52 -0
  101. package/src/components/table/rs-table-api.ts +99 -0
  102. package/src/components/table/rs-table-compat-matrix.ts +71 -0
  103. package/src/components/table/rs-table-echarts-adapter.ts +81 -0
  104. package/src/components/table/rs-table-feature-host.ts +178 -0
  105. package/src/components/table/rs-table-grid-nav.ts +122 -0
  106. package/src/components/table/rs-table-header-types.ts +47 -0
  107. package/src/components/table/rs-table-module-registry.ts +72 -0
  108. package/src/components/table/rs-table-props.ts +274 -0
  109. package/src/components/table/rs-table-view-context.ts +258 -0
  110. package/src/components/table/rs-table.css +84 -6
  111. package/src/components/table/table-chart-utils.ts +165 -0
  112. package/src/components/table/table-features.ts +284 -0
  113. package/src/components/table/table-summary-utils.ts +194 -0
  114. package/src/components/table-utils.ts +452 -2
  115. package/src/components/tabs-utils.ts +142 -2
  116. package/src/components/terminal-utils.ts +1 -1
  117. package/src/components/terminal-wheel.ts +1 -1
  118. package/src/components/time-picker-utils.ts +76 -3
  119. package/src/components/upload-utils.ts +122 -0
  120. package/src/components/virtual-list-utils.ts +15 -2
  121. package/src/composables/assembleRsTableApi.ts +79 -0
  122. package/src/composables/bindRsTableViewContext.ts +269 -0
  123. package/src/composables/createRsDialog.ts +357 -0
  124. package/src/composables/createRsTableEditEmitBridge.ts +38 -0
  125. package/src/composables/useRsLoadingBar.ts +36 -0
  126. package/src/composables/useRsTable.ts +134 -0
  127. package/src/composables/useRsTableA11y.ts +52 -0
  128. package/src/composables/useRsTableChartBridge.ts +79 -0
  129. package/src/composables/useRsTableColumnLayout.ts +320 -0
  130. package/src/composables/useRsTableColumnResize.ts +244 -0
  131. package/src/composables/useRsTableColumnVirtual.ts +180 -0
  132. package/src/composables/useRsTableColumns.ts +200 -0
  133. package/src/composables/useRsTableContextMenu.ts +257 -0
  134. package/src/composables/useRsTableCore.ts +256 -0
  135. package/src/composables/useRsTableEditActions.ts +606 -0
  136. package/src/composables/useRsTableEditLayer.ts +251 -0
  137. package/src/composables/useRsTableEngine.ts +357 -0
  138. package/src/composables/useRsTableGridKeyboard.ts +123 -0
  139. package/src/composables/useRsTableHeadless.ts +137 -0
  140. package/src/composables/useRsTableInteraction.ts +433 -0
  141. package/src/composables/useRsTableScrollHost.ts +75 -0
  142. package/src/composables/useRsTableScrollLayout.ts +255 -0
  143. package/src/composables/useRsTableSelectionSource.ts +89 -0
  144. package/src/composables/useRsTableShell.ts +416 -0
  145. package/src/composables/useRsTableShellChrome.ts +170 -0
  146. package/src/composables/useRsTableSummary.ts +113 -0
  147. package/src/composables/useRsTableViewProvide.ts +263 -0
  148. package/src/composables/useRsTableVirtual.ts +154 -0
  149. package/src/composables/useRsTabsNav.ts +4 -0
  150. package/src/composables/useTableEdit.ts +1 -1
  151. package/src/icons/lucide.ts +2 -2
  152. package/src/index.ts +309 -9
  153. package/src/locale/messages.ts +30 -0
  154. package/src/styles.css +305 -52
  155. package/src/theme/brand.example.css +21 -2
  156. package/src/theme/presets.ts +4 -0
  157. package/src/theme/types.ts +67 -0
  158. package/src/components/RsTableColgroup.vue +0 -17
  159. package/src/components/RsTableHeaderRow.vue +0 -30
@@ -0,0 +1,217 @@
1
+ <script setup lang="ts" generic="T extends import('../table-utils').RsTableRowData">
2
+ /**
3
+ * RsTable 表体视图:通过 ViewContext inject 取状态(多表互不串扰)。
4
+ * 行级 v-memo 保留,避免抽离后丢失细粒度跳过渲染。
5
+ */
6
+ import { isTableRowDisabled, resolveEntryKey } from '../table-utils'
7
+ import type { RsTableRowEntry } from '../table-utils'
8
+ import RsTableBodyRow from './RsTableBodyRow.vue'
9
+ import { useRsTableView } from './rs-table-view-context'
10
+
11
+ const view = useRsTableView<T>()
12
+
13
+ type RowEntry = Extract<RsTableRowEntry<T>, { type: 'row' }>
14
+
15
+ function editingColKeyFor(entry: RowEntry): string | null {
16
+ const key = view.rowKeyFor(entry)
17
+ return view.editingCell?.rowKey === key ? view.editingCell.colKey : null
18
+ }
19
+
20
+ function focusColKeyFor(entry: RowEntry): string | null {
21
+ const key = view.rowKeyFor(entry)
22
+ return view.focusCell?.rowKey === key ? view.focusCell.colKey : null
23
+ }
24
+
25
+ function memoEditingDraft(entry: RowEntry): string {
26
+ return view.editingCell?.rowKey === view.rowKeyFor(entry) ? view.editingDraft : ''
27
+ }
28
+ </script>
29
+
30
+ <template>
31
+ <tbody
32
+ @pointerover.passive="view.onBodyPointerOver($event)"
33
+ @pointerout.passive="view.onBodyPointerOut($event)"
34
+ >
35
+ <tr v-if="view.loading">
36
+ <td class="rs-table__empty" :colspan="view.bodyColspan">
37
+ <div class="rs-table__empty-content" :style="view.emptyContentStyle">
38
+ {{ view.bodyLabels.loading }}
39
+ </div>
40
+ </td>
41
+ </tr>
42
+ <tr v-else-if="!view.hasData">
43
+ <td class="rs-table__empty" :colspan="view.bodyColspan">
44
+ <div class="rs-table__empty-content" :style="view.emptyContentStyle">
45
+ <slot name="empty">{{ view.bodyLabels.empty }}</slot>
46
+ </div>
47
+ </td>
48
+ </tr>
49
+ <template v-else>
50
+ <tr
51
+ v-if="view.virtualScrollEnabled && view.virtualSlice.paddingTop > 0"
52
+ class="rs-table__virtual-pad"
53
+ >
54
+ <td :colspan="view.bodyColspan" :style="{ height: `${view.virtualSlice.paddingTop}px` }" />
55
+ </tr>
56
+ <template
57
+ v-for="entry in view.visibleEntries"
58
+ :key="resolveEntryKey(entry, view.rowKey)"
59
+ >
60
+ <tr v-if="entry.type === 'group'" class="rs-table__group-row">
61
+ <td class="rs-table__group-cell" :colspan="view.bodyColspan">
62
+ <slot name="group" :key="entry.key" :label="entry.label">
63
+ {{ entry.label }}
64
+ </slot>
65
+ </td>
66
+ </tr>
67
+ <tr v-else-if="entry.type === 'expand'" class="rs-table__expand-row">
68
+ <td class="rs-table__expand-cell" :colspan="view.bodyColspan">
69
+ <slot name="expand" :row="entry.row" :index="entry.rowIndex" />
70
+ </td>
71
+ </tr>
72
+ <RsTableBodyRow
73
+ v-else
74
+ v-memo="[
75
+ view.rowKeyFor(entry),
76
+ entry.row,
77
+ view.isRowSelected(entry),
78
+ view.isRowIndeterminate(entry),
79
+ view.isRowExpanded(entry),
80
+ view.isHighlighted(view.rowKeyFor(entry)),
81
+ view.rowEditPending(entry),
82
+ view.stagedMapSize,
83
+ memoEditingDraft(entry),
84
+ view.activeEditCellKey,
85
+ view.activeFocusCellKey,
86
+ view.activeErrorMapSize,
87
+ view.activeValidatingMapSize,
88
+ view.dragRowKeys.join(','),
89
+ view.dropRowTargetKey,
90
+ view.dropRowPosition,
91
+ view.columnPadLeft,
92
+ view.columnPadRight,
93
+ view.treeDepthOf(entry),
94
+ view.canExpandRow(entry),
95
+ view.treeLoadingKeySet.has(view.rowKeyFor(entry)),
96
+ view.visibleDataColumns.map((c) => c.key).join('\0'),
97
+ view.instanceId,
98
+ ]"
99
+ :row="entry.row"
100
+ :row-index="entry.rowIndex"
101
+ :row-key="view.rowKeyFor(entry)"
102
+ :columns="view.visibleDataColumns"
103
+ :column-pad-left="view.columnPadLeft"
104
+ :column-pad-right="view.columnPadRight"
105
+ :column-td-class-map="view.columnTdClassMap"
106
+ :column-style-map="view.columnStyleMap"
107
+ :show-row-drag-handle="view.showRowDragHandle"
108
+ :expandable="view.detailExpandable"
109
+ :tree-mode="view.treeMode"
110
+ :tree-depth="view.treeDepthOf(entry)"
111
+ :tree-has-children="view.canExpandRow(entry)"
112
+ :tree-indent="view.treeIndentPx"
113
+ :tree-column-key="view.treeExpandColumnKey"
114
+ :tree-loading="view.treeLoadingKeySet.has(view.rowKeyFor(entry))"
115
+ :selectable="view.showSelectColumn"
116
+ :show-index="view.showIndexColumn"
117
+ :show-edit-gutter="view.showEditGutterColumn"
118
+ :striped="view.striped"
119
+ :selection-type="view.selectionType"
120
+ :is-radio-selection="view.isRadioSelection"
121
+ :table-editable="view.editable"
122
+ :edit-trigger="view.editTrigger"
123
+ :row-commit="view.rowCommit"
124
+ :allow-null="view.allowNull"
125
+ :focus-mode="view.editFocusMode"
126
+ :null-label="view.nullLabel"
127
+ :show-row-status="view.showRowStatusColumn"
128
+ :selected="view.isRowSelected(entry)"
129
+ :indeterminate="view.isRowIndeterminate(entry)"
130
+ :highlighted="view.isHighlighted(view.rowKeyFor(entry))"
131
+ :expanded="view.isRowExpanded(entry)"
132
+ :disabled="isTableRowDisabled(entry.row)"
133
+ :row-drag-by-row="view.isRowDragByRow(entry)"
134
+ :dragging="view.isRowDragging(entry)"
135
+ :drop-target="view.isRowDropTarget(entry)"
136
+ :drop-position="view.isRowDropTarget(entry) ? view.dropRowPosition : null"
137
+ :drag-lead-style="view.dragLeadStyle"
138
+ :expand-lead-style="view.expandLeadStyle"
139
+ :select-lead-style="view.selectLeadStyle"
140
+ :index-lead-style="view.indexLeadStyle"
141
+ :gutter-lead-style="view.gutterLeadStyle"
142
+ :can-expand="view.canExpandRow(entry)"
143
+ :can-select="view.canSelectRow(entry)"
144
+ :row-edit-pending="view.rowEditPending(entry)"
145
+ :row-dirty="view.isRowDirty(view.rowKeyFor(entry))"
146
+ :show-gutter-rollback="view.rowCommit || view.isExternalRowPending(entry)"
147
+ :editing-col-key="editingColKeyFor(entry)"
148
+ :focus-col-key="focusColKeyFor(entry)"
149
+ :has-column-slot="view.hasColumnSlot"
150
+ :has-edit-slot="view.hasEditSlot"
151
+ :cell-tooltip-enabled="view.cellTooltipEnabled"
152
+ :cell-tooltip-mode="view.cellTooltipMode"
153
+ :cell-tooltip-text="view.cellTooltipText"
154
+ :cell-tooltip-fallback-title="view.cellTooltipFallbackTitle"
155
+ :get-cell-draft="view.getCellDraft"
156
+ :is-cell-dirty="view.isCellDirty"
157
+ :get-cell-error="view.getCellError"
158
+ :is-cell-validating="view.isCellValidating"
159
+ :drag-row-label="view.bodyLabels.dragRow"
160
+ :expand-row-label="view.bodyLabels.expandRow"
161
+ :collapse-row-label="view.bodyLabels.collapseRow"
162
+ :select-row-label="view.bodyLabels.selectRow"
163
+ :row-commit-label="view.bodyLabels.rowCommit"
164
+ :row-rollback-label="view.bodyLabels.rowRollback"
165
+ :gutter-commit-hint="view.bodyLabels.gutterCommit"
166
+ @click="view.onRowClick(entry, $event)"
167
+ @mousedown="view.onRowSelectMouseDown($event)"
168
+ @cell-click="(colKey, event) => view.onCellClick(entry, colKey, event)"
169
+ @cell-dblclick="(colKey, event) => view.onCellDblclick(entry, colKey, event)"
170
+ @cell-contextmenu="(colKey, event) => view.onCellContextmenu(entry, colKey, event)"
171
+ @dblclick="view.onRowDblclick(entry, $event)"
172
+ @contextmenu="view.onRowContextmenu(entry, $event)"
173
+ @dragover="view.onRowDragOver(entry, $event)"
174
+ @dragleave="view.onRowDragLeave($event)"
175
+ @drop="view.onRowDrop(entry, $event)"
176
+ @row-drag-start="view.onRowDragStart(entry, $event)"
177
+ @row-drag-end="view.onRowDragEnd()"
178
+ @toggle-expand="view.onToggleExpand(entry)"
179
+ @toggle-select="view.onToggleSelect(entry)"
180
+ @cell-start-edit="view.onCellStartEdit(entry, $event)"
181
+ @cell-commit="(colKey, value) => view.onCellCommit(entry, colKey, value)"
182
+ @cell-cancel="view.onCellCancel(entry, $event)"
183
+ @cell-update-draft="(colKey, value) => view.onCellUpdateDraft(entry, colKey, value)"
184
+ @cell-navigate="(colKey, direction) => view.onCellNavigate(entry, colKey, direction)"
185
+ @row-commit="view.onRowCommit(entry)"
186
+ @row-rollback="view.onRowRollback(entry)"
187
+ @gutter-commit="view.onGutterCommit(entry)"
188
+ >
189
+ <template v-for="column in view.visibleDataColumns" #[column.key]="slotProps">
190
+ <slot :name="column.key" v-bind="slotProps" />
191
+ </template>
192
+ <template
193
+ v-for="column in view.visibleDataColumns"
194
+ :key="`edit-${column.key}`"
195
+ #[`edit-${column.key}`]="slotProps"
196
+ >
197
+ <slot :name="`edit-${column.key}`" v-bind="slotProps" />
198
+ </template>
199
+ </RsTableBodyRow>
200
+ </template>
201
+ <tr
202
+ v-if="view.virtualScrollEnabled && view.virtualSlice.paddingBottom > 0"
203
+ class="rs-table__virtual-pad"
204
+ >
205
+ <td
206
+ :colspan="view.bodyColspan"
207
+ :style="{ height: `${view.virtualSlice.paddingBottom}px` }"
208
+ />
209
+ </tr>
210
+ <tr v-if="view.infinite && view.loadingMore">
211
+ <td class="rs-table__empty rs-table__empty--more" :colspan="view.bodyColspan">
212
+ {{ view.bodyLabels.loadingMore }}
213
+ </td>
214
+ </tr>
215
+ </template>
216
+ </tbody>
217
+ </template>
@@ -18,6 +18,13 @@ defineProps<{
18
18
  columnStyleMap: Map<string, Record<string, string> | undefined>
19
19
  showRowDragHandle: boolean
20
20
  expandable: boolean
21
+ /** 树表模式:在指定数据列内缩进展开 */
22
+ treeMode?: boolean
23
+ treeDepth?: number
24
+ treeHasChildren?: boolean
25
+ treeIndent?: number
26
+ treeColumnKey?: string | null
27
+ treeLoading?: boolean
21
28
  selectable: boolean
22
29
  showIndex: boolean
23
30
  showEditGutter: boolean
@@ -34,6 +41,8 @@ defineProps<{
34
41
  selected: boolean
35
42
  highlighted: boolean
36
43
  expanded: boolean
44
+ /** 树级联半选 */
45
+ indeterminate?: boolean
37
46
  disabled: boolean
38
47
  rowDragByRow: boolean
39
48
  dragging: boolean
@@ -99,7 +108,9 @@ const emit = defineEmits<{
99
108
 
100
109
  <template>
101
110
  <tr
111
+ role="row"
102
112
  class="rs-table__row"
113
+ :data-row-key="rowKey"
103
114
  :class="{
104
115
  'rs-table__row--selected': selected,
105
116
  'rs-table__row--highlighted': highlighted && !selected,
@@ -157,12 +168,19 @@ const emit = defineEmits<{
157
168
  :style="selectLeadStyle"
158
169
  @click.stop
159
170
  >
160
- <label class="rs-table__checkbox" :class="{ 'rs-table__checkbox--checked': selected }">
171
+ <label
172
+ class="rs-table__checkbox"
173
+ :class="{
174
+ 'rs-table__checkbox--checked': selected,
175
+ 'rs-table__checkbox--indeterminate': indeterminate && !selected,
176
+ }"
177
+ >
161
178
  <input
162
179
  :type="selectionType"
163
180
  class="rs-table__checkbox-input"
164
181
  :name="isRadioSelection ? 'rs-table-radio' : undefined"
165
182
  :checked="selected"
183
+ :indeterminate="Boolean(indeterminate && !selected)"
166
184
  :disabled="!canSelect"
167
185
  :aria-label="selectRowLabel"
168
186
  @change="emit('toggleSelect')"
@@ -218,7 +236,9 @@ const emit = defineEmits<{
218
236
  <td
219
237
  v-for="column in columns"
220
238
  :key="column.key"
239
+ role="gridcell"
221
240
  class="rs-table__td rs-table__td--data"
241
+ :data-col-key="column.key"
222
242
  :class="[
223
243
  columnTdClassMap.get(column.key),
224
244
  {
@@ -226,10 +246,12 @@ const emit = defineEmits<{
226
246
  'rs-table__td--editing': editingColKey === column.key,
227
247
  'rs-table__td--focused': focusColKey === column.key && editingColKey !== column.key,
228
248
  'rs-table__td--invalid': !!getCellError(rowKey, column.key),
249
+ 'rs-table__td--tree': treeMode && treeColumnKey === column.key,
229
250
  },
230
251
  ]"
231
252
  :style="columnStyleMap.get(column.key)"
232
253
  :draggable="rowDragByRow"
254
+ :tabindex="focusColKey === column.key && editingColKey !== column.key ? 0 : -1"
233
255
  :aria-selected="focusColKey === column.key || editingColKey === column.key ? 'true' : undefined"
234
256
  @click.stop="emit('cellClick', column.key, $event)"
235
257
  @dblclick.stop="emit('cellDblclick', column.key, $event)"
@@ -237,7 +259,74 @@ const emit = defineEmits<{
237
259
  @dragstart="rowDragByRow ? emit('rowDragStart', $event) : undefined"
238
260
  @dragend="rowDragByRow ? emit('rowDragEnd') : undefined"
239
261
  >
262
+ <div
263
+ v-if="treeMode && treeColumnKey === column.key"
264
+ class="rs-table__tree-cell"
265
+ :style="{ paddingInlineStart: `${(treeDepth ?? 0) * (treeIndent ?? 20)}px` }"
266
+ >
267
+ <button
268
+ v-if="treeHasChildren"
269
+ type="button"
270
+ class="rs-table__expand-btn rs-table__tree-toggle"
271
+ :class="{
272
+ 'rs-table__expand-btn--expanded': expanded,
273
+ 'rs-table__tree-toggle--loading': treeLoading,
274
+ }"
275
+ :disabled="treeLoading"
276
+ :aria-label="expanded ? collapseRowLabel : expandRowLabel"
277
+ @click.stop="emit('toggleExpand')"
278
+ >
279
+ ›
280
+ </button>
281
+ <span
282
+ v-else
283
+ class="rs-table__tree-leaf"
284
+ aria-hidden="true"
285
+ />
286
+ <span
287
+ :class="[
288
+ 'rs-table__tree-content',
289
+ { 'rs-table__cell-tip': cellTooltipEnabled(column, rowIndex) },
290
+ { 'rs-table__cell-tip--editing': editingColKey === column.key },
291
+ ]"
292
+ :data-rs-table-tip-mode="cellTooltipEnabled(column, rowIndex) ? cellTooltipMode(column, rowIndex) : undefined"
293
+ :data-rs-table-tip-text="cellTooltipText(column, row, rowIndex)"
294
+ :title="getCellError(rowKey, column.key) || cellTooltipFallbackTitle(column, row, rowIndex)"
295
+ >
296
+ <RsTableCell
297
+ :column="column"
298
+ :row="row"
299
+ :row-index="rowIndex"
300
+ :row-key="rowKey"
301
+ :table-editable="tableEditable"
302
+ :editing="editingColKey === column.key"
303
+ :dirty="isCellDirty(rowKey, column.key)"
304
+ :focused="focusColKey === column.key"
305
+ :draft="getCellDraft(rowKey, column.key)"
306
+ :has-custom-slot="hasColumnSlot(column.key)"
307
+ :has-edit-slot="hasEditSlot(column.key)"
308
+ :edit-trigger="editTrigger"
309
+ :row-commit="rowCommit"
310
+ :allow-null="allowNull"
311
+ :focus-mode="focusMode"
312
+ :null-label="nullLabel"
313
+ :error-message="getCellError(rowKey, column.key) ?? null"
314
+ :validating="isCellValidating(rowKey, column.key)"
315
+ @start-edit="emit('cellStartEdit', column.key)"
316
+ @commit="emit('cellCommit', column.key, $event)"
317
+ @cancel="emit('cellCancel', column.key)"
318
+ @update-draft="emit('cellUpdateDraft', column.key, $event)"
319
+ @navigate="emit('cellNavigate', column.key, $event)"
320
+ >
321
+ <slot :name="column.key" :row="row" :column="column" :index="rowIndex" />
322
+ <template v-if="hasEditSlot(column.key)" #editor="scope">
323
+ <slot :name="`edit-${column.key}`" v-bind="scope" />
324
+ </template>
325
+ </RsTableCell>
326
+ </span>
327
+ </div>
240
328
  <span
329
+ v-else
241
330
  :class="[
242
331
  { 'rs-table__cell-tip': cellTooltipEnabled(column, rowIndex) },
243
332
  { 'rs-table__cell-tip--editing': editingColKey === column.key },
@@ -0,0 +1,41 @@
1
+ <script setup lang="ts" generic="T extends import('../table-utils').RsTableRowData">
2
+ import { useRsTableView } from './rs-table-view-context'
3
+
4
+ const view = useRsTableView<T>()
5
+ </script>
6
+
7
+ <template>
8
+ <colgroup v-if="view.useFixedColumnLayout">
9
+ <col v-if="view.showRowDragHandle" :style="{ width: `${view.prefixWidths.drag}px` }">
10
+ <col v-if="view.detailExpandable" :style="{ width: `${view.prefixWidths.expand}px` }">
11
+ <col v-if="view.showSelectColumn" :style="{ width: `${view.prefixWidths.select}px` }">
12
+ <col
13
+ v-if="view.showEditGutterColumn"
14
+ data-col-key="gutter"
15
+ :style="{ width: `${view.resolvedGutterWidth}px` }"
16
+ >
17
+ <col
18
+ v-else-if="view.showIndexColumn"
19
+ :style="{ width: `${view.resolvedIndexWidth}px` }"
20
+ >
21
+ <col v-if="view.showRowStatusColumn" :style="{ width: `${view.prefixWidths.status}px` }">
22
+ <col
23
+ v-if="view.columnPadLeft > 0"
24
+ class="rs-table__col-pad"
25
+ :style="{ width: `${view.columnPadLeft}px` }"
26
+ >
27
+ <col
28
+ v-for="column in view.visibleDataColumns"
29
+ :key="column.key"
30
+ :data-col-key="column.key"
31
+ :style="{
32
+ width: `${view.resolvedDataColumnWidth(column.key, column.width ?? column.minWidth)}px`,
33
+ }"
34
+ >
35
+ <col
36
+ v-if="view.columnPadRight > 0"
37
+ class="rs-table__col-pad"
38
+ :style="{ width: `${view.columnPadRight}px` }"
39
+ >
40
+ </colgroup>
41
+ </template>
@@ -0,0 +1,158 @@
1
+ <script setup lang="ts" generic="T extends import('../table-utils').RsTableRowData">
2
+ import RsIcon from '../RsIcon.vue'
3
+ import RsTableHeaderFilter from './RsTableHeaderFilter.vue'
4
+ import { useRsTableView } from './rs-table-view-context'
5
+
6
+ const view = useRsTableView<T>()
7
+ </script>
8
+
9
+ <template>
10
+ <thead
11
+ class="rs-table__head"
12
+ @pointerover.passive="view.onHeaderPointerOver($event)"
13
+ @pointerout.passive="view.onHeaderPointerOut($event)"
14
+ >
15
+ <tr role="row">
16
+ <th
17
+ v-if="view.showRowDragHandle"
18
+ role="columnheader"
19
+ class="rs-table__th rs-table__th--drag"
20
+ :style="view.dragLeadHeaderStyle"
21
+ />
22
+ <th
23
+ v-if="view.detailExpandable"
24
+ role="columnheader"
25
+ class="rs-table__th rs-table__th--expand"
26
+ :style="view.expandLeadHeaderStyle"
27
+ />
28
+ <th
29
+ v-if="view.showSelectColumn"
30
+ role="columnheader"
31
+ class="rs-table__th rs-table__th--selection"
32
+ :style="view.selectLeadHeaderStyle"
33
+ >
34
+ <label
35
+ v-if="!view.isRadioSelection"
36
+ class="rs-table__checkbox"
37
+ :class="{
38
+ 'rs-table__checkbox--checked': view.selectAllState === 'checked',
39
+ 'rs-table__checkbox--indeterminate': view.selectAllState === 'indeterminate',
40
+ }"
41
+ >
42
+ <input
43
+ type="checkbox"
44
+ class="rs-table__checkbox-input"
45
+ :checked="view.selectAllState === 'checked'"
46
+ :aria-label="view.headerLabels.selectAll"
47
+ @change="view.onToggleSelectAll()"
48
+ >
49
+ <span class="rs-table__checkbox-box" aria-hidden="true" />
50
+ </label>
51
+ </th>
52
+ <th
53
+ v-if="view.showEditGutterColumn"
54
+ role="columnheader"
55
+ class="rs-table__th rs-table__th--gutter rs-table__cell--center"
56
+ :style="view.gutterLeadHeaderStyle"
57
+ />
58
+ <th
59
+ v-else-if="view.showIndexColumn"
60
+ role="columnheader"
61
+ class="rs-table__th rs-table__th--index rs-table__cell--center"
62
+ :style="view.indexLeadHeaderStyle"
63
+ >
64
+ {{ view.headerLabels.index }}
65
+ </th>
66
+ <th
67
+ v-if="view.showRowStatusColumn"
68
+ role="columnheader"
69
+ class="rs-table__th rs-table__th--status rs-table__cell--center"
70
+ >
71
+ {{ view.headerLabels.rowStatus }}
72
+ </th>
73
+ <th
74
+ v-if="view.columnPadLeft > 0"
75
+ role="presentation"
76
+ class="rs-table__th rs-table__col-pad"
77
+ :style="{ width: `${view.columnPadLeft}px`, minWidth: `${view.columnPadLeft}px` }"
78
+ />
79
+ <th
80
+ v-for="column in view.visibleDataColumns"
81
+ :key="column.key"
82
+ role="columnheader"
83
+ :data-col-key="column.key"
84
+ class="rs-table__th"
85
+ :class="[
86
+ `rs-table__cell--${column.align ?? 'left'}`,
87
+ { 'rs-table__th--sortable': column.sortable },
88
+ { 'rs-table__th--filterable': column.filterable },
89
+ { 'rs-table__cell--fixed': column.fixed },
90
+ { 'rs-table__th--dragging': view.dragColumnKey === column.key },
91
+ { 'rs-table__th--drop-target': view.dropColumnKey === column.key },
92
+ ]"
93
+ :style="view.columnHeaderStyleMap.get(column.key)"
94
+ @dragover="view.onColumnDragOver(column.key, $event)"
95
+ @drop="view.onColumnDrop(column.key, $event)"
96
+ >
97
+ <span
98
+ v-if="view.columnDraggable"
99
+ class="rs-table__column-drag-handle"
100
+ draggable="true"
101
+ :aria-label="view.headerLabels.dragColumn"
102
+ @click.stop
103
+ @dragstart.stop="view.onColumnDragStart(column.key, $event)"
104
+ @dragend.stop="view.onColumnDragEnd()"
105
+ />
106
+ <slot :name="`header-${column.key}`" :column="column">
107
+ <span
108
+ v-if="column.headerTip"
109
+ class="rs-table__th-label rs-table__th-label--tip"
110
+ :data-rs-table-header-tip="column.headerTip"
111
+ >{{ column.title }}</span>
112
+ <span v-else class="rs-table__th-label">{{ column.title }}</span>
113
+ </slot>
114
+ <span v-if="column.filterable || column.sortable" class="rs-table__header-actions">
115
+ <RsTableHeaderFilter
116
+ v-if="column.filterable"
117
+ :model-value="view.columnFilterValue(column.key)"
118
+ :column-title="column.title"
119
+ :filter-label="view.headerLabels.filterColumn"
120
+ :placeholder="view.headerLabels.filterPlaceholder"
121
+ :clear-label="view.headerLabels.filterClear"
122
+ :apply-label="view.headerLabels.filterApply"
123
+ :active="view.isColumnFilterActive(view.columnFiltersState, column.key)"
124
+ @update:model-value="view.updateColumnFilter(column.key, $event)"
125
+ />
126
+ <button
127
+ v-if="column.sortable"
128
+ type="button"
129
+ class="rs-table__sort"
130
+ :class="{ 'rs-table__sort--active': !!view.sortOrderFor(column.key) }"
131
+ :title="column.title"
132
+ :aria-label="column.title"
133
+ @click.stop="view.onHeaderClick(column)"
134
+ >
135
+ <RsIcon :name="view.sortIconName(column.key)" size="sm" />
136
+ <span
137
+ v-if="view.multiSort && view.sortPriorityFor(column.key) > 1"
138
+ class="rs-table__sort-priority"
139
+ >
140
+ {{ view.sortPriorityFor(column.key) }}
141
+ </span>
142
+ </button>
143
+ </span>
144
+ <span
145
+ v-if="view.resizable"
146
+ class="rs-table__resize-handle"
147
+ @mousedown.stop="view.onResizeStart(column.key, $event)"
148
+ @click.stop
149
+ />
150
+ </th>
151
+ <th
152
+ v-if="view.columnPadRight > 0"
153
+ class="rs-table__th rs-table__col-pad"
154
+ :style="{ width: `${view.columnPadRight}px`, minWidth: `${view.columnPadRight}px` }"
155
+ />
156
+ </tr>
157
+ </thead>
158
+ </template>
@@ -0,0 +1,52 @@
1
+ <script setup lang="ts" generic="T extends import('../table-utils').RsTableRowData">
2
+ /**
3
+ * 表格 footer 汇总行:按列渲染 summaryCells.text。
4
+ *
5
+ * 前缀列(拖拽/展开/选择/序号等)用 colspan 占位,与表体列对齐;
6
+ * padLeft / padRight 对齐列虚拟滚动的左右垫片。
7
+ */
8
+ import type { RsTableColumn } from '../table-utils'
9
+ import type { RsTableSummaryCell } from './table-summary-utils'
10
+
11
+ defineProps<{
12
+ /** 与表体一致的展示列(通常为 displayColumns) */
13
+ columns: RsTableColumn<T>[]
14
+ /** 与 columns 按 key 对应的汇总单元格 */
15
+ cells: RsTableSummaryCell[]
16
+ /** 前缀列占位 colspan(拖拽/展开/选择/序号/状态等) */
17
+ prefixColspan?: number
18
+ /** 列虚拟左侧垫片宽度(px) */
19
+ padLeft?: number
20
+ /** 列虚拟右侧垫片宽度(px) */
21
+ padRight?: number
22
+ }>()
23
+ </script>
24
+
25
+ <template>
26
+ <tr class="rs-table__summary-row">
27
+ <td
28
+ v-if="(prefixColspan ?? 0) > 0"
29
+ class="rs-table__summary-cell rs-table__summary-cell--prefix"
30
+ :colspan="prefixColspan"
31
+ />
32
+ <td
33
+ v-if="(padLeft ?? 0) > 0"
34
+ class="rs-table__summary-cell rs-table__summary-cell--pad"
35
+ :style="{ width: `${padLeft}px`, minWidth: `${padLeft}px` }"
36
+ />
37
+ <td
38
+ v-for="column in columns"
39
+ :key="column.key"
40
+ class="rs-table__summary-cell"
41
+ :class="column.align ? `rs-table__summary-cell--${column.align}` : undefined"
42
+ :data-col-key="column.key"
43
+ >
44
+ {{ cells.find((c) => c.key === column.key)?.text ?? '' }}
45
+ </td>
46
+ <td
47
+ v-if="(padRight ?? 0) > 0"
48
+ class="rs-table__summary-cell rs-table__summary-cell--pad"
49
+ :style="{ width: `${padRight}px`, minWidth: `${padRight}px` }"
50
+ />
51
+ </tr>
52
+ </template>