@aiao/rxdb-model 0.0.19 → 0.0.26

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 (181) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +43 -6
  3. package/dist/cn.d.ts +19 -0
  4. package/dist/cn.d.ts.map +1 -0
  5. package/dist/entity-capabilities.d.ts +31 -0
  6. package/dist/entity-capabilities.d.ts.map +1 -0
  7. package/dist/entity-detail/detail-tabs.d.ts +13 -2
  8. package/dist/entity-detail/detail-tabs.d.ts.map +1 -1
  9. package/dist/entity-detail/index.d.ts +5 -0
  10. package/dist/entity-detail/index.d.ts.map +1 -1
  11. package/dist/entity-detail/interfaces.d.ts +29 -1
  12. package/dist/entity-detail/interfaces.d.ts.map +1 -1
  13. package/dist/entity-field.utils.d.ts +60 -2
  14. package/dist/entity-field.utils.d.ts.map +1 -1
  15. package/dist/entity-form/form-data.d.ts +22 -1
  16. package/dist/entity-form/form-data.d.ts.map +1 -1
  17. package/dist/entity-form/form-fields.d.ts +25 -2
  18. package/dist/entity-form/form-fields.d.ts.map +1 -1
  19. package/dist/entity-form/form-validation.d.ts +20 -2
  20. package/dist/entity-form/form-validation.d.ts.map +1 -1
  21. package/dist/entity-form/index.d.ts +5 -0
  22. package/dist/entity-form/index.d.ts.map +1 -1
  23. package/dist/entity-form/interfaces.d.ts +65 -1
  24. package/dist/entity-form/interfaces.d.ts.map +1 -1
  25. package/dist/entity-list/manual-order-list.d.ts +99 -0
  26. package/dist/entity-list/manual-order-list.d.ts.map +1 -0
  27. package/dist/entity-table/columns/build-editable-columns.d.ts +26 -4
  28. package/dist/entity-table/columns/build-editable-columns.d.ts.map +1 -1
  29. package/dist/entity-table/columns/column-utils.d.ts +78 -12
  30. package/dist/entity-table/columns/column-utils.d.ts.map +1 -1
  31. package/dist/entity-table/editors/color-editor.d.ts +23 -0
  32. package/dist/entity-table/editors/color-editor.d.ts.map +1 -0
  33. package/dist/entity-table/editors/date-editor.d.ts +6 -1
  34. package/dist/entity-table/editors/date-editor.d.ts.map +1 -1
  35. package/dist/entity-table/editors/enum-editor.d.ts +11 -1
  36. package/dist/entity-table/editors/enum-editor.d.ts.map +1 -1
  37. package/dist/entity-table/editors/global-overlay-editor.d.ts +27 -4
  38. package/dist/entity-table/editors/global-overlay-editor.d.ts.map +1 -1
  39. package/dist/entity-table/editors/icon-list-editor.d.ts +13 -1
  40. package/dist/entity-table/editors/icon-list-editor.d.ts.map +1 -1
  41. package/dist/entity-table/editors/json-editor.d.ts +6 -1
  42. package/dist/entity-table/editors/json-editor.d.ts.map +1 -1
  43. package/dist/entity-table/editors/key-value-editor.d.ts +12 -2
  44. package/dist/entity-table/editors/key-value-editor.d.ts.map +1 -1
  45. package/dist/entity-table/editors/lucide-svg.d.ts +3 -2
  46. package/dist/entity-table/editors/lucide-svg.d.ts.map +1 -1
  47. package/dist/entity-table/editors/multiselect-editor.d.ts +24 -6
  48. package/dist/entity-table/editors/multiselect-editor.d.ts.map +1 -1
  49. package/dist/entity-table/editors/number-editor.d.ts +33 -3
  50. package/dist/entity-table/editors/number-editor.d.ts.map +1 -1
  51. package/dist/entity-table/editors/relation-editor.d.ts +14 -2
  52. package/dist/entity-table/editors/relation-editor.d.ts.map +1 -1
  53. package/dist/entity-table/editors/safe-list-editor.d.ts +1 -0
  54. package/dist/entity-table/editors/safe-list-editor.d.ts.map +1 -1
  55. package/dist/entity-table/editors/tags-editor.d.ts +12 -1
  56. package/dist/entity-table/editors/tags-editor.d.ts.map +1 -1
  57. package/dist/entity-table/editors/text-format-editor.d.ts +32 -0
  58. package/dist/entity-table/editors/text-format-editor.d.ts.map +1 -0
  59. package/dist/entity-table/editors/uuid-editor.d.ts +14 -1
  60. package/dist/entity-table/editors/uuid-editor.d.ts.map +1 -1
  61. package/dist/entity-table/index.d.ts +13 -3
  62. package/dist/entity-table/index.d.ts.map +1 -1
  63. package/dist/entity-table/interfaces.d.ts +13 -0
  64. package/dist/entity-table/interfaces.d.ts.map +1 -1
  65. package/dist/entity-table/vtable/table-clipboard.d.ts +31 -2
  66. package/dist/entity-table/vtable/table-clipboard.d.ts.map +1 -1
  67. package/dist/entity-table/vtable/table-factory.d.ts +25 -5
  68. package/dist/entity-table/vtable/table-factory.d.ts.map +1 -1
  69. package/dist/entity-table/vtable/table-keyboard.d.ts +18 -1
  70. package/dist/entity-table/vtable/table-keyboard.d.ts.map +1 -1
  71. package/dist/entity-table/vtable/table-operations.d.ts +44 -3
  72. package/dist/entity-table/vtable/table-operations.d.ts.map +1 -1
  73. package/dist/entity-table/vtable/table-row-move.d.ts +88 -0
  74. package/dist/entity-table/vtable/table-row-move.d.ts.map +1 -0
  75. package/dist/entity-table/vtable/table-theme.d.ts +47 -2
  76. package/dist/entity-table/vtable/table-theme.d.ts.map +1 -1
  77. package/dist/entity-table/vtable/table-tooltip.d.ts +7 -1
  78. package/dist/entity-table/vtable/table-tooltip.d.ts.map +1 -1
  79. package/dist/entity-table/vtable/vtable-compat.d.ts +33 -5
  80. package/dist/entity-table/vtable/vtable-compat.d.ts.map +1 -1
  81. package/dist/entity-value.utils.d.ts +50 -2
  82. package/dist/entity-value.utils.d.ts.map +1 -1
  83. package/dist/index.d.ts +9 -0
  84. package/dist/index.d.ts.map +1 -1
  85. package/dist/index.js +4392 -64471
  86. package/dist/query-builder/models/config.interface.d.ts +2 -2
  87. package/dist/query-builder/models/operator.interface.d.ts +6 -2
  88. package/dist/query-builder/models/operator.interface.d.ts.map +1 -1
  89. package/dist/query-builder/models/query-builder-state.d.ts +31 -2
  90. package/dist/query-builder/models/query-builder-state.d.ts.map +1 -1
  91. package/dist/query-builder/models/validation.interface.d.ts +2 -2
  92. package/dist/query-builder/services/operator-registry.service.d.ts +13 -3
  93. package/dist/query-builder/services/operator-registry.service.d.ts.map +1 -1
  94. package/dist/query-builder/services/query-builder.service.d.ts +16 -6
  95. package/dist/query-builder/services/query-builder.service.d.ts.map +1 -1
  96. package/dist/query-builder/services/query-converter.service.d.ts +10 -2
  97. package/dist/query-builder/services/query-converter.service.d.ts.map +1 -1
  98. package/dist/query-builder/services/schema-parser.service.d.ts +10 -2
  99. package/dist/query-builder/services/schema-parser.service.d.ts.map +1 -1
  100. package/dist/query-builder/services/validation.service.d.ts +34 -4
  101. package/dist/query-builder/services/validation.service.d.ts.map +1 -1
  102. package/dist/query-builder/utils/date.d.ts +12 -0
  103. package/dist/query-builder/utils/date.d.ts.map +1 -1
  104. package/dist/query-builder/utils/field-tree.d.ts +7 -1
  105. package/dist/query-builder/utils/field-tree.d.ts.map +1 -1
  106. package/dist/query-builder/utils/id.d.ts +2 -0
  107. package/dist/query-builder/utils/id.d.ts.map +1 -1
  108. package/dist/query-builder/utils/metadata-field-extractor.d.ts +15 -3
  109. package/dist/query-builder/utils/metadata-field-extractor.d.ts.map +1 -1
  110. package/dist/query-builder/utils/popover-position.d.ts +11 -0
  111. package/dist/query-builder/utils/popover-position.d.ts.map +1 -1
  112. package/dist/query-builder/utils/schema-factory.d.ts +7 -3
  113. package/dist/query-builder/utils/structural-equal.d.ts +4 -0
  114. package/dist/query-builder/utils/structural-equal.d.ts.map +1 -1
  115. package/dist/query-builder/utils/value-input.d.ts +21 -1
  116. package/dist/query-builder/utils/value-input.d.ts.map +1 -1
  117. package/dist/structural-equal.d.ts +5 -1
  118. package/dist/structural-equal.d.ts.map +1 -1
  119. package/package.json +34 -10
  120. package/src/cn.ts +21 -0
  121. package/src/entity-capabilities.ts +37 -0
  122. package/src/entity-detail/detail-tabs.ts +48 -0
  123. package/src/entity-detail/index.ts +7 -0
  124. package/src/entity-detail/interfaces.ts +55 -0
  125. package/src/entity-field.utils.ts +205 -0
  126. package/src/entity-form/form-data.ts +83 -0
  127. package/src/entity-form/form-fields.ts +48 -0
  128. package/src/entity-form/form-validation.ts +34 -0
  129. package/src/entity-form/index.ts +9 -0
  130. package/src/entity-form/interfaces.ts +116 -0
  131. package/src/entity-list/manual-order-list.ts +159 -0
  132. package/src/entity-table/columns/build-editable-columns.ts +184 -0
  133. package/src/entity-table/columns/column-utils.ts +657 -0
  134. package/src/entity-table/editors/color-editor.ts +239 -0
  135. package/src/entity-table/editors/date-editor.ts +169 -0
  136. package/src/entity-table/editors/enum-editor.ts +167 -0
  137. package/src/entity-table/editors/global-overlay-editor.ts +277 -0
  138. package/src/entity-table/editors/icon-list-editor.ts +130 -0
  139. package/src/entity-table/editors/json-editor.ts +212 -0
  140. package/src/entity-table/editors/key-value-editor.ts +595 -0
  141. package/src/entity-table/editors/lucide-svg.ts +78 -0
  142. package/src/entity-table/editors/multiselect-editor.ts +171 -0
  143. package/src/entity-table/editors/number-editor.ts +322 -0
  144. package/src/entity-table/editors/relation-editor.ts +316 -0
  145. package/src/entity-table/editors/safe-list-editor.ts +17 -0
  146. package/src/entity-table/editors/tags-editor.ts +239 -0
  147. package/src/entity-table/editors/text-format-editor.ts +220 -0
  148. package/src/entity-table/editors/uuid-editor.ts +198 -0
  149. package/src/entity-table/index.ts +96 -0
  150. package/src/entity-table/interfaces.ts +72 -0
  151. package/src/entity-table/vtable/table-clipboard.ts +419 -0
  152. package/src/entity-table/vtable/table-factory.ts +117 -0
  153. package/src/entity-table/vtable/table-keyboard.ts +89 -0
  154. package/src/entity-table/vtable/table-operations.ts +123 -0
  155. package/src/entity-table/vtable/table-row-move.ts +122 -0
  156. package/src/entity-table/vtable/table-theme.ts +181 -0
  157. package/src/entity-table/vtable/table-tooltip.ts +70 -0
  158. package/src/entity-table/vtable/vtable-compat.ts +84 -0
  159. package/src/entity-value.utils.ts +456 -0
  160. package/src/index.ts +57 -0
  161. package/src/query-builder/models/config.interface.ts +31 -0
  162. package/src/query-builder/models/index.ts +8 -0
  163. package/src/query-builder/models/operator.interface.ts +191 -0
  164. package/src/query-builder/models/query-builder-state.ts +222 -0
  165. package/src/query-builder/models/validation.interface.ts +184 -0
  166. package/src/query-builder/services/index.ts +9 -0
  167. package/src/query-builder/services/operator-registry.service.ts +243 -0
  168. package/src/query-builder/services/query-builder.service.ts +568 -0
  169. package/src/query-builder/services/query-converter.service.ts +153 -0
  170. package/src/query-builder/services/schema-parser.service.ts +309 -0
  171. package/src/query-builder/services/validation.service.ts +390 -0
  172. package/src/query-builder/utils/date.ts +35 -0
  173. package/src/query-builder/utils/field-tree.ts +400 -0
  174. package/src/query-builder/utils/id.ts +15 -0
  175. package/src/query-builder/utils/index.ts +12 -0
  176. package/src/query-builder/utils/metadata-field-extractor.ts +460 -0
  177. package/src/query-builder/utils/popover-position.ts +59 -0
  178. package/src/query-builder/utils/schema-factory.ts +75 -0
  179. package/src/query-builder/utils/structural-equal.ts +5 -0
  180. package/src/query-builder/utils/value-input.ts +105 -0
  181. package/src/structural-equal.ts +49 -0
@@ -0,0 +1,123 @@
1
+ import type * as VTable from '@visactor/vtable';
2
+ import type { ListTableConstructorOptions } from '@visactor/vtable';
3
+ import type { EntityTableRecord } from '../interfaces.js';
4
+ import { isRowDragEnabled } from './table-row-move.js';
5
+
6
+ declare const ngDevMode: boolean | undefined;
7
+
8
+ /**
9
+ * HACK: VTable 不提供禁用特定行拖拽的 API,通过 monkey-patch 内部属性实现
10
+ *
11
+ * 只读行、新增行不给手柄;`setRowDragEnabled(table, false)` 之后所有行都不给。
12
+ *
13
+ * TODO(vtable-upstream): 跟踪 VTable 上游 API,一旦提供 per-row dragOrder 配置即可移除此 hack
14
+ * @see https://github.com/AgoraIO/vtable/issues — 搜索 "drag order per row"
15
+ * @param table 目标表格实例
16
+ */
17
+ export function patchDragIconForReadonlyRows(table: VTable.ListTable): void {
18
+ const helper = (table as unknown as Record<string, unknown>)['internalProps'] as
19
+ { rowSeriesNumberHelper?: { getIcons: (col: number, row: number) => unknown[] } } | undefined;
20
+ if (!helper?.rowSeriesNumberHelper) {
21
+ if (typeof ngDevMode === 'undefined' || ngDevMode) {
22
+ console.warn('[entity-table] VTable internalProps.rowSeriesNumberHelper not found — drag icon patch skipped');
23
+ }
24
+ return;
25
+ }
26
+ const original = helper.rowSeriesNumberHelper.getIcons.bind(helper.rowSeriesNumberHelper);
27
+ helper.rowSeriesNumberHelper.getIcons = (col: number, row: number): unknown[] => {
28
+ if (!isRowDragEnabled(table)) return [];
29
+ const record = table.getRecordByCell(col, row) as EntityTableRecord | undefined;
30
+ if (record?._readonly || record?._isAddRow) return [];
31
+ return original(col, row);
32
+ };
33
+ }
34
+
35
+ /**
36
+ * 列头排序点击时 VTable 交出的状态
37
+ */
38
+ export interface HeaderSortClick {
39
+ /** 被点击列的字段 */
40
+ readonly field: unknown;
41
+ /** 点击后的排序方向:`asc` → `desc` → `normal` 轮转 */
42
+ readonly order: unknown;
43
+ }
44
+
45
+ /**
46
+ * 同步列头排序图标,不执行客户端排序
47
+ *
48
+ * @param table - 目标表格实例
49
+ * @param click - `sort_click` 事件参数
50
+ *
51
+ * @remarks
52
+ * 业务层在 `sort_click` 里返回 `false` 阻止 VTable 客户端排序、改由查询重排;
53
+ * 但返回 `false` 也让 VTable 跳过记录排序状态,图标停在 normal,下一次点击又从 asc 起,
54
+ * 永远到不了 desc / normal。这里补记状态(`executeSort = false`),图标与轮转才正常。
55
+ *
56
+ * @example
57
+ * ```typescript
58
+ * table.on('sort_click', args => {
59
+ * emitSort(args);
60
+ * syncHeaderSortIcon(table, args);
61
+ * return false;
62
+ * });
63
+ * ```
64
+ */
65
+ export function syncHeaderSortIcon(table: VTable.ListTable, click: HeaderSortClick): void {
66
+ const { field, order } = click;
67
+ const sorted = typeof field === 'string' && field !== '' && (order === 'asc' || order === 'desc');
68
+ table.updateSortState(sorted ? { field, order } : null, false);
69
+ }
70
+
71
+ /**
72
+ * 收集拖拽排序后的非只读行 ID 列表
73
+ *
74
+ * @param table 目标表格实例
75
+ * @param idField 记录 ID 字段名
76
+ * @returns 按当前行顺序排列的非只读行 ID
77
+ */
78
+ export function collectReorderedIds(table: VTable.ListTable, idField: string): string[] {
79
+ const rowCount = table.rowCount;
80
+ const orderedIds: string[] = [];
81
+ for (let row = 1; row < rowCount; row++) {
82
+ const record = table.getRecordByCell(0, row) as EntityTableRecord | undefined;
83
+ if (record && !record._readonly && !record._isAddRow) {
84
+ const id = record[idField];
85
+ if (typeof id === 'string') orderedIds.push(id);
86
+ }
87
+ }
88
+ return orderedIds;
89
+ }
90
+
91
+ /**
92
+ * 更新表格数据并恢复列宽(按 field 名匹配,容忍列增删)
93
+ *
94
+ * @param table 目标表格实例
95
+ * @param records 更新后的行数据
96
+ * @param prevColumns 更新前的列定义,用于读取旧列宽
97
+ * @param newColumns 更新后的列定义
98
+ */
99
+ export function updateTableRecords(
100
+ table: VTable.ListTable,
101
+ records: EntityTableRecord[],
102
+ prevColumns: ListTableConstructorOptions['columns'],
103
+ newColumns: ListTableConstructorOptions['columns']
104
+ ): void {
105
+ const prevCols = prevColumns as { field?: string }[];
106
+ const colCount = table.colCount;
107
+ const widthByField = new Map<string, number>();
108
+ for (let col = 1; col < colCount; col++) {
109
+ const field = prevCols[col - 1]?.field;
110
+ if (field) widthByField.set(field, table.getColWidth(col));
111
+ }
112
+ table.setRecords(records);
113
+ if (newColumns) table.updateColumns(newColumns as never);
114
+ const newColDefs = newColumns as { field?: string }[] | undefined;
115
+ const newColCount = table.colCount;
116
+ for (let col = 1; col < newColCount; col++) {
117
+ const field = newColDefs?.[col - 1]?.field;
118
+ if (field) {
119
+ const w = widthByField.get(field);
120
+ if (w !== undefined) table.setColWidth(col, w);
121
+ }
122
+ }
123
+ }
@@ -0,0 +1,122 @@
1
+ /**
2
+ * @fileoverview 表格单行拖动的读取与动态开关(US-028 阶段 B)
3
+ *
4
+ * VTable 只在建表时读 `rowSeriesNumber.dragOrder`,之后改选项不生效;手柄是否出现由
5
+ * `rowSeriesNumberHelper.getIcons` 决定。所以建表时统一打开 `dragOrder`,再由
6
+ * {@link setRowDragEnabled} 记下的开关在 `patchDragIconForReadonlyRows` 打的补丁里收掉手柄。
7
+ */
8
+ import type * as VTable from '@visactor/vtable';
9
+ import type { EntityTableRecord } from '../interfaces.js';
10
+
11
+ /**
12
+ * 单行拖放的结果:被拖行与它落点前后的邻居
13
+ *
14
+ * @remarks
15
+ * 形状对齐 `Repository.reorder(id, { prevId, nextId })`;落到首部时 `prevId` 为 `null`,
16
+ * 落到尾部时 `nextId` 为 `null`。只描述表格里看到的位置,是否允许落库由调用方决定。
17
+ */
18
+ export interface RowMoveEvent {
19
+ /** 被拖动行的主键 */
20
+ readonly id: string;
21
+ /** 落点之前那一行的主键;落到首部为 `null` */
22
+ readonly prevId: string | null;
23
+ /** 落点之后那一行的主键;落到尾部为 `null` */
24
+ readonly nextId: string | null;
25
+ }
26
+
27
+ /**
28
+ * VTable `change_header_position` 事件参数中本模块用到的部分
29
+ */
30
+ export interface HeaderPositionChange {
31
+ /** 拖动起点 */
32
+ readonly source: { readonly col: number; readonly row: number };
33
+ /** 放下后的位置 */
34
+ readonly target: { readonly col: number; readonly row: number };
35
+ /** 拖动的是行还是列 */
36
+ readonly movingColumnOrRow?: 'row' | 'column';
37
+ }
38
+
39
+ /** 第 0 行是表头,数据从第 1 行开始 */
40
+ const FIRST_DATA_ROW = 1;
41
+
42
+ const disabledTables = new WeakSet<VTable.ListTable>();
43
+
44
+ const idAt = (table: VTable.ListTable, idField: string, row: number): string | null => {
45
+ if (row < FIRST_DATA_ROW || row >= table.rowCount) return null;
46
+ const id = (table.getRecordByCell(0, row) as EntityTableRecord | undefined)?.[idField];
47
+ return typeof id === 'string' ? id : null;
48
+ };
49
+
50
+ /**
51
+ * 从一次行拖放读出被拖行与落点邻居
52
+ *
53
+ * @param table - 已完成换位的表格实例(VTable 先 `changeRecordOrder` 再派发事件)
54
+ * @param idField - 记录主键字段名
55
+ * @param change - `change_header_position` 的事件参数
56
+ * @returns 拖的是列、原位放下或被拖行主键不是字符串时为 `null`
57
+ *
58
+ * @example
59
+ * ```typescript
60
+ * table.on('change_header_position', args => {
61
+ * const move = readRowMove(table, 'id', args);
62
+ * if (move) emitRowMoved(move);
63
+ * });
64
+ * ```
65
+ */
66
+ export function readRowMove(
67
+ table: VTable.ListTable,
68
+ idField: string,
69
+ change: HeaderPositionChange
70
+ ): RowMoveEvent | null {
71
+ if (change.movingColumnOrRow === 'column' || change.source.row === change.target.row) return null;
72
+ const row = change.target.row;
73
+ const id = idAt(table, idField, row);
74
+ if (id === null) return null;
75
+ return { id, prevId: idAt(table, idField, row - 1), nextId: idAt(table, idField, row + 1) };
76
+ }
77
+
78
+ /**
79
+ * 运行时开关表格的行拖动手柄
80
+ *
81
+ * @param table - 目标表格实例(建表时需开启 `rowSeriesNumber.dragOrder`)
82
+ * @param enabled - `false` 时所有行都不显示手柄
83
+ *
84
+ * @remarks
85
+ * 只在状态真的变化时重建单元格;手柄的收起依赖 `patchDragIconForReadonlyRows` 已经打过补丁。
86
+ */
87
+ export function setRowDragEnabled(table: VTable.ListTable, enabled: boolean): void {
88
+ if (enabled === isRowDragEnabled(table)) return;
89
+ if (enabled) disabledTables.delete(table);
90
+ else disabledTables.add(table);
91
+ table.renderWithRecreateCells();
92
+ }
93
+
94
+ /**
95
+ * 表格当前是否允许行拖动
96
+ *
97
+ * @param table - 目标表格实例
98
+ * @returns 未调用过 {@link setRowDragEnabled} 时为 `true`
99
+ */
100
+ export function isRowDragEnabled(table: VTable.ListTable): boolean {
101
+ return !disabledTables.has(table);
102
+ }
103
+
104
+ /**
105
+ * 把表格行恢复成最近一次提交的顺序
106
+ *
107
+ * @param table - 目标表格实例
108
+ * @param records - 交给表格的那份记录数组(会被原地改回)
109
+ * @param committed - 最近一次 `setRecords` 时记下的顺序副本
110
+ *
111
+ * @remarks
112
+ * VTable 拖放换位是对传入数组原地 `splice`,交进去的记录数组此时已是拖后的顺序;
113
+ * 所以宿主组件每次交数据时要另存一份副本,失败或被拒时用它把数组改回去再重绘。
114
+ */
115
+ export function restoreTableRecords(
116
+ table: VTable.ListTable,
117
+ records: EntityTableRecord[],
118
+ committed: readonly EntityTableRecord[]
119
+ ): void {
120
+ records.splice(0, records.length, ...committed);
121
+ table.setRecords(records);
122
+ }
@@ -0,0 +1,181 @@
1
+ import * as VTable from '@visactor/vtable';
2
+ import type { SwitchStyle } from '@visactor/vtable/es/ts-types/column/style.js';
3
+
4
+ /** 系统字体栈 */
5
+ const SYSTEM_FONT = `-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif`;
6
+
7
+ /** daisyUI 主题 CSS 变量集合 */
8
+ export interface CSSVariables {
9
+ /** 根背景色 */
10
+ rootBg: string;
11
+ /** base-100 面板背景色 */
12
+ base100: string;
13
+ /** base-200 次级背景色 */
14
+ base200: string;
15
+ /** base-300 边框/分隔线颜色 */
16
+ base300: string;
17
+ /** 基础内容文字色 */
18
+ baseContent: string;
19
+ /** 主色调 */
20
+ primary: string;
21
+ /** 主色调上的文字颜色 */
22
+ primaryContent: string;
23
+ /** 圆角尺寸 */
24
+ radiusBox: string;
25
+ }
26
+
27
+ /**
28
+ * 返回默认颜色集合
29
+ *
30
+ * @returns 内置默认主题色,作为无 CSS 变量环境下的兜底
31
+ */
32
+ export function getDefaultColors(): CSSVariables {
33
+ return {
34
+ rootBg: '#fafbfc',
35
+ base100: '#ffffff',
36
+ base200: '#eef0f2',
37
+ base300: '#dcdee0',
38
+ baseContent: '#1e2328',
39
+ primary: '#167bff',
40
+ primaryContent: '#ffffff',
41
+ radiusBox: '20px'
42
+ };
43
+ }
44
+
45
+ /**
46
+ * 获取文档根元素的计算样式
47
+ *
48
+ * @returns 文档根元素计算样式;非浏览器环境(无 document)时返回 undefined
49
+ */
50
+ export function getDocumentRootStyle(): Pick<CSSStyleDeclaration, 'getPropertyValue'> | undefined {
51
+ if (typeof document === 'undefined') {
52
+ return undefined;
53
+ }
54
+
55
+ return getComputedStyle(document.documentElement);
56
+ }
57
+
58
+ /**
59
+ * 读取单个 CSS 变量值,为空时回退到默认值
60
+ *
61
+ * @param name CSS 变量名
62
+ * @param fallback 变量缺失或为空时的回退值
63
+ * @param style 样式来源,默认取文档根元素计算样式
64
+ * @returns 变量值(已去除首尾空白);无效或为空时返回 fallback
65
+ */
66
+ export function getCSSVariableValue(
67
+ name: string,
68
+ fallback: string,
69
+ style: Pick<CSSStyleDeclaration, 'getPropertyValue'> | undefined = getDocumentRootStyle()
70
+ ): string {
71
+ return style?.getPropertyValue(name).trim() || fallback;
72
+ }
73
+
74
+ /**
75
+ * 判断当前文档是否处于暗色模式
76
+ *
77
+ * @returns data-theme=dark 或系统 prefers-color-scheme: dark 时为 true;非浏览器环境返回 false
78
+ */
79
+ export function isDocumentDarkMode(): boolean {
80
+ if (typeof document === 'undefined') {
81
+ return false;
82
+ }
83
+
84
+ const theme = document.documentElement.getAttribute('data-theme');
85
+ if (theme === 'dark') return true;
86
+ if (theme === 'light') return false;
87
+
88
+ return (
89
+ typeof window !== 'undefined' &&
90
+ typeof window.matchMedia === 'function' &&
91
+ window.matchMedia('(prefers-color-scheme: dark)').matches
92
+ );
93
+ }
94
+
95
+ /**
96
+ * 从 CSS 自定义属性读取 daisyUI 主题色
97
+ *
98
+ * @returns 主题色集合;无 document 时返回默认色
99
+ */
100
+ export function getCSSVariables(): CSSVariables {
101
+ const defaults = getDefaultColors();
102
+ const style = getDocumentRootStyle();
103
+
104
+ if (!style) {
105
+ return defaults;
106
+ }
107
+
108
+ return {
109
+ rootBg: getCSSVariableValue('--root-bg', defaults.rootBg, style),
110
+ base100: getCSSVariableValue('--color-base-100', defaults.base100, style),
111
+ base200: getCSSVariableValue('--color-base-200', defaults.base200, style),
112
+ base300: getCSSVariableValue('--color-base-300', defaults.base300, style),
113
+ baseContent: getCSSVariableValue('--color-base-content', defaults.baseContent, style),
114
+ primary: getCSSVariableValue('--color-primary', defaults.primary, style),
115
+ primaryContent: getCSSVariableValue('--color-primary-content', defaults.primaryContent, style),
116
+ radiusBox: getCSSVariableValue('--radius-box', defaults.radiusBox, style)
117
+ };
118
+ }
119
+
120
+ /**
121
+ * 基于 daisyUI CSS 变量构建 VTable 主题
122
+ *
123
+ * @param isDark 是否暗色模式
124
+ * @param cssVars 主题 CSS 变量集合
125
+ * @returns VTable 主题对象
126
+ */
127
+ export function createTheme(isDark: boolean, cssVars: CSSVariables): ReturnType<typeof VTable.themes.DEFAULT.extends> {
128
+ const switchStyle: SwitchStyle = {
129
+ checkedFill: cssVars.primary,
130
+ uncheckedFill: isDark ? '#ffffff33' : '#00000033',
131
+ circleFill: isDark ? '#ffffffDD' : '#ffffffEE',
132
+ disableCheckedFill: isDark ? '#ffffff33' : '#00000033',
133
+ disableUncheckedFill: isDark ? '#ffffff11' : '#00000011'
134
+ };
135
+
136
+ return VTable.themes.DEFAULT.extends({
137
+ underlayBackgroundColor: cssVars.rootBg,
138
+ defaultStyle: {
139
+ borderColor: cssVars.base300,
140
+ color: cssVars.baseContent,
141
+ bgColor: cssVars.base100,
142
+ fontSize: 13,
143
+ fontFamily: SYSTEM_FONT
144
+ },
145
+ headerStyle: {
146
+ borderColor: cssVars.base300,
147
+ color: cssVars.baseContent,
148
+ bgColor: cssVars.base200,
149
+ fontSize: 13,
150
+ fontWeight: 600,
151
+ fontFamily: SYSTEM_FONT
152
+ },
153
+ bodyStyle: {
154
+ borderColor: cssVars.base300,
155
+ color: cssVars.baseContent,
156
+ bgColor: cssVars.base100,
157
+ hover: {
158
+ cellBgColor: cssVars.base200
159
+ }
160
+ },
161
+ frameStyle: {
162
+ borderColor: cssVars.base300,
163
+ borderLineWidth: 0.5
164
+ },
165
+ scrollStyle: {
166
+ scrollSliderColor: cssVars.base300,
167
+ scrollRailColor: cssVars.base200,
168
+ width: 8,
169
+ visible: 'scrolling',
170
+ hoverOn: true
171
+ },
172
+ selectionStyle: {
173
+ cellBorderColor: cssVars.primary,
174
+ cellBorderLineWidth: 2,
175
+ cellBgColor: `color-mix(in srgb, ${cssVars.primary} ${isDark ? '10' : '5'}%, transparent)`,
176
+ inlineRowBgColor: `color-mix(in srgb, ${cssVars.primary} ${isDark ? '5' : '2'}%, transparent)`,
177
+ inlineColumnBgColor: `color-mix(in srgb, ${cssVars.primary} ${isDark ? '5' : '2'}%, transparent)`
178
+ },
179
+ switchStyle
180
+ });
181
+ }
@@ -0,0 +1,70 @@
1
+ import type * as VTable from '@visactor/vtable';
2
+ import { getTableContainer } from './vtable-compat.js';
3
+
4
+ /**
5
+ * 计算 tooltip 在页面上的绝对位置
6
+ *
7
+ * @param container tooltip 所在容器元素
8
+ * @param rect 单元格矩形信息(x/left、y/top、height/bottom)
9
+ * @returns 页面坐标(位于单元格下方);容器、矩形或位置信息缺失时返回 null
10
+ */
11
+ export function computeTooltipPosition(
12
+ container: HTMLElement | null | undefined,
13
+ rect: Record<string, number> | null | undefined
14
+ ): { x: number; y: number } | null {
15
+ if (!container || !rect || (typeof rect['x'] !== 'number' && typeof rect['left'] !== 'number')) return null;
16
+ const br = container.getBoundingClientRect();
17
+ const x = typeof rect['x'] === 'number' ? rect['x'] : (rect['left'] ?? 0);
18
+ const y = typeof rect['y'] === 'number' ? rect['y'] : (rect['top'] ?? 0);
19
+ const height =
20
+ typeof rect['height'] === 'number' ? rect['height']
21
+ : typeof rect['bottom'] === 'number' ? rect['bottom'] - y
22
+ : 20;
23
+ return { x: br.left + x, y: br.top + y + height + 4 };
24
+ }
25
+
26
+ /** 默认 tooltip 延迟(毫秒) */
27
+ const DEFAULT_TOOLTIP_DELAY = 800;
28
+
29
+ /** 单元格错误 tooltip 生命周期管理 */
30
+ export class CellTooltipManager {
31
+ #timer: ReturnType<typeof setTimeout> | null = null;
32
+ #hoveredCell: { col: number; row: number } | null = null;
33
+
34
+ /** 延迟显示错误 tooltip */
35
+ showError(
36
+ table: VTable.ListTable,
37
+ col: number,
38
+ row: number,
39
+ content: string,
40
+ setTooltip: (tip: { x: number; y: number; content: string } | null) => void,
41
+ delay = DEFAULT_TOOLTIP_DELAY
42
+ ): void {
43
+ this.#hoveredCell = { col, row };
44
+ if (this.#timer !== null) clearTimeout(this.#timer);
45
+ this.#timer = setTimeout(() => {
46
+ if (this.#hoveredCell?.col !== col || this.#hoveredCell?.row !== row) return;
47
+ const containerEl = getTableContainer(table);
48
+ const rect = table.getCellRect(col, row) as unknown as Record<string, number> | undefined;
49
+ const pos = computeTooltipPosition(containerEl, rect);
50
+ setTooltip(pos ? { ...pos, content } : { x: 0, y: 0, content });
51
+ }, delay);
52
+ }
53
+
54
+ /** 隐藏 tooltip */
55
+ hide(setTooltip: (tip: null) => void): void {
56
+ if (this.#timer !== null) {
57
+ clearTimeout(this.#timer);
58
+ this.#timer = null;
59
+ }
60
+ this.#hoveredCell = null;
61
+ setTooltip(null);
62
+ }
63
+
64
+ /** 销毁时清理定时器 */
65
+ cleanup(): void {
66
+ if (this.#timer !== null) clearTimeout(this.#timer);
67
+ this.#timer = null;
68
+ this.#hoveredCell = null;
69
+ }
70
+ }
@@ -0,0 +1,84 @@
1
+ import type * as VTable from '@visactor/vtable';
2
+
3
+ /**
4
+ * VTable ListTable 类型补充工具
5
+ *
6
+ * VTable 官方类型未导出部分内部方法,此模块提供类型安全的包装器,
7
+ * 避免散落的 `as unknown as` 类型断言。
8
+ * 所有包装器在调用前做运行时 `in` 检查,VTable 版本升级移除 API 时安全降级。
9
+ */
10
+
11
+ /**
12
+ * VTable rowSeriesNumber 列占据 col=0,数据列从 col=1 开始。
13
+ * 将 VTable 列索引映射到 columns 数组索引时需减此偏移。
14
+ */
15
+ export const ROW_SERIES_COL_OFFSET = 1;
16
+
17
+ type AnyTable = Record<string, unknown>;
18
+
19
+ /**
20
+ * 设置 switch/checkbox/radio 单元格视觉状态
21
+ *
22
+ * @param table 目标表格实例
23
+ * @param col 列索引
24
+ * @param row 行索引
25
+ * @param value 目标布尔状态
26
+ */
27
+ export function setCellSwitchState(table: VTable.ListTable, col: number, row: number, value: boolean): void {
28
+ const t = table as unknown as AnyTable;
29
+ if (typeof t['setCellSwitchState'] === 'function') {
30
+ (t['setCellSwitchState'] as (c: number, r: number, v: boolean) => void)(col, row, value);
31
+ }
32
+ }
33
+
34
+ /**
35
+ * 获取表格 DOM 容器元素
36
+ *
37
+ * @param table 目标表格实例
38
+ * @returns 容器元素;VTable 未提供该方法时返回 undefined
39
+ */
40
+ export function getTableContainer(table: VTable.ListTable): HTMLElement | undefined {
41
+ const t = table as unknown as AnyTable;
42
+ if (typeof t['getContainer'] === 'function') {
43
+ return (t['getContainer'] as () => HTMLElement)();
44
+ }
45
+ return undefined;
46
+ }
47
+
48
+ /**
49
+ * 检查表格是否正在编辑中
50
+ *
51
+ * @param table 目标表格实例
52
+ * @returns 存在活动编辑器时为 true
53
+ */
54
+ export function isTableEditing(table: VTable.ListTable): boolean {
55
+ const em = (table as unknown as AnyTable)['editorManager'] as AnyTable | undefined;
56
+ return !!em?.['editingEditor'];
57
+ }
58
+
59
+ /**
60
+ * 完成当前单元格编辑
61
+ *
62
+ * @param table 目标表格实例
63
+ */
64
+ export function completeTableEdit(table: VTable.ListTable): void {
65
+ const em = (table as unknown as AnyTable)['editorManager'] as AnyTable | undefined;
66
+ if (em?.['editCell'] && typeof em['completeEdit'] === 'function') {
67
+ (em['completeEdit'] as () => void)();
68
+ }
69
+ }
70
+
71
+ /**
72
+ * 回写单元格值(不触发编辑事件)
73
+ *
74
+ * @param table 目标表格实例
75
+ * @param col 列索引
76
+ * @param row 行索引
77
+ * @param value 写入的值
78
+ */
79
+ export function changeCellValue(table: VTable.ListTable, col: number, row: number, value: unknown): void {
80
+ const t = table as unknown as AnyTable;
81
+ if (typeof t['changeCellValue'] === 'function') {
82
+ (t['changeCellValue'] as (...args: unknown[]) => void)(col, row, value, false, false);
83
+ }
84
+ }